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