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