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