xref: /haiku/src/preferences/locale/LocaleWindow.cpp (revision 3a5082aa46f958b1f49398c8b69458fa12dd581e)
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 "Locale.h"
9 #include "LocaleWindow.h"
10 #include "LanguageListView.h"
11 
12 #include <iostream>
13 
14 #include <Alert.h>
15 #include <Application.h>
16 #include <Button.h>
17 #include <Catalog.h>
18 #include <ControlLook.h>
19 #include <GroupLayout.h>
20 #include <LayoutBuilder.h>
21 #include <Locale.h>
22 #include <LocaleRoster.h>
23 #include <Screen.h>
24 #include <ScrollView.h>
25 #include <StringView.h>
26 #include <TabView.h>
27 #include <UnicodeChar.h>
28 
29 #include "FormatSettingsView.h"
30 
31 
32 #undef B_TRANSLATE_CONTEXT
33 #define B_TRANSLATE_CONTEXT "Locale Preflet Window"
34 
35 
36 static const uint32 kMsgLanguageInvoked = 'LaIv';
37 static const uint32 kMsgLanguageDragged = 'LaDr';
38 static const uint32 kMsgPreferredLanguageInvoked = 'PLIv';
39 static const uint32 kMsgPreferredLanguageDragged = 'PLDr';
40 static const uint32 kMsgPreferredLanguageDeleted = 'PLDl';
41 static const uint32 kMsgCountrySelection = 'csel';
42 static const uint32 kMsgDefaults = 'dflt';
43 
44 static const uint32 kMsgPreferredLanguagesChanged = 'lang';
45 
46 
47 static int
48 compare_typed_list_items(const BListItem* _a, const BListItem* _b)
49 {
50 	static BCollator collator;
51 
52 	LanguageListItem* a = (LanguageListItem*)_a;
53 	LanguageListItem* b = (LanguageListItem*)_b;
54 
55 	return collator.Compare(a->Text(), b->Text());
56 }
57 
58 
59 static int
60 compare_void_list_items(const void* _a, const void* _b)
61 {
62 	static BCollator collator;
63 
64 	LanguageListItem* a = *(LanguageListItem**)_a;
65 	LanguageListItem* b = *(LanguageListItem**)_b;
66 
67 	return collator.Compare(a->Text(), b->Text());
68 }
69 
70 
71 // #pragma mark -
72 
73 
74 LocaleWindow::LocaleWindow()
75 	:
76 	BWindow(BRect(0, 0, 0, 0), "Locale", B_TITLED_WINDOW, B_QUIT_ON_WINDOW_CLOSE
77 		| B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS)
78 {
79 	BLocale defaultLocale;
80 	be_locale_roster->GetDefaultLocale(&defaultLocale);
81 
82 	SetLayout(new BGroupLayout(B_HORIZONTAL));
83 
84 	float spacing = be_control_look->DefaultItemSpacing();
85 
86 	BTabView* tabView = new BTabView("tabview");
87 	BGroupView* languageTab = new BGroupView(B_TRANSLATE("Language"),
88 		B_HORIZONTAL, spacing);
89 
90 	// first list: available languages
91 	fLanguageListView = new LanguageListView("available",
92 		B_MULTIPLE_SELECTION_LIST);
93 	BScrollView* scrollView = new BScrollView("scroller", fLanguageListView,
94 		B_WILL_DRAW | B_FRAME_EVENTS, false, true);
95 
96 	fLanguageListView->SetInvocationMessage(new BMessage(kMsgLanguageInvoked));
97 	fLanguageListView->SetDragMessage(new BMessage(kMsgLanguageDragged));
98 
99 	// Fill the language list from the LocaleRoster data
100 	BMessage installedLanguages;
101 	if (be_locale_roster->GetInstalledLanguages(&installedLanguages) == B_OK) {
102 		BString currentID;
103 		LanguageListItem* lastAddedCountryItem = NULL;
104 
105 		for (int i = 0; installedLanguages.FindString("langs", i, &currentID)
106 				== B_OK; i++) {
107 			// Now get an human-readable, localized name for each language
108 			BLanguage* currentLanguage;
109 			be_locale_roster->GetLanguage(currentID.String(),
110 				&currentLanguage);
111 
112 			BString name;
113 			currentLanguage->GetName(name);
114 
115 			// TODO: as long as the app_server doesn't support font overlays,
116 			// use the translated name if problematic characters are used...
117 			const char* string = name.String();
118 			while (uint32 code = BUnicodeChar::FromUTF8(&string)) {
119 				if (code > 1424) {
120 					currentLanguage->GetTranslatedName(name);
121 					break;
122 				}
123 			}
124 
125 			LanguageListItem* item = new LanguageListItem(name,
126 				currentID.String(), currentLanguage->Code());
127 			if (currentLanguage->IsCountrySpecific()
128 				&& lastAddedCountryItem != NULL
129 				&& lastAddedCountryItem->Code() == item->Code()) {
130 				fLanguageListView->AddUnder(item, lastAddedCountryItem);
131 			} else {
132 				// This is a language variant, add it at top-level
133 				fLanguageListView->AddItem(item);
134 				if (!currentLanguage->IsCountrySpecific()) {
135 					item->SetExpanded(false);
136 					lastAddedCountryItem = item;
137 				}
138 			}
139 
140 			delete currentLanguage;
141 		}
142 
143 		fLanguageListView->FullListSortItems(compare_typed_list_items);
144 	} else {
145 		BAlert* alert = new BAlert("Error",
146 			B_TRANSLATE("Unable to find the available languages! You can't "
147 				"use this preflet!"),
148 			B_TRANSLATE("OK"), NULL, NULL,
149 			B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_STOP_ALERT);
150 		alert->Go();
151 	}
152 
153 	// Second list: active languages
154 	fPreferredListView = new LanguageListView("preferred",
155 		B_MULTIPLE_SELECTION_LIST);
156 	BScrollView* scrollViewEnabled = new BScrollView("scroller",
157 		fPreferredListView, B_WILL_DRAW | B_FRAME_EVENTS, false, true);
158 
159 	fPreferredListView->SetInvocationMessage(
160 		new BMessage(kMsgPreferredLanguageInvoked));
161 	fPreferredListView->SetDeleteMessage(
162 		new BMessage(kMsgPreferredLanguageDeleted));
163 	fPreferredListView->SetDragMessage(
164 		new BMessage(kMsgPreferredLanguageDragged));
165 
166 	BLayoutBuilder::Group<>(languageTab)
167 		.AddGroup(B_VERTICAL, spacing)
168 			.Add(new BStringView("", B_TRANSLATE("Available languages")))
169 			.Add(scrollView)
170 			.End()
171 		.AddGroup(B_VERTICAL, spacing)
172 			.Add(new BStringView("", B_TRANSLATE("Preferred languages")))
173 			.Add(scrollViewEnabled)
174 			.End()
175 		.SetInsets(spacing, spacing, spacing, spacing);
176 
177 	BView* countryTab = new BView(B_TRANSLATE("Country"), B_WILL_DRAW);
178 	countryTab->SetLayout(new BGroupLayout(B_VERTICAL, 0));
179 
180 	BListView* listView = new BListView("country", B_SINGLE_SELECTION_LIST);
181 	scrollView = new BScrollView("scroller", listView,
182 		B_WILL_DRAW | B_FRAME_EVENTS, false, true);
183 	listView->SetSelectionMessage(new BMessage(kMsgCountrySelection));
184 
185 	// get all available countries
186 	BMessage countryList;
187 	be_locale_roster->GetInstalledLanguages(&countryList);
188 	BString countryCode;
189 
190 	LanguageListItem* currentItem = NULL;
191 	for (int i = 0; countryList.FindString("langs", i, &countryCode) == B_OK;
192 			i++) {
193 		BLocale locale(countryCode);
194 		BString countryName;
195 
196 		locale.GetName(countryName);
197 
198 		LanguageListItem* item
199 			= new LanguageListItem(countryName, countryCode,
200 				NULL);
201 		listView->AddItem(item);
202 		if (!strcmp(countryCode, defaultLocale.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(defaultLocale);
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 			BLocale locale(item->ID());
364 			fFormatView->SetLocale(locale);
365 			break;
366 		}
367 
368 		default:
369 			BWindow::MessageReceived(message);
370 			break;
371 	}
372 }
373 
374 
375 bool
376 LocaleWindow::QuitRequested()
377 {
378 	return true;
379 }
380 
381 
382 void
383 LocaleWindow::SettingsChanged()
384 {
385 	fRevertButton->SetEnabled(true);
386 }
387 
388 
389 void
390 LocaleWindow::SettingsReverted()
391 {
392 	fRevertButton->SetEnabled(false);
393 }
394 
395 
396 void
397 LocaleWindow::_PreferredLanguagesChanged()
398 {
399 	BMessage update(kMsgSettingsChanged);
400 	int index = 0;
401 	while (index < fPreferredListView->FullListCountItems()) {
402 		// only include subitems: we can guess the superitem
403 		// from them anyway
404 		LanguageListItem* item = static_cast<LanguageListItem*>(
405 			fPreferredListView->FullListItemAt(index));
406 		if (item != NULL)
407 			update.AddString("language", item->ID());
408 
409 		index++;
410 	}
411 	be_app_messenger.SendMessage(&update);
412 
413 	_EnableDisableLanguages();
414 }
415 
416 
417 void
418 LocaleWindow::_EnableDisableLanguages()
419 {
420 	DisableUpdates();
421 
422 	for (int32 i = 0; i < fLanguageListView->FullListCountItems(); i++) {
423 		LanguageListItem* item = static_cast<LanguageListItem*>(
424 			fLanguageListView->FullListItemAt(i));
425 
426 		bool enable = fPreferredListView->ItemForLanguageID(item->ID()) == NULL;
427 		if (item->IsEnabled() != enable) {
428 			item->SetEnabled(enable);
429 
430 			int32 visibleIndex = fLanguageListView->IndexOf(item);
431 			if (visibleIndex >= 0) {
432 				if (!enable)
433 					fLanguageListView->Deselect(visibleIndex);
434 				fLanguageListView->InvalidateItem(visibleIndex);
435 			}
436 		}
437 	}
438 
439 	SettingsChanged();
440 
441 	EnableUpdates();
442 }
443 
444 
445 //! Get the preferred languages from the settings.
446 void
447 LocaleWindow::_UpdatePreferredFromLocaleRoster()
448 {
449 	DisableUpdates();
450 
451 	// Delete all existing items
452 	for (int32 index = fPreferredListView->CountItems(); index-- > 0; ) {
453 		delete fPreferredListView->ItemAt(index);
454 	}
455 	fPreferredListView->MakeEmpty();
456 
457 	// Add new ones from the locale roster
458 	BMessage preferredLanguages;
459 	be_locale_roster->GetPreferredLanguages(&preferredLanguages);
460 
461 	BString languageID;
462 	for (int32 index = 0; preferredLanguages.FindString("language", index,
463 			&languageID) == B_OK; index++) {
464 		int32 listIndex;
465 		LanguageListItem* item
466 			= fLanguageListView->ItemForLanguageID(languageID.String(),
467 				&listIndex);
468 		if (item != NULL) {
469 			// We found the item we were looking for, now copy it to
470 			// the other list
471 			fPreferredListView->AddItem(new LanguageListItem(*item));
472 		}
473 	}
474 
475 	_EnableDisableLanguages();
476 	EnableUpdates();
477 }
478 
479 
480 void
481 LocaleWindow::_InsertPreferredLanguage(LanguageListItem* item, int32 atIndex)
482 {
483 	if (item == NULL || fPreferredListView->ItemForLanguageID(
484 			item->ID().String()) != NULL)
485 		return;
486 
487 	if (atIndex == -1)
488 		atIndex = fPreferredListView->CountItems();
489 
490 	BLanguage* language = NULL;
491 	be_locale_roster->GetLanguage(item->Code(), &language);
492 
493 	LanguageListItem* baseItem = NULL;
494 	if (language != NULL) {
495 		baseItem = fPreferredListView->ItemForLanguageCode(language->Code(),
496 			&atIndex);
497 		delete language;
498 	}
499 
500 	DisableUpdates();
501 
502 	fPreferredListView->AddItem(new LanguageListItem(*item), atIndex);
503 
504 	// Replace other languages with the same base
505 
506 	if (baseItem != NULL) {
507 		fPreferredListView->RemoveItem(baseItem);
508 		delete baseItem;
509 	}
510 
511 	_PreferredLanguagesChanged();
512 
513 	EnableUpdates();
514 }
515 
516 
517 void
518 LocaleWindow::_Defaults()
519 {
520 	BMessage update(kMsgSettingsChanged);
521 	update.AddString("language", "en");
522 
523 	be_app_messenger.SendMessage(&update);
524 	SettingsChanged();
525 	_UpdatePreferredFromLocaleRoster();
526 }
527 
528