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
compare_typed_list_items(const BListItem * _a,const BListItem * _b)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
LocaleWindow()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, ¤tID)
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
~LocaleWindow()283 LocaleWindow::~LocaleWindow()
284 {
285 }
286
287
288 void
MessageReceived(BMessage * message)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
QuitRequested()462 LocaleWindow::QuitRequested()
463 {
464 return true;
465 }
466
467
468 void
Show()469 LocaleWindow::Show()
470 {
471 BWindow::Show();
472
473 Lock();
474 if (IsLocked()) {
475 fConventionsListView->ScrollToSelection();
476 Unlock();
477 }
478 }
479
480
481 void
_SettingsChanged()482 LocaleWindow::_SettingsChanged()
483 {
484 fRevertButton->SetEnabled(fFormatView->IsReversible() || _IsReversible());
485 }
486
487
488 void
_SettingsReverted()489 LocaleWindow::_SettingsReverted()
490 {
491 fRevertButton->SetEnabled(false);
492 }
493
494
495 bool
_IsReversible() const496 LocaleWindow::_IsReversible() const
497 {
498 BMessage preferredLanguages;
499 BLocaleRoster::Default()->GetPreferredLanguages(&preferredLanguages);
500
501 return !preferredLanguages.HasSameData(fInitialPreferredLanguages);
502 }
503
504
505 void
_PreferredLanguagesChanged()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
_EnableDisableLanguages()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
_Refresh(bool setInitial)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
_Revert()562 LocaleWindow::_Revert()
563 {
564 _SetPreferredLanguages(fInitialPreferredLanguages);
565 }
566
567
568 void
_SetPreferredLanguages(const BMessage & languages)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
_InsertPreferredLanguage(LanguageListItem * item,int32 atIndex)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
_Defaults()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