xref: /haiku/src/preferences/keymap/KeymapWindow.cpp (revision 37fedaf8494b34aad811abcc49e79aa32943f880)
1 /*
2  * Copyright 2004-2014 Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Alexandre Deckner, alex@zappotek.com
7  *		Axel Dörfler, axeld@pinc-software.de
8  *		Jérôme Duval
9  *		John Scipione, jscipione@gmai.com
10  *		Sandor Vroemisse
11  */
12 
13 
14 #include "KeymapWindow.h"
15 
16 #include <string.h>
17 #include <stdio.h>
18 
19 #include <Alert.h>
20 #include <Button.h>
21 #include <Catalog.h>
22 #include <Directory.h>
23 #include <File.h>
24 #include <FindDirectory.h>
25 #include <LayoutBuilder.h>
26 #include <ListView.h>
27 #include <Locale.h>
28 #include <MenuBar.h>
29 #include <MenuField.h>
30 #include <MenuItem.h>
31 #include <Path.h>
32 #include <PopUpMenu.h>
33 #include <Screen.h>
34 #include <ScrollView.h>
35 #include <StringView.h>
36 #include <TextControl.h>
37 
38 #include "KeyboardLayoutView.h"
39 #include "KeymapApplication.h"
40 #include "KeymapListItem.h"
41 #include "KeymapNames.h"
42 
43 
44 #undef B_TRANSLATION_CONTEXT
45 #define B_TRANSLATION_CONTEXT "Keymap window"
46 
47 
48 static const uint32 kMsgMenuFileOpen = 'mMFO';
49 static const uint32 kMsgMenuFileSaveAs = 'mMFA';
50 
51 static const uint32 kChangeKeyboardLayout = 'cKyL';
52 
53 static const uint32 kMsgSwitchShortcuts = 'swSc';
54 
55 static const uint32 kMsgMenuFontChanged = 'mMFC';
56 
57 static const uint32 kMsgSystemMapSelected = 'SmST';
58 static const uint32 kMsgUserMapSelected = 'UmST';
59 
60 static const uint32 kMsgDefaultKeymap = 'Dflt';
61 static const uint32 kMsgRevertKeymap = 'Rvrt';
62 static const uint32 kMsgKeymapUpdated = 'kMup';
63 
64 static const uint32 kMsgDeadKeyAcuteChanged = 'dkAc';
65 static const uint32 kMsgDeadKeyCircumflexChanged = 'dkCc';
66 static const uint32 kMsgDeadKeyDiaeresisChanged = 'dkDc';
67 static const uint32 kMsgDeadKeyGraveChanged = 'dkGc';
68 static const uint32 kMsgDeadKeyTildeChanged = 'dkTc';
69 
70 static const char* kDeadKeyTriggerNone = "<none>";
71 
72 static const char* kCurrentKeymapName = "(Current)";
73 static const char* kDefaultKeymapName = "US-International";
74 
75 
76 static int
77 compare_key_list_items(const void* a, const void* b)
78 {
79 	KeymapListItem* item1 = *(KeymapListItem**)a;
80 	KeymapListItem* item2 = *(KeymapListItem**)b;
81 	return BLocale::Default()->StringCompare(item1->Text(), item2->Text());
82 }
83 
84 
85 KeymapWindow::KeymapWindow()
86 	:
87 	BWindow(BRect(80, 50, 650, 300), B_TRANSLATE_SYSTEM_NAME("Keymap"),
88 		B_TITLED_WINDOW, B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS)
89 {
90 	fKeyboardLayoutView = new KeyboardLayoutView("layout");
91 	fKeyboardLayoutView->SetKeymap(&fCurrentMap);
92 	fKeyboardLayoutView->SetExplicitMinSize(BSize(B_SIZE_UNSET, 192));
93 
94 	fTextControl = new BTextControl(B_TRANSLATE("Sample and clipboard:"),
95 		"", NULL);
96 
97 	fSwitchShortcutsButton = new BButton("switch", "",
98 		new BMessage(kMsgSwitchShortcuts));
99 
100 	// controls pane
101 	BLayoutBuilder::Group<>(this, B_VERTICAL)
102 		.Add(_CreateMenu())
103 		.AddGroup(B_HORIZONTAL)
104 			.SetInsets(B_USE_DEFAULT_SPACING, 0, B_USE_DEFAULT_SPACING,
105 				B_USE_DEFAULT_SPACING)
106 			.Add(_CreateMapLists(), 0.25)
107 			.AddGroup(B_VERTICAL)
108 				.Add(fKeyboardLayoutView)
109 				.AddGroup(B_HORIZONTAL)
110 					.Add(_CreateDeadKeyMenuField(), 0.0)
111 					.AddGlue()
112 					.Add(fSwitchShortcutsButton)
113 					.End()
114 				.Add(fTextControl)
115 				.AddGlue(0.0)
116 				.AddGroup(B_HORIZONTAL)
117 					.Add(fDefaultsButton = new BButton("defaultsButton",
118 						B_TRANSLATE("Defaults"),
119 							new BMessage(kMsgDefaultKeymap)))
120 					.Add(fRevertButton = new BButton("revertButton",
121 						B_TRANSLATE("Revert"), new BMessage(kMsgRevertKeymap)))
122 					.AddGlue()
123 					.End()
124 				.End()
125 			.End()
126 		.End();
127 
128 	fKeyboardLayoutView->SetTarget(fTextControl->TextView());
129 	fTextControl->MakeFocus();
130 
131 	// Make sure the user keymap directory exists
132 	BPath path;
133 	find_directory(B_USER_SETTINGS_DIRECTORY, &path);
134 	path.Append("Keymap");
135 
136 	entry_ref ref;
137 	get_ref_for_path(path.Path(), &ref);
138 
139 	BDirectory userKeymapsDir(&ref);
140 	if (userKeymapsDir.InitCheck() != B_OK)
141 		create_directory(path.Path(), S_IRWXU | S_IRWXG | S_IRWXO);
142 
143 	BMessenger messenger(this);
144 	fOpenPanel = new BFilePanel(B_OPEN_PANEL, &messenger, &ref,
145 		B_FILE_NODE, false, NULL);
146 	fSavePanel = new BFilePanel(B_SAVE_PANEL, &messenger, &ref,
147 		B_FILE_NODE, false, NULL);
148 
149 	BRect windowFrame;
150 	BString keyboardLayout;
151 	_LoadSettings(windowFrame, keyboardLayout);
152 	_SetKeyboardLayout(keyboardLayout.String());
153 
154 	ResizeTo(windowFrame.Width(), windowFrame.Height());
155 	MoveTo(windowFrame.LeftTop());
156 
157 	// TODO: this might be a bug in the interface kit, but scrolling to
158 	// selection does not correctly work unless the window is shown.
159 	Show();
160 	Lock();
161 
162 	// Try and find the current map name in the two list views (if the name
163 	// was read at all)
164 	_SelectCurrentMap();
165 
166 	KeymapListItem* current
167 		= static_cast<KeymapListItem*>(fUserListView->FirstItem());
168 
169 	fCurrentMap.Load(current->EntryRef());
170 	fPreviousMap = fCurrentMap;
171 	fAppliedMap = fCurrentMap;
172 	fCurrentMap.SetTarget(this, new BMessage(kMsgKeymapUpdated));
173 
174 	_UpdateButtons();
175 
176 	_UpdateDeadKeyMenu();
177 	_UpdateSwitchShortcutButton();
178 
179 	Unlock();
180 }
181 
182 
183 KeymapWindow::~KeymapWindow()
184 {
185 	delete fOpenPanel;
186 	delete fSavePanel;
187 }
188 
189 
190 bool
191 KeymapWindow::QuitRequested()
192 {
193 	_SaveSettings();
194 
195 	be_app->PostMessage(B_QUIT_REQUESTED);
196 	return true;
197 }
198 
199 
200 void
201 KeymapWindow::MessageReceived(BMessage* message)
202 {
203 	switch (message->what) {
204 		case B_SIMPLE_DATA:
205 		case B_REFS_RECEIVED:
206 		{
207 			entry_ref ref;
208 			int32 i = 0;
209 			while (message->FindRef("refs", i++, &ref) == B_OK) {
210 				fCurrentMap.Load(ref);
211 				fAppliedMap = fCurrentMap;
212 			}
213 			fKeyboardLayoutView->SetKeymap(&fCurrentMap);
214 			fSystemListView->DeselectAll();
215 			fUserListView->DeselectAll();
216 			break;
217 		}
218 
219 		case B_SAVE_REQUESTED:
220 		{
221 			entry_ref ref;
222 			const char* name;
223 			if (message->FindRef("directory", &ref) == B_OK
224 				&& message->FindString("name", &name) == B_OK) {
225 				BDirectory directory(&ref);
226 				BEntry entry(&directory, name);
227 				entry.GetRef(&ref);
228 				fCurrentMap.SetName(name);
229 				fCurrentMap.Save(ref);
230 				fAppliedMap = fCurrentMap;
231 				_FillUserMaps();
232 				fCurrentMapName = name;
233 				_SelectCurrentMap();
234 			}
235 			break;
236 		}
237 
238 		case kMsgMenuFileOpen:
239 			fOpenPanel->Show();
240 			break;
241 		case kMsgMenuFileSaveAs:
242 			fSavePanel->Show();
243 			break;
244 		case kMsgShowModifierKeysWindow:
245 			be_app->PostMessage(kMsgShowModifierKeysWindow);
246 			break;
247 
248 		case kChangeKeyboardLayout:
249 		{
250 			entry_ref ref;
251 			BPath path;
252 			if (message->FindRef("ref", &ref) == B_OK)
253 				path.SetTo(&ref);
254 
255 			_SetKeyboardLayout(path.Path());
256 			break;
257 		}
258 
259 		case kMsgSwitchShortcuts:
260 			_SwitchShortcutKeys();
261 			break;
262 
263 		case kMsgMenuFontChanged:
264 		{
265 			BMenuItem* item = fFontMenu->FindMarked();
266 			if (item != NULL) {
267 				BFont font;
268 				font.SetFamilyAndStyle(item->Label(), NULL);
269 				fKeyboardLayoutView->SetBaseFont(font);
270 				fTextControl->TextView()->SetFontAndColor(&font);
271 			}
272 			break;
273 		}
274 
275 		case kMsgSystemMapSelected:
276 		case kMsgUserMapSelected:
277 		{
278 			BListView* listView;
279 			BListView* otherListView;
280 
281 			if (message->what == kMsgSystemMapSelected) {
282 				listView = fSystemListView;
283 				otherListView = fUserListView;
284 			} else {
285 				listView = fUserListView;
286 				otherListView = fSystemListView;
287 			}
288 
289 			int32 index = listView->CurrentSelection();
290 			if (index < 0)
291 				break;
292 
293 			// Deselect item in other BListView
294 			otherListView->DeselectAll();
295 
296 			if (index == 0 && listView == fUserListView) {
297 				// we can safely ignore the "(Current)" item
298 				break;
299 			}
300 
301 			KeymapListItem* item
302 				= static_cast<KeymapListItem*>(listView->ItemAt(index));
303 			if (item != NULL) {
304 				fCurrentMap.Load(item->EntryRef());
305 				fAppliedMap = fCurrentMap;
306 				fKeyboardLayoutView->SetKeymap(&fCurrentMap);
307 				_UseKeymap();
308 				_UpdateButtons();
309 			}
310 			break;
311 		}
312 
313 		case kMsgDefaultKeymap:
314 			_DefaultKeymap();
315 			_UpdateButtons();
316 			break;
317 
318 		case kMsgRevertKeymap:
319 			_RevertKeymap();
320 			_UpdateButtons();
321 			break;
322 
323 		case kMsgUpdateModifierKeys:
324 		{
325 			uint32 keyCode;
326 			bool unset;
327 			if (message->FindBool("unset", &unset) != B_OK)
328 				unset = false;
329 
330 			if (message->FindUInt32("left_shift_key", &keyCode) == B_OK) {
331 				fCurrentMap.SetModifier(unset ? 0x00 : keyCode,
332 					B_LEFT_SHIFT_KEY);
333 			}
334 
335 			if (message->FindUInt32("right_shift_key", &keyCode) == B_OK) {
336 				fCurrentMap.SetModifier(unset ? 0x00 : keyCode,
337 					B_RIGHT_SHIFT_KEY);
338 			}
339 
340 			if (message->FindUInt32("left_control_key", &keyCode) == B_OK) {
341 				fCurrentMap.SetModifier(unset ? 0x00 : keyCode,
342 					B_LEFT_CONTROL_KEY);
343 			}
344 
345 			if (message->FindUInt32("right_control_key", &keyCode) == B_OK) {
346 				fCurrentMap.SetModifier(unset ? 0x00 : keyCode,
347 					B_RIGHT_CONTROL_KEY);
348 			}
349 
350 			if (message->FindUInt32("left_option_key", &keyCode) == B_OK) {
351 				fCurrentMap.SetModifier(unset ? 0x00 : keyCode,
352 					B_LEFT_OPTION_KEY);
353 			}
354 
355 			if (message->FindUInt32("right_option_key", &keyCode) == B_OK) {
356 				fCurrentMap.SetModifier(unset ? 0x00 : keyCode,
357 					B_RIGHT_OPTION_KEY);
358 			}
359 
360 			if (message->FindUInt32("left_command_key", &keyCode) == B_OK) {
361 				fCurrentMap.SetModifier(unset ? 0x00 : keyCode,
362 					B_LEFT_COMMAND_KEY);
363 			}
364 
365 			if (message->FindUInt32("right_command_key", &keyCode) == B_OK) {
366 				fCurrentMap.SetModifier(unset ? 0x00 : keyCode,
367 					B_RIGHT_COMMAND_KEY);
368 			}
369 
370 			if (message->FindUInt32("menu_key", &keyCode) == B_OK)
371 				fCurrentMap.SetModifier(unset ? 0x00 : keyCode, B_MENU_KEY);
372 
373 			if (message->FindUInt32("caps_key", &keyCode) == B_OK)
374 				fCurrentMap.SetModifier(unset ? 0x00 : keyCode, B_CAPS_LOCK);
375 
376 			if (message->FindUInt32("num_key", &keyCode) == B_OK)
377 				fCurrentMap.SetModifier(unset ? 0x00 : keyCode, B_NUM_LOCK);
378 
379 			if (message->FindUInt32("scroll_key", &keyCode) == B_OK)
380 				fCurrentMap.SetModifier(unset ? 0x00 : keyCode, B_SCROLL_LOCK);
381 
382 			_UpdateButtons();
383 			fKeyboardLayoutView->SetKeymap(&fCurrentMap);
384 			break;
385 		}
386 
387 		case kMsgKeymapUpdated:
388 			_UpdateButtons();
389 			fSystemListView->DeselectAll();
390 			fUserListView->Select(0L);
391 			break;
392 
393 		case kMsgDeadKeyAcuteChanged:
394 		{
395 			BMenuItem* item = fAcuteMenu->FindMarked();
396 			if (item != NULL) {
397 				const char* trigger = item->Label();
398 				if (strcmp(trigger, kDeadKeyTriggerNone) == 0)
399 					trigger = NULL;
400 				fCurrentMap.SetDeadKeyTrigger(kDeadKeyAcute, trigger);
401 				fKeyboardLayoutView->Invalidate();
402 			}
403 			break;
404 		}
405 
406 		case kMsgDeadKeyCircumflexChanged:
407 		{
408 			BMenuItem* item = fCircumflexMenu->FindMarked();
409 			if (item != NULL) {
410 				const char* trigger = item->Label();
411 				if (strcmp(trigger, kDeadKeyTriggerNone) == 0)
412 					trigger = NULL;
413 				fCurrentMap.SetDeadKeyTrigger(kDeadKeyCircumflex, trigger);
414 				fKeyboardLayoutView->Invalidate();
415 			}
416 			break;
417 		}
418 
419 		case kMsgDeadKeyDiaeresisChanged:
420 		{
421 			BMenuItem* item = fDiaeresisMenu->FindMarked();
422 			if (item != NULL) {
423 				const char* trigger = item->Label();
424 				if (strcmp(trigger, kDeadKeyTriggerNone) == 0)
425 					trigger = NULL;
426 				fCurrentMap.SetDeadKeyTrigger(kDeadKeyDiaeresis, trigger);
427 				fKeyboardLayoutView->Invalidate();
428 			}
429 			break;
430 		}
431 
432 		case kMsgDeadKeyGraveChanged:
433 		{
434 			BMenuItem* item = fGraveMenu->FindMarked();
435 			if (item != NULL) {
436 				const char* trigger = item->Label();
437 				if (strcmp(trigger, kDeadKeyTriggerNone) == 0)
438 					trigger = NULL;
439 				fCurrentMap.SetDeadKeyTrigger(kDeadKeyGrave, trigger);
440 				fKeyboardLayoutView->Invalidate();
441 			}
442 			break;
443 		}
444 
445 		case kMsgDeadKeyTildeChanged:
446 		{
447 			BMenuItem* item = fTildeMenu->FindMarked();
448 			if (item != NULL) {
449 				const char* trigger = item->Label();
450 				if (strcmp(trigger, kDeadKeyTriggerNone) == 0)
451 					trigger = NULL;
452 				fCurrentMap.SetDeadKeyTrigger(kDeadKeyTilde, trigger);
453 				fKeyboardLayoutView->Invalidate();
454 			}
455 			break;
456 		}
457 
458 		default:
459 			BWindow::MessageReceived(message);
460 			break;
461 	}
462 }
463 
464 
465 BMenuBar*
466 KeymapWindow::_CreateMenu()
467 {
468 	BMenuBar* menuBar = new BMenuBar(Bounds(), "menubar");
469 
470 	// Create the File menu
471 	BMenu* menu = new BMenu(B_TRANSLATE("File"));
472 	menu->AddItem(new BMenuItem(B_TRANSLATE("Open" B_UTF8_ELLIPSIS),
473 		new BMessage(kMsgMenuFileOpen), 'O'));
474 	menu->AddItem(new BMenuItem(B_TRANSLATE("Save as" B_UTF8_ELLIPSIS),
475 		new BMessage(kMsgMenuFileSaveAs)));
476 	menu->AddSeparatorItem();
477 	menu->AddItem(new BMenuItem(
478 		B_TRANSLATE("Set modifier keys" B_UTF8_ELLIPSIS),
479 		new BMessage(kMsgShowModifierKeysWindow)));
480 	menu->AddSeparatorItem();
481 	menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
482 		new BMessage(B_QUIT_REQUESTED), 'Q'));
483 	menuBar->AddItem(menu);
484 
485 	// Create keyboard layout menu
486 	fLayoutMenu = new BMenu(B_TRANSLATE("Layout"));
487 	_AddKeyboardLayouts(fLayoutMenu);
488 	menuBar->AddItem(fLayoutMenu);
489 
490 	// Create the Font menu
491 	fFontMenu = new BMenu(B_TRANSLATE("Font"));
492 	fFontMenu->SetRadioMode(true);
493 	int32 numFamilies = count_font_families();
494 	font_family family, currentFamily;
495 	font_style currentStyle;
496 	uint32 flags;
497 
498 	be_plain_font->GetFamilyAndStyle(&currentFamily, &currentStyle);
499 
500 	for (int32 i = 0; i < numFamilies; i++) {
501 		if (get_font_family(i, &family, &flags) == B_OK) {
502 			BMenuItem* item
503 				= new BMenuItem(family, new BMessage(kMsgMenuFontChanged));
504 			fFontMenu->AddItem(item);
505 
506 			if (!strcmp(family, currentFamily))
507 				item->SetMarked(true);
508 		}
509 	}
510 	menuBar->AddItem(fFontMenu);
511 
512 	return menuBar;
513 }
514 
515 
516 BMenuField*
517 KeymapWindow::_CreateDeadKeyMenuField()
518 {
519 	BPopUpMenu* deadKeyMenu = new BPopUpMenu(B_TRANSLATE("Select dead keys"),
520 		false, false);
521 
522 	fAcuteMenu = new BMenu(B_TRANSLATE("Acute trigger"));
523 	fAcuteMenu->SetRadioMode(true);
524 	fAcuteMenu->AddItem(new BMenuItem("\xC2\xB4",
525 		new BMessage(kMsgDeadKeyAcuteChanged)));
526 	fAcuteMenu->AddItem(new BMenuItem("'",
527 		new BMessage(kMsgDeadKeyAcuteChanged)));
528 	fAcuteMenu->AddItem(new BMenuItem(kDeadKeyTriggerNone,
529 		new BMessage(kMsgDeadKeyAcuteChanged)));
530 	deadKeyMenu->AddItem(fAcuteMenu);
531 
532 	fCircumflexMenu = new BMenu(B_TRANSLATE("Circumflex trigger"));
533 	fCircumflexMenu->SetRadioMode(true);
534 	fCircumflexMenu->AddItem(new BMenuItem("^",
535 		new BMessage(kMsgDeadKeyCircumflexChanged)));
536 	fCircumflexMenu->AddItem(new BMenuItem(kDeadKeyTriggerNone,
537 		new BMessage(kMsgDeadKeyCircumflexChanged)));
538 	deadKeyMenu->AddItem(fCircumflexMenu);
539 
540 	fDiaeresisMenu = new BMenu(B_TRANSLATE("Diaeresis trigger"));
541 	fDiaeresisMenu->SetRadioMode(true);
542 	fDiaeresisMenu->AddItem(new BMenuItem("\xC2\xA8",
543 		new BMessage(kMsgDeadKeyDiaeresisChanged)));
544 	fDiaeresisMenu->AddItem(new BMenuItem("\"",
545 		new BMessage(kMsgDeadKeyDiaeresisChanged)));
546 	fDiaeresisMenu->AddItem(new BMenuItem(kDeadKeyTriggerNone,
547 		new BMessage(kMsgDeadKeyDiaeresisChanged)));
548 	deadKeyMenu->AddItem(fDiaeresisMenu);
549 
550 	fGraveMenu = new BMenu(B_TRANSLATE("Grave trigger"));
551 	fGraveMenu->SetRadioMode(true);
552 	fGraveMenu->AddItem(new BMenuItem("`",
553 		new BMessage(kMsgDeadKeyGraveChanged)));
554 	fGraveMenu->AddItem(new BMenuItem(kDeadKeyTriggerNone,
555 		new BMessage(kMsgDeadKeyGraveChanged)));
556 	deadKeyMenu->AddItem(fGraveMenu);
557 
558 	fTildeMenu = new BMenu(B_TRANSLATE("Tilde trigger"));
559 	fTildeMenu->SetRadioMode(true);
560 	fTildeMenu->AddItem(new BMenuItem("~",
561 		new BMessage(kMsgDeadKeyTildeChanged)));
562 	fTildeMenu->AddItem(new BMenuItem(kDeadKeyTriggerNone,
563 		new BMessage(kMsgDeadKeyTildeChanged)));
564 	deadKeyMenu->AddItem(fTildeMenu);
565 
566 	return new BMenuField(NULL, deadKeyMenu);
567 }
568 
569 
570 BView*
571 KeymapWindow::_CreateMapLists()
572 {
573 	// The System list
574 	fSystemListView = new BListView("systemList");
575 	fSystemListView->SetSelectionMessage(new BMessage(kMsgSystemMapSelected));
576 
577 	BScrollView* systemScroller = new BScrollView("systemScrollList",
578 		fSystemListView, 0, false, true);
579 
580 	// The User list
581 	fUserListView = new BListView("userList");
582 	fUserListView->SetSelectionMessage(new BMessage(kMsgUserMapSelected));
583 	BScrollView* userScroller = new BScrollView("userScrollList",
584 		fUserListView, 0, false, true);
585 
586 	// Saved keymaps
587 
588 	_FillSystemMaps();
589 	_FillUserMaps();
590 
591 	_SetListViewSize(fSystemListView);
592 	_SetListViewSize(fUserListView);
593 
594 	return BLayoutBuilder::Group<>(B_VERTICAL)
595 		.Add(new BStringView("system", B_TRANSLATE("System:")))
596 		.Add(systemScroller, 3)
597 		.Add(new BStringView("user", B_TRANSLATE("User:")))
598 		.Add(userScroller)
599 		.View();
600 }
601 
602 
603 void
604 KeymapWindow::_AddKeyboardLayouts(BMenu* menu)
605 {
606 	directory_which dataDirectories[] = {
607 		B_USER_NONPACKAGED_DATA_DIRECTORY,
608 		B_USER_DATA_DIRECTORY,
609 		B_SYSTEM_NONPACKAGED_DATA_DIRECTORY,
610 		B_SYSTEM_DATA_DIRECTORY,
611 	};
612 
613 	for (uint32 i = 0;
614 			i < sizeof(dataDirectories) / sizeof(dataDirectories[0]); i++) {
615 		BPath path;
616 		if (find_directory(dataDirectories[i], &path) != B_OK)
617 			continue;
618 
619 		if (path.Append("KeyboardLayouts") != B_OK)
620 			continue;
621 
622 		BDirectory directory;
623 		if (directory.SetTo(path.Path()) == B_OK)
624 			_AddKeyboardLayoutMenu(menu, directory);
625 	}
626 }
627 
628 
629 /*!	Adds a menu populated with the keyboard layouts found in the passed
630 	in directory to the passed in menu. Each subdirectory in the passed
631 	in directory is added as a submenu recursively.
632 */
633 void
634 KeymapWindow::_AddKeyboardLayoutMenu(BMenu* menu, BDirectory directory)
635 {
636 	entry_ref ref;
637 
638 	while (directory.GetNextRef(&ref) == B_OK) {
639 		if (menu->FindItem(ref.name) != NULL)
640 			continue;
641 
642 		BDirectory subdirectory;
643 		subdirectory.SetTo(&ref);
644 		if (subdirectory.InitCheck() == B_OK) {
645 			BMenu* submenu = new BMenu(B_TRANSLATE_NOCOLLECT(ref.name));
646 
647 			_AddKeyboardLayoutMenu(submenu, subdirectory);
648 			menu->AddItem(submenu);
649 		} else {
650 			BMessage* message = new BMessage(kChangeKeyboardLayout);
651 
652 			message->AddRef("ref", &ref);
653 			menu->AddItem(new BMenuItem(B_TRANSLATE_NOCOLLECT(ref.name),
654 				message));
655 		}
656 	}
657 }
658 
659 
660 /*!	Sets the keyboard layout with the passed in path and marks the
661 	corresponding menu item. If the path is not found in the menu this method
662 	sets the default keyboard layout and marks the corresponding menu item.
663 */
664 status_t
665 KeymapWindow::_SetKeyboardLayout(const char* path)
666 {
667 	status_t status = fKeyboardLayoutView->GetKeyboardLayout()->Load(path);
668 
669 	// mark a menu item (unmarking all others)
670 	_MarkKeyboardLayoutItem(path, fLayoutMenu);
671 
672 	if (path == NULL || path[0] == '\0' || status != B_OK) {
673 		fKeyboardLayoutView->GetKeyboardLayout()->SetDefault();
674 		BMenuItem* item = fLayoutMenu->FindItem(
675 			fKeyboardLayoutView->GetKeyboardLayout()->Name());
676 		if (item != NULL)
677 			item->SetMarked(true);
678 	}
679 
680 	// Refresh currently set layout
681 	fKeyboardLayoutView->SetKeyboardLayout(
682 		fKeyboardLayoutView->GetKeyboardLayout());
683 
684 	return status;
685 }
686 
687 
688 /*!	Marks a keyboard layout item by iterating through the menus recursively
689 	searching for the menu item with the passed in path. This method always
690 	iterates through all menu items and unmarks them. If no item with the
691 	passed in path is found it is up to the caller to set the default keyboard
692 	layout and mark item corresponding to the default keyboard layout path.
693 */
694 void
695 KeymapWindow::_MarkKeyboardLayoutItem(const char* path, BMenu* menu)
696 {
697 	BMenuItem* item = NULL;
698 	entry_ref ref;
699 
700 	for (int32 i = 0; i < menu->CountItems(); i++) {
701 		item = menu->ItemAt(i);
702 		if (item == NULL)
703 			continue;
704 
705 		// Unmark each item initially
706 		item->SetMarked(false);
707 
708 		BMenu* submenu = item->Submenu();
709 		if (submenu != NULL)
710 			_MarkKeyboardLayoutItem(path, submenu);
711 		else {
712 			if (item->Message()->FindRef("ref", &ref) == B_OK) {
713 				BPath layoutPath(&ref);
714 				if (path != NULL && path[0] != '\0' && layoutPath == path) {
715 					// Found it, mark the item
716 					item->SetMarked(true);
717 				}
718 			}
719 		}
720 	}
721 }
722 
723 
724 /*!	Sets the label of the "Switch Shorcuts" button to make it more
725 	descriptive what will happen when you press that button.
726 */
727 void
728 KeymapWindow::_UpdateSwitchShortcutButton()
729 {
730 	const char* label = B_TRANSLATE("Switch shortcut keys");
731 	if (fCurrentMap.KeyForModifier(B_LEFT_COMMAND_KEY) == 0x5d
732 		&& fCurrentMap.KeyForModifier(B_LEFT_CONTROL_KEY) == 0x5c) {
733 		label = B_TRANSLATE("Switch shortcut keys to Windows/Linux mode");
734 	} else if (fCurrentMap.KeyForModifier(B_LEFT_COMMAND_KEY) == 0x5c
735 		&& fCurrentMap.KeyForModifier(B_LEFT_CONTROL_KEY) == 0x5d) {
736 		label = B_TRANSLATE("Switch shortcut keys to Haiku mode");
737 	}
738 
739 	fSwitchShortcutsButton->SetLabel(label);
740 }
741 
742 
743 /*!	Marks the menu items corresponding to the dead key state of the current
744 	key map.
745 */
746 void
747 KeymapWindow::_UpdateDeadKeyMenu()
748 {
749 	BString trigger;
750 	fCurrentMap.GetDeadKeyTrigger(kDeadKeyAcute, trigger);
751 	if (!trigger.Length())
752 		trigger = kDeadKeyTriggerNone;
753 	BMenuItem* menuItem = fAcuteMenu->FindItem(trigger.String());
754 	if (menuItem)
755 		menuItem->SetMarked(true);
756 
757 	fCurrentMap.GetDeadKeyTrigger(kDeadKeyCircumflex, trigger);
758 	if (!trigger.Length())
759 		trigger = kDeadKeyTriggerNone;
760 	menuItem = fCircumflexMenu->FindItem(trigger.String());
761 	if (menuItem)
762 		menuItem->SetMarked(true);
763 
764 	fCurrentMap.GetDeadKeyTrigger(kDeadKeyDiaeresis, trigger);
765 	if (!trigger.Length())
766 		trigger = kDeadKeyTriggerNone;
767 	menuItem = fDiaeresisMenu->FindItem(trigger.String());
768 	if (menuItem)
769 		menuItem->SetMarked(true);
770 
771 	fCurrentMap.GetDeadKeyTrigger(kDeadKeyGrave, trigger);
772 	if (!trigger.Length())
773 		trigger = kDeadKeyTriggerNone;
774 	menuItem = fGraveMenu->FindItem(trigger.String());
775 	if (menuItem)
776 		menuItem->SetMarked(true);
777 
778 	fCurrentMap.GetDeadKeyTrigger(kDeadKeyTilde, trigger);
779 	if (!trigger.Length())
780 		trigger = kDeadKeyTriggerNone;
781 	menuItem = fTildeMenu->FindItem(trigger.String());
782 	if (menuItem)
783 		menuItem->SetMarked(true);
784 }
785 
786 
787 void
788 KeymapWindow::_UpdateButtons()
789 {
790 	if (fCurrentMap != fAppliedMap) {
791 		fCurrentMap.SetName(kCurrentKeymapName);
792 		_UseKeymap();
793 	}
794 
795 	fDefaultsButton->SetEnabled(
796 		fCurrentMapName.ICompare(kDefaultKeymapName) != 0);
797 	fRevertButton->SetEnabled(fCurrentMap != fPreviousMap);
798 
799 	_UpdateDeadKeyMenu();
800 	_UpdateSwitchShortcutButton();
801 }
802 
803 
804 void
805 KeymapWindow::_SwitchShortcutKeys()
806 {
807 	uint32 leftCommand = fCurrentMap.Map().left_command_key;
808 	uint32 leftControl = fCurrentMap.Map().left_control_key;
809 	uint32 rightCommand = fCurrentMap.Map().right_command_key;
810 	uint32 rightControl = fCurrentMap.Map().right_control_key;
811 
812 	// switch left side
813 	fCurrentMap.Map().left_command_key = leftControl;
814 	fCurrentMap.Map().left_control_key = leftCommand;
815 
816 	// switch right side
817 	fCurrentMap.Map().right_command_key = rightControl;
818 	fCurrentMap.Map().right_control_key = rightCommand;
819 
820 	fKeyboardLayoutView->SetKeymap(&fCurrentMap);
821 	_UpdateButtons();
822 }
823 
824 
825 //!	Restores the default keymap.
826 void
827 KeymapWindow::_DefaultKeymap()
828 {
829 	fCurrentMap.RestoreSystemDefault();
830 	fAppliedMap = fCurrentMap;
831 
832 	fKeyboardLayoutView->SetKeymap(&fCurrentMap);
833 
834 	fCurrentMapName = _GetActiveKeymapName();
835 	_SelectCurrentMap();
836 }
837 
838 
839 //!	Saves previous map to the "Key_map" file.
840 void
841 KeymapWindow::_RevertKeymap()
842 {
843 	entry_ref ref;
844 	_GetCurrentKeymap(ref);
845 
846 	status_t status = fPreviousMap.Save(ref);
847 	if (status != B_OK) {
848 		printf("error when saving keymap: %s", strerror(status));
849 		return;
850 	}
851 
852 	fPreviousMap.Use();
853 	fCurrentMap.Load(ref);
854 	fAppliedMap = fCurrentMap;
855 
856 	fKeyboardLayoutView->SetKeymap(&fCurrentMap);
857 
858 	fCurrentMapName = _GetActiveKeymapName();
859 	_SelectCurrentMap();
860 }
861 
862 
863 //!	Saves current map to the "Key_map" file.
864 void
865 KeymapWindow::_UseKeymap()
866 {
867 	entry_ref ref;
868 	_GetCurrentKeymap(ref);
869 
870 	status_t status = fCurrentMap.Save(ref);
871 	if (status != B_OK) {
872 		printf("error when saving : %s", strerror(status));
873 		return;
874 	}
875 
876 	fCurrentMap.Use();
877 	fAppliedMap.Load(ref);
878 
879 	fCurrentMapName = _GetActiveKeymapName();
880 	_SelectCurrentMap();
881 }
882 
883 
884 void
885 KeymapWindow::_FillSystemMaps()
886 {
887 	BListItem* item;
888 	while ((item = fSystemListView->RemoveItem(static_cast<int32>(0))))
889 		delete item;
890 
891 	// TODO: common keymaps!
892 	BPath path;
893 	if (find_directory(B_SYSTEM_DATA_DIRECTORY, &path) != B_OK)
894 		return;
895 
896 	path.Append("Keymaps");
897 
898 	BDirectory directory;
899 	entry_ref ref;
900 
901 	if (directory.SetTo(path.Path()) == B_OK) {
902 		while (directory.GetNextRef(&ref) == B_OK) {
903 			fSystemListView->AddItem(
904 				new KeymapListItem(ref, B_TRANSLATE_NOCOLLECT(ref.name)));
905 		}
906 	}
907 
908 	fSystemListView->SortItems(&compare_key_list_items);
909 }
910 
911 
912 void
913 KeymapWindow::_FillUserMaps()
914 {
915 	BListItem* item;
916 	while ((item = fUserListView->RemoveItem(static_cast<int32>(0))))
917 		delete item;
918 
919 	entry_ref ref;
920 	_GetCurrentKeymap(ref);
921 
922 	fUserListView->AddItem(new KeymapListItem(ref, B_TRANSLATE("(Current)")));
923 
924 	fCurrentMapName = _GetActiveKeymapName();
925 
926 	BPath path;
927 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
928 		return;
929 
930 	path.Append("Keymap");
931 
932 	BDirectory directory;
933 	if (directory.SetTo(path.Path()) == B_OK) {
934 		while (directory.GetNextRef(&ref) == B_OK) {
935 			fUserListView->AddItem(new KeymapListItem(ref));
936 		}
937 	}
938 
939 	fUserListView->SortItems(&compare_key_list_items);
940 }
941 
942 
943 void
944 KeymapWindow::_SetListViewSize(BListView* listView)
945 {
946 	float minWidth = 0;
947 	for (int32 i = 0; i < listView->CountItems(); i++) {
948 		BStringItem* item = (BStringItem*)listView->ItemAt(i);
949 		float width = listView->StringWidth(item->Text());
950 		if (width > minWidth)
951 			minWidth = width;
952 	}
953 
954 	listView->SetExplicitMinSize(BSize(minWidth + 8, 32));
955 }
956 
957 
958 status_t
959 KeymapWindow::_GetCurrentKeymap(entry_ref& ref)
960 {
961 	BPath path;
962 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
963 		return B_ERROR;
964 
965 	path.Append("Key_map");
966 
967 	return get_ref_for_path(path.Path(), &ref);
968 }
969 
970 
971 BString
972 KeymapWindow::_GetActiveKeymapName()
973 {
974 	BString mapName = kCurrentKeymapName;
975 		// safe default
976 
977 	entry_ref ref;
978 	_GetCurrentKeymap(ref);
979 
980 	BNode node(&ref);
981 
982 	if (node.InitCheck() == B_OK)
983 		node.ReadAttrString("keymap:name", &mapName);
984 
985 	return mapName;
986 }
987 
988 
989 bool
990 KeymapWindow::_SelectCurrentMap(BListView* view)
991 {
992 	if (fCurrentMapName.Length() <= 0)
993 		return false;
994 
995 	for (int32 i = 0; i < view->CountItems(); i++) {
996 		BStringItem* current = dynamic_cast<BStringItem *>(view->ItemAt(i));
997 		if (current != NULL && fCurrentMapName == current->Text()) {
998 			view->Select(i);
999 			view->ScrollToSelection();
1000 			return true;
1001 		}
1002 	}
1003 
1004 	return false;
1005 }
1006 
1007 
1008 void
1009 KeymapWindow::_SelectCurrentMap()
1010 {
1011 	if (!_SelectCurrentMap(fSystemListView)
1012 		&& !_SelectCurrentMap(fUserListView)) {
1013 		// Select the "(Current)" entry if no name matches
1014 		fUserListView->Select(0L);
1015 	}
1016 }
1017 
1018 
1019 status_t
1020 KeymapWindow::_GetSettings(BFile& file, int mode) const
1021 {
1022 	BPath path;
1023 	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path,
1024 		(mode & O_ACCMODE) != O_RDONLY);
1025 	if (status != B_OK)
1026 		return status;
1027 
1028 	path.Append("Keymap settings");
1029 
1030 	return file.SetTo(path.Path(), mode);
1031 }
1032 
1033 
1034 status_t
1035 KeymapWindow::_LoadSettings(BRect& windowFrame, BString& keyboardLayout)
1036 {
1037 	BScreen screen(this);
1038 
1039 	windowFrame.Set(-1, -1, 669, 357);
1040 	// See if we can use a larger default size
1041 	if (screen.Frame().Width() > 1200) {
1042 		windowFrame.right = 899;
1043 		windowFrame.bottom = 349;
1044 	}
1045 
1046 	keyboardLayout = "";
1047 
1048 	BFile file;
1049 	status_t status = _GetSettings(file, B_READ_ONLY);
1050 	if (status == B_OK) {
1051 		BMessage settings;
1052 		status = settings.Unflatten(&file);
1053 		if (status == B_OK) {
1054 			BRect frame;
1055 			if (settings.FindRect("window frame", &frame) == B_OK)
1056 				windowFrame = frame;
1057 
1058 			settings.FindString("keyboard layout", &keyboardLayout);
1059 		}
1060 	}
1061 
1062 	if (!screen.Frame().Contains(windowFrame)) {
1063 		// Make sure the window is not larger than the screen
1064 		if (windowFrame.Width() > screen.Frame().Width())
1065 			windowFrame.right = windowFrame.left + screen.Frame().Width();
1066 		if (windowFrame.Height() > screen.Frame().Height())
1067 			windowFrame.bottom = windowFrame.top + screen.Frame().Height();
1068 
1069 		// Make sure the window is on screen (and center if it isn't)
1070 		if (windowFrame.left < screen.Frame().left
1071 			|| windowFrame.right > screen.Frame().right
1072 			|| windowFrame.top < screen.Frame().top
1073 			|| windowFrame.bottom > screen.Frame().bottom) {
1074 			windowFrame.OffsetTo(BAlert::AlertPosition(windowFrame.Width(),
1075 				windowFrame.Height()));
1076 		}
1077 	}
1078 
1079 	return status;
1080 }
1081 
1082 
1083 status_t
1084 KeymapWindow::_SaveSettings()
1085 {
1086 	BFile file;
1087 	status_t status
1088 		= _GetSettings(file, B_WRITE_ONLY | B_ERASE_FILE | B_CREATE_FILE);
1089 	if (status != B_OK)
1090 		return status;
1091 
1092 	BMessage settings('keym');
1093 	settings.AddRect("window frame", Frame());
1094 
1095 	BPath path = _GetMarkedKeyboardLayoutPath(fLayoutMenu);
1096 	if (path.InitCheck() == B_OK)
1097 		settings.AddString("keyboard layout", path.Path());
1098 
1099 	return settings.Flatten(&file);
1100 }
1101 
1102 
1103 /*!	Gets the path of the currently marked keyboard layout item
1104 	by searching through each of the menus recursively until
1105 	a marked item is found.
1106 */
1107 BPath
1108 KeymapWindow::_GetMarkedKeyboardLayoutPath(BMenu* menu)
1109 {
1110 	BPath path;
1111 	BMenuItem* item = NULL;
1112 	entry_ref ref;
1113 
1114 	for (int32 i = 0; i < menu->CountItems(); i++) {
1115 		item = menu->ItemAt(i);
1116 		if (item == NULL)
1117 			continue;
1118 
1119 		BMenu* submenu = item->Submenu();
1120 		if (submenu != NULL)
1121 			return _GetMarkedKeyboardLayoutPath(submenu);
1122 		else {
1123 			if (item->IsMarked()
1124 				&& item->Message()->FindRef("ref", &ref) == B_OK) {
1125 				path.SetTo(&ref);
1126 				return path;
1127 			}
1128 		}
1129 	}
1130 
1131 	return path;
1132 }
1133