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