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 <ControlLook.h> 17 #include <FormattingConventions.h> 18 #include <GroupLayout.h> 19 #include <LayoutBuilder.h> 20 #include <Locale.h> 21 #include <MutableLocaleRoster.h> 22 #include <Screen.h> 23 #include <ScrollView.h> 24 #include <StringView.h> 25 #include <TabView.h> 26 #include <UnicodeChar.h> 27 28 #include "FormatSettingsView.h" 29 #include "LocalePreflet.h" 30 #include "LanguageListView.h" 31 32 33 using BPrivate::gMutableLocaleRoster; 34 35 36 #undef B_TRANSLATE_CONTEXT 37 #define B_TRANSLATE_CONTEXT "Locale Preflet Window" 38 39 40 static const uint32 kMsgLanguageInvoked = 'LaIv'; 41 static const uint32 kMsgLanguageDragged = 'LaDr'; 42 static const uint32 kMsgPreferredLanguageInvoked = 'PLIv'; 43 static const uint32 kMsgPreferredLanguageDragged = 'PLDr'; 44 static const uint32 kMsgPreferredLanguageDeleted = 'PLDl'; 45 static const uint32 kMsgConventionsSelection = 'csel'; 46 static const uint32 kMsgDefaults = 'dflt'; 47 48 static const uint32 kMsgPreferredLanguagesChanged = 'lang'; 49 50 51 static int 52 compare_typed_list_items(const BListItem* _a, const BListItem* _b) 53 { 54 static BCollator collator; 55 56 LanguageListItem* a = (LanguageListItem*)_a; 57 LanguageListItem* b = (LanguageListItem*)_b; 58 59 return collator.Compare(a->Text(), b->Text()); 60 } 61 62 63 // #pragma mark - 64 65 LocaleWindow::LocaleWindow() 66 : 67 BWindow(BRect(0, 0, 0, 0), B_TRANSLATE("Locale"), B_TITLED_WINDOW, 68 B_QUIT_ON_WINDOW_CLOSE | B_ASYNCHRONOUS_CONTROLS 69 | B_AUTO_UPDATE_SIZE_LIMITS), 70 fInitialConventionsItem(NULL), 71 fDefaultConventionsItem(NULL) 72 { 73 SetLayout(new BGroupLayout(B_HORIZONTAL)); 74 75 float spacing = be_control_look->DefaultItemSpacing(); 76 77 BTabView* tabView = new BTabView("tabview"); 78 BGroupView* languageTab = new BGroupView(B_TRANSLATE("Language"), 79 B_HORIZONTAL, spacing); 80 81 // first list: available languages 82 fLanguageListView = new LanguageListView("available", 83 B_MULTIPLE_SELECTION_LIST); 84 BScrollView* scrollView = new BScrollView("scroller", fLanguageListView, 85 B_WILL_DRAW | B_FRAME_EVENTS, true, true); 86 87 fLanguageListView->SetInvocationMessage(new BMessage(kMsgLanguageInvoked)); 88 fLanguageListView->SetDragMessage(new BMessage(kMsgLanguageDragged)); 89 90 BFont font; 91 fLanguageListView->GetFont(&font); 92 93 // Fill the language list from the LocaleRoster data 94 BMessage availableLanguages; 95 if (be_locale_roster->GetAvailableLanguages(&availableLanguages) == B_OK) { 96 BString currentID; 97 LanguageListItem* lastAddedCountryItem = NULL; 98 99 for (int i = 0; availableLanguages.FindString("language", i, ¤tID) 100 == B_OK; i++) { 101 // Now get the human-readable, native name for each language 102 BString name; 103 BLanguage currentLanguage(currentID.String()); 104 currentLanguage.GetNativeName(name); 105 106 // TODO: the following block fails to detect a couple of language 107 // names as containing glyphs we can't render. Why's that? 108 bool hasGlyphs[name.CountChars()]; 109 font.GetHasGlyphs(name.String(), name.CountChars(), hasGlyphs); 110 for (int32 i = 0; i < name.CountChars(); ++i) { 111 if (!hasGlyphs[i]) { 112 // replace by name translated to current language 113 currentLanguage.GetName(name); 114 break; 115 } 116 } 117 118 LanguageListItem* item = new LanguageListItem(name, 119 currentID.String(), currentLanguage.Code(), 120 currentLanguage.CountryCode()); 121 if (currentLanguage.IsCountrySpecific() 122 && lastAddedCountryItem != NULL 123 && lastAddedCountryItem->Code() == item->Code()) { 124 fLanguageListView->AddUnder(item, lastAddedCountryItem); 125 } else { 126 // This is a language variant, add it at top-level 127 fLanguageListView->AddItem(item); 128 if (!currentLanguage.IsCountrySpecific()) { 129 item->SetExpanded(false); 130 lastAddedCountryItem = item; 131 } 132 } 133 } 134 135 fLanguageListView->FullListSortItems(compare_typed_list_items); 136 } else { 137 BAlert* alert = new BAlert("Error", 138 B_TRANSLATE("Unable to find the available languages! You can't " 139 "use this preflet!"), 140 B_TRANSLATE("OK"), NULL, NULL, 141 B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_STOP_ALERT); 142 alert->Go(); 143 } 144 145 // Second list: active languages 146 fPreferredListView = new LanguageListView("preferred", 147 B_MULTIPLE_SELECTION_LIST); 148 BScrollView* scrollViewEnabled = new BScrollView("scroller", 149 fPreferredListView, B_WILL_DRAW | B_FRAME_EVENTS, true, true); 150 151 fPreferredListView->SetInvocationMessage( 152 new BMessage(kMsgPreferredLanguageInvoked)); 153 fPreferredListView->SetDeleteMessage( 154 new BMessage(kMsgPreferredLanguageDeleted)); 155 fPreferredListView->SetDragMessage( 156 new BMessage(kMsgPreferredLanguageDragged)); 157 158 BLayoutBuilder::Group<>(languageTab) 159 .AddGroup(B_VERTICAL, spacing) 160 .Add(new BStringView("", B_TRANSLATE("Available languages"))) 161 .Add(scrollView) 162 .End() 163 .AddGroup(B_VERTICAL, spacing) 164 .Add(new BStringView("", B_TRANSLATE("Preferred languages"))) 165 .Add(scrollViewEnabled) 166 .End() 167 .SetInsets(spacing, spacing, spacing, spacing); 168 169 BView* countryTab = new BView(B_TRANSLATE("Formatting"), B_WILL_DRAW); 170 countryTab->SetLayout(new BGroupLayout(B_VERTICAL, 0)); 171 172 fConventionsListView = new LanguageListView("formatting", 173 B_SINGLE_SELECTION_LIST); 174 scrollView = new BScrollView("scroller", fConventionsListView, 175 B_WILL_DRAW | B_FRAME_EVENTS, true, true); 176 fConventionsListView->SetSelectionMessage( 177 new BMessage(kMsgConventionsSelection)); 178 179 // get all available formatting conventions (by language) 180 BFormattingConventions defaultConventions; 181 be_locale->GetFormattingConventions(&defaultConventions); 182 BString conventionID; 183 fInitialConventionsItem = NULL; 184 LanguageListItem* lastAddedConventionsItem = NULL; 185 for (int i = 0; 186 availableLanguages.FindString("language", i, &conventionID) == B_OK; 187 i++) { 188 BFormattingConventions convention(conventionID); 189 BString conventionName; 190 convention.GetName(conventionName); 191 192 LanguageListItem* item = new LanguageListItem(conventionName, 193 conventionID, convention.LanguageCode(), convention.CountryCode()); 194 if (!strcmp(conventionID, "en_US")) 195 fDefaultConventionsItem = item; 196 if (conventionID.FindFirst('_') >= 0 197 && lastAddedConventionsItem != NULL 198 && lastAddedConventionsItem->Code() == item->Code()) { 199 if (!strcmp(conventionID, defaultConventions.ID())) { 200 fConventionsListView->Expand(lastAddedConventionsItem); 201 fInitialConventionsItem = item; 202 } 203 fConventionsListView->AddUnder(item, lastAddedConventionsItem); 204 } else { 205 // This conventions-item isn't country-specific, add it at top-level 206 fConventionsListView->AddItem(item); 207 if (conventionID.FindFirst('_') < 0) { 208 item->SetExpanded(false); 209 lastAddedConventionsItem = item; 210 } 211 if (!strcmp(conventionID, defaultConventions.ID())) 212 fInitialConventionsItem = item; 213 } 214 } 215 216 fConventionsListView->FullListSortItems(compare_typed_list_items); 217 if (fInitialConventionsItem != NULL) { 218 fConventionsListView->Select(fConventionsListView->IndexOf( 219 fInitialConventionsItem)); 220 } 221 222 fConventionsListView->SetExplicitMinSize(BSize(20 * be_plain_font->Size(), 223 B_SIZE_UNSET)); 224 225 fFormatView = new FormatSettingsView(); 226 227 countryTab->AddChild(BLayoutBuilder::Group<>(B_HORIZONTAL, spacing) 228 .AddGroup(B_VERTICAL, 3) 229 .Add(scrollView) 230 .End() 231 .Add(fFormatView) 232 .SetInsets(spacing, spacing, spacing, spacing)); 233 234 tabView->AddTab(languageTab); 235 tabView->AddTab(countryTab); 236 237 BButton* button 238 = new BButton(B_TRANSLATE("Defaults"), new BMessage(kMsgDefaults)); 239 240 fRevertButton 241 = new BButton(B_TRANSLATE("Revert"), new BMessage(kMsgRevert)); 242 fRevertButton->SetEnabled(false); 243 244 BLayoutBuilder::Group<>(this, B_VERTICAL, spacing) 245 .Add(tabView) 246 .AddGroup(B_HORIZONTAL, spacing) 247 .Add(button) 248 .Add(fRevertButton) 249 .AddGlue() 250 .End() 251 .SetInsets(spacing, spacing, spacing, spacing) 252 .End(); 253 254 _Refresh(true); 255 _SettingsReverted(); 256 CenterOnScreen(); 257 } 258 259 260 LocaleWindow::~LocaleWindow() 261 { 262 } 263 264 265 void 266 LocaleWindow::MessageReceived(BMessage* message) 267 { 268 switch (message->what) { 269 case kMsgDefaults: 270 _Defaults(); 271 break; 272 273 case kMsgRevert: 274 { 275 _Revert(); 276 fFormatView->Revert(); 277 fConventionsListView->DeselectAll(); 278 if (fInitialConventionsItem != NULL) { 279 BListItem* superitem 280 = fConventionsListView->Superitem(fInitialConventionsItem); 281 if (superitem != NULL) 282 superitem->SetExpanded(true); 283 fConventionsListView->Select(fConventionsListView->IndexOf( 284 fInitialConventionsItem)); 285 fConventionsListView->ScrollToSelection(); 286 } 287 _SettingsReverted(); 288 break; 289 } 290 291 case kMsgSettingsChanged: 292 _SettingsChanged(); 293 break; 294 295 case kMsgLanguageDragged: 296 { 297 void* target = NULL; 298 if (message->FindPointer("drop_target", &target) != B_OK 299 || target != fPreferredListView) 300 break; 301 302 // Add from available languages to preferred languages 303 int32 dropIndex; 304 if (message->FindInt32("drop_index", &dropIndex) != B_OK) 305 dropIndex = fPreferredListView->CountItems(); 306 307 int32 index = 0; 308 for (int32 i = 0; message->FindInt32("index", i, &index) == B_OK; 309 i++) { 310 LanguageListItem* item = static_cast<LanguageListItem*>( 311 fLanguageListView->FullListItemAt(index)); 312 _InsertPreferredLanguage(item, dropIndex++); 313 } 314 break; 315 } 316 case kMsgLanguageInvoked: 317 { 318 int32 index = 0; 319 for (int32 i = 0; message->FindInt32("index", i, &index) == B_OK; 320 i++) { 321 LanguageListItem* item = static_cast<LanguageListItem*>( 322 fLanguageListView->ItemAt(index)); 323 _InsertPreferredLanguage(item); 324 } 325 break; 326 } 327 328 case kMsgPreferredLanguageDragged: 329 { 330 void* target = NULL; 331 if (fPreferredListView->CountItems() == 1 332 || message->FindPointer("drop_target", &target) != B_OK) 333 break; 334 335 if (target == fPreferredListView) { 336 // change ordering 337 int32 dropIndex = message->FindInt32("drop_index"); 338 int32 index = 0; 339 if (message->FindInt32("index", &index) == B_OK 340 && dropIndex != index) { 341 BListItem* item = fPreferredListView->RemoveItem(index); 342 if (dropIndex > index) 343 index--; 344 fPreferredListView->AddItem(item, dropIndex); 345 346 _PreferredLanguagesChanged(); 347 } 348 break; 349 } 350 351 // supposed to fall through - remove item 352 } 353 case kMsgPreferredLanguageDeleted: 354 case kMsgPreferredLanguageInvoked: 355 { 356 if (fPreferredListView->CountItems() == 1) 357 break; 358 359 // Remove from preferred languages 360 int32 index = 0; 361 if (message->FindInt32("index", &index) == B_OK) { 362 delete fPreferredListView->RemoveItem(index); 363 _PreferredLanguagesChanged(); 364 365 if (message->what == kMsgPreferredLanguageDeleted) 366 fPreferredListView->Select(index); 367 } 368 break; 369 } 370 371 case kMsgConventionsSelection: 372 { 373 // Country selection changed. 374 // Get the new selected country from the ListView and send it to the 375 // main app event handler. 376 void* listView; 377 if (message->FindPointer("source", &listView) != B_OK) 378 break; 379 380 BListView* conventionsList = static_cast<BListView*>(listView); 381 382 LanguageListItem* item = static_cast<LanguageListItem*> 383 (conventionsList->ItemAt(conventionsList->CurrentSelection())); 384 BFormattingConventions conventions(item->ID()); 385 gMutableLocaleRoster->SetDefaultFormattingConventions(conventions); 386 387 _SettingsChanged(); 388 fFormatView->Refresh(); 389 break; 390 } 391 392 default: 393 BWindow::MessageReceived(message); 394 break; 395 } 396 } 397 398 399 bool 400 LocaleWindow::QuitRequested() 401 { 402 return true; 403 } 404 405 406 void 407 LocaleWindow::Show() 408 { 409 BWindow::Show(); 410 411 Lock(); 412 if (IsLocked()) { 413 fConventionsListView->ScrollToSelection(); 414 Unlock(); 415 } 416 } 417 418 419 void 420 LocaleWindow::_SettingsChanged() 421 { 422 bool haveAnythingToRevert = fFormatView->IsReversible() || _IsReversible(); 423 fRevertButton->SetEnabled(haveAnythingToRevert); 424 } 425 426 427 void 428 LocaleWindow::_SettingsReverted() 429 { 430 fRevertButton->SetEnabled(false); 431 } 432 433 434 bool 435 LocaleWindow::_IsReversible() const 436 { 437 BMessage preferredLanguages; 438 be_locale_roster->GetPreferredLanguages(&preferredLanguages); 439 440 return !preferredLanguages.HasSameData(fInitialPreferredLanguages); 441 } 442 443 444 void 445 LocaleWindow::_PreferredLanguagesChanged() 446 { 447 BMessage preferredLanguages; 448 int index = 0; 449 while (index < fPreferredListView->FullListCountItems()) { 450 // only include subitems: we can guess the superitem from them anyway 451 LanguageListItem* item = static_cast<LanguageListItem*>( 452 fPreferredListView->FullListItemAt(index)); 453 if (item != NULL) 454 preferredLanguages.AddString("language", item->ID()); 455 index++; 456 } 457 gMutableLocaleRoster->SetPreferredLanguages(&preferredLanguages); 458 459 _EnableDisableLanguages(); 460 } 461 462 463 void 464 LocaleWindow::_EnableDisableLanguages() 465 { 466 DisableUpdates(); 467 468 for (int32 i = 0; i < fLanguageListView->FullListCountItems(); i++) { 469 LanguageListItem* item = static_cast<LanguageListItem*>( 470 fLanguageListView->FullListItemAt(i)); 471 472 bool enable = fPreferredListView->ItemForLanguageID(item->ID()) == NULL; 473 if (item->IsEnabled() != enable) { 474 item->SetEnabled(enable); 475 476 int32 visibleIndex = fLanguageListView->IndexOf(item); 477 if (visibleIndex >= 0) { 478 if (!enable) 479 fLanguageListView->Deselect(visibleIndex); 480 fLanguageListView->InvalidateItem(visibleIndex); 481 } 482 } 483 } 484 485 EnableUpdates(); 486 } 487 488 489 void 490 LocaleWindow::_Refresh(bool setInitial) 491 { 492 BMessage preferredLanguages; 493 be_locale_roster->GetPreferredLanguages(&preferredLanguages); 494 if (setInitial) 495 fInitialPreferredLanguages = preferredLanguages; 496 497 _SetPreferredLanguages(preferredLanguages); 498 } 499 500 501 void 502 LocaleWindow::_Revert() 503 { 504 _SetPreferredLanguages(fInitialPreferredLanguages); 505 } 506 507 508 void 509 LocaleWindow::_SetPreferredLanguages(const BMessage& languages) 510 { 511 DisableUpdates(); 512 513 // Delete all existing items 514 for (int32 index = fPreferredListView->CountItems(); index-- > 0; ) { 515 delete fPreferredListView->ItemAt(index); 516 } 517 fPreferredListView->MakeEmpty(); 518 519 BString languageID; 520 for (int32 index = 0; 521 languages.FindString("language", index, &languageID) == B_OK; index++) { 522 int32 listIndex; 523 LanguageListItem* item = fLanguageListView->ItemForLanguageID( 524 languageID.String(), &listIndex); 525 if (item != NULL) { 526 // We found the item we were looking for, now copy it to 527 // the other list 528 fPreferredListView->AddItem(new LanguageListItem(*item)); 529 } 530 } 531 532 _EnableDisableLanguages(); 533 EnableUpdates(); 534 } 535 536 537 void 538 LocaleWindow::_InsertPreferredLanguage(LanguageListItem* item, int32 atIndex) 539 { 540 if (item == NULL || fPreferredListView->ItemForLanguageID( 541 item->ID().String()) != NULL) 542 return; 543 544 if (atIndex == -1) 545 atIndex = fPreferredListView->CountItems(); 546 547 BLanguage language(item->Code()); 548 LanguageListItem* baseItem 549 = fPreferredListView->ItemForLanguageCode(language.Code(), &atIndex); 550 551 DisableUpdates(); 552 553 fPreferredListView->AddItem(new LanguageListItem(*item), atIndex); 554 555 // Replace other languages sharing the same base 556 if (baseItem != NULL) { 557 fPreferredListView->RemoveItem(baseItem); 558 delete baseItem; 559 } 560 561 _PreferredLanguagesChanged(); 562 563 EnableUpdates(); 564 } 565 566 567 void 568 LocaleWindow::_Defaults() 569 { 570 BMessage preferredLanguages; 571 preferredLanguages.AddString("language", "en"); 572 gMutableLocaleRoster->SetPreferredLanguages(&preferredLanguages); 573 _SetPreferredLanguages(preferredLanguages); 574 575 BFormattingConventions conventions("en_US"); 576 gMutableLocaleRoster->SetDefaultFormattingConventions(conventions); 577 578 fConventionsListView->DeselectAll(); 579 if (fDefaultConventionsItem != NULL) { 580 BListItem* superitem 581 = fConventionsListView->Superitem(fDefaultConventionsItem); 582 if (superitem != NULL && !superitem->IsExpanded()) 583 superitem->SetExpanded(true); 584 fConventionsListView->Select(fConventionsListView->IndexOf( 585 fDefaultConventionsItem)); 586 fConventionsListView->ScrollToSelection(); 587 } 588 } 589 590