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