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_ALL_WORKSPACES), 155 fDefaultKeymapItem(NULL) 156 { 157 SetSizeLimits(450, 16384, 350, 16384); 158 159 rgb_color textColor = ui_color(B_PANEL_TEXT_COLOR); 160 fInfoTextView = new BTextView(""); 161 fInfoTextView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 162 fInfoTextView->SetFontAndColor(be_plain_font, B_FONT_ALL, &textColor); 163 fInfoTextView->MakeEditable(false); 164 fInfoTextView->MakeSelectable(false); 165 166 // Carefully designed to not exceed the 640x480 resolution with a 12pt font. 167 float width = fInfoTextView->StringWidth("Thank you for trying out Haiku," 168 " We hope you like it!") * 1.5; 169 float height = be_plain_font->Size() * 36; 170 171 BRect newFrame(0, 0, width, height); 172 newFrame = newFrame & BScreen().Frame(); 173 ResizeTo(newFrame.Width(), newFrame.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 fKeymapsMenuField = new BMenuField("", "", new BMenu("")); 196 fKeymapsMenuField->Menu()->SetLabelFromMarked(true); 197 198 _InitCatalog(true); 199 _PopulateLanguages(); 200 _PopulateKeymaps(); 201 202 BLayoutBuilder::Group<>(this, B_VERTICAL, 0) 203 .AddGroup(B_HORIZONTAL) 204 .AddGroup(B_VERTICAL) 205 .AddGroup(B_VERTICAL) 206 .Add(fLanguagesLabelView) 207 .Add(languagesScrollView) 208 .End() 209 .AddGrid(0.f) 210 .AddMenuField(fKeymapsMenuField, 0, 0) 211 .End() 212 .End() 213 .Add(fInfoTextView) 214 .SetInsets(B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING, 215 B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING) 216 .End() 217 .Add(new BSeparatorView(B_HORIZONTAL)) 218 .AddGroup(B_HORIZONTAL) 219 .AddGlue() 220 .Add(fInstallerButton) 221 .Add(fDesktopButton) 222 .SetInsets(B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING, 223 B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING) 224 .End(); 225 226 fLanguagesListView->MakeFocus(); 227 228 _UpdateStrings(); 229 CenterOnScreen(); 230 Show(); 231 } 232 233 234 void 235 BootPromptWindow::MessageReceived(BMessage* message) 236 { 237 switch (message->what) { 238 case MSG_LANGUAGE_SELECTED: 239 if (LanguageItem* item = static_cast<LanguageItem*>( 240 fLanguagesListView->ItemAt( 241 fLanguagesListView->CurrentSelection(0)))) { 242 BMessage preferredLanguages; 243 preferredLanguages.AddString("language", item->Language()); 244 MutableLocaleRoster::Default()->SetPreferredLanguages( 245 &preferredLanguages); 246 _InitCatalog(true); 247 248 // Select default keymap by language 249 BLanguage language(item->Language()); 250 BMenuItem* keymapItem = _KeymapItemForLanguage(language); 251 if (keymapItem != NULL) { 252 keymapItem->SetMarked(true); 253 _ActivateKeymap(keymapItem->Message()); 254 } 255 } 256 // Calling it here is a cheap way of preventing the user to have 257 // no item selected. Always the current item will be selected. 258 _UpdateStrings(); 259 break; 260 261 case MSG_KEYMAP_SELECTED: 262 _ActivateKeymap(message); 263 break; 264 265 default: 266 BWindow::MessageReceived(message); 267 } 268 } 269 270 271 void 272 BootPromptWindow::_InitCatalog(bool saveSettings) 273 { 274 // Initilialize the Locale Kit 275 BPrivate::ForceUnloadCatalog(); 276 277 if (!saveSettings) 278 return; 279 280 BMessage settings; 281 BString language; 282 if (BLocaleRoster::Default()->GetCatalog()->GetLanguage(&language) == B_OK) 283 settings.AddString("language", language.String()); 284 285 MutableLocaleRoster::Default()->SetPreferredLanguages(&settings); 286 287 BFormattingConventions conventions(language.String()); 288 MutableLocaleRoster::Default()->SetDefaultFormattingConventions( 289 conventions); 290 } 291 292 293 void 294 BootPromptWindow::_UpdateStrings() 295 { 296 SetTitle(B_TRANSLATE("Welcome to Haiku!")); 297 298 fInfoTextView->SetText(B_TRANSLATE_COMMENT( 299 "Thank you for trying out Haiku! We hope you'll like it!\n\n" 300 "You can select your preferred language and keyboard " 301 "layout from the list on the left which will then be used instantly. " 302 "You can easily change both settings from the Desktop later on on " 303 "the fly.\n\n" 304 305 "Do you wish to run the Installer or continue booting to the " 306 "Desktop?\n", 307 308 "For other languages, a note could be added: \"" 309 "Note: Localization of Haiku applications and other components is " 310 "an on-going effort. You will frequently encounter untranslated " 311 "strings, but if you like, you can join in the work at " 312 "<www.haiku-os.org>.\"")); 313 314 fDesktopButton->SetLabel(B_TRANSLATE("Boot to Desktop")); 315 fInstallerButton->SetLabel(B_TRANSLATE("Run Installer")); 316 317 fLanguagesLabelView->SetText(B_TRANSLATE("Language")); 318 fKeymapsMenuField->SetLabel(B_TRANSLATE("Keymap")); 319 if (fKeymapsMenuField->Menu()->FindMarked() == NULL) 320 fKeymapsMenuField->MenuItem()->SetLabel(B_TRANSLATE("Custom")); 321 } 322 323 324 void 325 BootPromptWindow::_PopulateLanguages() 326 { 327 // TODO: detect language/country from IP address 328 329 // Get current first preferred language of the user 330 BMessage preferredLanguages; 331 BLocaleRoster::Default()->GetPreferredLanguages(&preferredLanguages); 332 const char* firstPreferredLanguage; 333 if (preferredLanguages.FindString("language", &firstPreferredLanguage) 334 != B_OK) { 335 // Fall back to built-in language of this application. 336 firstPreferredLanguage = "en"; 337 } 338 339 BMessage installedCatalogs; 340 BLocaleRoster::Default()->GetAvailableCatalogs(&installedCatalogs, 341 "x-vnd.Haiku-FirstBootPrompt"); 342 343 BFont font; 344 fLanguagesListView->GetFont(&font); 345 346 // Try to instantiate a BCatalog for each language, it will only work 347 // for translations of this application. So the list of languages will be 348 // limited to catalogs written for this application, which is on purpose! 349 350 const char* languageID; 351 LanguageItem* currentItem = NULL; 352 for (int32 i = 0; installedCatalogs.FindString("language", i, &languageID) 353 == B_OK; i++) { 354 BLanguage* language; 355 if (BLocaleRoster::Default()->GetLanguage(languageID, &language) 356 == B_OK) { 357 BString name; 358 language->GetNativeName(name); 359 360 // TODO: the following block fails to detect a couple of language 361 // names as containing glyphs we can't render. Why's that? 362 bool hasGlyphs[name.CountChars()]; 363 font.GetHasGlyphs(name.String(), name.CountChars(), hasGlyphs); 364 for (int32 i = 0; i < name.CountChars(); ++i) { 365 if (!hasGlyphs[i]) { 366 // replace by name translated to current language 367 language->GetName(name); 368 break; 369 } 370 } 371 372 LanguageItem* item = new LanguageItem(name.String(), 373 languageID); 374 fLanguagesListView->AddItem(item); 375 // Select this item if it is the first preferred language 376 if (strcmp(firstPreferredLanguage, languageID) == 0) 377 currentItem = item; 378 379 delete language; 380 } else 381 fprintf(stderr, "failed to get BLanguage for %s\n", languageID); 382 } 383 384 fLanguagesListView->SortItems(compare_void_list_items); 385 if (currentItem != NULL) 386 fLanguagesListView->Select(fLanguagesListView->IndexOf(currentItem)); 387 fLanguagesListView->ScrollToSelection(); 388 389 // Re-enable sending the selection message. 390 fLanguagesListView->SetSelectionMessage( 391 new BMessage(MSG_LANGUAGE_SELECTED)); 392 } 393 394 395 void 396 BootPromptWindow::_PopulateKeymaps() 397 { 398 // Get the name of the current keymap, so we can mark the correct entry 399 // in the list view. 400 BString currentName; 401 entry_ref currentRef; 402 if (_GetCurrentKeymapRef(currentRef) == B_OK) { 403 BNode node(¤tRef); 404 node.ReadAttrString("keymap:name", ¤tName); 405 } 406 407 // TODO: common keymaps! 408 BPath path; 409 if (find_directory(B_SYSTEM_DATA_DIRECTORY, &path) != B_OK 410 || path.Append("Keymaps") != B_OK) { 411 return; 412 } 413 414 // US-International is the default keymap, if we could not found a 415 // matching one 416 BString usInternational("US-International"); 417 418 // Populate the menu 419 BDirectory directory; 420 if (directory.SetTo(path.Path()) == B_OK) { 421 entry_ref ref; 422 BList itemsList; 423 while (directory.GetNextRef(&ref) == B_OK) { 424 BMessage* message = new BMessage(MSG_KEYMAP_SELECTED); 425 message->AddRef("ref", &ref); 426 BMenuItem* item = new BMenuItem(ref.name, message); 427 itemsList.AddItem(item); 428 if (currentName == ref.name) 429 item->SetMarked(true); 430 431 if (usInternational == ref.name) 432 fDefaultKeymapItem = item; 433 } 434 itemsList.SortItems(compare_void_menu_items); 435 fKeymapsMenuField->Menu()->AddList(&itemsList, 0); 436 } 437 } 438 439 440 void 441 BootPromptWindow::_ActivateKeymap(const BMessage* message) const 442 { 443 entry_ref ref; 444 if (message == NULL || message->FindRef("ref", &ref) != B_OK) 445 return; 446 447 // Load and use the new keymap 448 Keymap keymap; 449 if (keymap.Load(ref) != B_OK) { 450 fprintf(stderr, "Failed to load new keymap file (%s).\n", ref.name); 451 return; 452 } 453 454 // Get entry_ref to the Key_map file in the user settings. 455 entry_ref currentRef; 456 if (_GetCurrentKeymapRef(currentRef) != B_OK) { 457 fprintf(stderr, "Failed to get ref to user keymap file.\n"); 458 return; 459 } 460 461 if (keymap.Save(currentRef) != B_OK) { 462 fprintf(stderr, "Failed to save new keymap file (%s).\n", ref.name); 463 return; 464 } 465 466 keymap.Use(); 467 } 468 469 470 status_t 471 BootPromptWindow::_GetCurrentKeymapRef(entry_ref& ref) const 472 { 473 BPath path; 474 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK 475 || path.Append("Key_map") != B_OK) { 476 return B_ERROR; 477 } 478 479 return get_ref_for_path(path.Path(), &ref); 480 } 481 482 483 BMenuItem* 484 BootPromptWindow::_KeymapItemForLanguage(BLanguage& language) const 485 { 486 BLanguage english("en"); 487 BString name; 488 if (language.GetName(name, &english) != B_OK) 489 return fDefaultKeymapItem; 490 491 // Check special mappings first 492 for (size_t i = 0; i < kLanguageKeymapMappingsSize; i += 2) { 493 if (!strcmp(name, kLanguageKeymapMappings[i])) { 494 name = kLanguageKeymapMappings[i + 1]; 495 break; 496 } 497 } 498 499 BMenu* menu = fKeymapsMenuField->Menu(); 500 for (int32 i = 0; i < menu->CountItems(); i++) { 501 BMenuItem* item = menu->ItemAt(i); 502 BMessage* message = item->Message(); 503 504 entry_ref ref; 505 if (message->FindRef("ref", &ref) == B_OK 506 && name == ref.name) 507 return item; 508 } 509 510 return fDefaultKeymapItem; 511 } 512