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