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