xref: /haiku/src/preferences/locale/LocaleWindow.cpp (revision e2932f63b00ba76ab3769a9c217754a4d03868ca)
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_TRANSLATE_CONTEXT
38 #define B_TRANSLATE_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->Go();
151 	}
152 
153 	// Second list: active languages
154 	fPreferredListView = new LanguageListView("preferred",
155 		B_MULTIPLE_SELECTION_LIST);
156 	BScrollView* scrollViewEnabled = new BScrollView("scroller",
157 		fPreferredListView, B_WILL_DRAW | B_FRAME_EVENTS, true, true);
158 
159 	fPreferredListView->SetInvocationMessage(
160 		new BMessage(kMsgPreferredLanguageInvoked));
161 	fPreferredListView->SetDeleteMessage(
162 		new BMessage(kMsgPreferredLanguageDeleted));
163 	fPreferredListView->SetDragMessage(
164 		new BMessage(kMsgPreferredLanguageDragged));
165 
166 	BLayoutBuilder::Group<>(languageTab)
167 		.AddGroup(B_VERTICAL, spacing)
168 			.Add(new BStringView("", B_TRANSLATE("Available languages")))
169 			.Add(scrollView)
170 			.End()
171 		.AddGroup(B_VERTICAL, spacing)
172 			.Add(new BStringView("", B_TRANSLATE("Preferred languages")))
173 			.Add(scrollViewEnabled)
174 			.End()
175 		.SetInsets(spacing, spacing, spacing, spacing);
176 
177 	BView* countryTab = new BView(B_TRANSLATE("Formatting"), B_WILL_DRAW);
178 	countryTab->SetLayout(new BGroupLayout(B_VERTICAL, 0));
179 
180 	fConventionsListView = new LanguageListView("formatting",
181 		B_SINGLE_SELECTION_LIST);
182 	scrollView = new BScrollView("scroller", fConventionsListView,
183 		B_WILL_DRAW | B_FRAME_EVENTS, true, true);
184 	fConventionsListView->SetSelectionMessage(
185 		new BMessage(kMsgConventionsSelection));
186 
187 	// get all available formatting conventions (by language)
188 	BFormattingConventions initialConventions;
189 	BLocale::Default()->GetFormattingConventions(&initialConventions);
190 	BString conventionsID;
191 	fInitialConventionsItem = NULL;
192 	LanguageListItem* currentToplevelItem = NULL;
193 	for (int i = 0;
194 		availableLanguages.FindString("language", i, &conventionsID) == B_OK;
195 		i++) {
196 		BFormattingConventions conventions(conventionsID);
197 		BString conventionsName;
198 		conventions.GetName(conventionsName);
199 
200 		LanguageListItem* item;
201 		if (conventions.AreCountrySpecific()) {
202 			item = new LanguageListItemWithFlag(conventionsName, conventionsID,
203 				conventions.LanguageCode(), conventions.CountryCode());
204 		} else {
205 			item = new LanguageListItem(conventionsName, conventionsID,
206 				conventions.LanguageCode());
207 		}
208 		if (!strcmp(conventionsID, "en_US"))
209 			fDefaultConventionsItem = item;
210 		if (conventions.AreCountrySpecific()
211 			&& currentToplevelItem != NULL
212 			&& currentToplevelItem->Code() == item->Code()) {
213 			if (!strcmp(conventionsID, initialConventions.ID())) {
214 				fConventionsListView->Expand(currentToplevelItem);
215 				fInitialConventionsItem = item;
216 			}
217 			fConventionsListView->AddUnder(item, currentToplevelItem);
218 		} else {
219 			// This conventions-item isn't country-specific, add it at top-level
220 			fConventionsListView->AddItem(item);
221 			item->SetExpanded(false);
222 			currentToplevelItem = item;
223 			if (!strcmp(conventionsID, initialConventions.ID()))
224 				fInitialConventionsItem = item;
225 		}
226 	}
227 
228 	fConventionsListView->FullListSortItems(compare_typed_list_items);
229 	if (fInitialConventionsItem != NULL) {
230 		fConventionsListView->Select(fConventionsListView->IndexOf(
231 			fInitialConventionsItem));
232 	}
233 
234 	fConventionsListView->SetExplicitMinSize(BSize(20 * be_plain_font->Size(),
235 		B_SIZE_UNSET));
236 
237 	fFormatView = new FormatSettingsView();
238 
239 	countryTab->AddChild(BLayoutBuilder::Group<>(B_HORIZONTAL, spacing)
240 		.AddGroup(B_VERTICAL, 3)
241 			.Add(scrollView)
242 			.End()
243 		.Add(fFormatView)
244 		.SetInsets(spacing, spacing, spacing, spacing));
245 
246 	BView* optionsTab = new BView(B_TRANSLATE("Options"), B_WILL_DRAW);
247 	optionsTab->SetLayout(new BGroupLayout(B_VERTICAL, 0));
248 
249 	fFilesystemTranslationCheckbox = new BCheckBox("filesystemTranslation",
250 		B_TRANSLATE("Translate application and folder names in Deskbar and Tracker."),
251 		new BMessage(kMsgFilesystemTranslationChanged));
252 
253 	fFilesystemTranslationCheckbox->SetValue(
254 		BLocaleRoster::Default()->IsFilesystemTranslationPreferred());
255 
256 	optionsTab->AddChild(BLayoutBuilder::Group<>(B_VERTICAL, spacing)
257 		.Add(fFilesystemTranslationCheckbox)
258 		.AddGlue()
259 		.SetInsets(spacing, spacing, spacing, spacing));
260 
261 	tabView->AddTab(languageTab);
262 	tabView->AddTab(countryTab);
263 	tabView->AddTab(optionsTab);
264 
265 	BButton* button
266 		= new BButton(B_TRANSLATE("Defaults"), new BMessage(kMsgDefaults));
267 
268 	fRevertButton
269 		= new BButton(B_TRANSLATE("Revert"), new BMessage(kMsgRevert));
270 	fRevertButton->SetEnabled(false);
271 
272 	BLayoutBuilder::Group<>(this, B_VERTICAL, spacing)
273 		.Add(tabView)
274 		.AddGroup(B_HORIZONTAL, spacing)
275 			.Add(button)
276 			.Add(fRevertButton)
277 			.AddGlue()
278 			.End()
279 		.SetInsets(spacing, spacing, spacing, spacing)
280 		.End();
281 
282 	_Refresh(true);
283 	_SettingsReverted();
284 	CenterOnScreen();
285 }
286 
287 
288 LocaleWindow::~LocaleWindow()
289 {
290 }
291 
292 
293 void
294 LocaleWindow::MessageReceived(BMessage* message)
295 {
296 	switch (message->what) {
297 		case kMsgDefaults:
298 			_Defaults();
299 			break;
300 
301 		case kMsgRevert:
302 		{
303 			_Revert();
304 			fFormatView->Revert();
305 			fConventionsListView->DeselectAll();
306 			if (fInitialConventionsItem != NULL) {
307 				BListItem* superitem
308 					= fConventionsListView->Superitem(fInitialConventionsItem);
309 				if (superitem != NULL)
310 					superitem->SetExpanded(true);
311 				fConventionsListView->Select(fConventionsListView->IndexOf(
312 						fInitialConventionsItem));
313 				fConventionsListView->ScrollToSelection();
314 			}
315 			_SettingsReverted();
316 			break;
317 		}
318 
319 		case kMsgSettingsChanged:
320 			_SettingsChanged();
321 			break;
322 
323 		case kMsgLanguageDragged:
324 		{
325 			void* target = NULL;
326 			if (message->FindPointer("drop_target", &target) != B_OK
327 				|| target != fPreferredListView)
328 				break;
329 
330 			// Add from available languages to preferred languages
331 			int32 dropIndex;
332 			if (message->FindInt32("drop_index", &dropIndex) != B_OK)
333 				dropIndex = fPreferredListView->CountItems();
334 
335 			int32 index = 0;
336 			for (int32 i = 0; message->FindInt32("index", i, &index) == B_OK;
337 					i++) {
338 				LanguageListItem* item = static_cast<LanguageListItem*>(
339 					fLanguageListView->ItemAt(index));
340 				_InsertPreferredLanguage(item, dropIndex++);
341 			}
342 			break;
343 		}
344 		case kMsgLanguageInvoked:
345 		{
346 			int32 index = 0;
347 			for (int32 i = 0; message->FindInt32("index", i, &index) == B_OK;
348 					i++) {
349 				LanguageListItem* item = static_cast<LanguageListItem*>(
350 					fLanguageListView->ItemAt(index));
351 				_InsertPreferredLanguage(item);
352 			}
353 			break;
354 		}
355 
356 		case kMsgPreferredLanguageDragged:
357 		{
358 			void* target = NULL;
359 			if (fPreferredListView->CountItems() == 1
360 				|| message->FindPointer("drop_target", &target) != B_OK)
361 				break;
362 
363 			if (target == fPreferredListView) {
364 				// change ordering
365 				int32 dropIndex = message->FindInt32("drop_index");
366 				int32 index = 0;
367 				for (int32 i = 0;
368 						message->FindInt32("index", i, &index) == B_OK;
369 						i++, dropIndex++) {
370 					if (dropIndex > index) {
371 						dropIndex--;
372 						index -= i;
373 					}
374 					BListItem* item = fPreferredListView->RemoveItem(index);
375 					fPreferredListView->AddItem(item, dropIndex);
376 				}
377 
378 				_PreferredLanguagesChanged();
379 				break;
380 			}
381 
382 			// supposed to fall through - remove item
383 		}
384 		case kMsgPreferredLanguageDeleted:
385 		case kMsgPreferredLanguageInvoked:
386 		{
387 			if (fPreferredListView->CountItems() == 1)
388 				break;
389 
390 			// Remove from preferred languages
391 			int32 index = 0;
392 			for (int32 i = 0; message->FindInt32("index", i, &index) == B_OK;
393 					i++) {
394 				delete fPreferredListView->RemoveItem(index - i);
395 
396 				if (message->what == kMsgPreferredLanguageDeleted) {
397 					int32 count = fPreferredListView->CountItems();
398 					fPreferredListView->Select(
399 						index < count ? index : count - 1);
400 				}
401 			}
402 
403 			_PreferredLanguagesChanged();
404 			break;
405 		}
406 
407 		case kMsgConventionsSelection:
408 		{
409 			// Country selection changed.
410 			// Get the new selected country from the ListView and send it to the
411 			// main app event handler.
412 			void* listView;
413 			if (message->FindPointer("source", &listView) != B_OK)
414 				break;
415 
416 			BListView* conventionsList = static_cast<BListView*>(listView);
417 			if (conventionsList == NULL)
418 				break;
419 
420 			LanguageListItem* item = static_cast<LanguageListItem*>
421 				(conventionsList->ItemAt(conventionsList->CurrentSelection()));
422 			if (item == NULL)
423 				break;
424 
425 			BFormattingConventions conventions(item->ID());
426 			MutableLocaleRoster::Default()->SetDefaultFormattingConventions(
427 				conventions);
428 
429 			_SettingsChanged();
430 			fFormatView->Refresh();
431 			break;
432 		}
433 
434 		case kMsgFilesystemTranslationChanged:
435 		{
436 			MutableLocaleRoster::Default()->SetFilesystemTranslationPreferred(
437 				fFilesystemTranslationCheckbox->Value());
438 
439 			BAlert* alert = new BAlert(B_TRANSLATE("Locale"),
440 				B_TRANSLATE("Deskbar and Tracker need to be restarted for this "
441 				"change to take effect. Would you like to restart them now?"),
442 				B_TRANSLATE("Cancel"), B_TRANSLATE("Restart"), NULL,
443 				B_WIDTH_FROM_WIDEST, B_IDEA_ALERT);
444 			alert->SetShortcut(0, B_ESCAPE);
445 			alert->Go(new BInvoker(new BMessage(kMsgRestartTrackerAndDeskbar),
446 				NULL, be_app));
447 			break;
448 		}
449 
450 		default:
451 			BWindow::MessageReceived(message);
452 			break;
453 	}
454 }
455 
456 
457 bool
458 LocaleWindow::QuitRequested()
459 {
460 	return true;
461 }
462 
463 
464 void
465 LocaleWindow::Show()
466 {
467 	BWindow::Show();
468 
469 	Lock();
470 	if (IsLocked()) {
471 		fConventionsListView->ScrollToSelection();
472 		Unlock();
473 	}
474 }
475 
476 
477 void
478 LocaleWindow::_SettingsChanged()
479 {
480 	bool haveAnythingToRevert = fFormatView->IsReversible() || _IsReversible();
481 	fRevertButton->SetEnabled(haveAnythingToRevert);
482 }
483 
484 
485 void
486 LocaleWindow::_SettingsReverted()
487 {
488 	fRevertButton->SetEnabled(false);
489 }
490 
491 
492 bool
493 LocaleWindow::_IsReversible() const
494 {
495 	BMessage preferredLanguages;
496 	BLocaleRoster::Default()->GetPreferredLanguages(&preferredLanguages);
497 
498 	return !preferredLanguages.HasSameData(fInitialPreferredLanguages);
499 }
500 
501 
502 void
503 LocaleWindow::_PreferredLanguagesChanged()
504 {
505 	BMessage preferredLanguages;
506 	int index = 0;
507 	while (index < fPreferredListView->CountItems()) {
508 		LanguageListItem* item = static_cast<LanguageListItem*>(
509 			fPreferredListView->ItemAt(index));
510 		if (item != NULL)
511 			preferredLanguages.AddString("language", item->ID());
512 		index++;
513 	}
514 	MutableLocaleRoster::Default()->SetPreferredLanguages(&preferredLanguages);
515 
516 	_EnableDisableLanguages();
517 }
518 
519 
520 void
521 LocaleWindow::_EnableDisableLanguages()
522 {
523 	DisableUpdates();
524 
525 	for (int32 i = 0; i < fLanguageListView->FullListCountItems(); i++) {
526 		LanguageListItem* item = static_cast<LanguageListItem*>(
527 			fLanguageListView->FullListItemAt(i));
528 
529 		bool enable = fPreferredListView->ItemForLanguageID(item->ID()) == NULL;
530 		if (item->IsEnabled() != enable) {
531 			item->SetEnabled(enable);
532 
533 			int32 visibleIndex = fLanguageListView->IndexOf(item);
534 			if (visibleIndex >= 0) {
535 				if (!enable)
536 					fLanguageListView->Deselect(visibleIndex);
537 				fLanguageListView->InvalidateItem(visibleIndex);
538 			}
539 		}
540 	}
541 
542 	EnableUpdates();
543 }
544 
545 
546 void
547 LocaleWindow::_Refresh(bool setInitial)
548 {
549 	BMessage preferredLanguages;
550 	BLocaleRoster::Default()->GetPreferredLanguages(&preferredLanguages);
551 	if (setInitial)
552 		fInitialPreferredLanguages = preferredLanguages;
553 
554 	_SetPreferredLanguages(preferredLanguages);
555 }
556 
557 
558 void
559 LocaleWindow::_Revert()
560 {
561 	_SetPreferredLanguages(fInitialPreferredLanguages);
562 }
563 
564 
565 void
566 LocaleWindow::_SetPreferredLanguages(const BMessage& languages)
567 {
568 	DisableUpdates();
569 
570 	// Delete all existing items
571 	for (int32 index = fPreferredListView->CountItems(); index-- > 0; ) {
572 		delete fPreferredListView->ItemAt(index);
573 	}
574 	fPreferredListView->MakeEmpty();
575 
576 	BString languageID;
577 	for (int32 index = 0;
578 		languages.FindString("language", index, &languageID) == B_OK; index++) {
579 		int32 listIndex;
580 		LanguageListItem* item = fLanguageListView->ItemForLanguageID(
581 			languageID.String(), &listIndex);
582 		if (item != NULL) {
583 			// We found the item we were looking for, now copy it to
584 			// the other list
585 			fPreferredListView->AddItem(new LanguageListItem(*item));
586 		}
587 	}
588 
589 	_EnableDisableLanguages();
590 	EnableUpdates();
591 }
592 
593 
594 void
595 LocaleWindow::_InsertPreferredLanguage(LanguageListItem* item, int32 atIndex)
596 {
597 	if (item == NULL || fPreferredListView->ItemForLanguageID(
598 			item->ID().String()) != NULL)
599 		return;
600 
601 	if (atIndex == -1)
602 		atIndex = fPreferredListView->CountItems();
603 
604 	BLanguage language(item->Code());
605 	LanguageListItem* baseItem
606 		= fPreferredListView->ItemForLanguageCode(language.Code(), &atIndex);
607 
608 	DisableUpdates();
609 
610 	fPreferredListView->AddItem(new LanguageListItem(*item), atIndex);
611 
612 	// Replace other languages sharing the same base
613 	if (baseItem != NULL) {
614 		fPreferredListView->RemoveItem(baseItem);
615 		delete baseItem;
616 	}
617 
618 	_PreferredLanguagesChanged();
619 
620 	EnableUpdates();
621 }
622 
623 
624 void
625 LocaleWindow::_Defaults()
626 {
627 	BMessage preferredLanguages;
628 	preferredLanguages.AddString("language", "en");
629 	MutableLocaleRoster::Default()->SetPreferredLanguages(&preferredLanguages);
630 	_SetPreferredLanguages(preferredLanguages);
631 
632 	BFormattingConventions conventions("en_US");
633 	MutableLocaleRoster::Default()->SetDefaultFormattingConventions(
634 		conventions);
635 
636 	fConventionsListView->DeselectAll();
637 	if (fDefaultConventionsItem != NULL) {
638 		BListItem* superitem
639 			= fConventionsListView->Superitem(fDefaultConventionsItem);
640 		if (superitem != NULL && !superitem->IsExpanded())
641 			superitem->SetExpanded(true);
642 		fConventionsListView->Select(fConventionsListView->IndexOf(
643 				fDefaultConventionsItem));
644 		fConventionsListView->ScrollToSelection();
645 	}
646 }
647