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