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