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