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