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
34using BPrivate::MutableLocaleRoster;
35
36
37#undef B_TRANSLATION_CONTEXT
38#define B_TRANSLATION_CONTEXT "Locale Preflet Window"
39
40
41static const uint32 kMsgLanguageInvoked = 'LaIv';
42static const uint32 kMsgLanguageDragged = 'LaDr';
43static const uint32 kMsgPreferredLanguageInvoked = 'PLIv';
44static const uint32 kMsgPreferredLanguageDragged = 'PLDr';
45static const uint32 kMsgPreferredLanguageDeleted = 'PLDl';
46static const uint32 kMsgConventionsSelection = 'csel';
47static const uint32 kMsgDefaults = 'dflt';
48
49static const uint32 kMsgPreferredLanguagesChanged = 'lang';
50static const uint32 kMsgFilesystemTranslationChanged = 'fsys';
51
52
53static int
54compare_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
67LocaleWindow::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
289LocaleWindow::~LocaleWindow()
290{
291}
292
293
294void
295LocaleWindow::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
462bool
463LocaleWindow::QuitRequested()
464{
465	return true;
466}
467
468
469void
470LocaleWindow::Show()
471{
472	BWindow::Show();
473
474	Lock();
475	if (IsLocked()) {
476		fConventionsListView->ScrollToSelection();
477		Unlock();
478	}
479}
480
481
482void
483LocaleWindow::_SettingsChanged()
484{
485	fRevertButton->SetEnabled(fFormatView->IsReversible() || _IsReversible());
486}
487
488
489void
490LocaleWindow::_SettingsReverted()
491{
492	fRevertButton->SetEnabled(false);
493}
494
495
496bool
497LocaleWindow::_IsReversible() const
498{
499	BMessage preferredLanguages;
500	BLocaleRoster::Default()->GetPreferredLanguages(&preferredLanguages);
501
502	return !preferredLanguages.HasSameData(fInitialPreferredLanguages);
503}
504
505
506void
507LocaleWindow::_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
524void
525LocaleWindow::_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
550void
551LocaleWindow::_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
562void
563LocaleWindow::_Revert()
564{
565	_SetPreferredLanguages(fInitialPreferredLanguages);
566}
567
568
569void
570LocaleWindow::_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
598void
599LocaleWindow::_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
628void
629LocaleWindow::_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