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