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