xref: /haiku/src/preferences/locale/LocaleWindow.cpp (revision 56187df6ebd9d1fb16bf954fce8df0999e9e3048)
1 /*
2  * Copyright 2005-2010, Axel Dörfler, axeld@pinc-software.de.
3  * Copyright 2009-2010, Adrien Destugues <pulkomandy@gmail.com>.
4  * All rights reserved. Distributed under the terms of the MIT License.
5  */
6 
7 
8 #include "LocaleWindow.h"
9 
10 #include <iostream>
11 
12 #include <Alert.h>
13 #include <Application.h>
14 #include <Button.h>
15 #include <Catalog.h>
16 #include <ControlLook.h>
17 #include <GroupLayout.h>
18 #include <LayoutBuilder.h>
19 #include <Locale.h>
20 #include <LocaleRoster.h>
21 #include <Screen.h>
22 #include <ScrollView.h>
23 #include <StringView.h>
24 #include <TabView.h>
25 #include <UnicodeChar.h>
26 
27 #include "FormatSettingsView.h"
28 #include "LocalePreflet.h"
29 #include "LanguageListView.h"
30 
31 
32 #include <stdio.h>
33 
34 
35 #undef B_TRANSLATE_CONTEXT
36 #define B_TRANSLATE_CONTEXT "Locale Preflet Window"
37 
38 
39 static const uint32 kMsgLanguageInvoked = 'LaIv';
40 static const uint32 kMsgLanguageDragged = 'LaDr';
41 static const uint32 kMsgPreferredLanguageInvoked = 'PLIv';
42 static const uint32 kMsgPreferredLanguageDragged = 'PLDr';
43 static const uint32 kMsgPreferredLanguageDeleted = 'PLDl';
44 static const uint32 kMsgCountrySelection = 'csel';
45 static const uint32 kMsgDefaults = 'dflt';
46 
47 static const uint32 kMsgPreferredLanguagesChanged = 'lang';
48 
49 
50 static int
51 compare_typed_list_items(const BListItem* _a, const BListItem* _b)
52 {
53 	static BCollator collator;
54 
55 	LanguageListItem* a = (LanguageListItem*)_a;
56 	LanguageListItem* b = (LanguageListItem*)_b;
57 
58 	return collator.Compare(a->Text(), b->Text());
59 }
60 
61 
62 static int
63 compare_void_list_items(const void* _a, const void* _b)
64 {
65 	static BCollator collator;
66 
67 	LanguageListItem* a = *(LanguageListItem**)_a;
68 	LanguageListItem* b = *(LanguageListItem**)_b;
69 
70 	return collator.Compare(a->Text(), b->Text());
71 }
72 
73 
74 // #pragma mark -
75 
76 
77 LocaleWindow::LocaleWindow()
78 	:
79 	BWindow(BRect(0, 0, 0, 0), B_TRANSLATE("Locale"), B_TITLED_WINDOW,
80 		B_QUIT_ON_WINDOW_CLOSE | B_ASYNCHRONOUS_CONTROLS
81 			| B_AUTO_UPDATE_SIZE_LIMITS)
82 {
83 	SetLayout(new BGroupLayout(B_HORIZONTAL));
84 
85 	float spacing = be_control_look->DefaultItemSpacing();
86 
87 	BTabView* tabView = new BTabView("tabview");
88 	BGroupView* languageTab = new BGroupView(B_TRANSLATE("Language"),
89 		B_HORIZONTAL, spacing);
90 
91 	// first list: available languages
92 	fLanguageListView = new LanguageListView("available",
93 		B_MULTIPLE_SELECTION_LIST);
94 	BScrollView* scrollView = new BScrollView("scroller", fLanguageListView,
95 		B_WILL_DRAW | B_FRAME_EVENTS, false, true);
96 
97 	fLanguageListView->SetInvocationMessage(new BMessage(kMsgLanguageInvoked));
98 	fLanguageListView->SetDragMessage(new BMessage(kMsgLanguageDragged));
99 
100 	BFont font;
101 	fLanguageListView->GetFont(&font);
102 
103 	// Fill the language list from the LocaleRoster data
104 	BMessage installedLanguages;
105 	if (be_locale_roster->GetAvailableLanguages(&installedLanguages) == B_OK) {
106 		BString currentID;
107 		LanguageListItem* lastAddedCountryItem = NULL;
108 
109 		for (int i = 0; installedLanguages.FindString("language", i, &currentID)
110 				== B_OK; i++) {
111 			// Now get the human-readable, native name for each language
112 			BString name;
113 			BLanguage currentLanguage(currentID.String());
114 			currentLanguage.GetNativeName(name);
115 
116 			// TODO: the following block fails to detect a couple of language
117 			// names as containing glyphs we can't render. Why's that?
118 			bool hasGlyphs[name.CountChars()];
119 			font.GetHasGlyphs(name.String(), name.CountChars(), hasGlyphs);
120 			for (int32 i = 0; i < name.CountChars(); ++i) {
121 				if (!hasGlyphs[i]) {
122 					// replace by name translated to current language
123 					currentLanguage.GetName(name);
124 					break;
125 				}
126 			}
127 
128 			LanguageListItem* item = new LanguageListItem(name,
129 				currentID.String(), currentLanguage.Code());
130 			if (currentLanguage.IsCountrySpecific()
131 				&& lastAddedCountryItem != NULL
132 				&& lastAddedCountryItem->Code() == item->Code()) {
133 				fLanguageListView->AddUnder(item, lastAddedCountryItem);
134 			} else {
135 				// This is a language variant, add it at top-level
136 				fLanguageListView->AddItem(item);
137 				if (!currentLanguage.IsCountrySpecific()) {
138 					item->SetExpanded(false);
139 					lastAddedCountryItem = item;
140 				}
141 			}
142 		}
143 
144 		fLanguageListView->FullListSortItems(compare_typed_list_items);
145 	} else {
146 		BAlert* alert = new BAlert("Error",
147 			B_TRANSLATE("Unable to find the available languages! You can't "
148 				"use this preflet!"),
149 			B_TRANSLATE("OK"), NULL, NULL,
150 			B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_STOP_ALERT);
151 		alert->Go();
152 	}
153 
154 	// Second list: active languages
155 	fPreferredListView = new LanguageListView("preferred",
156 		B_MULTIPLE_SELECTION_LIST);
157 	BScrollView* scrollViewEnabled = new BScrollView("scroller",
158 		fPreferredListView, B_WILL_DRAW | B_FRAME_EVENTS, false, true);
159 
160 	fPreferredListView->SetInvocationMessage(
161 		new BMessage(kMsgPreferredLanguageInvoked));
162 	fPreferredListView->SetDeleteMessage(
163 		new BMessage(kMsgPreferredLanguageDeleted));
164 	fPreferredListView->SetDragMessage(
165 		new BMessage(kMsgPreferredLanguageDragged));
166 
167 	BLayoutBuilder::Group<>(languageTab)
168 		.AddGroup(B_VERTICAL, spacing)
169 			.Add(new BStringView("", B_TRANSLATE("Available languages")))
170 			.Add(scrollView)
171 			.End()
172 		.AddGroup(B_VERTICAL, spacing)
173 			.Add(new BStringView("", B_TRANSLATE("Preferred languages")))
174 			.Add(scrollViewEnabled)
175 			.End()
176 		.SetInsets(spacing, spacing, spacing, spacing);
177 
178 	BView* countryTab = new BView(B_TRANSLATE("Formatting"), B_WILL_DRAW);
179 	countryTab->SetLayout(new BGroupLayout(B_VERTICAL, 0));
180 
181 	BListView* listView = new BListView("formatting", B_SINGLE_SELECTION_LIST);
182 	scrollView = new BScrollView("scroller", listView,
183 		B_WILL_DRAW | B_FRAME_EVENTS, false, true);
184 	listView->SetSelectionMessage(new BMessage(kMsgCountrySelection));
185 
186 	// get all available formatting conventions (by language)
187 	BString formattingConventionCode;
188 	LanguageListItem* currentItem = NULL;
189 	BCountry defaultFormattingConvention;
190 	be_locale->GetCountry(&defaultFormattingConvention);
191 	for (int i = 0;
192 		installedLanguages.FindString("language", i, &formattingConventionCode)
193 			== B_OK; i++) {
194 		BCountry formattingConvention(formattingConventionCode);
195 		BString formattingConventionName;
196 		formattingConvention.GetName(formattingConventionName);
197 
198 		LanguageListItem* item = new LanguageListItem(formattingConventionName,
199 			formattingConventionCode, NULL);
200 		listView->AddItem(item);
201 		if (!strcmp(formattingConventionCode,
202 				defaultFormattingConvention.Code()))
203 			currentItem = item;
204 	}
205 
206 	listView->SortItems(compare_void_list_items);
207 	if (currentItem != NULL)
208 		listView->Select(listView->IndexOf(currentItem));
209 
210 	// TODO: find a real solution intead of this hack
211 	listView->SetExplicitMinSize(
212 		BSize(25 * be_plain_font->Size(), B_SIZE_UNSET));
213 
214 	fFormatView = new FormatView(*be_locale);
215 
216 	countryTab->AddChild(BLayoutBuilder::Group<>(B_HORIZONTAL, spacing)
217 		.AddGroup(B_VERTICAL, 3)
218 			.Add(scrollView)
219 			.End()
220 		.Add(fFormatView)
221 		.SetInsets(spacing, spacing, spacing, spacing));
222 
223 	listView->ScrollToSelection();
224 
225 	tabView->AddTab(languageTab);
226 	tabView->AddTab(countryTab);
227 
228 	BButton* button = new BButton(B_TRANSLATE("Defaults"),
229 		new BMessage(kMsgDefaults));
230 
231 	fRevertButton = new BButton(B_TRANSLATE("Revert"),
232 		new BMessage(kMsgRevert));
233 	fRevertButton->SetEnabled(false);
234 
235 	BLayoutBuilder::Group<>(this, B_VERTICAL, spacing)
236 		.Add(tabView)
237 		.AddGroup(B_HORIZONTAL, spacing)
238 			.Add(button)
239 			.Add(fRevertButton)
240 			.AddGlue()
241 			.End()
242 		.SetInsets(spacing, spacing, spacing, spacing)
243 		.End();
244 
245 	_UpdatePreferredFromLocaleRoster();
246 	SettingsReverted();
247 	CenterOnScreen();
248 }
249 
250 
251 LocaleWindow::~LocaleWindow()
252 {
253 }
254 
255 
256 void
257 LocaleWindow::MessageReceived(BMessage* message)
258 {
259 	switch (message->what) {
260 		case kMsgDefaults:
261 			_Defaults();
262 			break;
263 
264 		case kMsgRevert:
265 			be_app_messenger.SendMessage(message);
266 			_UpdatePreferredFromLocaleRoster();
267 			break;
268 
269 		case kMsgLanguageDragged:
270 		{
271 			void* target = NULL;
272 			if (message->FindPointer("drop_target", &target) != B_OK
273 				|| target != fPreferredListView)
274 				break;
275 
276 			// Add from available languages to preferred languages
277 			int32 dropIndex;
278 			if (message->FindInt32("drop_index", &dropIndex) != B_OK)
279 				dropIndex = fPreferredListView->CountItems();
280 
281 			int32 index = 0;
282 			for (int32 i = 0; message->FindInt32("index", i, &index) == B_OK;
283 					i++) {
284 				LanguageListItem* item = static_cast<LanguageListItem*>(
285 					fLanguageListView->FullListItemAt(index));
286 				_InsertPreferredLanguage(item, dropIndex++);
287 			}
288 			break;
289 		}
290 		case kMsgLanguageInvoked:
291 		{
292 			int32 index = 0;
293 			for (int32 i = 0; message->FindInt32("index", i, &index) == B_OK;
294 					i++) {
295 				LanguageListItem* item = static_cast<LanguageListItem*>(
296 					fLanguageListView->ItemAt(index));
297 				_InsertPreferredLanguage(item);
298 			}
299 			break;
300 		}
301 
302 		case kMsgPreferredLanguageDragged:
303 		{
304 			void* target = NULL;
305 			if (fPreferredListView->CountItems() == 1
306 				|| message->FindPointer("drop_target", &target) != B_OK)
307 				break;
308 
309 			if (target == fPreferredListView) {
310 				// change ordering
311 				int32 dropIndex = message->FindInt32("drop_index");
312 				int32 index = 0;
313 				if (message->FindInt32("index", &index) == B_OK
314 					&& dropIndex != index) {
315 					BListItem* item = fPreferredListView->RemoveItem(index);
316 					if (dropIndex > index)
317 						index--;
318 					fPreferredListView->AddItem(item, dropIndex);
319 
320 					_PreferredLanguagesChanged();
321 				}
322 				break;
323 			}
324 
325 			// supposed to fall through - remove item
326 		}
327 		case kMsgPreferredLanguageDeleted:
328 		case kMsgPreferredLanguageInvoked:
329 		{
330 			if (fPreferredListView->CountItems() == 1)
331 				break;
332 
333 			// Remove from preferred languages
334 			int32 index = 0;
335 			if (message->FindInt32("index", &index) == B_OK) {
336 				delete fPreferredListView->RemoveItem(index);
337 				_PreferredLanguagesChanged();
338 
339 				if (message->what == kMsgPreferredLanguageDeleted)
340 					fPreferredListView->Select(index);
341 			}
342 			break;
343 		}
344 
345 		case kMsgCountrySelection:
346 		{
347 			// Country selection changed.
348 			// Get the new selected country from the ListView and send it to the
349 			// main app event handler.
350 			void* listView;
351 			if (message->FindPointer("source", &listView) != B_OK)
352 				break;
353 
354 			BListView* countryList = static_cast<BListView*>(listView);
355 
356 			LanguageListItem* item = static_cast<LanguageListItem*>
357 				(countryList->ItemAt(countryList->CurrentSelection()));
358 			BMessage newMessage(kMsgSettingsChanged);
359 			newMessage.AddString("country", item->ID());
360 			be_app_messenger.SendMessage(&newMessage);
361 			SettingsChanged();
362 
363 			BCountry country(item->ID());
364 			BLocale locale(NULL, &country);
365 			fFormatView->SetLocale(locale);
366 			break;
367 		}
368 
369 		default:
370 			BWindow::MessageReceived(message);
371 			break;
372 	}
373 }
374 
375 
376 bool
377 LocaleWindow::QuitRequested()
378 {
379 	return true;
380 }
381 
382 
383 void
384 LocaleWindow::SettingsChanged()
385 {
386 	fRevertButton->SetEnabled(true);
387 }
388 
389 
390 void
391 LocaleWindow::SettingsReverted()
392 {
393 	fRevertButton->SetEnabled(false);
394 }
395 
396 
397 void
398 LocaleWindow::_PreferredLanguagesChanged()
399 {
400 	BMessage update(kMsgSettingsChanged);
401 	int index = 0;
402 	while (index < fPreferredListView->FullListCountItems()) {
403 		// only include subitems: we can guess the superitem
404 		// from them anyway
405 		LanguageListItem* item = static_cast<LanguageListItem*>(
406 			fPreferredListView->FullListItemAt(index));
407 		if (item != NULL)
408 			update.AddString("language", item->ID());
409 
410 		index++;
411 	}
412 	be_app_messenger.SendMessage(&update);
413 
414 	_EnableDisableLanguages();
415 }
416 
417 
418 void
419 LocaleWindow::_EnableDisableLanguages()
420 {
421 	DisableUpdates();
422 
423 	for (int32 i = 0; i < fLanguageListView->FullListCountItems(); i++) {
424 		LanguageListItem* item = static_cast<LanguageListItem*>(
425 			fLanguageListView->FullListItemAt(i));
426 
427 		bool enable = fPreferredListView->ItemForLanguageID(item->ID()) == NULL;
428 		if (item->IsEnabled() != enable) {
429 			item->SetEnabled(enable);
430 
431 			int32 visibleIndex = fLanguageListView->IndexOf(item);
432 			if (visibleIndex >= 0) {
433 				if (!enable)
434 					fLanguageListView->Deselect(visibleIndex);
435 				fLanguageListView->InvalidateItem(visibleIndex);
436 			}
437 		}
438 	}
439 
440 	SettingsChanged();
441 
442 	EnableUpdates();
443 }
444 
445 
446 //! Get the preferred languages from the settings.
447 void
448 LocaleWindow::_UpdatePreferredFromLocaleRoster()
449 {
450 	DisableUpdates();
451 
452 	// Delete all existing items
453 	for (int32 index = fPreferredListView->CountItems(); index-- > 0; ) {
454 		delete fPreferredListView->ItemAt(index);
455 	}
456 	fPreferredListView->MakeEmpty();
457 
458 	// Add new ones from the locale roster
459 	BMessage preferredLanguages;
460 	be_locale_roster->GetPreferredLanguages(&preferredLanguages);
461 
462 	BString languageID;
463 	for (int32 index = 0; preferredLanguages.FindString("language", index,
464 			&languageID) == B_OK; index++) {
465 		int32 listIndex;
466 		LanguageListItem* item
467 			= fLanguageListView->ItemForLanguageID(languageID.String(),
468 				&listIndex);
469 		if (item != NULL) {
470 			// We found the item we were looking for, now copy it to
471 			// the other list
472 			fPreferredListView->AddItem(new LanguageListItem(*item));
473 		}
474 	}
475 
476 	_EnableDisableLanguages();
477 	EnableUpdates();
478 }
479 
480 
481 void
482 LocaleWindow::_InsertPreferredLanguage(LanguageListItem* item, int32 atIndex)
483 {
484 	if (item == NULL || fPreferredListView->ItemForLanguageID(
485 			item->ID().String()) != NULL)
486 		return;
487 
488 	if (atIndex == -1)
489 		atIndex = fPreferredListView->CountItems();
490 
491 	BLanguage* language = NULL;
492 	be_locale_roster->GetLanguage(item->Code(), &language);
493 
494 	LanguageListItem* baseItem = NULL;
495 	if (language != NULL) {
496 		baseItem = fPreferredListView->ItemForLanguageCode(language->Code(),
497 			&atIndex);
498 		delete language;
499 	}
500 
501 	DisableUpdates();
502 
503 	fPreferredListView->AddItem(new LanguageListItem(*item), atIndex);
504 
505 	// Replace other languages with the same base
506 
507 	if (baseItem != NULL) {
508 		fPreferredListView->RemoveItem(baseItem);
509 		delete baseItem;
510 	}
511 
512 	_PreferredLanguagesChanged();
513 
514 	EnableUpdates();
515 }
516 
517 
518 void
519 LocaleWindow::_Defaults()
520 {
521 	BMessage update(kMsgSettingsChanged);
522 	update.AddString("language", "en");
523 
524 	be_app_messenger.SendMessage(&update);
525 	SettingsChanged();
526 	_UpdatePreferredFromLocaleRoster();
527 }
528 
529