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 "Locale.h" 9 #include "LocaleWindow.h" 10 #include "LanguageListView.h" 11 12 #include <iostream> 13 14 #include <Alert.h> 15 #include <Application.h> 16 #include <Button.h> 17 #include <Catalog.h> 18 #include <ControlLook.h> 19 #include <GroupLayout.h> 20 #include <LayoutBuilder.h> 21 #include <Locale.h> 22 #include <LocaleRoster.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 31 32 #undef B_TRANSLATE_CONTEXT 33 #define B_TRANSLATE_CONTEXT "Locale Preflet Window" 34 35 36 static const uint32 kMsgLanguageInvoked = 'LaIv'; 37 static const uint32 kMsgLanguageDragged = 'LaDr'; 38 static const uint32 kMsgPreferredLanguageInvoked = 'PLIv'; 39 static const uint32 kMsgPreferredLanguageDragged = 'PLDr'; 40 static const uint32 kMsgPreferredLanguageDeleted = 'PLDl'; 41 static const uint32 kMsgCountrySelection = 'csel'; 42 static const uint32 kMsgDefaults = 'dflt'; 43 44 static const uint32 kMsgPreferredLanguagesChanged = 'lang'; 45 46 47 static int 48 compare_typed_list_items(const BListItem* _a, const BListItem* _b) 49 { 50 static BCollator collator; 51 52 LanguageListItem* a = (LanguageListItem*)_a; 53 LanguageListItem* b = (LanguageListItem*)_b; 54 55 return collator.Compare(a->Text(), b->Text()); 56 } 57 58 59 static int 60 compare_void_list_items(const void* _a, const void* _b) 61 { 62 static BCollator collator; 63 64 LanguageListItem* a = *(LanguageListItem**)_a; 65 LanguageListItem* b = *(LanguageListItem**)_b; 66 67 return collator.Compare(a->Text(), b->Text()); 68 } 69 70 71 // #pragma mark - 72 73 74 LocaleWindow::LocaleWindow() 75 : 76 BWindow(BRect(0, 0, 0, 0), "Locale", B_TITLED_WINDOW, B_QUIT_ON_WINDOW_CLOSE 77 | B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS) 78 { 79 BLocale defaultLocale; 80 be_locale_roster->GetDefaultLocale(&defaultLocale); 81 82 SetLayout(new BGroupLayout(B_HORIZONTAL)); 83 84 float spacing = be_control_look->DefaultItemSpacing(); 85 86 BTabView* tabView = new BTabView("tabview"); 87 BGroupView* languageTab = new BGroupView(B_TRANSLATE("Language"), 88 B_HORIZONTAL, spacing); 89 90 // first list: available languages 91 fLanguageListView = new LanguageListView("available", 92 B_MULTIPLE_SELECTION_LIST); 93 BScrollView* scrollView = new BScrollView("scroller", fLanguageListView, 94 B_WILL_DRAW | B_FRAME_EVENTS, false, true); 95 96 fLanguageListView->SetInvocationMessage(new BMessage(kMsgLanguageInvoked)); 97 fLanguageListView->SetDragMessage(new BMessage(kMsgLanguageDragged)); 98 99 // Fill the language list from the LocaleRoster data 100 BMessage installedLanguages; 101 if (be_locale_roster->GetInstalledLanguages(&installedLanguages) == B_OK) { 102 BString currentID; 103 LanguageListItem* lastAddedCountryItem = NULL; 104 105 for (int i = 0; installedLanguages.FindString("langs", i, ¤tID) 106 == B_OK; i++) { 107 // Now get an human-readable, localized name for each language 108 BLanguage* currentLanguage; 109 be_locale_roster->GetLanguage(currentID.String(), 110 ¤tLanguage); 111 112 BString name; 113 currentLanguage->GetName(name); 114 115 // TODO: as long as the app_server doesn't support font overlays, 116 // use the translated name if problematic characters are used... 117 const char* string = name.String(); 118 while (uint32 code = BUnicodeChar::FromUTF8(&string)) { 119 if (code > 1424) { 120 currentLanguage->GetTranslatedName(name); 121 break; 122 } 123 } 124 125 LanguageListItem* item = new LanguageListItem(name, 126 currentID.String(), currentLanguage->Code()); 127 if (currentLanguage->IsCountrySpecific() 128 && lastAddedCountryItem != NULL 129 && lastAddedCountryItem->Code() == item->Code()) { 130 fLanguageListView->AddUnder(item, lastAddedCountryItem); 131 } else { 132 // This is a language variant, add it at top-level 133 fLanguageListView->AddItem(item); 134 if (!currentLanguage->IsCountrySpecific()) { 135 item->SetExpanded(false); 136 lastAddedCountryItem = item; 137 } 138 } 139 140 delete currentLanguage; 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, false, 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("Country"), B_WILL_DRAW); 178 countryTab->SetLayout(new BGroupLayout(B_VERTICAL, 0)); 179 180 BListView* listView = new BListView("country", B_SINGLE_SELECTION_LIST); 181 scrollView = new BScrollView("scroller", listView, 182 B_WILL_DRAW | B_FRAME_EVENTS, false, true); 183 listView->SetSelectionMessage(new BMessage(kMsgCountrySelection)); 184 185 // get all available countries 186 BMessage countryList; 187 be_locale_roster->GetInstalledLanguages(&countryList); 188 BString countryCode; 189 190 LanguageListItem* currentItem = NULL; 191 for (int i = 0; countryList.FindString("langs", i, &countryCode) == B_OK; 192 i++) { 193 BLocale locale(countryCode); 194 BString countryName; 195 196 locale.GetName(countryName); 197 198 LanguageListItem* item 199 = new LanguageListItem(countryName, countryCode, 200 NULL); 201 listView->AddItem(item); 202 if (!strcmp(countryCode, defaultLocale.Code())) 203 currentItem = item; 204 } 205 206 listView->SortItems(compare_void_list_items); 207 if (currentItem != NULL) 208 listView->Select(listView->IndexOf(currentItem)); 209 210 // TODO: find a real solution intead of this hack 211 listView->SetExplicitMinSize( 212 BSize(25 * be_plain_font->Size(), B_SIZE_UNSET)); 213 214 fFormatView = new FormatView(defaultLocale); 215 216 countryTab->AddChild(BLayoutBuilder::Group<>(B_HORIZONTAL, spacing) 217 .AddGroup(B_VERTICAL, 3) 218 .Add(scrollView) 219 .End() 220 .Add(fFormatView) 221 .SetInsets(spacing, spacing, spacing, spacing)); 222 223 listView->ScrollToSelection(); 224 225 tabView->AddTab(languageTab); 226 tabView->AddTab(countryTab); 227 228 BButton* button = new BButton(B_TRANSLATE("Defaults"), 229 new BMessage(kMsgDefaults)); 230 231 fRevertButton = new BButton(B_TRANSLATE("Revert"), 232 new BMessage(kMsgRevert)); 233 fRevertButton->SetEnabled(false); 234 235 BLayoutBuilder::Group<>(this, B_VERTICAL, spacing) 236 .Add(tabView) 237 .AddGroup(B_HORIZONTAL, spacing) 238 .Add(button) 239 .Add(fRevertButton) 240 .AddGlue() 241 .End() 242 .SetInsets(spacing, spacing, spacing, spacing) 243 .End(); 244 245 _UpdatePreferredFromLocaleRoster(); 246 SettingsReverted(); 247 CenterOnScreen(); 248 } 249 250 251 LocaleWindow::~LocaleWindow() 252 { 253 } 254 255 256 void 257 LocaleWindow::MessageReceived(BMessage* message) 258 { 259 switch (message->what) { 260 case kMsgDefaults: 261 _Defaults(); 262 break; 263 264 case kMsgRevert: 265 be_app_messenger.SendMessage(message); 266 _UpdatePreferredFromLocaleRoster(); 267 break; 268 269 case kMsgLanguageDragged: 270 { 271 void* target = NULL; 272 if (message->FindPointer("drop_target", &target) != B_OK 273 || target != fPreferredListView) 274 break; 275 276 // Add from available languages to preferred languages 277 int32 dropIndex; 278 if (message->FindInt32("drop_index", &dropIndex) != B_OK) 279 dropIndex = fPreferredListView->CountItems(); 280 281 int32 index = 0; 282 for (int32 i = 0; message->FindInt32("index", i, &index) == B_OK; 283 i++) { 284 LanguageListItem* item = static_cast<LanguageListItem*>( 285 fLanguageListView->FullListItemAt(index)); 286 _InsertPreferredLanguage(item, dropIndex++); 287 } 288 break; 289 } 290 case kMsgLanguageInvoked: 291 { 292 int32 index = 0; 293 for (int32 i = 0; message->FindInt32("index", i, &index) == B_OK; 294 i++) { 295 LanguageListItem* item = static_cast<LanguageListItem*>( 296 fLanguageListView->ItemAt(index)); 297 _InsertPreferredLanguage(item); 298 } 299 break; 300 } 301 302 case kMsgPreferredLanguageDragged: 303 { 304 void* target = NULL; 305 if (fPreferredListView->CountItems() == 1 306 || message->FindPointer("drop_target", &target) != B_OK) 307 break; 308 309 if (target == fPreferredListView) { 310 // change ordering 311 int32 dropIndex = message->FindInt32("drop_index"); 312 int32 index = 0; 313 if (message->FindInt32("index", &index) == B_OK 314 && dropIndex != index) { 315 BListItem* item = fPreferredListView->RemoveItem(index); 316 if (dropIndex > index) 317 index--; 318 fPreferredListView->AddItem(item, dropIndex); 319 320 _PreferredLanguagesChanged(); 321 } 322 break; 323 } 324 325 // supposed to fall through - remove item 326 } 327 case kMsgPreferredLanguageDeleted: 328 case kMsgPreferredLanguageInvoked: 329 { 330 if (fPreferredListView->CountItems() == 1) 331 break; 332 333 // Remove from preferred languages 334 int32 index = 0; 335 if (message->FindInt32("index", &index) == B_OK) { 336 delete fPreferredListView->RemoveItem(index); 337 _PreferredLanguagesChanged(); 338 339 if (message->what == kMsgPreferredLanguageDeleted) 340 fPreferredListView->Select(index); 341 } 342 break; 343 } 344 345 case kMsgCountrySelection: 346 { 347 // Country selection changed. 348 // Get the new selected country from the ListView and send it to the 349 // main app event handler. 350 void* listView; 351 if (message->FindPointer("source", &listView) != B_OK) 352 break; 353 354 BListView* countryList = static_cast<BListView*>(listView); 355 356 LanguageListItem* item = static_cast<LanguageListItem*> 357 (countryList->ItemAt(countryList->CurrentSelection())); 358 BMessage newMessage(kMsgSettingsChanged); 359 newMessage.AddString("country", item->ID()); 360 be_app_messenger.SendMessage(&newMessage); 361 SettingsChanged(); 362 363 BLocale locale(item->ID()); 364 fFormatView->SetLocale(locale); 365 break; 366 } 367 368 default: 369 BWindow::MessageReceived(message); 370 break; 371 } 372 } 373 374 375 bool 376 LocaleWindow::QuitRequested() 377 { 378 return true; 379 } 380 381 382 void 383 LocaleWindow::SettingsChanged() 384 { 385 fRevertButton->SetEnabled(true); 386 } 387 388 389 void 390 LocaleWindow::SettingsReverted() 391 { 392 fRevertButton->SetEnabled(false); 393 } 394 395 396 void 397 LocaleWindow::_PreferredLanguagesChanged() 398 { 399 BMessage update(kMsgSettingsChanged); 400 int index = 0; 401 while (index < fPreferredListView->FullListCountItems()) { 402 // only include subitems: we can guess the superitem 403 // from them anyway 404 LanguageListItem* item = static_cast<LanguageListItem*>( 405 fPreferredListView->FullListItemAt(index)); 406 if (item != NULL) 407 update.AddString("language", item->ID()); 408 409 index++; 410 } 411 be_app_messenger.SendMessage(&update); 412 413 _EnableDisableLanguages(); 414 } 415 416 417 void 418 LocaleWindow::_EnableDisableLanguages() 419 { 420 DisableUpdates(); 421 422 for (int32 i = 0; i < fLanguageListView->FullListCountItems(); i++) { 423 LanguageListItem* item = static_cast<LanguageListItem*>( 424 fLanguageListView->FullListItemAt(i)); 425 426 bool enable = fPreferredListView->ItemForLanguageID(item->ID()) == NULL; 427 if (item->IsEnabled() != enable) { 428 item->SetEnabled(enable); 429 430 int32 visibleIndex = fLanguageListView->IndexOf(item); 431 if (visibleIndex >= 0) { 432 if (!enable) 433 fLanguageListView->Deselect(visibleIndex); 434 fLanguageListView->InvalidateItem(visibleIndex); 435 } 436 } 437 } 438 439 SettingsChanged(); 440 441 EnableUpdates(); 442 } 443 444 445 //! Get the preferred languages from the settings. 446 void 447 LocaleWindow::_UpdatePreferredFromLocaleRoster() 448 { 449 DisableUpdates(); 450 451 // Delete all existing items 452 for (int32 index = fPreferredListView->CountItems(); index-- > 0; ) { 453 delete fPreferredListView->ItemAt(index); 454 } 455 fPreferredListView->MakeEmpty(); 456 457 // Add new ones from the locale roster 458 BMessage preferredLanguages; 459 be_locale_roster->GetPreferredLanguages(&preferredLanguages); 460 461 BString languageID; 462 for (int32 index = 0; preferredLanguages.FindString("language", index, 463 &languageID) == B_OK; index++) { 464 int32 listIndex; 465 LanguageListItem* item 466 = fLanguageListView->ItemForLanguageID(languageID.String(), 467 &listIndex); 468 if (item != NULL) { 469 // We found the item we were looking for, now copy it to 470 // the other list 471 fPreferredListView->AddItem(new LanguageListItem(*item)); 472 } 473 } 474 475 _EnableDisableLanguages(); 476 EnableUpdates(); 477 } 478 479 480 void 481 LocaleWindow::_InsertPreferredLanguage(LanguageListItem* item, int32 atIndex) 482 { 483 if (item == NULL || fPreferredListView->ItemForLanguageID( 484 item->ID().String()) != NULL) 485 return; 486 487 if (atIndex == -1) 488 atIndex = fPreferredListView->CountItems(); 489 490 BLanguage* language = NULL; 491 be_locale_roster->GetLanguage(item->Code(), &language); 492 493 LanguageListItem* baseItem = NULL; 494 if (language != NULL) { 495 baseItem = fPreferredListView->ItemForLanguageCode(language->Code(), 496 &atIndex); 497 delete language; 498 } 499 500 DisableUpdates(); 501 502 fPreferredListView->AddItem(new LanguageListItem(*item), atIndex); 503 504 // Replace other languages with the same base 505 506 if (baseItem != NULL) { 507 fPreferredListView->RemoveItem(baseItem); 508 delete baseItem; 509 } 510 511 _PreferredLanguagesChanged(); 512 513 EnableUpdates(); 514 } 515 516 517 void 518 LocaleWindow::_Defaults() 519 { 520 BMessage update(kMsgSettingsChanged); 521 update.AddString("language", "en"); 522 523 be_app_messenger.SendMessage(&update); 524 SettingsChanged(); 525 _UpdatePreferredFromLocaleRoster(); 526 } 527 528