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 <LayoutBuilder.h> 25 #include <ListView.h> 26 #include <Locale.h> 27 #include <Menu.h> 28 #include <MutableLocaleRoster.h> 29 #include <ObjectList.h> 30 #include <Path.h> 31 #include <Screen.h> 32 #include <ScrollView.h> 33 #include <SeparatorView.h> 34 #include <StringItem.h> 35 #include <StringView.h> 36 #include <TextView.h> 37 #include <UnicodeChar.h> 38 39 #include "BootPrompt.h" 40 #include "Keymap.h" 41 42 43 using BPrivate::MutableLocaleRoster; 44 45 46 enum { 47 MSG_LANGUAGE_SELECTED = 'lngs', 48 MSG_KEYMAP_SELECTED = 'kmps' 49 }; 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 static const size_t kLanguageKeymapMappingsSize 67 = sizeof(kLanguageKeymapMappings) / sizeof(kLanguageKeymapMappings[0]); 68 69 70 class LanguageItem : public BStringItem { 71 public: 72 LanguageItem(const char* label, const char* language) 73 : 74 BStringItem(label), 75 fLanguage(language) 76 { 77 fIcon = new(std::nothrow) BBitmap(BRect(0, 0, 15, 15), B_RGBA32); 78 if (fIcon != NULL 79 && (!fIcon->IsValid() 80 || BLocaleRoster::Default()->GetFlagIconForLanguage(fIcon, 81 language) != B_OK)) { 82 delete fIcon; 83 fIcon = NULL; 84 } 85 } 86 87 ~LanguageItem() 88 { 89 delete fIcon; 90 } 91 92 const char* Language() const 93 { 94 return fLanguage.String(); 95 } 96 97 void DrawItem(BView* owner, BRect frame, bool complete) 98 { 99 BStringItem::DrawItem(owner, frame, true/*complete*/); 100 101 // Draw the icon 102 if (fIcon != NULL) { 103 frame.left = frame.right - kFlagWidth; 104 BRect iconFrame(frame); 105 iconFrame.Set(iconFrame.left, iconFrame.top + 1, 106 iconFrame.left + kFlagWidth - 2, 107 iconFrame.top + kFlagWidth - 1); 108 109 owner->SetDrawingMode(B_OP_OVER); 110 owner->DrawBitmap(fIcon, iconFrame); 111 owner->SetDrawingMode(B_OP_COPY); 112 } 113 } 114 115 private: 116 static const int kFlagWidth = 16; 117 118 BString fLanguage; 119 BBitmap* fIcon; 120 }; 121 122 123 static int 124 compare_void_list_items(const void* _a, const void* _b) 125 { 126 static BCollator collator; 127 128 LanguageItem* a = *(LanguageItem**)_a; 129 LanguageItem* b = *(LanguageItem**)_b; 130 131 return collator.Compare(a->Text(), b->Text()); 132 } 133 134 135 static int 136 compare_void_menu_items(const void* _a, const void* _b) 137 { 138 static BCollator collator; 139 140 BMenuItem* a = *(BMenuItem**)_a; 141 BMenuItem* b = *(BMenuItem**)_b; 142 143 return collator.Compare(a->Label(), b->Label()); 144 } 145 146 147 // #pragma mark - 148 149 150 BootPromptWindow::BootPromptWindow() 151 : 152 BWindow(BRect(0, 0, 530, 400), "", 153 B_TITLED_WINDOW, B_NOT_ZOOMABLE | B_NOT_MINIMIZABLE | B_NOT_CLOSABLE 154 | B_NOT_RESIZABLE | B_AUTO_UPDATE_SIZE_LIMITS, 155 B_ALL_WORKSPACES), 156 fDefaultKeymapItem(NULL) 157 { 158 SetSizeLimits(450, 16384, 350, 16384); 159 160 rgb_color textColor = ui_color(B_PANEL_TEXT_COLOR); 161 fInfoTextView = new BTextView(""); 162 fInfoTextView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 163 fInfoTextView->SetFontAndColor(be_plain_font, B_FONT_ALL, &textColor); 164 fInfoTextView->MakeEditable(false); 165 fInfoTextView->MakeSelectable(false); 166 fInfoTextView->MakeResizable(false); 167 168 // Carefully designed to not exceed the 640x480 resolution with a 12pt font. 169 float width = fInfoTextView->StringWidth("Thank you for trying out Haiku," 170 " We hope you like it!") * 1.5; 171 float height = be_plain_font->Size() * 23; 172 173 fInfoTextView->SetExplicitMinSize(BSize(width, height)); 174 175 fDesktopButton = new BButton("", new BMessage(MSG_BOOT_DESKTOP)); 176 fDesktopButton->SetTarget(be_app); 177 fDesktopButton->MakeDefault(true); 178 179 fInstallerButton = new BButton("", new BMessage(MSG_RUN_INSTALLER)); 180 fInstallerButton->SetTarget(be_app); 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 fLanguagesListView = new BListView(); 188 fLanguagesListView->SetFlags( 189 fLanguagesListView->Flags() | B_FULL_UPDATE_ON_RESIZE); 190 // Our ListItem rendering depends on the width of the view, so 191 // we need a full update 192 BScrollView* languagesScrollView = new BScrollView("languagesScroll", 193 fLanguagesListView, B_WILL_DRAW, false, true); 194 195 // Make sure the language list view is always wide enough to show the 196 // largest language, with some extra space 197 fLanguagesListView->SetExplicitSize( 198 BSize(fLanguagesListView->StringWidth("Portuguese (Brazil)") + 32, 199 height)); 200 201 fKeymapsMenuField = new BMenuField("", "", new BMenu("")); 202 fKeymapsMenuField->Menu()->SetLabelFromMarked(true); 203 204 _InitCatalog(true); 205 _PopulateLanguages(); 206 _PopulateKeymaps(); 207 208 BLayoutBuilder::Group<>(this, B_VERTICAL, 0) 209 .AddGroup(B_HORIZONTAL) 210 .Add(fLanguagesLabelView) 211 .SetInsets(B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING, 212 B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING) 213 .End() 214 .AddGroup(B_HORIZONTAL) 215 .Add(languagesScrollView) 216 .Add(fInfoTextView) 217 .SetInsets(B_USE_WINDOW_SPACING, 0) 218 .End() 219 .AddGroup(B_HORIZONTAL) 220 .Add(fKeymapsMenuField) 221 .AddGlue() 222 .SetInsets(B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING) 223 .End() 224 .Add(new BSeparatorView(B_HORIZONTAL)) 225 .AddGroup(B_HORIZONTAL) 226 .AddGlue() 227 .Add(fInstallerButton) 228 .Add(fDesktopButton) 229 .SetInsets(B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING, 230 B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING) 231 .End(); 232 233 fLanguagesListView->MakeFocus(); 234 235 // Force the info text view to use a reasonable size 236 fInfoTextView->SetText("x\n\n\n\n\n\n\n\n\n\n\n\n\n\nx"); 237 ResizeToPreferred(); 238 239 _UpdateStrings(); 240 CenterOnScreen(); 241 Show(); 242 } 243 244 245 void 246 BootPromptWindow::MessageReceived(BMessage* message) 247 { 248 switch (message->what) { 249 case MSG_LANGUAGE_SELECTED: 250 if (LanguageItem* item = static_cast<LanguageItem*>( 251 fLanguagesListView->ItemAt( 252 fLanguagesListView->CurrentSelection(0)))) { 253 BMessage preferredLanguages; 254 preferredLanguages.AddString("language", item->Language()); 255 MutableLocaleRoster::Default()->SetPreferredLanguages( 256 &preferredLanguages); 257 _InitCatalog(true); 258 259 // Select default keymap by language 260 BLanguage language(item->Language()); 261 BMenuItem* keymapItem = _KeymapItemForLanguage(language); 262 if (keymapItem != NULL) { 263 keymapItem->SetMarked(true); 264 _ActivateKeymap(keymapItem->Message()); 265 } 266 } 267 // Calling it here is a cheap way of preventing the user to have 268 // no item selected. Always the current item will be selected. 269 _UpdateStrings(); 270 break; 271 272 case MSG_KEYMAP_SELECTED: 273 _ActivateKeymap(message); 274 break; 275 276 default: 277 BWindow::MessageReceived(message); 278 } 279 } 280 281 282 void 283 BootPromptWindow::_InitCatalog(bool saveSettings) 284 { 285 // Initilialize the Locale Kit 286 BPrivate::ForceUnloadCatalog(); 287 288 if (!saveSettings) 289 return; 290 291 BMessage settings; 292 BString language; 293 if (BLocaleRoster::Default()->GetCatalog()->GetLanguage(&language) == B_OK) 294 settings.AddString("language", language.String()); 295 296 MutableLocaleRoster::Default()->SetPreferredLanguages(&settings); 297 298 BFormattingConventions conventions(language.String()); 299 MutableLocaleRoster::Default()->SetDefaultFormattingConventions( 300 conventions); 301 } 302 303 304 void 305 BootPromptWindow::_UpdateStrings() 306 { 307 SetTitle(B_TRANSLATE("Welcome to Haiku!")); 308 309 fInfoTextView->SetText(B_TRANSLATE_COMMENT( 310 "Thank you for trying out Haiku! We hope you'll like it!\n\n" 311 "You can select your preferred language and keyboard " 312 "layout from the list on the left which will then be used instantly. " 313 "You can easily change both settings from the Desktop later on on " 314 "the fly.\n\n" 315 316 "Do you wish to run the Installer or continue booting to the " 317 "Desktop?\n", 318 319 "For other languages, a note could be added: \"" 320 "Note: Localization of Haiku applications and other components is " 321 "an on-going effort. You will frequently encounter untranslated " 322 "strings, but if you like, you can join in the work at " 323 "<www.haiku-os.org>.\"")); 324 325 fDesktopButton->SetLabel(B_TRANSLATE("Boot to Desktop")); 326 fInstallerButton->SetLabel(B_TRANSLATE("Run Installer")); 327 328 fLanguagesLabelView->SetText(B_TRANSLATE("Language")); 329 fKeymapsMenuField->SetLabel(B_TRANSLATE("Keymap")); 330 if (fKeymapsMenuField->Menu()->FindMarked() == NULL) 331 fKeymapsMenuField->MenuItem()->SetLabel(B_TRANSLATE("Custom")); 332 } 333 334 335 void 336 BootPromptWindow::_PopulateLanguages() 337 { 338 // TODO: detect language/country from IP address 339 340 // Get current first preferred language of the user 341 BMessage preferredLanguages; 342 BLocaleRoster::Default()->GetPreferredLanguages(&preferredLanguages); 343 const char* firstPreferredLanguage; 344 if (preferredLanguages.FindString("language", &firstPreferredLanguage) 345 != B_OK) { 346 // Fall back to built-in language of this application. 347 firstPreferredLanguage = "en"; 348 } 349 350 BMessage installedCatalogs; 351 BLocaleRoster::Default()->GetAvailableCatalogs(&installedCatalogs, 352 "x-vnd.Haiku-FirstBootPrompt"); 353 354 BFont font; 355 fLanguagesListView->GetFont(&font); 356 357 // Try to instantiate a BCatalog for each language, it will only work 358 // for translations of this application. So the list of languages will be 359 // limited to catalogs written for this application, which is on purpose! 360 361 const char* languageID; 362 LanguageItem* currentItem = NULL; 363 for (int32 i = 0; installedCatalogs.FindString("language", i, &languageID) 364 == B_OK; i++) { 365 BLanguage* language; 366 if (BLocaleRoster::Default()->GetLanguage(languageID, &language) 367 == B_OK) { 368 BString name; 369 language->GetNativeName(name); 370 371 // TODO: the following block fails to detect a couple of language 372 // names as containing glyphs we can't render. Why's that? 373 bool hasGlyphs[name.CountChars()]; 374 font.GetHasGlyphs(name.String(), name.CountChars(), hasGlyphs); 375 for (int32 i = 0; i < name.CountChars(); ++i) { 376 if (!hasGlyphs[i]) { 377 // replace by name translated to current language 378 language->GetName(name); 379 break; 380 } 381 } 382 383 LanguageItem* item = new LanguageItem(name.String(), 384 languageID); 385 fLanguagesListView->AddItem(item); 386 // Select this item if it is the first preferred language 387 if (strcmp(firstPreferredLanguage, languageID) == 0) 388 currentItem = item; 389 390 delete language; 391 } else 392 fprintf(stderr, "failed to get BLanguage for %s\n", languageID); 393 } 394 395 fLanguagesListView->SortItems(compare_void_list_items); 396 if (currentItem != NULL) 397 fLanguagesListView->Select(fLanguagesListView->IndexOf(currentItem)); 398 fLanguagesListView->ScrollToSelection(); 399 400 // Re-enable sending the selection message. 401 fLanguagesListView->SetSelectionMessage( 402 new BMessage(MSG_LANGUAGE_SELECTED)); 403 } 404 405 406 void 407 BootPromptWindow::_PopulateKeymaps() 408 { 409 // Get the name of the current keymap, so we can mark the correct entry 410 // in the list view. 411 BString currentName; 412 entry_ref currentRef; 413 if (_GetCurrentKeymapRef(currentRef) == B_OK) { 414 BNode node(¤tRef); 415 node.ReadAttrString("keymap:name", ¤tName); 416 } 417 418 // TODO: common keymaps! 419 BPath path; 420 if (find_directory(B_SYSTEM_DATA_DIRECTORY, &path) != B_OK 421 || path.Append("Keymaps") != B_OK) { 422 return; 423 } 424 425 // US-International is the default keymap, if we could not found a 426 // matching one 427 BString usInternational("US-International"); 428 429 // Populate the menu 430 BDirectory directory; 431 if (directory.SetTo(path.Path()) == B_OK) { 432 entry_ref ref; 433 BList itemsList; 434 while (directory.GetNextRef(&ref) == B_OK) { 435 BMessage* message = new BMessage(MSG_KEYMAP_SELECTED); 436 message->AddRef("ref", &ref); 437 BMenuItem* item = new BMenuItem(ref.name, message); 438 itemsList.AddItem(item); 439 if (currentName == ref.name) 440 item->SetMarked(true); 441 442 if (usInternational == ref.name) 443 fDefaultKeymapItem = item; 444 } 445 itemsList.SortItems(compare_void_menu_items); 446 fKeymapsMenuField->Menu()->AddList(&itemsList, 0); 447 } 448 } 449 450 451 void 452 BootPromptWindow::_ActivateKeymap(const BMessage* message) const 453 { 454 entry_ref ref; 455 if (message == NULL || message->FindRef("ref", &ref) != B_OK) 456 return; 457 458 // Load and use the new keymap 459 Keymap keymap; 460 if (keymap.Load(ref) != B_OK) { 461 fprintf(stderr, "Failed to load new keymap file (%s).\n", ref.name); 462 return; 463 } 464 465 // Get entry_ref to the Key_map file in the user settings. 466 entry_ref currentRef; 467 if (_GetCurrentKeymapRef(currentRef) != B_OK) { 468 fprintf(stderr, "Failed to get ref to user keymap file.\n"); 469 return; 470 } 471 472 if (keymap.Save(currentRef) != B_OK) { 473 fprintf(stderr, "Failed to save new keymap file (%s).\n", ref.name); 474 return; 475 } 476 477 keymap.Use(); 478 } 479 480 481 status_t 482 BootPromptWindow::_GetCurrentKeymapRef(entry_ref& ref) const 483 { 484 BPath path; 485 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK 486 || path.Append("Key_map") != B_OK) { 487 return B_ERROR; 488 } 489 490 return get_ref_for_path(path.Path(), &ref); 491 } 492 493 494 BMenuItem* 495 BootPromptWindow::_KeymapItemForLanguage(BLanguage& language) const 496 { 497 BLanguage english("en"); 498 BString name; 499 if (language.GetName(name, &english) != B_OK) 500 return fDefaultKeymapItem; 501 502 // Check special mappings first 503 for (size_t i = 0; i < kLanguageKeymapMappingsSize; i += 2) { 504 if (!strcmp(name, kLanguageKeymapMappings[i])) { 505 name = kLanguageKeymapMappings[i + 1]; 506 break; 507 } 508 } 509 510 BMenu* menu = fKeymapsMenuField->Menu(); 511 for (int32 i = 0; i < menu->CountItems(); i++) { 512 BMenuItem* item = menu->ItemAt(i); 513 BMessage* message = item->Message(); 514 515 entry_ref ref; 516 if (message->FindRef("ref", &ref) == B_OK 517 && name == ref.name) 518 return item; 519 } 520 521 return fDefaultKeymapItem; 522 } 523