vfs: check userland buffers before reading them.
[haiku.git] / src / preferences / keymap / KeymapWindow.cpp
blobb25fa9d2e8aeb35d8f63b4d87690c0ee068c00d6
1 /*
2 * Copyright 2004-2015 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
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
14 #include "KeymapWindow.h"
16 #include <string.h>
17 #include <stdio.h>
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>
38 #include "KeyboardLayoutView.h"
39 #include "KeymapApplication.h"
40 #include "KeymapListItem.h"
41 #include "KeymapNames.h"
44 #undef B_TRANSLATION_CONTEXT
45 #define B_TRANSLATION_CONTEXT "Keymap window"
48 static const uint32 kMsgMenuFileOpen = 'mMFO';
49 static const uint32 kMsgMenuFileSaveAs = 'mMFA';
51 static const uint32 kChangeKeyboardLayout = 'cKyL';
53 static const uint32 kMsgSwitchShortcuts = 'swSc';
55 static const uint32 kMsgMenuFontChanged = 'mMFC';
57 static const uint32 kMsgSystemMapSelected = 'SmST';
58 static const uint32 kMsgUserMapSelected = 'UmST';
60 static const uint32 kMsgDefaultKeymap = 'Dflt';
61 static const uint32 kMsgRevertKeymap = 'Rvrt';
62 static const uint32 kMsgKeymapUpdated = 'kMup';
64 static const uint32 kMsgDeadKeyAcuteChanged = 'dkAc';
65 static const uint32 kMsgDeadKeyCircumflexChanged = 'dkCc';
66 static const uint32 kMsgDeadKeyDiaeresisChanged = 'dkDc';
67 static const uint32 kMsgDeadKeyGraveChanged = 'dkGc';
68 static const uint32 kMsgDeadKeyTildeChanged = 'dkTc';
70 static const char* kDeadKeyTriggerNone = "<none>";
72 static const char* kCurrentKeymapName = "(Current)";
73 static const char* kDefaultKeymapName = "US-International";
76 static int
77 compare_key_list_items(const void* a, const void* b)
79 KeymapListItem* item1 = *(KeymapListItem**)a;
80 KeymapListItem* item2 = *(KeymapListItem**)b;
81 return BLocale::Default()->StringCompare(item1->Text(), item2->Text());
85 KeymapWindow::KeymapWindow()
87 BWindow(BRect(80, 50, 650, 300), B_TRANSLATE_SYSTEM_NAME("Keymap"),
88 B_TITLED_WINDOW, B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS)
90 fKeyboardLayoutView = new KeyboardLayoutView("layout");
91 fKeyboardLayoutView->SetKeymap(&fCurrentMap);
92 fKeyboardLayoutView->SetExplicitMinSize(BSize(B_SIZE_UNSET, 192));
94 fTextControl = new BTextControl(B_TRANSLATE("Sample and clipboard:"),
95 "", NULL);
97 fSwitchShortcutsButton = new BButton("switch", "",
98 new BMessage(kMsgSwitchShortcuts));
100 // controls pane
101 BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
102 .Add(_CreateMenu())
103 .AddGroup(B_HORIZONTAL)
104 .SetInsets(B_USE_WINDOW_SPACING)
105 .Add(_CreateMapLists(), 0.25)
106 .AddGroup(B_VERTICAL)
107 .Add(fKeyboardLayoutView)
108 .AddGroup(B_HORIZONTAL)
109 .Add(_CreateDeadKeyMenuField(), 0.0)
110 .AddGlue()
111 .Add(fSwitchShortcutsButton)
112 .End()
113 .Add(fTextControl)
114 .AddGlue(0.0)
115 .AddGroup(B_HORIZONTAL)
116 .AddGlue()
117 .Add(fDefaultsButton = new BButton("defaultsButton",
118 B_TRANSLATE("Defaults"),
119 new BMessage(kMsgDefaultKeymap)))
120 .Add(fRevertButton = new BButton("revertButton",
121 B_TRANSLATE("Revert"), new BMessage(kMsgRevertKeymap)))
122 .End()
123 .End()
124 .End()
125 .End();
127 fKeyboardLayoutView->SetTarget(fTextControl->TextView());
128 fTextControl->MakeFocus();
130 // Make sure the user keymap directory exists
131 BPath path;
132 find_directory(B_USER_SETTINGS_DIRECTORY, &path);
133 path.Append("Keymap");
135 entry_ref ref;
136 BEntry entry(path.Path(), true); // follow symlink
137 BDirectory userKeymapsDir(&entry);
138 if (userKeymapsDir.InitCheck() != B_OK
139 && create_directory(path.Path(), S_IRWXU | S_IRWXG | S_IRWXO)
140 == B_OK) {
141 get_ref_for_path(path.Path(), &ref);
142 } else if (entry.InitCheck() == B_OK)
143 entry.GetRef(&ref);
144 else
145 get_ref_for_path(path.Path(), &ref);
147 BMessenger messenger(this);
148 fOpenPanel = new BFilePanel(B_OPEN_PANEL, &messenger, &ref,
149 B_FILE_NODE, false, NULL);
150 fSavePanel = new BFilePanel(B_SAVE_PANEL, &messenger, &ref,
151 B_FILE_NODE, false, NULL);
153 BRect windowFrame;
154 if (_LoadSettings(windowFrame) == B_OK) {
155 ResizeTo(windowFrame.Width(), windowFrame.Height());
156 MoveTo(windowFrame.LeftTop());
157 MoveOnScreen();
158 } else
159 CenterOnScreen();
161 // TODO: this might be a bug in the interface kit, but scrolling to
162 // selection does not correctly work unless the window is shown.
163 Show();
164 Lock();
166 // Try and find the current map name in the two list views (if the name
167 // was read at all)
168 _SelectCurrentMap();
170 KeymapListItem* current
171 = static_cast<KeymapListItem*>(fUserListView->FirstItem());
173 fCurrentMap.Load(current->EntryRef());
174 fPreviousMap = fCurrentMap;
175 fAppliedMap = fCurrentMap;
176 fCurrentMap.SetTarget(this, new BMessage(kMsgKeymapUpdated));
178 _UpdateButtons();
180 _UpdateDeadKeyMenu();
181 _UpdateSwitchShortcutButton();
183 Unlock();
187 KeymapWindow::~KeymapWindow()
189 delete fOpenPanel;
190 delete fSavePanel;
194 bool
195 KeymapWindow::QuitRequested()
197 _SaveSettings();
199 be_app->PostMessage(B_QUIT_REQUESTED);
200 return true;
204 void
205 KeymapWindow::MessageReceived(BMessage* message)
207 switch (message->what) {
208 case B_SIMPLE_DATA:
209 case B_REFS_RECEIVED:
211 entry_ref ref;
212 int32 i = 0;
213 while (message->FindRef("refs", i++, &ref) == B_OK) {
214 fCurrentMap.Load(ref);
215 fAppliedMap = fCurrentMap;
217 fKeyboardLayoutView->SetKeymap(&fCurrentMap);
218 fSystemListView->DeselectAll();
219 fUserListView->DeselectAll();
220 break;
223 case B_SAVE_REQUESTED:
225 entry_ref ref;
226 const char* name;
227 if (message->FindRef("directory", &ref) == B_OK
228 && message->FindString("name", &name) == B_OK) {
229 BDirectory directory(&ref);
230 BEntry entry(&directory, name);
231 entry.GetRef(&ref);
232 fCurrentMap.SetName(name);
233 fCurrentMap.Save(ref);
234 fAppliedMap = fCurrentMap;
235 _FillUserMaps();
236 fCurrentMapName = name;
237 _SelectCurrentMap();
239 break;
242 case kMsgMenuFileOpen:
243 fOpenPanel->Show();
244 break;
245 case kMsgMenuFileSaveAs:
246 fSavePanel->Show();
247 break;
248 case kMsgShowModifierKeysWindow:
249 be_app->PostMessage(kMsgShowModifierKeysWindow);
250 break;
252 case kChangeKeyboardLayout:
254 entry_ref ref;
255 BPath path;
256 if (message->FindRef("ref", &ref) == B_OK)
257 path.SetTo(&ref);
259 _SetKeyboardLayout(path.Path());
260 break;
263 case kMsgSwitchShortcuts:
264 _SwitchShortcutKeys();
265 break;
267 case kMsgMenuFontChanged:
269 BMenuItem* item = fFontMenu->FindMarked();
270 if (item != NULL) {
271 BFont font;
272 font.SetFamilyAndStyle(item->Label(), NULL);
273 fKeyboardLayoutView->SetBaseFont(font);
274 fTextControl->TextView()->SetFontAndColor(&font);
276 break;
279 case kMsgSystemMapSelected:
280 case kMsgUserMapSelected:
282 BListView* listView;
283 BListView* otherListView;
285 if (message->what == kMsgSystemMapSelected) {
286 listView = fSystemListView;
287 otherListView = fUserListView;
288 } else {
289 listView = fUserListView;
290 otherListView = fSystemListView;
293 int32 index = listView->CurrentSelection();
294 if (index < 0)
295 break;
297 // Deselect item in other BListView
298 otherListView->DeselectAll();
300 if (index == 0 && listView == fUserListView) {
301 // we can safely ignore the "(Current)" item
302 break;
305 KeymapListItem* item
306 = static_cast<KeymapListItem*>(listView->ItemAt(index));
307 if (item != NULL) {
308 fCurrentMap.Load(item->EntryRef());
309 fAppliedMap = fCurrentMap;
310 fKeyboardLayoutView->SetKeymap(&fCurrentMap);
311 _UseKeymap();
312 _UpdateButtons();
314 break;
317 case kMsgDefaultKeymap:
318 _DefaultKeymap();
319 _UpdateButtons();
320 break;
322 case kMsgRevertKeymap:
323 _RevertKeymap();
324 _UpdateButtons();
325 break;
327 case kMsgUpdateNormalKeys:
329 uint32 keyCode;
330 if (message->FindUInt32("keyCode", &keyCode) != B_OK)
331 break;
333 bool unset;
334 if (message->FindBool("unset", &unset) == B_OK && unset) {
335 fCurrentMap.SetKey(keyCode, modifiers(), 0, "", 0);
336 _UpdateButtons();
337 fKeyboardLayoutView->SetKeymap(&fCurrentMap);
339 break;
342 case kMsgUpdateModifierKeys:
344 uint32 keyCode;
345 bool unset;
346 if (message->FindBool("unset", &unset) != B_OK)
347 unset = false;
349 if (message->FindUInt32("left_shift_key", &keyCode) == B_OK) {
350 fCurrentMap.SetModifier(unset ? 0x00 : keyCode,
351 B_LEFT_SHIFT_KEY);
354 if (message->FindUInt32("right_shift_key", &keyCode) == B_OK) {
355 fCurrentMap.SetModifier(unset ? 0x00 : keyCode,
356 B_RIGHT_SHIFT_KEY);
359 if (message->FindUInt32("left_control_key", &keyCode) == B_OK) {
360 fCurrentMap.SetModifier(unset ? 0x00 : keyCode,
361 B_LEFT_CONTROL_KEY);
364 if (message->FindUInt32("right_control_key", &keyCode) == B_OK) {
365 fCurrentMap.SetModifier(unset ? 0x00 : keyCode,
366 B_RIGHT_CONTROL_KEY);
369 if (message->FindUInt32("left_option_key", &keyCode) == B_OK) {
370 fCurrentMap.SetModifier(unset ? 0x00 : keyCode,
371 B_LEFT_OPTION_KEY);
374 if (message->FindUInt32("right_option_key", &keyCode) == B_OK) {
375 fCurrentMap.SetModifier(unset ? 0x00 : keyCode,
376 B_RIGHT_OPTION_KEY);
379 if (message->FindUInt32("left_command_key", &keyCode) == B_OK) {
380 fCurrentMap.SetModifier(unset ? 0x00 : keyCode,
381 B_LEFT_COMMAND_KEY);
384 if (message->FindUInt32("right_command_key", &keyCode) == B_OK) {
385 fCurrentMap.SetModifier(unset ? 0x00 : keyCode,
386 B_RIGHT_COMMAND_KEY);
389 if (message->FindUInt32("menu_key", &keyCode) == B_OK)
390 fCurrentMap.SetModifier(unset ? 0x00 : keyCode, B_MENU_KEY);
392 if (message->FindUInt32("caps_key", &keyCode) == B_OK)
393 fCurrentMap.SetModifier(unset ? 0x00 : keyCode, B_CAPS_LOCK);
395 if (message->FindUInt32("num_key", &keyCode) == B_OK)
396 fCurrentMap.SetModifier(unset ? 0x00 : keyCode, B_NUM_LOCK);
398 if (message->FindUInt32("scroll_key", &keyCode) == B_OK)
399 fCurrentMap.SetModifier(unset ? 0x00 : keyCode, B_SCROLL_LOCK);
401 _UpdateButtons();
402 fKeyboardLayoutView->SetKeymap(&fCurrentMap);
403 break;
406 case kMsgKeymapUpdated:
407 _UpdateButtons();
408 fSystemListView->DeselectAll();
409 fUserListView->Select(0L);
410 break;
412 case kMsgDeadKeyAcuteChanged:
414 BMenuItem* item = fAcuteMenu->FindMarked();
415 if (item != NULL) {
416 const char* trigger = item->Label();
417 if (strcmp(trigger, kDeadKeyTriggerNone) == 0)
418 trigger = NULL;
419 fCurrentMap.SetDeadKeyTrigger(kDeadKeyAcute, trigger);
420 fKeyboardLayoutView->Invalidate();
422 break;
425 case kMsgDeadKeyCircumflexChanged:
427 BMenuItem* item = fCircumflexMenu->FindMarked();
428 if (item != NULL) {
429 const char* trigger = item->Label();
430 if (strcmp(trigger, kDeadKeyTriggerNone) == 0)
431 trigger = NULL;
432 fCurrentMap.SetDeadKeyTrigger(kDeadKeyCircumflex, trigger);
433 fKeyboardLayoutView->Invalidate();
435 break;
438 case kMsgDeadKeyDiaeresisChanged:
440 BMenuItem* item = fDiaeresisMenu->FindMarked();
441 if (item != NULL) {
442 const char* trigger = item->Label();
443 if (strcmp(trigger, kDeadKeyTriggerNone) == 0)
444 trigger = NULL;
445 fCurrentMap.SetDeadKeyTrigger(kDeadKeyDiaeresis, trigger);
446 fKeyboardLayoutView->Invalidate();
448 break;
451 case kMsgDeadKeyGraveChanged:
453 BMenuItem* item = fGraveMenu->FindMarked();
454 if (item != NULL) {
455 const char* trigger = item->Label();
456 if (strcmp(trigger, kDeadKeyTriggerNone) == 0)
457 trigger = NULL;
458 fCurrentMap.SetDeadKeyTrigger(kDeadKeyGrave, trigger);
459 fKeyboardLayoutView->Invalidate();
461 break;
464 case kMsgDeadKeyTildeChanged:
466 BMenuItem* item = fTildeMenu->FindMarked();
467 if (item != NULL) {
468 const char* trigger = item->Label();
469 if (strcmp(trigger, kDeadKeyTriggerNone) == 0)
470 trigger = NULL;
471 fCurrentMap.SetDeadKeyTrigger(kDeadKeyTilde, trigger);
472 fKeyboardLayoutView->Invalidate();
474 break;
477 default:
478 BWindow::MessageReceived(message);
479 break;
484 BMenuBar*
485 KeymapWindow::_CreateMenu()
487 BMenuBar* menuBar = new BMenuBar(Bounds(), "menubar");
489 // Create the File menu
490 BMenu* menu = new BMenu(B_TRANSLATE("File"));
491 menu->AddItem(new BMenuItem(B_TRANSLATE("Open" B_UTF8_ELLIPSIS),
492 new BMessage(kMsgMenuFileOpen), 'O'));
493 menu->AddItem(new BMenuItem(B_TRANSLATE("Save as" B_UTF8_ELLIPSIS),
494 new BMessage(kMsgMenuFileSaveAs)));
495 menu->AddSeparatorItem();
496 menu->AddItem(new BMenuItem(
497 B_TRANSLATE("Set modifier keys" B_UTF8_ELLIPSIS),
498 new BMessage(kMsgShowModifierKeysWindow)));
499 menu->AddSeparatorItem();
500 menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
501 new BMessage(B_QUIT_REQUESTED), 'Q'));
502 menuBar->AddItem(menu);
504 // Create keyboard layout menu
505 fLayoutMenu = new BMenu(B_TRANSLATE("Layout"));
506 _AddKeyboardLayouts(fLayoutMenu);
507 menuBar->AddItem(fLayoutMenu);
509 // Create the Font menu
510 fFontMenu = new BMenu(B_TRANSLATE("Font"));
511 fFontMenu->SetRadioMode(true);
512 int32 numFamilies = count_font_families();
513 font_family family, currentFamily;
514 font_style currentStyle;
515 uint32 flags;
517 be_plain_font->GetFamilyAndStyle(&currentFamily, &currentStyle);
519 for (int32 i = 0; i < numFamilies; i++) {
520 if (get_font_family(i, &family, &flags) == B_OK) {
521 BMenuItem* item
522 = new BMenuItem(family, new BMessage(kMsgMenuFontChanged));
523 fFontMenu->AddItem(item);
525 if (!strcmp(family, currentFamily))
526 item->SetMarked(true);
529 menuBar->AddItem(fFontMenu);
531 return menuBar;
535 BMenuField*
536 KeymapWindow::_CreateDeadKeyMenuField()
538 BPopUpMenu* deadKeyMenu = new BPopUpMenu(B_TRANSLATE("Select dead keys"),
539 false, false);
541 fAcuteMenu = new BMenu(B_TRANSLATE("Acute trigger"));
542 fAcuteMenu->SetRadioMode(true);
543 fAcuteMenu->AddItem(new BMenuItem("\xC2\xB4",
544 new BMessage(kMsgDeadKeyAcuteChanged)));
545 fAcuteMenu->AddItem(new BMenuItem("'",
546 new BMessage(kMsgDeadKeyAcuteChanged)));
547 fAcuteMenu->AddItem(new BMenuItem(kDeadKeyTriggerNone,
548 new BMessage(kMsgDeadKeyAcuteChanged)));
549 deadKeyMenu->AddItem(fAcuteMenu);
551 fCircumflexMenu = new BMenu(B_TRANSLATE("Circumflex trigger"));
552 fCircumflexMenu->SetRadioMode(true);
553 fCircumflexMenu->AddItem(new BMenuItem("^",
554 new BMessage(kMsgDeadKeyCircumflexChanged)));
555 fCircumflexMenu->AddItem(new BMenuItem(kDeadKeyTriggerNone,
556 new BMessage(kMsgDeadKeyCircumflexChanged)));
557 deadKeyMenu->AddItem(fCircumflexMenu);
559 fDiaeresisMenu = new BMenu(B_TRANSLATE("Diaeresis trigger"));
560 fDiaeresisMenu->SetRadioMode(true);
561 fDiaeresisMenu->AddItem(new BMenuItem("\xC2\xA8",
562 new BMessage(kMsgDeadKeyDiaeresisChanged)));
563 fDiaeresisMenu->AddItem(new BMenuItem("\"",
564 new BMessage(kMsgDeadKeyDiaeresisChanged)));
565 fDiaeresisMenu->AddItem(new BMenuItem(kDeadKeyTriggerNone,
566 new BMessage(kMsgDeadKeyDiaeresisChanged)));
567 deadKeyMenu->AddItem(fDiaeresisMenu);
569 fGraveMenu = new BMenu(B_TRANSLATE("Grave trigger"));
570 fGraveMenu->SetRadioMode(true);
571 fGraveMenu->AddItem(new BMenuItem("`",
572 new BMessage(kMsgDeadKeyGraveChanged)));
573 fGraveMenu->AddItem(new BMenuItem(kDeadKeyTriggerNone,
574 new BMessage(kMsgDeadKeyGraveChanged)));
575 deadKeyMenu->AddItem(fGraveMenu);
577 fTildeMenu = new BMenu(B_TRANSLATE("Tilde trigger"));
578 fTildeMenu->SetRadioMode(true);
579 fTildeMenu->AddItem(new BMenuItem("~",
580 new BMessage(kMsgDeadKeyTildeChanged)));
581 fTildeMenu->AddItem(new BMenuItem(kDeadKeyTriggerNone,
582 new BMessage(kMsgDeadKeyTildeChanged)));
583 deadKeyMenu->AddItem(fTildeMenu);
585 return new BMenuField(NULL, deadKeyMenu);
589 BView*
590 KeymapWindow::_CreateMapLists()
592 // The System list
593 fSystemListView = new BListView("systemList");
594 fSystemListView->SetSelectionMessage(new BMessage(kMsgSystemMapSelected));
596 BScrollView* systemScroller = new BScrollView("systemScrollList",
597 fSystemListView, 0, false, true);
599 // The User list
600 fUserListView = new BListView("userList");
601 fUserListView->SetSelectionMessage(new BMessage(kMsgUserMapSelected));
602 BScrollView* userScroller = new BScrollView("userScrollList",
603 fUserListView, 0, false, true);
605 // Saved keymaps
607 _FillSystemMaps();
608 _FillUserMaps();
610 _SetListViewSize(fSystemListView);
611 _SetListViewSize(fUserListView);
613 return BLayoutBuilder::Group<>(B_VERTICAL)
614 .Add(new BStringView("system", B_TRANSLATE("System:")))
615 .Add(systemScroller, 3)
616 .Add(new BStringView("user", B_TRANSLATE("User:")))
617 .Add(userScroller)
618 .View();
622 void
623 KeymapWindow::_AddKeyboardLayouts(BMenu* menu)
625 directory_which dataDirectories[] = {
626 B_USER_NONPACKAGED_DATA_DIRECTORY,
627 B_USER_DATA_DIRECTORY,
628 B_SYSTEM_NONPACKAGED_DATA_DIRECTORY,
629 B_SYSTEM_DATA_DIRECTORY,
632 for (uint32 i = 0;
633 i < sizeof(dataDirectories) / sizeof(dataDirectories[0]); i++) {
634 BPath path;
635 if (find_directory(dataDirectories[i], &path) != B_OK)
636 continue;
638 if (path.Append("KeyboardLayouts") != B_OK)
639 continue;
641 BDirectory directory;
642 if (directory.SetTo(path.Path()) == B_OK)
643 _AddKeyboardLayoutMenu(menu, directory);
648 /*! Adds a menu populated with the keyboard layouts found in the passed
649 in directory to the passed in menu. Each subdirectory in the passed
650 in directory is added as a submenu recursively.
652 void
653 KeymapWindow::_AddKeyboardLayoutMenu(BMenu* menu, BDirectory directory)
655 entry_ref ref;
657 while (directory.GetNextRef(&ref) == B_OK) {
658 if (menu->FindItem(ref.name) != NULL)
659 continue;
661 BDirectory subdirectory;
662 subdirectory.SetTo(&ref);
663 if (subdirectory.InitCheck() == B_OK) {
664 BMenu* submenu = new BMenu(B_TRANSLATE_NOCOLLECT(ref.name));
666 _AddKeyboardLayoutMenu(submenu, subdirectory);
667 menu->AddItem(submenu, (int32)0);
668 } else {
669 BMessage* message = new BMessage(kChangeKeyboardLayout);
671 message->AddRef("ref", &ref);
672 menu->AddItem(new BMenuItem(B_TRANSLATE_NOCOLLECT(ref.name),
673 message), (int32)0);
679 /*! Sets the keyboard layout with the passed in path and marks the
680 corresponding menu item. If the path is not found in the menu this method
681 sets the default keyboard layout and marks the corresponding menu item.
683 status_t
684 KeymapWindow::_SetKeyboardLayout(const char* path)
686 status_t status = fKeyboardLayoutView->GetKeyboardLayout()->Load(path);
688 // mark a menu item (unmarking all others)
689 _MarkKeyboardLayoutItem(path, fLayoutMenu);
691 if (path == NULL || path[0] == '\0' || status != B_OK) {
692 fKeyboardLayoutView->GetKeyboardLayout()->SetDefault();
693 BMenuItem* item = fLayoutMenu->FindItem(
694 fKeyboardLayoutView->GetKeyboardLayout()->Name());
695 if (item != NULL)
696 item->SetMarked(true);
699 // Refresh currently set layout
700 fKeyboardLayoutView->SetKeyboardLayout(
701 fKeyboardLayoutView->GetKeyboardLayout());
703 return status;
707 /*! Marks a keyboard layout item by iterating through the menus recursively
708 searching for the menu item with the passed in path. This method always
709 iterates through all menu items and unmarks them. If no item with the
710 passed in path is found it is up to the caller to set the default keyboard
711 layout and mark item corresponding to the default keyboard layout path.
713 void
714 KeymapWindow::_MarkKeyboardLayoutItem(const char* path, BMenu* menu)
716 BMenuItem* item = NULL;
717 entry_ref ref;
719 for (int32 i = 0; i < menu->CountItems(); i++) {
720 item = menu->ItemAt(i);
721 if (item == NULL)
722 continue;
724 // Unmark each item initially
725 item->SetMarked(false);
727 BMenu* submenu = item->Submenu();
728 if (submenu != NULL)
729 _MarkKeyboardLayoutItem(path, submenu);
730 else {
731 if (item->Message()->FindRef("ref", &ref) == B_OK) {
732 BPath layoutPath(&ref);
733 if (path != NULL && path[0] != '\0' && layoutPath == path) {
734 // Found it, mark the item
735 item->SetMarked(true);
743 /*! Sets the label of the "Switch Shorcuts" button to make it more
744 descriptive what will happen when you press that button.
746 void
747 KeymapWindow::_UpdateSwitchShortcutButton()
749 const char* label = B_TRANSLATE("Switch shortcut keys");
750 if (fCurrentMap.KeyForModifier(B_LEFT_COMMAND_KEY) == 0x5d
751 && fCurrentMap.KeyForModifier(B_LEFT_CONTROL_KEY) == 0x5c) {
752 label = B_TRANSLATE("Switch shortcut keys to Windows/Linux mode");
753 } else if (fCurrentMap.KeyForModifier(B_LEFT_COMMAND_KEY) == 0x5c
754 && fCurrentMap.KeyForModifier(B_LEFT_CONTROL_KEY) == 0x5d) {
755 label = B_TRANSLATE("Switch shortcut keys to Haiku mode");
758 fSwitchShortcutsButton->SetLabel(label);
762 /*! Marks the menu items corresponding to the dead key state of the current
763 key map.
765 void
766 KeymapWindow::_UpdateDeadKeyMenu()
768 BString trigger;
769 fCurrentMap.GetDeadKeyTrigger(kDeadKeyAcute, trigger);
770 if (!trigger.Length())
771 trigger = kDeadKeyTriggerNone;
772 BMenuItem* menuItem = fAcuteMenu->FindItem(trigger.String());
773 if (menuItem)
774 menuItem->SetMarked(true);
776 fCurrentMap.GetDeadKeyTrigger(kDeadKeyCircumflex, trigger);
777 if (!trigger.Length())
778 trigger = kDeadKeyTriggerNone;
779 menuItem = fCircumflexMenu->FindItem(trigger.String());
780 if (menuItem)
781 menuItem->SetMarked(true);
783 fCurrentMap.GetDeadKeyTrigger(kDeadKeyDiaeresis, trigger);
784 if (!trigger.Length())
785 trigger = kDeadKeyTriggerNone;
786 menuItem = fDiaeresisMenu->FindItem(trigger.String());
787 if (menuItem)
788 menuItem->SetMarked(true);
790 fCurrentMap.GetDeadKeyTrigger(kDeadKeyGrave, trigger);
791 if (!trigger.Length())
792 trigger = kDeadKeyTriggerNone;
793 menuItem = fGraveMenu->FindItem(trigger.String());
794 if (menuItem)
795 menuItem->SetMarked(true);
797 fCurrentMap.GetDeadKeyTrigger(kDeadKeyTilde, trigger);
798 if (!trigger.Length())
799 trigger = kDeadKeyTriggerNone;
800 menuItem = fTildeMenu->FindItem(trigger.String());
801 if (menuItem)
802 menuItem->SetMarked(true);
806 void
807 KeymapWindow::_UpdateButtons()
809 if (fCurrentMap != fAppliedMap) {
810 fCurrentMap.SetName(kCurrentKeymapName);
811 _UseKeymap();
814 fDefaultsButton->SetEnabled(
815 fCurrentMapName.ICompare(kDefaultKeymapName) != 0);
816 fRevertButton->SetEnabled(fCurrentMap != fPreviousMap);
818 _UpdateDeadKeyMenu();
819 _UpdateSwitchShortcutButton();
823 void
824 KeymapWindow::_SwitchShortcutKeys()
826 uint32 leftCommand = fCurrentMap.Map().left_command_key;
827 uint32 leftControl = fCurrentMap.Map().left_control_key;
828 uint32 rightCommand = fCurrentMap.Map().right_command_key;
829 uint32 rightControl = fCurrentMap.Map().right_control_key;
831 // switch left side
832 fCurrentMap.Map().left_command_key = leftControl;
833 fCurrentMap.Map().left_control_key = leftCommand;
835 // switch right side
836 fCurrentMap.Map().right_command_key = rightControl;
837 fCurrentMap.Map().right_control_key = rightCommand;
839 fKeyboardLayoutView->SetKeymap(&fCurrentMap);
840 _UpdateButtons();
844 //! Restores the default keymap.
845 void
846 KeymapWindow::_DefaultKeymap()
848 fCurrentMap.RestoreSystemDefault();
849 fAppliedMap = fCurrentMap;
851 fKeyboardLayoutView->SetKeymap(&fCurrentMap);
853 fCurrentMapName = _GetActiveKeymapName();
854 _SelectCurrentMap();
858 //! Saves previous map to the "Key_map" file.
859 void
860 KeymapWindow::_RevertKeymap()
862 entry_ref ref;
863 _GetCurrentKeymap(ref);
865 status_t status = fPreviousMap.Save(ref);
866 if (status != B_OK) {
867 printf("error when saving keymap: %s", strerror(status));
868 return;
871 fPreviousMap.Use();
872 fCurrentMap.Load(ref);
873 fAppliedMap = fCurrentMap;
875 fKeyboardLayoutView->SetKeymap(&fCurrentMap);
877 fCurrentMapName = _GetActiveKeymapName();
878 _SelectCurrentMap();
882 //! Saves current map to the "Key_map" file.
883 void
884 KeymapWindow::_UseKeymap()
886 entry_ref ref;
887 _GetCurrentKeymap(ref);
889 status_t status = fCurrentMap.Save(ref);
890 if (status != B_OK) {
891 printf("error when saving : %s", strerror(status));
892 return;
895 fCurrentMap.Use();
896 fAppliedMap.Load(ref);
898 fCurrentMapName = _GetActiveKeymapName();
899 _SelectCurrentMap();
903 void
904 KeymapWindow::_FillSystemMaps()
906 BListItem* item;
907 while ((item = fSystemListView->RemoveItem(static_cast<int32>(0))))
908 delete item;
910 // TODO: common keymaps!
911 BPath path;
912 if (find_directory(B_SYSTEM_DATA_DIRECTORY, &path) != B_OK)
913 return;
915 path.Append("Keymaps");
917 BDirectory directory;
918 entry_ref ref;
920 if (directory.SetTo(path.Path()) == B_OK) {
921 while (directory.GetNextRef(&ref) == B_OK) {
922 fSystemListView->AddItem(
923 new KeymapListItem(ref, B_TRANSLATE_NOCOLLECT(ref.name)));
927 fSystemListView->SortItems(&compare_key_list_items);
931 void
932 KeymapWindow::_FillUserMaps()
934 BListItem* item;
935 while ((item = fUserListView->RemoveItem(static_cast<int32>(0))))
936 delete item;
938 entry_ref ref;
939 _GetCurrentKeymap(ref);
941 fUserListView->AddItem(new KeymapListItem(ref, B_TRANSLATE("(Current)")));
943 fCurrentMapName = _GetActiveKeymapName();
945 BPath path;
946 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
947 return;
949 path.Append("Keymap");
951 BDirectory directory;
952 if (directory.SetTo(path.Path()) == B_OK) {
953 while (directory.GetNextRef(&ref) == B_OK) {
954 fUserListView->AddItem(new KeymapListItem(ref));
958 fUserListView->SortItems(&compare_key_list_items);
962 void
963 KeymapWindow::_SetListViewSize(BListView* listView)
965 float minWidth = 0;
966 for (int32 i = 0; i < listView->CountItems(); i++) {
967 BStringItem* item = (BStringItem*)listView->ItemAt(i);
968 float width = listView->StringWidth(item->Text());
969 if (width > minWidth)
970 minWidth = width;
973 listView->SetExplicitMinSize(BSize(minWidth + 8, 32));
977 status_t
978 KeymapWindow::_GetCurrentKeymap(entry_ref& ref)
980 BPath path;
981 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
982 return B_ERROR;
984 path.Append("Key_map");
986 return get_ref_for_path(path.Path(), &ref);
990 BString
991 KeymapWindow::_GetActiveKeymapName()
993 BString mapName = kCurrentKeymapName;
994 // safe default
996 entry_ref ref;
997 _GetCurrentKeymap(ref);
999 BNode node(&ref);
1001 if (node.InitCheck() == B_OK)
1002 node.ReadAttrString("keymap:name", &mapName);
1004 return mapName;
1008 bool
1009 KeymapWindow::_SelectCurrentMap(BListView* view)
1011 if (fCurrentMapName.Length() <= 0)
1012 return false;
1014 for (int32 i = 0; i < view->CountItems(); i++) {
1015 BStringItem* current = dynamic_cast<BStringItem *>(view->ItemAt(i));
1016 if (current != NULL && fCurrentMapName == current->Text()) {
1017 view->Select(i);
1018 view->ScrollToSelection();
1019 return true;
1023 return false;
1027 void
1028 KeymapWindow::_SelectCurrentMap()
1030 if (!_SelectCurrentMap(fSystemListView)
1031 && !_SelectCurrentMap(fUserListView)) {
1032 // Select the "(Current)" entry if no name matches
1033 fUserListView->Select(0L);
1038 status_t
1039 KeymapWindow::_GetSettings(BFile& file, int mode) const
1041 BPath path;
1042 status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path,
1043 (mode & O_ACCMODE) != O_RDONLY);
1044 if (status != B_OK)
1045 return status;
1047 path.Append("Keymap settings");
1049 return file.SetTo(path.Path(), mode);
1053 status_t
1054 KeymapWindow::_LoadSettings(BRect& windowFrame)
1056 BScreen screen(this);
1058 windowFrame.Set(-1, -1, 669, 357);
1059 // See if we can use a larger default size
1060 if (screen.Frame().Width() > 1200) {
1061 windowFrame.right = 899;
1062 windowFrame.bottom = 349;
1064 float scaling = be_plain_font->Size() / 12.0f;
1065 windowFrame.right *= scaling;
1066 windowFrame.bottom *= scaling;
1068 BFile file;
1069 status_t status = _GetSettings(file, B_READ_ONLY);
1070 if (status == B_OK) {
1071 BMessage settings;
1072 status = settings.Unflatten(&file);
1073 if (status == B_OK) {
1074 BRect frame;
1075 status = settings.FindRect("window frame", &frame);
1076 if (status == B_OK)
1077 windowFrame = frame;
1079 const char* layoutPath;
1080 if (settings.FindString("keyboard layout", &layoutPath) == B_OK)
1081 _SetKeyboardLayout(layoutPath);
1085 return status;
1089 status_t
1090 KeymapWindow::_SaveSettings()
1092 BFile file;
1093 status_t status
1094 = _GetSettings(file, B_WRITE_ONLY | B_ERASE_FILE | B_CREATE_FILE);
1095 if (status != B_OK)
1096 return status;
1098 BMessage settings('keym');
1099 settings.AddRect("window frame", Frame());
1101 BPath path = _GetMarkedKeyboardLayoutPath(fLayoutMenu);
1102 if (path.InitCheck() == B_OK)
1103 settings.AddString("keyboard layout", path.Path());
1105 return settings.Flatten(&file);
1109 /*! Gets the path of the currently marked keyboard layout item
1110 by searching through each of the menus recursively until
1111 a marked item is found.
1113 BPath
1114 KeymapWindow::_GetMarkedKeyboardLayoutPath(BMenu* menu)
1116 BPath path;
1117 BMenuItem* item = NULL;
1118 entry_ref ref;
1120 for (int32 i = 0; i < menu->CountItems(); i++) {
1121 item = menu->ItemAt(i);
1122 if (item == NULL)
1123 continue;
1125 BMenu* submenu = item->Submenu();
1126 if (submenu != NULL) {
1127 path = _GetMarkedKeyboardLayoutPath(submenu);
1128 if (path.InitCheck() == B_OK)
1129 return path;
1130 } else {
1131 if (item->IsMarked()
1132 && item->Message()->FindRef("ref", &ref) == B_OK) {
1133 path.SetTo(&ref);
1134 return path;
1139 return path;