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