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