1 /* 2 * Copyright 2004-2009 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 #include "KeymapWindow.h" 13 14 #include <string.h> 15 #include <stdio.h> 16 17 #include <Alert.h> 18 #include <Button.h> 19 #include <Directory.h> 20 #include <FindDirectory.h> 21 #include <GroupLayoutBuilder.h> 22 #include <ListView.h> 23 #include <MenuBar.h> 24 #include <MenuItem.h> 25 #include <Path.h> 26 #include <Screen.h> 27 #include <ScrollView.h> 28 #include <StringView.h> 29 #include <TextControl.h> 30 31 #include "KeyboardLayoutView.h" 32 #include "KeymapApplication.h" 33 #include "KeymapListItem.h" 34 #include "KeymapMessageFilter.h" 35 36 37 static const uint32 kMsgMenuFileOpen = 'mMFO'; 38 static const uint32 kMsgMenuFileSave = 'mMFS'; 39 static const uint32 kMsgMenuFileSaveAs = 'mMFA'; 40 41 static const uint32 kChangeKeyboardLayout = 'cKyL'; 42 43 static const uint32 kMsgSwitchShortcuts = 'swSc'; 44 45 static const uint32 kMsgMenuFontChanged = 'mMFC'; 46 47 static const uint32 kMsgSystemMapSelected = 'SmST'; 48 static const uint32 kMsgUserMapSelected = 'UmST'; 49 50 static const uint32 kMsgUseKeymap = 'UkyM'; 51 static const uint32 kMsgRevertKeymap = 'Rvrt'; 52 static const uint32 kMsgKeymapUpdated = 'upkM'; 53 54 55 KeymapWindow::KeymapWindow() 56 : BWindow(BRect(80, 50, 880, 380), "Keymap", B_TITLED_WINDOW, 57 B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS), 58 fFirstTime(true) 59 { 60 SetLayout(new BGroupLayout(B_VERTICAL)); 61 62 fKeyboardLayoutView = new KeyboardLayoutView("layout"); 63 fKeyboardLayoutView->SetKeymap(&fCurrentMap); 64 65 fTextControl = new BTextControl("Sample and Clipboard:", "", NULL); 66 67 fSwitchShortcutsButton = new BButton("switch", "", 68 new BMessage(kMsgSwitchShortcuts)); 69 70 fUseButton = new BButton("useButton", "Use", new BMessage(kMsgUseKeymap)); 71 fRevertButton = new BButton("revertButton", "Revert", 72 new BMessage(kMsgRevertKeymap)); 73 74 // controls pane 75 AddChild(BGroupLayoutBuilder(B_VERTICAL) 76 .Add(_CreateMenu()) 77 .Add(BGroupLayoutBuilder(B_HORIZONTAL, 10) 78 .Add(_CreateMapLists(), 0.25) 79 .Add(BGroupLayoutBuilder(B_VERTICAL, 10) 80 .Add(fKeyboardLayoutView) 81 //.Add(new BStringView("text label", "Sample and Clipboard:")) 82 .Add(BGroupLayoutBuilder(B_HORIZONTAL, 10) 83 .Add(fTextControl) 84 .Add(fSwitchShortcutsButton)) 85 .AddGlue(0.0) 86 .Add(BGroupLayoutBuilder(B_HORIZONTAL, 10) 87 .AddGlue(0.0) 88 .Add(fUseButton) 89 .Add(fRevertButton))) 90 .SetInsets(10, 10, 10, 10))); 91 92 fKeyboardLayoutView->SetTarget(fTextControl->TextView()); 93 fTextControl->MakeFocus(); 94 fTextControl->TextView()->AddFilter(new KeymapMessageFilter( 95 B_PROGRAMMED_DELIVERY, B_ANY_SOURCE, &fCurrentMap)); 96 97 _UpdateButtons(); 98 99 // Make sure the user keymap directory exists 100 BPath path; 101 find_directory(B_USER_SETTINGS_DIRECTORY, &path); 102 path.Append("Keymap"); 103 104 entry_ref ref; 105 get_ref_for_path(path.Path(), &ref); 106 107 BDirectory userKeymapsDir(&ref); 108 if (userKeymapsDir.InitCheck() != B_OK) { 109 create_directory(path.Path(), S_IRWXU | S_IRWXG | S_IRWXO); 110 } 111 112 BMessenger messenger(this); 113 fOpenPanel = new BFilePanel(B_OPEN_PANEL, &messenger, &ref, 114 B_FILE_NODE, false, NULL); 115 fSavePanel = new BFilePanel(B_SAVE_PANEL, &messenger, &ref, 116 B_FILE_NODE, false, NULL); 117 118 BScreen screen(this); 119 120 float width = Frame().Width(); 121 float height = Frame().Height(); 122 123 // Make sure we can fit on screen 124 if (screen.Frame().Width() < Frame().Width()) 125 width = screen.Frame().Width(); 126 if (screen.Frame().Height() < Frame().Height()) 127 height = screen.Frame().Height(); 128 129 // See if we can use a larger default size 130 if (screen.Frame().Width() > 1200) { 131 width = 900; 132 height = 400; 133 } 134 135 // TODO: store and restore position and size! 136 ResizeTo(width, height); 137 MoveTo(BAlert::AlertPosition(width, height)); 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 _UpdateSwitchShortcutButton(); 157 158 Unlock(); 159 } 160 161 162 KeymapWindow::~KeymapWindow(void) 163 { 164 delete fOpenPanel; 165 delete fSavePanel; 166 } 167 168 169 bool 170 KeymapWindow::QuitRequested() 171 { 172 be_app->PostMessage(B_QUIT_REQUESTED); 173 return true; 174 } 175 176 177 void 178 KeymapWindow::MessageReceived(BMessage* message) 179 { 180 switch (message->what) { 181 case B_SIMPLE_DATA: 182 case B_REFS_RECEIVED: 183 { 184 entry_ref ref; 185 int32 i = 0; 186 while (message->FindRef("refs", i++, &ref) == B_OK) { 187 fCurrentMap.Load(ref); 188 } 189 fKeyboardLayoutView->SetKeymap(&fCurrentMap); 190 fSystemListView->DeselectAll(); 191 fUserListView->DeselectAll(); 192 break; 193 } 194 195 case B_SAVE_REQUESTED: 196 { 197 entry_ref ref; 198 const char *name; 199 if (message->FindRef("directory", &ref) == B_OK 200 && message->FindString("name", &name) == B_OK) { 201 BDirectory directory(&ref); 202 BEntry entry(&directory, name); 203 entry.GetRef(&ref); 204 fCurrentMap.Save(ref); 205 206 _FillUserMaps(); 207 } 208 break; 209 } 210 211 case kMsgMenuFileOpen: 212 fOpenPanel->Show(); 213 break; 214 case kMsgMenuFileSave: 215 break; 216 case kMsgMenuFileSaveAs: 217 fSavePanel->Show(); 218 break; 219 220 case kChangeKeyboardLayout: 221 { 222 entry_ref ref; 223 if (message->FindRef("ref", &ref) == B_OK 224 && fKeyboardLayoutView->GetKeyboardLayout()->Load(ref) 225 == B_OK) { 226 fKeyboardLayoutView->SetKeyboardLayout( 227 fKeyboardLayoutView->GetKeyboardLayout()); 228 } else { 229 fKeyboardLayoutView->GetKeyboardLayout()->SetDefault(); 230 fLayoutMenu->ItemAt(0)->SetMarked(true); 231 } 232 233 fKeyboardLayoutView->SetKeyboardLayout( 234 fKeyboardLayoutView->GetKeyboardLayout()); 235 break; 236 } 237 238 case kMsgSwitchShortcuts: 239 _SwitchShortcutKeys(); 240 break; 241 242 case kMsgMenuFontChanged: 243 { 244 BMenuItem *item = fFontMenu->FindMarked(); 245 if (item != NULL) { 246 BFont font; 247 font.SetFamilyAndStyle(item->Label(), NULL); 248 fKeyboardLayoutView->SetFont(font); 249 fTextControl->TextView()->SetFontAndColor(&font); 250 } 251 break; 252 } 253 254 case kMsgSystemMapSelected: 255 case kMsgUserMapSelected: 256 { 257 BListView* listView; 258 BListView* otherListView; 259 260 if (message->what == kMsgSystemMapSelected) { 261 fUserListView->DeselectAll(); 262 listView = fSystemListView; 263 otherListView = fUserListView; 264 } else { 265 listView = fUserListView; 266 otherListView = fSystemListView; 267 } 268 269 int32 index = listView->CurrentSelection(); 270 if (index < 0) 271 break; 272 273 // Deselect item in other BListView 274 otherListView->DeselectAll(); 275 276 if (index == 0 && listView == fUserListView) { 277 // we can safely ignore the "(Current)" item 278 break; 279 } 280 281 KeymapListItem* item 282 = static_cast<KeymapListItem*>(listView->ItemAt(index)); 283 if (item != NULL) { 284 if (!fFirstTime) 285 fCurrentMap.Load(item->EntryRef()); 286 else 287 fFirstTime = false; 288 289 fKeyboardLayoutView->SetKeymap(&fCurrentMap); 290 _UpdateButtons(); 291 } 292 break; 293 } 294 295 case kMsgUseKeymap: 296 _UseKeymap(); 297 _UpdateButtons(); 298 break; 299 case kMsgRevertKeymap: 300 _RevertKeymap(); 301 _UpdateButtons(); 302 break; 303 304 case kMsgKeymapUpdated: 305 _UpdateButtons(); 306 fSystemListView->DeselectAll(); 307 fUserListView->Select(0L); 308 break; 309 310 default: 311 BWindow::MessageReceived(message); 312 break; 313 } 314 } 315 316 317 BMenuBar* 318 KeymapWindow::_CreateMenu() 319 { 320 BMenuBar* menuBar = new BMenuBar(Bounds(), "menubar"); 321 BMenuItem* item; 322 323 // Create the File menu 324 BMenu* menu = new BMenu("File"); 325 menu->AddItem(new BMenuItem("Open" B_UTF8_ELLIPSIS, 326 new BMessage(kMsgMenuFileOpen), 'O')); 327 menu->AddSeparatorItem(); 328 item = new BMenuItem("Save", new BMessage(kMsgMenuFileSave), 'S'); 329 item->SetEnabled(false); 330 menu->AddItem(item); 331 menu->AddItem(new BMenuItem("Save As" B_UTF8_ELLIPSIS, 332 new BMessage(kMsgMenuFileSaveAs))); 333 menu->AddSeparatorItem(); 334 menu->AddItem(new BMenuItem("Quit", 335 new BMessage(B_QUIT_REQUESTED), 'Q')); 336 menuBar->AddItem(menu); 337 338 // Create keyboard layout menu 339 fLayoutMenu = new BMenu("Layout"); 340 fLayoutMenu->SetRadioMode(true); 341 fLayoutMenu->AddItem(item = new BMenuItem( 342 fKeyboardLayoutView->GetKeyboardLayout()->Name(), 343 new BMessage(kChangeKeyboardLayout))); 344 item->SetMarked(true); 345 346 _AddKeyboardLayouts(fLayoutMenu); 347 menuBar->AddItem(fLayoutMenu); 348 349 // Create the Font menu 350 fFontMenu = new BMenu("Font"); 351 fFontMenu->SetRadioMode(true); 352 int32 numFamilies = count_font_families(); 353 font_family family, currentFamily; 354 font_style currentStyle; 355 uint32 flags; 356 357 be_plain_font->GetFamilyAndStyle(¤tFamily, ¤tStyle); 358 359 for (int32 i = 0; i < numFamilies; i++) { 360 if (get_font_family(i, &family, &flags) == B_OK) { 361 BMenuItem *item = 362 new BMenuItem(family, new BMessage(kMsgMenuFontChanged)); 363 fFontMenu->AddItem(item); 364 365 if (!strcmp(family, currentFamily)) 366 item->SetMarked(true); 367 } 368 } 369 menuBar->AddItem(fFontMenu); 370 371 return menuBar; 372 } 373 374 375 BView* 376 KeymapWindow::_CreateMapLists() 377 { 378 // The System list 379 fSystemListView = new BListView("systemList"); 380 fSystemListView->SetSelectionMessage(new BMessage(kMsgSystemMapSelected)); 381 382 BScrollView* systemScroller = new BScrollView("systemScrollList", 383 fSystemListView, 0, false, true); 384 385 // The User list 386 fUserListView = new BListView("userList"); 387 fUserListView->SetSelectionMessage(new BMessage(kMsgUserMapSelected)); 388 BScrollView* userScroller = new BScrollView("userScrollList", 389 fUserListView, 0, false, true); 390 391 // Saved keymaps 392 393 _FillSystemMaps(); 394 _FillUserMaps(); 395 396 _SetListViewSize(fSystemListView); 397 _SetListViewSize(fUserListView); 398 399 return BGroupLayoutBuilder(B_VERTICAL) 400 .Add(new BStringView("system", "System:")) 401 .Add(systemScroller, 3) 402 .Add(new BStringView("user", "User:")) 403 .Add(userScroller); 404 } 405 406 407 void 408 KeymapWindow::_AddKeyboardLayouts(BMenu* menu) 409 { 410 directory_which dataDirectories[] = { 411 B_USER_DATA_DIRECTORY, 412 B_COMMON_DATA_DIRECTORY, 413 B_BEOS_DATA_DIRECTORY 414 }; 415 416 for (uint32 i = 0; 417 i < sizeof(dataDirectories) / sizeof(dataDirectories[0]); i++) { 418 BPath path; 419 if (find_directory(dataDirectories[i], &path) != B_OK) 420 continue; 421 422 path.Append("KeyboardLayouts"); 423 424 BDirectory directory; 425 if (directory.SetTo(path.Path()) == B_OK) { 426 entry_ref ref; 427 while (directory.GetNextRef(&ref) == B_OK) { 428 if (menu->FindItem(ref.name) != NULL) 429 continue; 430 431 BMessage* message = new BMessage(kChangeKeyboardLayout); 432 message->AddRef("ref", &ref); 433 434 menu->AddItem(new BMenuItem(ref.name, message)); 435 } 436 } 437 } 438 } 439 440 441 /*! Sets the label of the "Switch Shorcuts" button to make it more 442 descriptive what will happen when you press that button. 443 */ 444 void 445 KeymapWindow::_UpdateSwitchShortcutButton() 446 { 447 const char* label = "Switch Shortcut Keys"; 448 if (fCurrentMap.KeyForModifier(B_LEFT_COMMAND_KEY) == 0x5d 449 && fCurrentMap.KeyForModifier(B_LEFT_CONTROL_KEY) == 0x5c) { 450 label = "Switch Shortcut Keys To Windows/Linux Mode"; 451 } else if (fCurrentMap.KeyForModifier(B_LEFT_COMMAND_KEY) == 0x5c 452 && fCurrentMap.KeyForModifier(B_LEFT_CONTROL_KEY) == 0x5d) { 453 label = "Switch Shortcut Keys To Haiku Mode"; 454 } 455 456 fSwitchShortcutsButton->SetLabel(label); 457 } 458 459 460 void 461 KeymapWindow::_UpdateButtons() 462 { 463 fUseButton->SetEnabled(!fCurrentMap.Equals(fAppliedMap)); 464 fRevertButton->SetEnabled(!fCurrentMap.Equals(fPreviousMap)); 465 466 _UpdateSwitchShortcutButton(); 467 } 468 469 470 void 471 KeymapWindow::_SwitchShortcutKeys() 472 { 473 uint32 leftCommand = fCurrentMap.Map().left_command_key; 474 uint32 leftControl = fCurrentMap.Map().left_control_key; 475 uint32 rightCommand = fCurrentMap.Map().right_command_key; 476 uint32 rightControl = fCurrentMap.Map().right_control_key; 477 478 // switch left side 479 fCurrentMap.Map().left_command_key = leftControl; 480 fCurrentMap.Map().left_control_key = leftCommand; 481 482 // switch right side 483 fCurrentMap.Map().right_command_key = rightControl; 484 fCurrentMap.Map().right_control_key = rightCommand; 485 486 fKeyboardLayoutView->SetKeymap(&fCurrentMap); 487 _UpdateButtons(); 488 } 489 490 491 //! Saves previous map to the "Key_map" file. 492 void 493 KeymapWindow::_RevertKeymap() 494 { 495 entry_ref ref; 496 _GetCurrentKeymap(ref); 497 498 status_t status = fPreviousMap.Save(ref); 499 if (status != B_OK) { 500 printf("error when saving keymap: %s", strerror(status)); 501 return; 502 } 503 504 fPreviousMap.Use(); 505 fCurrentMap.Load(ref); 506 fAppliedMap = fCurrentMap; 507 508 fKeyboardLayoutView->SetKeymap(&fCurrentMap); 509 510 fCurrentMapName = _GetActiveKeymapName(); 511 _SelectCurrentMap(); 512 } 513 514 515 void 516 KeymapWindow::_UseKeymap() 517 { 518 entry_ref ref; 519 _GetCurrentKeymap(ref); 520 521 status_t status = fCurrentMap.Save(ref); 522 if (status != B_OK) { 523 printf("error when saving : %s", strerror(status)); 524 return; 525 } 526 527 fCurrentMap.Use(); 528 fAppliedMap.Load(ref); 529 530 fCurrentMapName = _GetActiveKeymapName(); 531 } 532 533 534 void 535 KeymapWindow::_FillSystemMaps() 536 { 537 BListItem *item; 538 while ((item = fSystemListView->RemoveItem(static_cast<int32>(0)))) 539 delete item; 540 541 // TODO: common keymaps! 542 BPath path; 543 if (find_directory(B_SYSTEM_DATA_DIRECTORY, &path) != B_OK) 544 return; 545 546 path.Append("Keymaps"); 547 548 BDirectory directory; 549 entry_ref ref; 550 551 if (directory.SetTo(path.Path()) == B_OK) { 552 while (directory.GetNextRef(&ref) == B_OK) { 553 fSystemListView->AddItem(new KeymapListItem(ref)); 554 } 555 } 556 } 557 558 559 void 560 KeymapWindow::_FillUserMaps() 561 { 562 BListItem* item; 563 while ((item = fUserListView->RemoveItem(static_cast<int32>(0)))) 564 delete item; 565 566 entry_ref ref; 567 _GetCurrentKeymap(ref); 568 569 fUserListView->AddItem(new KeymapListItem(ref, "(Current)")); 570 571 fCurrentMapName = _GetActiveKeymapName(); 572 573 BPath path; 574 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 575 return; 576 577 path.Append("Keymap"); 578 579 BDirectory directory; 580 if (directory.SetTo(path.Path()) == B_OK) { 581 while (directory.GetNextRef(&ref) == B_OK) { 582 fUserListView->AddItem(new KeymapListItem(ref)); 583 } 584 } 585 } 586 587 588 void 589 KeymapWindow::_SetListViewSize(BListView* listView) 590 { 591 float minWidth = 0; 592 for (int32 i = 0; i < listView->CountItems(); i++) { 593 BStringItem* item = (BStringItem*)listView->ItemAt(i); 594 float width = listView->StringWidth(item->Text()); 595 if (width > minWidth) 596 minWidth = width; 597 } 598 599 listView->SetExplicitMinSize(BSize(minWidth + 8, 32)); 600 } 601 602 603 status_t 604 KeymapWindow::_GetCurrentKeymap(entry_ref& ref) 605 { 606 BPath path; 607 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 608 return B_ERROR; 609 610 path.Append("Key_map"); 611 612 return get_ref_for_path(path.Path(), &ref); 613 } 614 615 616 BString 617 KeymapWindow::_GetActiveKeymapName() 618 { 619 BString mapName = "(Current)"; // safe default 620 621 entry_ref ref; 622 _GetCurrentKeymap(ref); 623 624 BNode node(&ref); 625 626 if (node.InitCheck() == B_OK) 627 node.ReadAttrString("keymap:name", &mapName); 628 629 return mapName; 630 } 631 632 633 bool 634 KeymapWindow::_SelectCurrentMap(BListView* view) 635 { 636 if (fCurrentMapName.Length() <= 0) 637 return false; 638 639 for (int32 i = 0; i < view->CountItems(); i++) { 640 BStringItem* current = dynamic_cast<BStringItem *>(view->ItemAt(i)); 641 if (current != NULL && fCurrentMapName == current->Text()) { 642 view->Select(i); 643 view->ScrollToSelection(); 644 return true; 645 } 646 } 647 648 return false; 649 } 650 651 652 void 653 KeymapWindow::_SelectCurrentMap() 654 { 655 if (!_SelectCurrentMap(fSystemListView) 656 && !_SelectCurrentMap(fUserListView)) { 657 // Select the "(Current)" entry if no name matches 658 fUserListView->Select(0L); 659 fFirstTime = false; 660 } 661 } 662