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