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