xref: /haiku/src/apps/firstbootprompt/BootPromptWindow.cpp (revision de0e15ae8c1b2bab0c2217fd7a91b26dc6111761)
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(&currentRef);
392 		node.ReadAttrString("keymap:name", &currentName);
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