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