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