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