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 <GroupLayout.h> 18 #include <LayoutBuilder.h> 19 #include <Locale.h> 20 #include <LocaleRoster.h> 21 #include <Screen.h> 22 #include <ScrollView.h> 23 #include <StringView.h> 24 #include <TabView.h> 25 #include <UnicodeChar.h> 26 27 #include "FormatSettingsView.h" 28 #include "LocalePreflet.h" 29 #include "LanguageListView.h" 30 31 32 #include <stdio.h> 33 34 35 #undef B_TRANSLATE_CONTEXT 36 #define B_TRANSLATE_CONTEXT "Locale Preflet Window" 37 38 39 static const uint32 kMsgLanguageInvoked = 'LaIv'; 40 static const uint32 kMsgLanguageDragged = 'LaDr'; 41 static const uint32 kMsgPreferredLanguageInvoked = 'PLIv'; 42 static const uint32 kMsgPreferredLanguageDragged = 'PLDr'; 43 static const uint32 kMsgPreferredLanguageDeleted = 'PLDl'; 44 static const uint32 kMsgCountrySelection = 'csel'; 45 static const uint32 kMsgDefaults = 'dflt'; 46 47 static const uint32 kMsgPreferredLanguagesChanged = 'lang'; 48 49 50 static int 51 compare_typed_list_items(const BListItem* _a, const BListItem* _b) 52 { 53 static BCollator collator; 54 55 LanguageListItem* a = (LanguageListItem*)_a; 56 LanguageListItem* b = (LanguageListItem*)_b; 57 58 return collator.Compare(a->Text(), b->Text()); 59 } 60 61 62 static int 63 compare_void_list_items(const void* _a, const void* _b) 64 { 65 static BCollator collator; 66 67 LanguageListItem* a = *(LanguageListItem**)_a; 68 LanguageListItem* b = *(LanguageListItem**)_b; 69 70 return collator.Compare(a->Text(), b->Text()); 71 } 72 73 74 // #pragma mark - 75 76 77 LocaleWindow::LocaleWindow() 78 : 79 BWindow(BRect(0, 0, 0, 0), B_TRANSLATE("Locale"), B_TITLED_WINDOW, 80 B_QUIT_ON_WINDOW_CLOSE | B_ASYNCHRONOUS_CONTROLS 81 | B_AUTO_UPDATE_SIZE_LIMITS) 82 { 83 SetLayout(new BGroupLayout(B_HORIZONTAL)); 84 85 float spacing = be_control_look->DefaultItemSpacing(); 86 87 BTabView* tabView = new BTabView("tabview"); 88 BGroupView* languageTab = new BGroupView(B_TRANSLATE("Language"), 89 B_HORIZONTAL, spacing); 90 91 // first list: available languages 92 fLanguageListView = new LanguageListView("available", 93 B_MULTIPLE_SELECTION_LIST); 94 BScrollView* scrollView = new BScrollView("scroller", fLanguageListView, 95 B_WILL_DRAW | B_FRAME_EVENTS, false, true); 96 97 fLanguageListView->SetInvocationMessage(new BMessage(kMsgLanguageInvoked)); 98 fLanguageListView->SetDragMessage(new BMessage(kMsgLanguageDragged)); 99 100 BFont font; 101 fLanguageListView->GetFont(&font); 102 103 // Fill the language list from the LocaleRoster data 104 BMessage installedLanguages; 105 if (be_locale_roster->GetAvailableLanguages(&installedLanguages) == B_OK) { 106 BString currentID; 107 LanguageListItem* lastAddedCountryItem = NULL; 108 109 for (int i = 0; installedLanguages.FindString("language", i, ¤tID) 110 == B_OK; i++) { 111 // Now get the human-readable, native name for each language 112 BString name; 113 BLanguage currentLanguage(currentID.String()); 114 currentLanguage.GetNativeName(name); 115 116 // TODO: the following block fails to detect a couple of language 117 // names as containing glyphs we can't render. Why's that? 118 bool hasGlyphs[name.CountChars()]; 119 font.GetHasGlyphs(name.String(), name.CountChars(), hasGlyphs); 120 for (int32 i = 0; i < name.CountChars(); ++i) { 121 if (!hasGlyphs[i]) { 122 // replace by name translated to current language 123 currentLanguage.GetName(name); 124 break; 125 } 126 } 127 128 LanguageListItem* item = new LanguageListItem(name, 129 currentID.String(), currentLanguage.Code()); 130 if (currentLanguage.IsCountrySpecific() 131 && lastAddedCountryItem != NULL 132 && lastAddedCountryItem->Code() == item->Code()) { 133 fLanguageListView->AddUnder(item, lastAddedCountryItem); 134 } else { 135 // This is a language variant, add it at top-level 136 fLanguageListView->AddItem(item); 137 if (!currentLanguage.IsCountrySpecific()) { 138 item->SetExpanded(false); 139 lastAddedCountryItem = item; 140 } 141 } 142 } 143 144 fLanguageListView->FullListSortItems(compare_typed_list_items); 145 } else { 146 BAlert* alert = new BAlert("Error", 147 B_TRANSLATE("Unable to find the available languages! You can't " 148 "use this preflet!"), 149 B_TRANSLATE("OK"), NULL, NULL, 150 B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_STOP_ALERT); 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, false, 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 BListView* listView = new BListView("formatting", B_SINGLE_SELECTION_LIST); 182 scrollView = new BScrollView("scroller", listView, 183 B_WILL_DRAW | B_FRAME_EVENTS, false, true); 184 listView->SetSelectionMessage(new BMessage(kMsgCountrySelection)); 185 186 // get all available formatting conventions (by language) 187 BString formattingConventionCode; 188 LanguageListItem* currentItem = NULL; 189 BCountry defaultFormattingConvention; 190 be_locale->GetCountry(&defaultFormattingConvention); 191 for (int i = 0; 192 installedLanguages.FindString("language", i, &formattingConventionCode) 193 == B_OK; i++) { 194 BCountry formattingConvention(formattingConventionCode); 195 BString formattingConventionName; 196 formattingConvention.GetName(formattingConventionName); 197 198 LanguageListItem* item = new LanguageListItem(formattingConventionName, 199 formattingConventionCode, NULL); 200 listView->AddItem(item); 201 if (!strcmp(formattingConventionCode, 202 defaultFormattingConvention.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(*be_locale); 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 BCountry country(item->ID()); 364 BLocale locale(NULL, &country); 365 fFormatView->SetLocale(locale); 366 break; 367 } 368 369 default: 370 BWindow::MessageReceived(message); 371 break; 372 } 373 } 374 375 376 bool 377 LocaleWindow::QuitRequested() 378 { 379 return true; 380 } 381 382 383 void 384 LocaleWindow::SettingsChanged() 385 { 386 fRevertButton->SetEnabled(true); 387 } 388 389 390 void 391 LocaleWindow::SettingsReverted() 392 { 393 fRevertButton->SetEnabled(false); 394 } 395 396 397 void 398 LocaleWindow::_PreferredLanguagesChanged() 399 { 400 BMessage update(kMsgSettingsChanged); 401 int index = 0; 402 while (index < fPreferredListView->FullListCountItems()) { 403 // only include subitems: we can guess the superitem 404 // from them anyway 405 LanguageListItem* item = static_cast<LanguageListItem*>( 406 fPreferredListView->FullListItemAt(index)); 407 if (item != NULL) 408 update.AddString("language", item->ID()); 409 410 index++; 411 } 412 be_app_messenger.SendMessage(&update); 413 414 _EnableDisableLanguages(); 415 } 416 417 418 void 419 LocaleWindow::_EnableDisableLanguages() 420 { 421 DisableUpdates(); 422 423 for (int32 i = 0; i < fLanguageListView->FullListCountItems(); i++) { 424 LanguageListItem* item = static_cast<LanguageListItem*>( 425 fLanguageListView->FullListItemAt(i)); 426 427 bool enable = fPreferredListView->ItemForLanguageID(item->ID()) == NULL; 428 if (item->IsEnabled() != enable) { 429 item->SetEnabled(enable); 430 431 int32 visibleIndex = fLanguageListView->IndexOf(item); 432 if (visibleIndex >= 0) { 433 if (!enable) 434 fLanguageListView->Deselect(visibleIndex); 435 fLanguageListView->InvalidateItem(visibleIndex); 436 } 437 } 438 } 439 440 SettingsChanged(); 441 442 EnableUpdates(); 443 } 444 445 446 //! Get the preferred languages from the settings. 447 void 448 LocaleWindow::_UpdatePreferredFromLocaleRoster() 449 { 450 DisableUpdates(); 451 452 // Delete all existing items 453 for (int32 index = fPreferredListView->CountItems(); index-- > 0; ) { 454 delete fPreferredListView->ItemAt(index); 455 } 456 fPreferredListView->MakeEmpty(); 457 458 // Add new ones from the locale roster 459 BMessage preferredLanguages; 460 be_locale_roster->GetPreferredLanguages(&preferredLanguages); 461 462 BString languageID; 463 for (int32 index = 0; preferredLanguages.FindString("language", index, 464 &languageID) == B_OK; index++) { 465 int32 listIndex; 466 LanguageListItem* item 467 = fLanguageListView->ItemForLanguageID(languageID.String(), 468 &listIndex); 469 if (item != NULL) { 470 // We found the item we were looking for, now copy it to 471 // the other list 472 fPreferredListView->AddItem(new LanguageListItem(*item)); 473 } 474 } 475 476 _EnableDisableLanguages(); 477 EnableUpdates(); 478 } 479 480 481 void 482 LocaleWindow::_InsertPreferredLanguage(LanguageListItem* item, int32 atIndex) 483 { 484 if (item == NULL || fPreferredListView->ItemForLanguageID( 485 item->ID().String()) != NULL) 486 return; 487 488 if (atIndex == -1) 489 atIndex = fPreferredListView->CountItems(); 490 491 BLanguage* language = NULL; 492 be_locale_roster->GetLanguage(item->Code(), &language); 493 494 LanguageListItem* baseItem = NULL; 495 if (language != NULL) { 496 baseItem = fPreferredListView->ItemForLanguageCode(language->Code(), 497 &atIndex); 498 delete language; 499 } 500 501 DisableUpdates(); 502 503 fPreferredListView->AddItem(new LanguageListItem(*item), atIndex); 504 505 // Replace other languages with the same base 506 507 if (baseItem != NULL) { 508 fPreferredListView->RemoveItem(baseItem); 509 delete baseItem; 510 } 511 512 _PreferredLanguagesChanged(); 513 514 EnableUpdates(); 515 } 516 517 518 void 519 LocaleWindow::_Defaults() 520 { 521 BMessage update(kMsgSettingsChanged); 522 update.AddString("language", "en"); 523 524 be_app_messenger.SendMessage(&update); 525 SettingsChanged(); 526 _UpdatePreferredFromLocaleRoster(); 527 } 528 529