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