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