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