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