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 i = 0; 354 for (int32 index = 0; 355 message->FindInt32("index", i, &index) == B_OK; i++) { 356 LanguageListItem* item = static_cast<LanguageListItem*>( 357 fLanguageListView->ItemAt(index)); 358 _InsertPreferredLanguage(item, dropIndex++); 359 } 360 361 fPreferredListView->Select(dropIndex - i, dropIndex - 1); 362 break; 363 } 364 365 case kMsgLanguageInvoked: 366 { 367 int32 index = 0; 368 for (int32 i = 0; message->FindInt32("index", i, &index) == B_OK; 369 i++) { 370 LanguageListItem* item = static_cast<LanguageListItem*>( 371 fLanguageListView->ItemAt(index)); 372 _InsertPreferredLanguage(item); 373 } 374 break; 375 } 376 377 case kMsgPreferredLanguageDragged: 378 { 379 void* target = NULL; 380 if (fPreferredListView->CountItems() == 1 381 || message->FindPointer("drop_target", &target) != B_OK) 382 break; 383 384 if (target == fPreferredListView) { 385 // change ordering 386 int32 dropIndex = message->FindInt32("drop_index"); 387 int32 i = 0; 388 for (int32 index = 0; 389 message->FindInt32("index", i, &index) == B_OK; 390 i++, dropIndex++) { 391 if (dropIndex > index) { 392 dropIndex--; 393 index -= i; 394 } 395 BListItem* item = fPreferredListView->RemoveItem(index); 396 fPreferredListView->AddItem(item, dropIndex); 397 } 398 399 fPreferredListView->Select(dropIndex - i, dropIndex - 1); 400 _PreferredLanguagesChanged(); 401 break; 402 } 403 404 // supposed to fall through - remove item 405 } 406 case kMsgPreferredLanguageDeleted: 407 case kMsgPreferredLanguageInvoked: 408 { 409 if (fPreferredListView->CountItems() == 1) 410 break; 411 412 // Remove from preferred languages 413 int32 index = 0; 414 for (int32 i = 0; message->FindInt32("index", i, &index) == B_OK; 415 i++) { 416 delete fPreferredListView->RemoveItem(index - i); 417 418 if (message->what == kMsgPreferredLanguageDeleted) { 419 int32 count = fPreferredListView->CountItems(); 420 fPreferredListView->Select( 421 index < count ? index : count - 1); 422 } 423 } 424 425 _PreferredLanguagesChanged(); 426 break; 427 } 428 429 case kMsgConventionsSelection: 430 { 431 // Country selection changed. 432 // Get the new selected country from the ListView and send it to the 433 // main app event handler. 434 void* listView; 435 if (message->FindPointer("source", &listView) != B_OK) 436 break; 437 438 BListView* conventionsList = static_cast<BListView*>(listView); 439 if (conventionsList == NULL) 440 break; 441 442 LanguageListItem* item = static_cast<LanguageListItem*> 443 (conventionsList->ItemAt(conventionsList->CurrentSelection())); 444 if (item == NULL) 445 break; 446 447 BFormattingConventions conventions(item->ID()); 448 MutableLocaleRoster::Default()->SetDefaultFormattingConventions( 449 conventions); 450 451 _SettingsChanged(); 452 fFormatView->Refresh(); 453 break; 454 } 455 456 case kMsgFilesystemTranslationChanged: 457 { 458 MutableLocaleRoster::Default()->SetFilesystemTranslationPreferred( 459 fFilesystemTranslationCheckbox->Value()); 460 461 BAlert* alert = new BAlert(B_TRANSLATE("Locale"), 462 B_TRANSLATE("Deskbar and Tracker need to be restarted for this " 463 "change to take effect. Would you like to restart them now?"), 464 B_TRANSLATE("Cancel"), B_TRANSLATE("Restart"), NULL, 465 B_WIDTH_FROM_WIDEST, B_IDEA_ALERT); 466 alert->SetShortcut(0, B_ESCAPE); 467 alert->Go(new BInvoker(new BMessage(kMsgRestartTrackerAndDeskbar), 468 NULL, be_app)); 469 break; 470 } 471 472 default: 473 BWindow::MessageReceived(message); 474 break; 475 } 476 } 477 478 479 bool 480 LocaleWindow::QuitRequested() 481 { 482 return true; 483 } 484 485 486 void 487 LocaleWindow::Show() 488 { 489 BWindow::Show(); 490 491 Lock(); 492 if (IsLocked()) { 493 fConventionsListView->ScrollToSelection(); 494 Unlock(); 495 } 496 } 497 498 499 void 500 LocaleWindow::_SettingsChanged() 501 { 502 fRevertButton->SetEnabled(fFormatView->IsReversible() || _IsReversible()); 503 } 504 505 506 void 507 LocaleWindow::_SettingsReverted() 508 { 509 fRevertButton->SetEnabled(false); 510 } 511 512 513 bool 514 LocaleWindow::_IsReversible() const 515 { 516 BMessage preferredLanguages; 517 BLocaleRoster::Default()->GetPreferredLanguages(&preferredLanguages); 518 519 return !preferredLanguages.HasSameData(fInitialPreferredLanguages); 520 } 521 522 523 void 524 LocaleWindow::_PreferredLanguagesChanged() 525 { 526 BMessage preferredLanguages; 527 int index = 0; 528 while (index < fPreferredListView->CountItems()) { 529 LanguageListItem* item = static_cast<LanguageListItem*>( 530 fPreferredListView->ItemAt(index)); 531 if (item != NULL) 532 preferredLanguages.AddString("language", item->ID()); 533 index++; 534 } 535 MutableLocaleRoster::Default()->SetPreferredLanguages(&preferredLanguages); 536 537 _EnableDisableLanguages(); 538 } 539 540 541 void 542 LocaleWindow::_EnableDisableLanguages() 543 { 544 DisableUpdates(); 545 546 for (int32 i = 0; i < fLanguageListView->FullListCountItems(); i++) { 547 LanguageListItem* item = static_cast<LanguageListItem*>( 548 fLanguageListView->FullListItemAt(i)); 549 550 bool enable = fPreferredListView->ItemForLanguageID(item->ID()) == NULL; 551 if (item->IsEnabled() != enable) { 552 item->SetEnabled(enable); 553 554 int32 visibleIndex = fLanguageListView->IndexOf(item); 555 if (visibleIndex >= 0) { 556 if (!enable) 557 fLanguageListView->Deselect(visibleIndex); 558 fLanguageListView->InvalidateItem(visibleIndex); 559 } 560 } 561 } 562 563 EnableUpdates(); 564 } 565 566 567 void 568 LocaleWindow::_Refresh(bool setInitial) 569 { 570 BMessage preferredLanguages; 571 BLocaleRoster::Default()->GetPreferredLanguages(&preferredLanguages); 572 if (setInitial) 573 fInitialPreferredLanguages = preferredLanguages; 574 575 _SetPreferredLanguages(preferredLanguages); 576 } 577 578 579 void 580 LocaleWindow::_Revert() 581 { 582 _SetPreferredLanguages(fInitialPreferredLanguages); 583 } 584 585 586 void 587 LocaleWindow::_SetPreferredLanguages(const BMessage& languages) 588 { 589 DisableUpdates(); 590 591 // Delete all existing items 592 for (int32 index = fPreferredListView->CountItems(); index-- > 0; ) { 593 delete fPreferredListView->ItemAt(index); 594 } 595 fPreferredListView->MakeEmpty(); 596 597 BString languageID; 598 for (int32 index = 0; 599 languages.FindString("language", index, &languageID) == B_OK; index++) { 600 int32 listIndex; 601 LanguageListItem* item = fLanguageListView->ItemForLanguageID( 602 languageID.String(), &listIndex); 603 if (item != NULL) { 604 // We found the item we were looking for, now copy it to 605 // the other list 606 fPreferredListView->AddItem(new LanguageListItem(*item)); 607 } 608 } 609 610 _EnableDisableLanguages(); 611 EnableUpdates(); 612 } 613 614 615 void 616 LocaleWindow::_InsertPreferredLanguage(LanguageListItem* item, int32 atIndex) 617 { 618 if (item == NULL || fPreferredListView->ItemForLanguageID( 619 item->ID().String()) != NULL) 620 return; 621 622 if (atIndex == -1) 623 atIndex = fPreferredListView->CountItems(); 624 625 BLanguage language(item->Code()); 626 LanguageListItem* baseItem 627 = fPreferredListView->ItemForLanguageCode(language.Code(), &atIndex); 628 629 DisableUpdates(); 630 631 fPreferredListView->AddItem(new LanguageListItem(*item), atIndex); 632 633 // Replace other languages sharing the same base 634 if (baseItem != NULL) { 635 fPreferredListView->RemoveItem(baseItem); 636 delete baseItem; 637 } 638 639 _PreferredLanguagesChanged(); 640 641 EnableUpdates(); 642 } 643 644 645 void 646 LocaleWindow::_Defaults() 647 { 648 BMessage preferredLanguages; 649 preferredLanguages.AddString("language", "en"); 650 MutableLocaleRoster::Default()->SetPreferredLanguages(&preferredLanguages); 651 _SetPreferredLanguages(preferredLanguages); 652 653 BFormattingConventions conventions("en_US"); 654 MutableLocaleRoster::Default()->SetDefaultFormattingConventions( 655 conventions); 656 657 fConventionsListView->DeselectAll(); 658 if (fDefaultConventionsItem != NULL) { 659 BListItem* superitem 660 = fConventionsListView->Superitem(fDefaultConventionsItem); 661 if (superitem != NULL && !superitem->IsExpanded()) 662 fConventionsListView->Expand(superitem); 663 fConventionsListView->Select(fConventionsListView->IndexOf( 664 fDefaultConventionsItem)); 665 fConventionsListView->ScrollToSelection(); 666 } 667 } 668