xref: /haiku/src/preferences/locale/LocaleWindow.cpp (revision e5d65858f2361fe0552495b61620c84dcee6bc00)
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