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