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