xref: /haiku/src/preferences/locale/LocaleWindow.cpp (revision c90684742e7361651849be4116d0e5de3a817194)
1 /*
2  * Copyright 2005-2010, Axel Dörfler, axeld@pinc-software.de.
3  * Copyright 2009-2010, Adrien Destugues <pulkomandy@gmail.com>.
4  * All rights reserved. Distributed under the terms of the MIT License.
5  */
6 
7 
8 #include "LocaleWindow.h"
9 
10 #include <iostream>
11 
12 #include <Alert.h>
13 #include <Application.h>
14 #include <Button.h>
15 #include <Catalog.h>
16 #include <ControlLook.h>
17 #include <FormattingConventions.h>
18 #include <GroupLayout.h>
19 #include <LayoutBuilder.h>
20 #include <Locale.h>
21 #include <MutableLocaleRoster.h>
22 #include <Screen.h>
23 #include <ScrollView.h>
24 #include <StringView.h>
25 #include <TabView.h>
26 #include <UnicodeChar.h>
27 
28 #include "FormatSettingsView.h"
29 #include "LocalePreflet.h"
30 #include "LanguageListView.h"
31 
32 
33 using BPrivate::gMutableLocaleRoster;
34 
35 
36 #undef B_TRANSLATE_CONTEXT
37 #define B_TRANSLATE_CONTEXT "Locale Preflet Window"
38 
39 
40 static const uint32 kMsgLanguageInvoked = 'LaIv';
41 static const uint32 kMsgLanguageDragged = 'LaDr';
42 static const uint32 kMsgPreferredLanguageInvoked = 'PLIv';
43 static const uint32 kMsgPreferredLanguageDragged = 'PLDr';
44 static const uint32 kMsgPreferredLanguageDeleted = 'PLDl';
45 static const uint32 kMsgConventionsSelection = 'csel';
46 static const uint32 kMsgDefaults = 'dflt';
47 
48 static const uint32 kMsgPreferredLanguagesChanged = 'lang';
49 
50 
51 static int
52 compare_typed_list_items(const BListItem* _a, const BListItem* _b)
53 {
54 	static BCollator collator;
55 
56 	LanguageListItem* a = (LanguageListItem*)_a;
57 	LanguageListItem* b = (LanguageListItem*)_b;
58 
59 	return collator.Compare(a->Text(), b->Text());
60 }
61 
62 
63 // #pragma mark -
64 
65 LocaleWindow::LocaleWindow()
66 	:
67 	BWindow(BRect(0, 0, 0, 0), B_TRANSLATE("Locale"), B_TITLED_WINDOW,
68 		B_QUIT_ON_WINDOW_CLOSE | B_ASYNCHRONOUS_CONTROLS
69 			| B_AUTO_UPDATE_SIZE_LIMITS),
70 	fInitialConventionsItem(NULL),
71 	fDefaultConventionsItem(NULL)
72 {
73 	SetLayout(new BGroupLayout(B_HORIZONTAL));
74 
75 	float spacing = be_control_look->DefaultItemSpacing();
76 
77 	BTabView* tabView = new BTabView("tabview");
78 	BGroupView* languageTab = new BGroupView(B_TRANSLATE("Language"),
79 		B_HORIZONTAL, spacing);
80 
81 	// first list: available languages
82 	fLanguageListView = new LanguageListView("available",
83 		B_MULTIPLE_SELECTION_LIST);
84 	BScrollView* scrollView = new BScrollView("scroller", fLanguageListView,
85 		B_WILL_DRAW | B_FRAME_EVENTS, true, true);
86 
87 	fLanguageListView->SetInvocationMessage(new BMessage(kMsgLanguageInvoked));
88 	fLanguageListView->SetDragMessage(new BMessage(kMsgLanguageDragged));
89 
90 	BFont font;
91 	fLanguageListView->GetFont(&font);
92 
93 	// Fill the language list from the LocaleRoster data
94 	BMessage availableLanguages;
95 	if (be_locale_roster->GetAvailableLanguages(&availableLanguages) == B_OK) {
96 		BString currentID;
97 		LanguageListItem* lastAddedCountryItem = NULL;
98 
99 		for (int i = 0; availableLanguages.FindString("language", i, &currentID)
100 				== B_OK; i++) {
101 			// Now get the human-readable, native name for each language
102 			BString name;
103 			BLanguage currentLanguage(currentID.String());
104 			currentLanguage.GetNativeName(name);
105 
106 			// TODO: the following block fails to detect a couple of language
107 			// names as containing glyphs we can't render. Why's that?
108 			bool hasGlyphs[name.CountChars()];
109 			font.GetHasGlyphs(name.String(), name.CountChars(), hasGlyphs);
110 			for (int32 i = 0; i < name.CountChars(); ++i) {
111 				if (!hasGlyphs[i]) {
112 					// replace by name translated to current language
113 					currentLanguage.GetName(name);
114 					break;
115 				}
116 			}
117 
118 			LanguageListItem* item = new LanguageListItem(name,
119 				currentID.String(), currentLanguage.Code(),
120 				currentLanguage.CountryCode());
121 			if (currentLanguage.IsCountrySpecific()
122 				&& lastAddedCountryItem != NULL
123 				&& lastAddedCountryItem->Code() == item->Code()) {
124 				fLanguageListView->AddUnder(item, lastAddedCountryItem);
125 			} else {
126 				// This is a language variant, add it at top-level
127 				fLanguageListView->AddItem(item);
128 				if (!currentLanguage.IsCountrySpecific()) {
129 					item->SetExpanded(false);
130 					lastAddedCountryItem = item;
131 				}
132 			}
133 		}
134 
135 		fLanguageListView->FullListSortItems(compare_typed_list_items);
136 	} else {
137 		BAlert* alert = new BAlert("Error",
138 			B_TRANSLATE("Unable to find the available languages! You can't "
139 				"use this preflet!"),
140 			B_TRANSLATE("OK"), NULL, NULL,
141 			B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_STOP_ALERT);
142 		alert->Go();
143 	}
144 
145 	// Second list: active languages
146 	fPreferredListView = new LanguageListView("preferred",
147 		B_MULTIPLE_SELECTION_LIST);
148 	BScrollView* scrollViewEnabled = new BScrollView("scroller",
149 		fPreferredListView, B_WILL_DRAW | B_FRAME_EVENTS, true, true);
150 
151 	fPreferredListView->SetInvocationMessage(
152 		new BMessage(kMsgPreferredLanguageInvoked));
153 	fPreferredListView->SetDeleteMessage(
154 		new BMessage(kMsgPreferredLanguageDeleted));
155 	fPreferredListView->SetDragMessage(
156 		new BMessage(kMsgPreferredLanguageDragged));
157 
158 	BLayoutBuilder::Group<>(languageTab)
159 		.AddGroup(B_VERTICAL, spacing)
160 			.Add(new BStringView("", B_TRANSLATE("Available languages")))
161 			.Add(scrollView)
162 			.End()
163 		.AddGroup(B_VERTICAL, spacing)
164 			.Add(new BStringView("", B_TRANSLATE("Preferred languages")))
165 			.Add(scrollViewEnabled)
166 			.End()
167 		.SetInsets(spacing, spacing, spacing, spacing);
168 
169 	BView* countryTab = new BView(B_TRANSLATE("Formatting"), B_WILL_DRAW);
170 	countryTab->SetLayout(new BGroupLayout(B_VERTICAL, 0));
171 
172 	fConventionsListView = new LanguageListView("formatting",
173 		B_SINGLE_SELECTION_LIST);
174 	scrollView = new BScrollView("scroller", fConventionsListView,
175 		B_WILL_DRAW | B_FRAME_EVENTS, true, true);
176 	fConventionsListView->SetSelectionMessage(
177 		new BMessage(kMsgConventionsSelection));
178 
179 	// get all available formatting conventions (by language)
180 	BFormattingConventions defaultConventions;
181 	be_locale->GetFormattingConventions(&defaultConventions);
182 	BString conventionID;
183 	fInitialConventionsItem = NULL;
184 	LanguageListItem* lastAddedConventionsItem = NULL;
185 	for (int i = 0;
186 		availableLanguages.FindString("language", i, &conventionID) == B_OK;
187 		i++) {
188 		BFormattingConventions convention(conventionID);
189 		BString conventionName;
190 		convention.GetName(conventionName);
191 
192 		LanguageListItem* item = new LanguageListItem(conventionName,
193 			conventionID, convention.LanguageCode(), convention.CountryCode());
194 		if (!strcmp(conventionID, "en_US"))
195 			fDefaultConventionsItem = item;
196 		if (conventionID.FindFirst('_') >= 0
197 			&& lastAddedConventionsItem != NULL
198 			&& lastAddedConventionsItem->Code() == item->Code()) {
199 			if (!strcmp(conventionID, defaultConventions.ID())) {
200 				fConventionsListView->Expand(lastAddedConventionsItem);
201 				fInitialConventionsItem = item;
202 			}
203 			fConventionsListView->AddUnder(item, lastAddedConventionsItem);
204 		} else {
205 			// This conventions-item isn't country-specific, add it at top-level
206 			fConventionsListView->AddItem(item);
207 			if (conventionID.FindFirst('_') < 0) {
208 				item->SetExpanded(false);
209 				lastAddedConventionsItem = item;
210 			}
211 			if (!strcmp(conventionID, defaultConventions.ID()))
212 				fInitialConventionsItem = item;
213 		}
214 	}
215 
216 	fConventionsListView->FullListSortItems(compare_typed_list_items);
217 	if (fInitialConventionsItem != NULL) {
218 		fConventionsListView->Select(fConventionsListView->IndexOf(
219 			fInitialConventionsItem));
220 	}
221 
222 	fConventionsListView->SetExplicitMinSize(BSize(20 * be_plain_font->Size(),
223 		B_SIZE_UNSET));
224 
225 	fFormatView = new FormatSettingsView();
226 
227 	countryTab->AddChild(BLayoutBuilder::Group<>(B_HORIZONTAL, spacing)
228 		.AddGroup(B_VERTICAL, 3)
229 			.Add(scrollView)
230 			.End()
231 		.Add(fFormatView)
232 		.SetInsets(spacing, spacing, spacing, spacing));
233 
234 	tabView->AddTab(languageTab);
235 	tabView->AddTab(countryTab);
236 
237 	BButton* button
238 		= new BButton(B_TRANSLATE("Defaults"), new BMessage(kMsgDefaults));
239 
240 	fRevertButton
241 		= new BButton(B_TRANSLATE("Revert"), new BMessage(kMsgRevert));
242 	fRevertButton->SetEnabled(false);
243 
244 	BLayoutBuilder::Group<>(this, B_VERTICAL, spacing)
245 		.Add(tabView)
246 		.AddGroup(B_HORIZONTAL, spacing)
247 			.Add(button)
248 			.Add(fRevertButton)
249 			.AddGlue()
250 			.End()
251 		.SetInsets(spacing, spacing, spacing, spacing)
252 		.End();
253 
254 	_Refresh(true);
255 	_SettingsReverted();
256 	CenterOnScreen();
257 }
258 
259 
260 LocaleWindow::~LocaleWindow()
261 {
262 }
263 
264 
265 void
266 LocaleWindow::MessageReceived(BMessage* message)
267 {
268 	switch (message->what) {
269 		case kMsgDefaults:
270 			_Defaults();
271 			break;
272 
273 		case kMsgRevert:
274 		{
275 			_Revert();
276 			fFormatView->Revert();
277 			fConventionsListView->DeselectAll();
278 			if (fInitialConventionsItem != NULL) {
279 				BListItem* superitem
280 					= fConventionsListView->Superitem(fInitialConventionsItem);
281 				if (superitem != NULL)
282 					superitem->SetExpanded(true);
283 				fConventionsListView->Select(fConventionsListView->IndexOf(
284 						fInitialConventionsItem));
285 				fConventionsListView->ScrollToSelection();
286 			}
287 			_SettingsReverted();
288 			break;
289 		}
290 
291 		case kMsgSettingsChanged:
292 			_SettingsChanged();
293 			break;
294 
295 		case kMsgLanguageDragged:
296 		{
297 			void* target = NULL;
298 			if (message->FindPointer("drop_target", &target) != B_OK
299 				|| target != fPreferredListView)
300 				break;
301 
302 			// Add from available languages to preferred languages
303 			int32 dropIndex;
304 			if (message->FindInt32("drop_index", &dropIndex) != B_OK)
305 				dropIndex = fPreferredListView->CountItems();
306 
307 			int32 index = 0;
308 			for (int32 i = 0; message->FindInt32("index", i, &index) == B_OK;
309 					i++) {
310 				LanguageListItem* item = static_cast<LanguageListItem*>(
311 					fLanguageListView->FullListItemAt(index));
312 				_InsertPreferredLanguage(item, dropIndex++);
313 			}
314 			break;
315 		}
316 		case kMsgLanguageInvoked:
317 		{
318 			int32 index = 0;
319 			for (int32 i = 0; message->FindInt32("index", i, &index) == B_OK;
320 					i++) {
321 				LanguageListItem* item = static_cast<LanguageListItem*>(
322 					fLanguageListView->ItemAt(index));
323 				_InsertPreferredLanguage(item);
324 			}
325 			break;
326 		}
327 
328 		case kMsgPreferredLanguageDragged:
329 		{
330 			void* target = NULL;
331 			if (fPreferredListView->CountItems() == 1
332 				|| message->FindPointer("drop_target", &target) != B_OK)
333 				break;
334 
335 			if (target == fPreferredListView) {
336 				// change ordering
337 				int32 dropIndex = message->FindInt32("drop_index");
338 				int32 index = 0;
339 				if (message->FindInt32("index", &index) == B_OK
340 					&& dropIndex != index) {
341 					BListItem* item = fPreferredListView->RemoveItem(index);
342 					if (dropIndex > index)
343 						index--;
344 					fPreferredListView->AddItem(item, dropIndex);
345 
346 					_PreferredLanguagesChanged();
347 				}
348 				break;
349 			}
350 
351 			// supposed to fall through - remove item
352 		}
353 		case kMsgPreferredLanguageDeleted:
354 		case kMsgPreferredLanguageInvoked:
355 		{
356 			if (fPreferredListView->CountItems() == 1)
357 				break;
358 
359 			// Remove from preferred languages
360 			int32 index = 0;
361 			if (message->FindInt32("index", &index) == B_OK) {
362 				delete fPreferredListView->RemoveItem(index);
363 				_PreferredLanguagesChanged();
364 
365 				if (message->what == kMsgPreferredLanguageDeleted)
366 					fPreferredListView->Select(index);
367 			}
368 			break;
369 		}
370 
371 		case kMsgConventionsSelection:
372 		{
373 			// Country selection changed.
374 			// Get the new selected country from the ListView and send it to the
375 			// main app event handler.
376 			void* listView;
377 			if (message->FindPointer("source", &listView) != B_OK)
378 				break;
379 
380 			BListView* conventionsList = static_cast<BListView*>(listView);
381 
382 			LanguageListItem* item = static_cast<LanguageListItem*>
383 				(conventionsList->ItemAt(conventionsList->CurrentSelection()));
384 			BFormattingConventions conventions(item->ID());
385 			gMutableLocaleRoster->SetDefaultFormattingConventions(conventions);
386 
387 			_SettingsChanged();
388 			fFormatView->Refresh();
389 			break;
390 		}
391 
392 		default:
393 			BWindow::MessageReceived(message);
394 			break;
395 	}
396 }
397 
398 
399 bool
400 LocaleWindow::QuitRequested()
401 {
402 	return true;
403 }
404 
405 
406 void
407 LocaleWindow::Show()
408 {
409 	BWindow::Show();
410 
411 	Lock();
412 	if (IsLocked()) {
413 		fConventionsListView->ScrollToSelection();
414 		Unlock();
415 	}
416 }
417 
418 
419 void
420 LocaleWindow::_SettingsChanged()
421 {
422 	bool haveAnythingToRevert = fFormatView->IsReversible() || _IsReversible();
423 	fRevertButton->SetEnabled(haveAnythingToRevert);
424 }
425 
426 
427 void
428 LocaleWindow::_SettingsReverted()
429 {
430 	fRevertButton->SetEnabled(false);
431 }
432 
433 
434 bool
435 LocaleWindow::_IsReversible() const
436 {
437 	BMessage preferredLanguages;
438 	be_locale_roster->GetPreferredLanguages(&preferredLanguages);
439 
440 	return !preferredLanguages.HasSameData(fInitialPreferredLanguages);
441 }
442 
443 
444 void
445 LocaleWindow::_PreferredLanguagesChanged()
446 {
447 	BMessage preferredLanguages;
448 	int index = 0;
449 	while (index < fPreferredListView->FullListCountItems()) {
450 		// only include subitems: we can guess the superitem from them anyway
451 		LanguageListItem* item = static_cast<LanguageListItem*>(
452 			fPreferredListView->FullListItemAt(index));
453 		if (item != NULL)
454 			preferredLanguages.AddString("language", item->ID());
455 		index++;
456 	}
457 	gMutableLocaleRoster->SetPreferredLanguages(&preferredLanguages);
458 
459 	_EnableDisableLanguages();
460 }
461 
462 
463 void
464 LocaleWindow::_EnableDisableLanguages()
465 {
466 	DisableUpdates();
467 
468 	for (int32 i = 0; i < fLanguageListView->FullListCountItems(); i++) {
469 		LanguageListItem* item = static_cast<LanguageListItem*>(
470 			fLanguageListView->FullListItemAt(i));
471 
472 		bool enable = fPreferredListView->ItemForLanguageID(item->ID()) == NULL;
473 		if (item->IsEnabled() != enable) {
474 			item->SetEnabled(enable);
475 
476 			int32 visibleIndex = fLanguageListView->IndexOf(item);
477 			if (visibleIndex >= 0) {
478 				if (!enable)
479 					fLanguageListView->Deselect(visibleIndex);
480 				fLanguageListView->InvalidateItem(visibleIndex);
481 			}
482 		}
483 	}
484 
485 	EnableUpdates();
486 }
487 
488 
489 void
490 LocaleWindow::_Refresh(bool setInitial)
491 {
492 	BMessage preferredLanguages;
493 	be_locale_roster->GetPreferredLanguages(&preferredLanguages);
494 	if (setInitial)
495 		fInitialPreferredLanguages = preferredLanguages;
496 
497 	_SetPreferredLanguages(preferredLanguages);
498 }
499 
500 
501 void
502 LocaleWindow::_Revert()
503 {
504 	_SetPreferredLanguages(fInitialPreferredLanguages);
505 }
506 
507 
508 void
509 LocaleWindow::_SetPreferredLanguages(const BMessage& languages)
510 {
511 	DisableUpdates();
512 
513 	// Delete all existing items
514 	for (int32 index = fPreferredListView->CountItems(); index-- > 0; ) {
515 		delete fPreferredListView->ItemAt(index);
516 	}
517 	fPreferredListView->MakeEmpty();
518 
519 	BString languageID;
520 	for (int32 index = 0;
521 		languages.FindString("language", index, &languageID) == B_OK; index++) {
522 		int32 listIndex;
523 		LanguageListItem* item = fLanguageListView->ItemForLanguageID(
524 			languageID.String(), &listIndex);
525 		if (item != NULL) {
526 			// We found the item we were looking for, now copy it to
527 			// the other list
528 			fPreferredListView->AddItem(new LanguageListItem(*item));
529 		}
530 	}
531 
532 	_EnableDisableLanguages();
533 	EnableUpdates();
534 }
535 
536 
537 void
538 LocaleWindow::_InsertPreferredLanguage(LanguageListItem* item, int32 atIndex)
539 {
540 	if (item == NULL || fPreferredListView->ItemForLanguageID(
541 			item->ID().String()) != NULL)
542 		return;
543 
544 	if (atIndex == -1)
545 		atIndex = fPreferredListView->CountItems();
546 
547 	BLanguage language(item->Code());
548 	LanguageListItem* baseItem
549 		= fPreferredListView->ItemForLanguageCode(language.Code(), &atIndex);
550 
551 	DisableUpdates();
552 
553 	fPreferredListView->AddItem(new LanguageListItem(*item), atIndex);
554 
555 	// Replace other languages sharing the same base
556 	if (baseItem != NULL) {
557 		fPreferredListView->RemoveItem(baseItem);
558 		delete baseItem;
559 	}
560 
561 	_PreferredLanguagesChanged();
562 
563 	EnableUpdates();
564 }
565 
566 
567 void
568 LocaleWindow::_Defaults()
569 {
570 	BMessage preferredLanguages;
571 	preferredLanguages.AddString("language", "en");
572 	gMutableLocaleRoster->SetPreferredLanguages(&preferredLanguages);
573 	_SetPreferredLanguages(preferredLanguages);
574 
575 	BFormattingConventions conventions("en_US");
576 	gMutableLocaleRoster->SetDefaultFormattingConventions(conventions);
577 
578 	fConventionsListView->DeselectAll();
579 	if (fDefaultConventionsItem != NULL) {
580 		BListItem* superitem
581 			= fConventionsListView->Superitem(fDefaultConventionsItem);
582 		if (superitem != NULL && !superitem->IsExpanded())
583 			superitem->SetExpanded(true);
584 		fConventionsListView->Select(fConventionsListView->IndexOf(
585 				fDefaultConventionsItem));
586 		fConventionsListView->ScrollToSelection();
587 	}
588 }
589 
590