xref: /haiku/src/preferences/locale/LocaleWindow.cpp (revision a127b88ecbfab58f64944c98aa47722a18e363b2)
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 
35 using BPrivate::MutableLocaleRoster;
36 
37 
38 #undef B_TRANSLATION_CONTEXT
39 #define B_TRANSLATION_CONTEXT "Locale Preflet Window"
40 
41 
42 static const uint32 kMsgLanguageInvoked = 'LaIv';
43 static const uint32 kMsgLanguageDragged = 'LaDr';
44 static const uint32 kMsgPreferredLanguageInvoked = 'PLIv';
45 static const uint32 kMsgPreferredLanguageDragged = 'PLDr';
46 static const uint32 kMsgPreferredLanguageDeleted = 'PLDl';
47 static const uint32 kMsgConventionsSelection = 'csel';
48 static const uint32 kMsgDefaults = 'dflt';
49 
50 static const uint32 kMsgPreferredLanguagesChanged = 'lang';
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 {
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 
283 LocaleWindow::~LocaleWindow()
284 {
285 }
286 
287 
288 void
289 LocaleWindow::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 
461 bool
462 LocaleWindow::QuitRequested()
463 {
464 	return true;
465 }
466 
467 
468 void
469 LocaleWindow::Show()
470 {
471 	BWindow::Show();
472 
473 	Lock();
474 	if (IsLocked()) {
475 		fConventionsListView->ScrollToSelection();
476 		Unlock();
477 	}
478 }
479 
480 
481 void
482 LocaleWindow::_SettingsChanged()
483 {
484 	fRevertButton->SetEnabled(fFormatView->IsReversible() || _IsReversible());
485 }
486 
487 
488 void
489 LocaleWindow::_SettingsReverted()
490 {
491 	fRevertButton->SetEnabled(false);
492 }
493 
494 
495 bool
496 LocaleWindow::_IsReversible() const
497 {
498 	BMessage preferredLanguages;
499 	BLocaleRoster::Default()->GetPreferredLanguages(&preferredLanguages);
500 
501 	return !preferredLanguages.HasSameData(fInitialPreferredLanguages);
502 }
503 
504 
505 void
506 LocaleWindow::_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 
523 void
524 LocaleWindow::_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 
549 void
550 LocaleWindow::_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 
561 void
562 LocaleWindow::_Revert()
563 {
564 	_SetPreferredLanguages(fInitialPreferredLanguages);
565 }
566 
567 
568 void
569 LocaleWindow::_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 
597 void
598 LocaleWindow::_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 
627 void
628 LocaleWindow::_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