1 /* 2 * Copyright 2010, Stephan Aßmus <superstippi@gmx.de> 3 * Copyright 2010, Adrien Destugues <pulkomandy@pulkomandy.ath.cx> 4 * Copyright 2011, Axel Dörfler, axeld@pinc-software.de. 5 * All rights reserved. Distributed under the terms of the MIT License. 6 */ 7 8 9 #include "BootPromptWindow.h" 10 11 #include <new> 12 #include <stdio.h> 13 14 #include <Bitmap.h> 15 #include <Button.h> 16 #include <Catalog.h> 17 #include <ControlLook.h> 18 #include <Directory.h> 19 #include <Entry.h> 20 #include <Font.h> 21 #include <FindDirectory.h> 22 #include <File.h> 23 #include <FormattingConventions.h> 24 #include <IconUtils.h> 25 #include <IconView.h> 26 #include <LayoutBuilder.h> 27 #include <ListView.h> 28 #include <Locale.h> 29 #include <Menu.h> 30 #include <MutableLocaleRoster.h> 31 #include <ObjectList.h> 32 #include <Path.h> 33 #include <Screen.h> 34 #include <ScrollView.h> 35 #include <SeparatorView.h> 36 #include <StringItem.h> 37 #include <StringView.h> 38 #include <TextView.h> 39 #include <UnicodeChar.h> 40 41 #include "BootPrompt.h" 42 #include "Keymap.h" 43 #include "KeymapNames.h" 44 45 46 using BPrivate::MutableLocaleRoster; 47 48 49 enum { 50 MSG_LANGUAGE_SELECTED = 'lngs', 51 MSG_KEYMAP_SELECTED = 'kmps' 52 }; 53 54 55 #undef B_TRANSLATION_CONTEXT 56 #define B_TRANSLATION_CONTEXT "BootPromptWindow" 57 58 59 namespace BPrivate { 60 void ForceUnloadCatalog(); 61 }; 62 63 64 static const char* kLanguageKeymapMappings[] = { 65 // While there is a "Dutch" keymap, it apparently has not been widely 66 // adopted, and the US-International keymap is common 67 "Dutch", "US-International", 68 69 // Cyrillic keymaps are not usable alone, as latin alphabet is required to 70 // use Terminal. So we stay in US international until the user has a chance 71 // to set up KeymapSwitcher. 72 "Belarusian", "US-International", 73 "Russian", "US-International", 74 "Ukrainian", "US-International", 75 76 // Turkish has two layouts, we must pick one 77 "Turkish", "Turkish (Type-Q)", 78 }; 79 static const size_t kLanguageKeymapMappingsSize 80 = sizeof(kLanguageKeymapMappings) / sizeof(kLanguageKeymapMappings[0]); 81 82 83 class LanguageItem : public BStringItem { 84 public: 85 LanguageItem(const char* label, const char* language) 86 : 87 BStringItem(label), 88 fLanguage(language) 89 { 90 } 91 92 ~LanguageItem() 93 { 94 } 95 96 const char* Language() const 97 { 98 return fLanguage.String(); 99 } 100 101 void DrawItem(BView* owner, BRect frame, bool complete) 102 { 103 BStringItem::DrawItem(owner, frame, true/*complete*/); 104 } 105 106 private: 107 BString fLanguage; 108 }; 109 110 111 static int 112 compare_void_list_items(const void* _a, const void* _b) 113 { 114 static BCollator collator; 115 116 LanguageItem* a = *(LanguageItem**)_a; 117 LanguageItem* b = *(LanguageItem**)_b; 118 119 return collator.Compare(a->Text(), b->Text()); 120 } 121 122 123 static int 124 compare_void_menu_items(const void* _a, const void* _b) 125 { 126 static BCollator collator; 127 128 BMenuItem* a = *(BMenuItem**)_a; 129 BMenuItem* b = *(BMenuItem**)_b; 130 131 return collator.Compare(a->Label(), b->Label()); 132 } 133 134 135 // #pragma mark - 136 137 138 BootPromptWindow::BootPromptWindow() 139 : 140 BWindow(BRect(0, 0, 530, 400), "", 141 B_TITLED_WINDOW, B_NOT_ZOOMABLE | B_NOT_MINIMIZABLE | B_NOT_CLOSABLE 142 | B_NOT_RESIZABLE | B_AUTO_UPDATE_SIZE_LIMITS, 143 B_ALL_WORKSPACES), 144 fDefaultKeymapItem(NULL) 145 { 146 SetSizeLimits(450, 16384, 350, 16384); 147 148 rgb_color textColor = ui_color(B_PANEL_TEXT_COLOR); 149 fInfoTextView = new BTextView(""); 150 fInfoTextView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 151 fInfoTextView->SetFontAndColor(be_plain_font, B_FONT_ALL, &textColor); 152 fInfoTextView->MakeEditable(false); 153 fInfoTextView->MakeSelectable(false); 154 fInfoTextView->MakeResizable(false); 155 156 BResources* res = BApplication::AppResources(); 157 size_t size = 0; 158 const uint8_t* data; 159 160 BBitmap desktopIcon(BRect(0, 0, 23, 23), B_RGBA32); 161 data = (const uint8_t*)res->LoadResource('VICN', "Desktop", &size); 162 BIconUtils::GetVectorIcon(data, size, &desktopIcon); 163 164 BBitmap installerIcon(BRect(0, 0, 23, 23), B_RGBA32); 165 data = (const uint8_t*)res->LoadResource('VICN', "Installer", &size); 166 BIconUtils::GetVectorIcon(data, size, &installerIcon); 167 168 fDesktopButton = new BButton("", new BMessage(MSG_BOOT_DESKTOP)); 169 fDesktopButton->SetTarget(be_app); 170 fDesktopButton->MakeDefault(true); 171 fDesktopButton->SetIcon(&desktopIcon); 172 173 fInstallerButton = new BButton("", new BMessage(MSG_RUN_INSTALLER)); 174 fInstallerButton->SetTarget(be_app); 175 fInstallerButton->SetIcon(&installerIcon); 176 177 data = (const uint8_t*)res->LoadResource('VICN', "Language", &size); 178 IconView* languageIcon = new IconView(B_LARGE_ICON); 179 languageIcon->SetIcon(data, size, B_LARGE_ICON); 180 181 data = (const uint8_t*)res->LoadResource('VICN', "Keymap", &size); 182 IconView* keymapIcon = new IconView(B_LARGE_ICON); 183 keymapIcon->SetIcon(data, size, B_LARGE_ICON); 184 185 fLanguagesLabelView = new BStringView("languagesLabel", ""); 186 fLanguagesLabelView->SetFont(be_bold_font); 187 fLanguagesLabelView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, 188 B_SIZE_UNSET)); 189 190 fKeymapsMenuLabel = new BStringView("keymapsLabel", ""); 191 fKeymapsMenuLabel->SetFont(be_bold_font); 192 fKeymapsMenuLabel->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, 193 B_SIZE_UNSET)); 194 // Make sure there is enough space to display the text even in verbose 195 // locales, to avoid width changes on language changes 196 float labelWidth = fKeymapsMenuLabel->StringWidth("Disposition du clavier") 197 + 16; 198 fKeymapsMenuLabel->SetExplicitMinSize(BSize(labelWidth, B_SIZE_UNSET)); 199 200 fLanguagesListView = new BListView(); 201 BScrollView* languagesScrollView = new BScrollView("languagesScroll", 202 fLanguagesListView, B_WILL_DRAW, false, true); 203 204 // Carefully designed to not exceed the 640x480 resolution with a 12pt font. 205 float width = 640 * be_plain_font->Size() / 12 - (labelWidth + 64); 206 float height = be_plain_font->Size() * 23; 207 fInfoTextView->SetExplicitMinSize(BSize(width, height)); 208 fInfoTextView->SetExplicitMaxSize(BSize(width, B_SIZE_UNSET)); 209 210 // Make sure the language list view is always wide enough to show the 211 // largest language 212 fLanguagesListView->SetExplicitMinSize( 213 BSize(fLanguagesListView->StringWidth("Português (Brasil)"), 214 height)); 215 216 fKeymapsMenuField = new BMenuField("", "", new BMenu("")); 217 fKeymapsMenuField->Menu()->SetLabelFromMarked(true); 218 219 _InitCatalog(true); 220 _PopulateLanguages(); 221 _PopulateKeymaps(); 222 223 BLayoutBuilder::Group<>(this, B_HORIZONTAL) 224 .SetInsets(B_USE_WINDOW_SPACING) 225 .AddGroup(B_VERTICAL, 0) 226 .SetInsets(0, 0, 0, B_USE_SMALL_SPACING) 227 .AddGroup(B_HORIZONTAL) 228 .Add(languageIcon) 229 .Add(fLanguagesLabelView) 230 .SetInsets(0, 0, 0, B_USE_SMALL_SPACING) 231 .End() 232 .Add(languagesScrollView) 233 .AddGroup(B_HORIZONTAL) 234 .Add(keymapIcon) 235 .Add(fKeymapsMenuLabel) 236 .SetInsets(0, B_USE_DEFAULT_SPACING, 0, 237 B_USE_SMALL_SPACING) 238 .End() 239 .Add(fKeymapsMenuField) 240 .End() 241 .AddGroup(B_VERTICAL) 242 .SetInsets(0) 243 .Add(fInfoTextView) 244 .AddGroup(B_HORIZONTAL) 245 .SetInsets(0) 246 .AddGlue() 247 .Add(fInstallerButton) 248 .Add(fDesktopButton) 249 .End() 250 .End(); 251 252 fLanguagesListView->MakeFocus(); 253 254 // Force the info text view to use a reasonable size 255 fInfoTextView->SetText("x\n\n\n\n\n\n\n\n\n\n\n\n\n\nx"); 256 ResizeToPreferred(); 257 258 _UpdateStrings(); 259 CenterOnScreen(); 260 Show(); 261 } 262 263 264 void 265 BootPromptWindow::MessageReceived(BMessage* message) 266 { 267 switch (message->what) { 268 case MSG_LANGUAGE_SELECTED: 269 if (LanguageItem* item = static_cast<LanguageItem*>( 270 fLanguagesListView->ItemAt( 271 fLanguagesListView->CurrentSelection(0)))) { 272 BMessage preferredLanguages; 273 preferredLanguages.AddString("language", item->Language()); 274 MutableLocaleRoster::Default()->SetPreferredLanguages( 275 &preferredLanguages); 276 _InitCatalog(true); 277 _UpdateKeymapsMenu(); 278 279 // Select default keymap by language 280 BLanguage language(item->Language()); 281 BMenuItem* keymapItem = _KeymapItemForLanguage(language); 282 if (keymapItem != NULL) { 283 keymapItem->SetMarked(true); 284 _ActivateKeymap(keymapItem->Message()); 285 } 286 } 287 // Calling it here is a cheap way of preventing the user to have 288 // no item selected. Always the current item will be selected. 289 _UpdateStrings(); 290 break; 291 292 case MSG_KEYMAP_SELECTED: 293 _ActivateKeymap(message); 294 break; 295 296 default: 297 BWindow::MessageReceived(message); 298 } 299 } 300 301 302 void 303 BootPromptWindow::_InitCatalog(bool saveSettings) 304 { 305 // Initilialize the Locale Kit 306 BPrivate::ForceUnloadCatalog(); 307 308 if (!saveSettings) 309 return; 310 311 BMessage settings; 312 BString language; 313 if (BLocaleRoster::Default()->GetCatalog()->GetLanguage(&language) == B_OK) 314 settings.AddString("language", language.String()); 315 316 MutableLocaleRoster::Default()->SetPreferredLanguages(&settings); 317 318 BFormattingConventions conventions(language.String()); 319 MutableLocaleRoster::Default()->SetDefaultFormattingConventions( 320 conventions); 321 } 322 323 324 void 325 BootPromptWindow::_UpdateStrings() 326 { 327 SetTitle(B_TRANSLATE("Welcome to Haiku!")); 328 329 fInfoTextView->SetText(B_TRANSLATE_COMMENT( 330 "Thank you for trying out Haiku! We hope you'll like it!\n\n" 331 "You can select your preferred language and keyboard " 332 "layout from the list on the left which will then be used instantly. " 333 "You can easily change both settings from the Desktop later on on " 334 "the fly.\n\n" 335 336 "Do you wish to run the Installer or continue booting to the " 337 "Desktop?\n", 338 339 "For other languages, a note could be added: \"" 340 "Note: Localization of Haiku applications and other components is " 341 "an on-going effort. You will frequently encounter untranslated " 342 "strings, but if you like, you can join in the work at " 343 "<www.haiku-os.org>.\"")); 344 345 fDesktopButton->SetLabel(B_TRANSLATE("Boot to Desktop")); 346 fInstallerButton->SetLabel(B_TRANSLATE("Run Installer")); 347 348 fLanguagesLabelView->SetText(B_TRANSLATE("Language")); 349 fKeymapsMenuLabel->SetText(B_TRANSLATE("Keymap")); 350 if (fKeymapsMenuField->Menu()->FindMarked() == NULL) 351 fKeymapsMenuField->MenuItem()->SetLabel(B_TRANSLATE("Custom")); 352 } 353 354 355 void 356 BootPromptWindow::_PopulateLanguages() 357 { 358 // TODO: detect language/country from IP address 359 360 // Get current first preferred language of the user 361 BMessage preferredLanguages; 362 BLocaleRoster::Default()->GetPreferredLanguages(&preferredLanguages); 363 const char* firstPreferredLanguage; 364 if (preferredLanguages.FindString("language", &firstPreferredLanguage) 365 != B_OK) { 366 // Fall back to built-in language of this application. 367 firstPreferredLanguage = "en"; 368 } 369 370 BMessage installedCatalogs; 371 BLocaleRoster::Default()->GetAvailableCatalogs(&installedCatalogs, 372 "x-vnd.Haiku-FirstBootPrompt"); 373 374 BFont font; 375 fLanguagesListView->GetFont(&font); 376 377 // Try to instantiate a BCatalog for each language, it will only work 378 // for translations of this application. So the list of languages will be 379 // limited to catalogs written for this application, which is on purpose! 380 381 const char* languageID; 382 LanguageItem* currentItem = NULL; 383 for (int32 i = 0; installedCatalogs.FindString("language", i, &languageID) 384 == B_OK; i++) { 385 BLanguage* language; 386 if (BLocaleRoster::Default()->GetLanguage(languageID, &language) 387 == B_OK) { 388 BString name; 389 language->GetNativeName(name); 390 391 // TODO: the following block fails to detect a couple of language 392 // names as containing glyphs we can't render. Why's that? 393 bool hasGlyphs[name.CountChars()]; 394 font.GetHasGlyphs(name.String(), name.CountChars(), hasGlyphs); 395 for (int32 i = 0; i < name.CountChars(); ++i) { 396 if (!hasGlyphs[i]) { 397 // replace by name translated to current language 398 language->GetName(name); 399 break; 400 } 401 } 402 403 LanguageItem* item = new LanguageItem(name.String(), 404 languageID); 405 fLanguagesListView->AddItem(item); 406 // Select this item if it is the first preferred language 407 if (strcmp(firstPreferredLanguage, languageID) == 0) 408 currentItem = item; 409 410 delete language; 411 } else 412 fprintf(stderr, "failed to get BLanguage for %s\n", languageID); 413 } 414 415 fLanguagesListView->SortItems(compare_void_list_items); 416 if (currentItem != NULL) 417 fLanguagesListView->Select(fLanguagesListView->IndexOf(currentItem)); 418 fLanguagesListView->ScrollToSelection(); 419 420 // Re-enable sending the selection message. 421 fLanguagesListView->SetSelectionMessage( 422 new BMessage(MSG_LANGUAGE_SELECTED)); 423 } 424 425 426 void 427 BootPromptWindow::_UpdateKeymapsMenu() 428 { 429 BMenu *menu = fKeymapsMenuField->Menu(); 430 BMenuItem* item; 431 BList itemsList; 432 433 // Recreate keymapmenu items list, since BMenu could not sort its items. 434 while ((item = menu->ItemAt(0)) != NULL) { 435 BMessage* message = item->Message(); 436 entry_ref ref; 437 message->FindRef("ref", &ref); 438 item-> SetLabel(B_TRANSLATE_NOCOLLECT_ALL((ref.name), 439 "KeymapNames", NULL)); 440 itemsList.AddItem(item); 441 menu->RemoveItem((int32)0); 442 } 443 itemsList.SortItems(compare_void_menu_items); 444 fKeymapsMenuField->Menu()->AddList(&itemsList, 0); 445 } 446 447 448 void 449 BootPromptWindow::_PopulateKeymaps() 450 { 451 // Get the name of the current keymap, so we can mark the correct entry 452 // in the list view. 453 BString currentName; 454 entry_ref currentRef; 455 if (_GetCurrentKeymapRef(currentRef) == B_OK) { 456 BNode node(¤tRef); 457 node.ReadAttrString("keymap:name", ¤tName); 458 } 459 460 // TODO: common keymaps! 461 BPath path; 462 if (find_directory(B_SYSTEM_DATA_DIRECTORY, &path) != B_OK 463 || path.Append("Keymaps") != B_OK) { 464 return; 465 } 466 467 // US-International is the default keymap, if we could not found a 468 // matching one 469 BString usInternational("US-International"); 470 471 // Populate the menu 472 BDirectory directory; 473 if (directory.SetTo(path.Path()) == B_OK) { 474 entry_ref ref; 475 BList itemsList; 476 while (directory.GetNextRef(&ref) == B_OK) { 477 BMessage* message = new BMessage(MSG_KEYMAP_SELECTED); 478 message->AddRef("ref", &ref); 479 BMenuItem* item = 480 new BMenuItem(B_TRANSLATE_NOCOLLECT_ALL((ref.name), 481 "KeymapNames", NULL), message); 482 itemsList.AddItem(item); 483 if (currentName == ref.name) 484 item->SetMarked(true); 485 486 if (usInternational == ref.name) 487 fDefaultKeymapItem = item; 488 } 489 itemsList.SortItems(compare_void_menu_items); 490 fKeymapsMenuField->Menu()->AddList(&itemsList, 0); 491 } 492 } 493 494 495 void 496 BootPromptWindow::_ActivateKeymap(const BMessage* message) const 497 { 498 entry_ref ref; 499 if (message == NULL || message->FindRef("ref", &ref) != B_OK) 500 return; 501 502 // Load and use the new keymap 503 Keymap keymap; 504 if (keymap.Load(ref) != B_OK) { 505 fprintf(stderr, "Failed to load new keymap file (%s).\n", ref.name); 506 return; 507 } 508 509 // Get entry_ref to the Key_map file in the user settings. 510 entry_ref currentRef; 511 if (_GetCurrentKeymapRef(currentRef) != B_OK) { 512 fprintf(stderr, "Failed to get ref to user keymap file.\n"); 513 return; 514 } 515 516 if (keymap.Save(currentRef) != B_OK) { 517 fprintf(stderr, "Failed to save new keymap file (%s).\n", ref.name); 518 return; 519 } 520 521 keymap.Use(); 522 } 523 524 525 status_t 526 BootPromptWindow::_GetCurrentKeymapRef(entry_ref& ref) const 527 { 528 BPath path; 529 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK 530 || path.Append("Key_map") != B_OK) { 531 return B_ERROR; 532 } 533 534 return get_ref_for_path(path.Path(), &ref); 535 } 536 537 538 BMenuItem* 539 BootPromptWindow::_KeymapItemForLanguage(BLanguage& language) const 540 { 541 BLanguage english("en"); 542 BString name; 543 if (language.GetName(name, &english) != B_OK) 544 return fDefaultKeymapItem; 545 546 // Check special mappings first 547 for (size_t i = 0; i < kLanguageKeymapMappingsSize; i += 2) { 548 if (!strcmp(name, kLanguageKeymapMappings[i])) { 549 name = kLanguageKeymapMappings[i + 1]; 550 break; 551 } 552 } 553 554 BMenu* menu = fKeymapsMenuField->Menu(); 555 for (int32 i = 0; i < menu->CountItems(); i++) { 556 BMenuItem* item = menu->ItemAt(i); 557 BMessage* message = item->Message(); 558 559 entry_ref ref; 560 if (message->FindRef("ref", &ref) == B_OK 561 && name == ref.name) 562 return item; 563 } 564 565 return fDefaultKeymapItem; 566 } 567