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