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