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