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
49 static const uint32 kMsgMenuFileOpen = 'mMFO';
50 static const uint32 kMsgMenuFileSaveAs = 'mMFA';
51
52 static const uint32 kChangeKeyboardLayout = 'cKyL';
53
54 static const uint32 kMsgSwitchShortcuts = 'swSc';
55
56 static const uint32 kMsgMenuFontChanged = 'mMFC';
57
58 static const uint32 kMsgSystemMapSelected = 'SmST';
59 static const uint32 kMsgUserMapSelected = 'UmST';
60
61 static const uint32 kMsgDefaultKeymap = 'Dflt';
62 static const uint32 kMsgRevertKeymap = 'Rvrt';
63 static const uint32 kMsgKeymapUpdated = 'kMup';
64
65 static const uint32 kMsgDeadKeyAcuteChanged = 'dkAc';
66 static const uint32 kMsgDeadKeyCircumflexChanged = 'dkCc';
67 static const uint32 kMsgDeadKeyDiaeresisChanged = 'dkDc';
68 static const uint32 kMsgDeadKeyGraveChanged = 'dkGc';
69 static const uint32 kMsgDeadKeyTildeChanged = 'dkTc';
70
71 static const char* kDeadKeyTriggerNone = "<none>";
72
73 static const char* kCurrentKeymapName = "(Current)";
74 static const char* kDefaultKeymapName = "US-International";
75
76 static const float kDefaultHeight = 440;
77 static const float kDefaultWidth = 1000;
78
79 static int
compare_key_list_items(const void * a,const void * b)80 compare_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
KeymapWindow()87 KeymapWindow::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
~KeymapWindow()202 KeymapWindow::~KeymapWindow()
203 {
204 delete fOpenPanel;
205 delete fSavePanel;
206 }
207
208
209 bool
QuitRequested()210 KeymapWindow::QuitRequested()
211 {
212 _SaveSettings();
213
214 be_app->PostMessage(B_QUIT_REQUESTED);
215 return true;
216 }
217
218
219 void
MessageReceived(BMessage * message)220 KeymapWindow::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
504 BMenuBar*
_CreateMenu()505 KeymapWindow::_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(¤tFamily, ¤tStyle);
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
555 BMenuField*
_CreateDeadKeyMenuField()556 KeymapWindow::_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
609 BView*
_CreateMapLists()610 KeymapWindow::_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
642 void
_AddKeyboardLayouts(BMenu * menu)643 KeymapWindow::_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 */
672 void
_AddKeyboardLayoutMenu(BMenu * menu,BDirectory directory)673 KeymapWindow::_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 */
704 status_t
_SetKeyboardLayout(const char * path)705 KeymapWindow::_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 */
734 void
_MarkKeyboardLayoutItem(const char * path,BMenu * menu)735 KeymapWindow::_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 */
767 void
_UpdateSwitchShortcutButton()768 KeymapWindow::_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 */
786 void
_UpdateDeadKeyMenu()787 KeymapWindow::_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
827 void
_UpdateButtons()828 KeymapWindow::_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
844 void
_SwitchShortcutKeys()845 KeymapWindow::_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.
866 void
_DefaultKeymap()867 KeymapWindow::_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.
880 void
_RevertKeymap()881 KeymapWindow::_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.
904 void
_UseKeymap()905 KeymapWindow::_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
924 void
_FillSystemMaps()925 KeymapWindow::_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
954 void
_FillUserMaps()955 KeymapWindow::_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
985 void
_SetListViewSize(BListView * listView)986 KeymapWindow::_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
1000 status_t
_GetCurrentKeymap(entry_ref & ref)1001 KeymapWindow::_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
1013 BString
_GetActiveKeymapName()1014 KeymapWindow::_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
1031 bool
_SelectCurrentMap(BListView * view)1032 KeymapWindow::_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
1051 void
_SelectCurrentMap()1052 KeymapWindow::_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
1062 status_t
_GetSettings(BFile & file,int mode) const1063 KeymapWindow::_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
1077 status_t
_LoadSettings(BRect & windowFrame)1078 KeymapWindow::_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
1113 status_t
_SaveSettings()1114 KeymapWindow::_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 */
1137 BPath
_GetMarkedKeyboardLayoutPath(BMenu * menu)1138 KeymapWindow::_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