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
46static const uint32 kMsgMenuFileOpen = 'mMFO';
47static const uint32 kMsgMenuFileSaveAs = 'mMFA';
48
49static const uint32 kChangeKeyboardLayout = 'cKyL';
50
51static const uint32 kMsgSwitchShortcuts = 'swSc';
52
53static const uint32 kMsgMenuFontChanged = 'mMFC';
54
55static const uint32 kMsgSystemMapSelected = 'SmST';
56static const uint32 kMsgUserMapSelected = 'UmST';
57
58static const uint32 kMsgRevertKeymap = 'Rvrt';
59static const uint32 kMsgKeymapUpdated = 'kMup';
60
61static const uint32 kMsgDeadKeyAcuteChanged = 'dkAc';
62static const uint32 kMsgDeadKeyCircumflexChanged = 'dkCc';
63static const uint32 kMsgDeadKeyDiaeresisChanged = 'dkDc';
64static const uint32 kMsgDeadKeyGraveChanged = 'dkGc';
65static const uint32 kMsgDeadKeyTildeChanged = 'dkTc';
66
67static const char* kDeadKeyTriggerNone = "<none>";
68
69static const char* kCurrentKeymapName = "(Current)";
70
71
72KeymapWindow::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
165KeymapWindow::~KeymapWindow()
166{
167	delete fOpenPanel;
168	delete fSavePanel;
169}
170
171
172bool
173KeymapWindow::QuitRequested()
174{
175	_SaveSettings();
176
177	be_app->PostMessage(B_QUIT_REQUESTED);
178	return true;
179}
180
181
182void
183KeymapWindow::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
411BMenuBar*
412KeymapWindow::_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
462BMenuField*
463KeymapWindow::_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
516BView*
517KeymapWindow::_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
549void
550KeymapWindow::_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*/
577void
578KeymapWindow::_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*/
607status_t
608KeymapWindow::_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*/
637void
638KeymapWindow::_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*/
670void
671KeymapWindow::_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*/
689void
690KeymapWindow::_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
730void
731KeymapWindow::_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
745void
746KeymapWindow::_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.
767void
768KeymapWindow::_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.
791void
792KeymapWindow::_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
811void
812KeymapWindow::_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
836void
837KeymapWindow::_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
865void
866KeymapWindow::_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
880status_t
881KeymapWindow::_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
893BString
894KeymapWindow::_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
910bool
911KeymapWindow::_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
929void
930KeymapWindow::_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
940status_t
941KeymapWindow::_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
955status_t
956KeymapWindow::_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
1004status_t
1005KeymapWindow::_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*/
1028BPath
1029KeymapWindow::_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