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