xref: /haiku/src/preferences/locale/LocaleWindow.cpp (revision 7a74a5df454197933bc6e80a542102362ee98703)
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 kMsgDefaults:
299 			_Defaults();
300 			break;
301 
302 		case kMsgRevert:
303 		{
304 			_Revert();
305 			fFormatView->Revert();
306 			fConventionsListView->DeselectAll();
307 			if (fInitialConventionsItem != NULL) {
308 				BListItem* superitem
309 					= fConventionsListView->Superitem(fInitialConventionsItem);
310 				if (superitem != NULL)
311 					superitem->SetExpanded(true);
312 				fConventionsListView->Select(fConventionsListView->IndexOf(
313 						fInitialConventionsItem));
314 				fConventionsListView->ScrollToSelection();
315 			}
316 			_SettingsReverted();
317 			break;
318 		}
319 
320 		case kMsgSettingsChanged:
321 			_SettingsChanged();
322 			break;
323 
324 		case kMsgLanguageDragged:
325 		{
326 			void* target = NULL;
327 			if (message->FindPointer("drop_target", &target) != B_OK
328 				|| target != fPreferredListView)
329 				break;
330 
331 			// Add from available languages to preferred languages
332 			int32 dropIndex;
333 			if (message->FindInt32("drop_index", &dropIndex) != B_OK)
334 				dropIndex = fPreferredListView->CountItems();
335 
336 			int32 index = 0;
337 			for (int32 i = 0; message->FindInt32("index", i, &index) == B_OK;
338 					i++) {
339 				LanguageListItem* item = static_cast<LanguageListItem*>(
340 					fLanguageListView->ItemAt(index));
341 				_InsertPreferredLanguage(item, dropIndex++);
342 			}
343 			break;
344 		}
345 		case kMsgLanguageInvoked:
346 		{
347 			int32 index = 0;
348 			for (int32 i = 0; message->FindInt32("index", i, &index) == B_OK;
349 					i++) {
350 				LanguageListItem* item = static_cast<LanguageListItem*>(
351 					fLanguageListView->ItemAt(index));
352 				_InsertPreferredLanguage(item);
353 			}
354 			break;
355 		}
356 
357 		case kMsgPreferredLanguageDragged:
358 		{
359 			void* target = NULL;
360 			if (fPreferredListView->CountItems() == 1
361 				|| message->FindPointer("drop_target", &target) != B_OK)
362 				break;
363 
364 			if (target == fPreferredListView) {
365 				// change ordering
366 				int32 dropIndex = message->FindInt32("drop_index");
367 				int32 index = 0;
368 				for (int32 i = 0;
369 						message->FindInt32("index", i, &index) == B_OK;
370 						i++, dropIndex++) {
371 					if (dropIndex > index) {
372 						dropIndex--;
373 						index -= i;
374 					}
375 					BListItem* item = fPreferredListView->RemoveItem(index);
376 					fPreferredListView->AddItem(item, dropIndex);
377 				}
378 
379 				_PreferredLanguagesChanged();
380 				break;
381 			}
382 
383 			// supposed to fall through - remove item
384 		}
385 		case kMsgPreferredLanguageDeleted:
386 		case kMsgPreferredLanguageInvoked:
387 		{
388 			if (fPreferredListView->CountItems() == 1)
389 				break;
390 
391 			// Remove from preferred languages
392 			int32 index = 0;
393 			for (int32 i = 0; message->FindInt32("index", i, &index) == B_OK;
394 					i++) {
395 				delete fPreferredListView->RemoveItem(index - i);
396 
397 				if (message->what == kMsgPreferredLanguageDeleted) {
398 					int32 count = fPreferredListView->CountItems();
399 					fPreferredListView->Select(
400 						index < count ? index : count - 1);
401 				}
402 			}
403 
404 			_PreferredLanguagesChanged();
405 			break;
406 		}
407 
408 		case kMsgConventionsSelection:
409 		{
410 			// Country selection changed.
411 			// Get the new selected country from the ListView and send it to the
412 			// main app event handler.
413 			void* listView;
414 			if (message->FindPointer("source", &listView) != B_OK)
415 				break;
416 
417 			BListView* conventionsList = static_cast<BListView*>(listView);
418 			if (conventionsList == NULL)
419 				break;
420 
421 			LanguageListItem* item = static_cast<LanguageListItem*>
422 				(conventionsList->ItemAt(conventionsList->CurrentSelection()));
423 			if (item == NULL)
424 				break;
425 
426 			BFormattingConventions conventions(item->ID());
427 			MutableLocaleRoster::Default()->SetDefaultFormattingConventions(
428 				conventions);
429 
430 			_SettingsChanged();
431 			fFormatView->Refresh();
432 			break;
433 		}
434 
435 		case kMsgFilesystemTranslationChanged:
436 		{
437 			MutableLocaleRoster::Default()->SetFilesystemTranslationPreferred(
438 				fFilesystemTranslationCheckbox->Value());
439 
440 			BAlert* alert = new BAlert(B_TRANSLATE("Locale"),
441 				B_TRANSLATE("Deskbar and Tracker need to be restarted for this "
442 				"change to take effect. Would you like to restart them now?"),
443 				B_TRANSLATE("Cancel"), B_TRANSLATE("Restart"), NULL,
444 				B_WIDTH_FROM_WIDEST, B_IDEA_ALERT);
445 			alert->SetShortcut(0, B_ESCAPE);
446 			alert->Go(new BInvoker(new BMessage(kMsgRestartTrackerAndDeskbar),
447 				NULL, be_app));
448 			break;
449 		}
450 
451 		default:
452 			BWindow::MessageReceived(message);
453 			break;
454 	}
455 }
456 
457 
458 bool
459 LocaleWindow::QuitRequested()
460 {
461 	return true;
462 }
463 
464 
465 void
466 LocaleWindow::Show()
467 {
468 	BWindow::Show();
469 
470 	Lock();
471 	if (IsLocked()) {
472 		fConventionsListView->ScrollToSelection();
473 		Unlock();
474 	}
475 }
476 
477 
478 void
479 LocaleWindow::_SettingsChanged()
480 {
481 	bool haveAnythingToRevert = fFormatView->IsReversible() || _IsReversible();
482 	fRevertButton->SetEnabled(haveAnythingToRevert);
483 }
484 
485 
486 void
487 LocaleWindow::_SettingsReverted()
488 {
489 	fRevertButton->SetEnabled(false);
490 }
491 
492 
493 bool
494 LocaleWindow::_IsReversible() const
495 {
496 	BMessage preferredLanguages;
497 	BLocaleRoster::Default()->GetPreferredLanguages(&preferredLanguages);
498 
499 	return !preferredLanguages.HasSameData(fInitialPreferredLanguages);
500 }
501 
502 
503 void
504 LocaleWindow::_PreferredLanguagesChanged()
505 {
506 	BMessage preferredLanguages;
507 	int index = 0;
508 	while (index < fPreferredListView->CountItems()) {
509 		LanguageListItem* item = static_cast<LanguageListItem*>(
510 			fPreferredListView->ItemAt(index));
511 		if (item != NULL)
512 			preferredLanguages.AddString("language", item->ID());
513 		index++;
514 	}
515 	MutableLocaleRoster::Default()->SetPreferredLanguages(&preferredLanguages);
516 
517 	_EnableDisableLanguages();
518 }
519 
520 
521 void
522 LocaleWindow::_EnableDisableLanguages()
523 {
524 	DisableUpdates();
525 
526 	for (int32 i = 0; i < fLanguageListView->FullListCountItems(); i++) {
527 		LanguageListItem* item = static_cast<LanguageListItem*>(
528 			fLanguageListView->FullListItemAt(i));
529 
530 		bool enable = fPreferredListView->ItemForLanguageID(item->ID()) == NULL;
531 		if (item->IsEnabled() != enable) {
532 			item->SetEnabled(enable);
533 
534 			int32 visibleIndex = fLanguageListView->IndexOf(item);
535 			if (visibleIndex >= 0) {
536 				if (!enable)
537 					fLanguageListView->Deselect(visibleIndex);
538 				fLanguageListView->InvalidateItem(visibleIndex);
539 			}
540 		}
541 	}
542 
543 	EnableUpdates();
544 }
545 
546 
547 void
548 LocaleWindow::_Refresh(bool setInitial)
549 {
550 	BMessage preferredLanguages;
551 	BLocaleRoster::Default()->GetPreferredLanguages(&preferredLanguages);
552 	if (setInitial)
553 		fInitialPreferredLanguages = preferredLanguages;
554 
555 	_SetPreferredLanguages(preferredLanguages);
556 }
557 
558 
559 void
560 LocaleWindow::_Revert()
561 {
562 	_SetPreferredLanguages(fInitialPreferredLanguages);
563 }
564 
565 
566 void
567 LocaleWindow::_SetPreferredLanguages(const BMessage& languages)
568 {
569 	DisableUpdates();
570 
571 	// Delete all existing items
572 	for (int32 index = fPreferredListView->CountItems(); index-- > 0; ) {
573 		delete fPreferredListView->ItemAt(index);
574 	}
575 	fPreferredListView->MakeEmpty();
576 
577 	BString languageID;
578 	for (int32 index = 0;
579 		languages.FindString("language", index, &languageID) == B_OK; index++) {
580 		int32 listIndex;
581 		LanguageListItem* item = fLanguageListView->ItemForLanguageID(
582 			languageID.String(), &listIndex);
583 		if (item != NULL) {
584 			// We found the item we were looking for, now copy it to
585 			// the other list
586 			fPreferredListView->AddItem(new LanguageListItem(*item));
587 		}
588 	}
589 
590 	_EnableDisableLanguages();
591 	EnableUpdates();
592 }
593 
594 
595 void
596 LocaleWindow::_InsertPreferredLanguage(LanguageListItem* item, int32 atIndex)
597 {
598 	if (item == NULL || fPreferredListView->ItemForLanguageID(
599 			item->ID().String()) != NULL)
600 		return;
601 
602 	if (atIndex == -1)
603 		atIndex = fPreferredListView->CountItems();
604 
605 	BLanguage language(item->Code());
606 	LanguageListItem* baseItem
607 		= fPreferredListView->ItemForLanguageCode(language.Code(), &atIndex);
608 
609 	DisableUpdates();
610 
611 	fPreferredListView->AddItem(new LanguageListItem(*item), atIndex);
612 
613 	// Replace other languages sharing the same base
614 	if (baseItem != NULL) {
615 		fPreferredListView->RemoveItem(baseItem);
616 		delete baseItem;
617 	}
618 
619 	_PreferredLanguagesChanged();
620 
621 	EnableUpdates();
622 }
623 
624 
625 void
626 LocaleWindow::_Defaults()
627 {
628 	BMessage preferredLanguages;
629 	preferredLanguages.AddString("language", "en");
630 	MutableLocaleRoster::Default()->SetPreferredLanguages(&preferredLanguages);
631 	_SetPreferredLanguages(preferredLanguages);
632 
633 	BFormattingConventions conventions("en_US");
634 	MutableLocaleRoster::Default()->SetDefaultFormattingConventions(
635 		conventions);
636 
637 	fConventionsListView->DeselectAll();
638 	if (fDefaultConventionsItem != NULL) {
639 		BListItem* superitem
640 			= fConventionsListView->Superitem(fDefaultConventionsItem);
641 		if (superitem != NULL && !superitem->IsExpanded())
642 			superitem->SetExpanded(true);
643 		fConventionsListView->Select(fConventionsListView->IndexOf(
644 				fDefaultConventionsItem));
645 		fConventionsListView->ScrollToSelection();
646 	}
647 }
648