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