1 /* 2 * Copyright 1999-2009 Jeremy Friesner 3 * Copyright 2009-2010 Haiku, Inc. All rights reserved. 4 * Distributed under the terms of the MIT License. 5 * 6 * Authors: 7 * Jeremy Friesner 8 * Fredrik Modéen 9 */ 10 11 12 #include "ShortcutsWindow.h" 13 14 #include <math.h> 15 #include <stdio.h> 16 17 #include <Alert.h> 18 #include <Application.h> 19 #include <Button.h> 20 #include <Catalog.h> 21 #include <Clipboard.h> 22 #include <ColumnListView.h> 23 #include <ColumnTypes.h> 24 #include <ControlLook.h> 25 #include <File.h> 26 #include <FilePanel.h> 27 #include <FindDirectory.h> 28 #include <Input.h> 29 #include <LayoutBuilder.h> 30 #include <Locale.h> 31 #include <Message.h> 32 #include <Menu.h> 33 #include <MenuBar.h> 34 #include <MenuItem.h> 35 #include <MessageFilter.h> 36 #include <Path.h> 37 #include <PopUpMenu.h> 38 #include <Screen.h> 39 #include <ScrollBar.h> 40 #include <ScrollView.h> 41 #include <String.h> 42 #include <SupportDefs.h> 43 #include <usb/USB_hid.h> 44 #include <usb/USB_hid_page_consumer.h> 45 46 #include "EditWindow.h" 47 #include "KeyInfos.h" 48 #include "MetaKeyStateMap.h" 49 #include "ParseCommandLine.h" 50 #include "PopUpColumn.h" 51 #include "ShortcutsFilterConstants.h" 52 #include "ShortcutsSpec.h" 53 54 55 // Window sizing constraints 56 #define MAX_WIDTH 10000 57 #define MAX_HEIGHT 10000 58 // SetSizeLimits does not provide a mechanism for specifying an 59 // unrestricted maximum. 10,000 seems to be the most common value used 60 // in other Haiku system applications. 61 62 #define WINDOW_SETTINGS_FILE_NAME "Shortcuts_window_settings" 63 // Because the "shortcuts_settings" file (SHORTCUTS_SETTING_FILE_NAME) is 64 // already used as a communications method between this configurator and 65 // the "shortcut_catcher" input_server filter, it should not be overloaded 66 // with window position information, instead, a separate file is used. 67 68 #undef B_TRANSLATION_CONTEXT 69 #define B_TRANSLATION_CONTEXT "ShortcutsWindow" 70 71 #define ERROR "Shortcuts error" 72 #define WARNING "Shortcuts warning" 73 74 75 // Creates a pop-up-menu that reflects the possible states of the specified 76 // meta-key. 77 static BPopUpMenu* 78 CreateMetaPopUp(int column) 79 { 80 MetaKeyStateMap& map = GetNthKeyMap(column); 81 BPopUpMenu* popup = new BPopUpMenu(NULL, false); 82 int stateCount = map.GetNumStates(); 83 84 for (int i = 0; i < stateCount; i++) 85 popup->AddItem(new BMenuItem(map.GetNthStateDesc(i), NULL)); 86 87 return popup; 88 } 89 90 91 // Creates a pop-up that allows the user to choose a key-cap visually 92 static BPopUpMenu* 93 CreateKeysPopUp() 94 { 95 BPopUpMenu* popup = new BPopUpMenu(NULL, false); 96 int numKeys = GetNumKeyIndices(); 97 for (int i = 0; i < numKeys; i++) { 98 const char* next = GetKeyName(i); 99 if (next != NULL) 100 popup->AddItem(new BMenuItem(next, NULL)); 101 } 102 103 return popup; 104 } 105 106 107 ShortcutsWindow::ShortcutsWindow() 108 : 109 BWindow(BRect(0, 0, 0, 0), B_TRANSLATE_SYSTEM_NAME("Shortcuts"), 110 B_TITLED_WINDOW, B_AUTO_UPDATE_SIZE_LIMITS), 111 fSavePanel(NULL), 112 fOpenPanel(NULL), 113 fSelectPanel(NULL), 114 fKeySetModified(false), 115 fLastOpenWasAppend(false) 116 { 117 ShortcutsSpec::InitializeMetaMaps(); 118 119 BMenuBar* menuBar = new BMenuBar("Menu Bar"); 120 121 BMenu* fileMenu = new BMenu(B_TRANSLATE("File")); 122 fileMenu->AddItem(new BMenuItem(B_TRANSLATE("Open KeySet" B_UTF8_ELLIPSIS), 123 new BMessage(OPEN_KEYSET), 'O')); 124 fileMenu->AddItem(new BMenuItem( 125 B_TRANSLATE("Append KeySet" B_UTF8_ELLIPSIS), 126 new BMessage(APPEND_KEYSET), 'A')); 127 fileMenu->AddItem(new BMenuItem(B_TRANSLATE("Revert to saved"), 128 new BMessage(REVERT_KEYSET), 'R')); 129 fileMenu->AddItem(new BSeparatorItem); 130 fileMenu->AddItem(new BMenuItem( 131 B_TRANSLATE("Save KeySet as" B_UTF8_ELLIPSIS), 132 new BMessage(SAVE_KEYSET_AS), 'S')); 133 fileMenu->AddItem(new BSeparatorItem); 134 fileMenu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), 135 new BMessage(B_QUIT_REQUESTED), 'Q')); 136 menuBar->AddItem(fileMenu); 137 138 fColumnListView = new BColumnListView(NULL, 139 B_WILL_DRAW | B_FRAME_EVENTS, B_FANCY_BORDER, false); 140 141 float cellWidth = be_plain_font->StringWidth("Either") + 20; 142 // ShortcutsSpec does not seem to translate the string "Either". 143 144 for (int i = 0; i < ShortcutsSpec::NUM_META_COLUMNS; i++) { 145 const char* name = ShortcutsSpec::GetColumnName(i); 146 float headerWidth = be_plain_font->StringWidth(name) + 20; 147 float width = max_c(headerWidth, cellWidth); 148 149 fColumnListView->AddColumn(new PopUpColumn(CreateMetaPopUp(i), name, 150 width, width - 1, width * 1.5, B_TRUNCATE_END, false, true, 1), 151 fColumnListView->CountColumns()); 152 } 153 154 float keyCellWidth = be_plain_font->StringWidth("Caps Lock") + 20; 155 fColumnListView->AddColumn(new PopUpColumn(CreateKeysPopUp(), 156 B_TRANSLATE("Key"), keyCellWidth, keyCellWidth - 10, 157 keyCellWidth + 30, B_TRUNCATE_END), 158 fColumnListView->CountColumns()); 159 BPopUpMenu* popup = new BPopUpMenu(NULL, false); 160 popup->AddItem(new BMenuItem( 161 B_TRANSLATE("(Choose application with file requester)"), NULL)); 162 popup->AddItem(new BMenuItem( 163 B_TRANSLATE("*InsertString \"Your Text Here\""), NULL)); 164 popup->AddItem(new BMenuItem( 165 B_TRANSLATE("*MoveMouse +20 +0"), NULL)); 166 popup->AddItem(new BMenuItem(B_TRANSLATE("*MoveMouseTo 50% 50%"), NULL)); 167 popup->AddItem(new BMenuItem(B_TRANSLATE("*MouseButton 1"), NULL)); 168 popup->AddItem(new BMenuItem( 169 B_TRANSLATE("*LaunchHandler text/html"), NULL)); 170 popup->AddItem(new BMenuItem( 171 B_TRANSLATE("*Multi \"*MoveMouseTo 100% 0\" \"*MouseButton 1\""), 172 NULL)); 173 popup->AddItem(new BMenuItem(B_TRANSLATE("*MouseDown"), NULL)); 174 popup->AddItem(new BMenuItem(B_TRANSLATE("*MouseUp"), NULL)); 175 popup->AddItem(new BMenuItem( 176 B_TRANSLATE("*SendMessage application/x-vnd.Be-TRAK 'Tfnd'"), NULL)); 177 popup->AddItem(new BMenuItem(B_TRANSLATE("*Beep"), NULL)); 178 fColumnListView->AddColumn(new PopUpColumn(popup, B_TRANSLATE("Application"), 179 300.0, 223.0, 324.0, B_TRUNCATE_END, true), 180 fColumnListView->CountColumns()); 181 182 fColumnListView->SetSelectionMessage(new BMessage(HOTKEY_ITEM_SELECTED)); 183 fColumnListView->SetSelectionMode(B_SINGLE_SELECTION_LIST); 184 fColumnListView->SetTarget(this); 185 186 fAddButton = new BButton("add", B_TRANSLATE("Add new shortcut"), 187 new BMessage(ADD_HOTKEY_ITEM)); 188 189 fRemoveButton = new BButton("remove", 190 B_TRANSLATE("Remove selected shortcut"), 191 new BMessage(REMOVE_HOTKEY_ITEM)); 192 fRemoveButton->SetEnabled(false); 193 194 fSaveButton = new BButton("save", B_TRANSLATE("Save & apply"), 195 new BMessage(SAVE_KEYSET)); 196 fSaveButton->SetEnabled(false); 197 198 CenterOnScreen(); 199 200 fColumnListView->ResizeAllColumnsToPreferred(); 201 202 entry_ref windowSettingsRef; 203 if (_GetWindowSettingsFile(&windowSettingsRef)) { 204 // The window settings file is not accepted via B_REFS_RECEIVED; this 205 // is a behind-the-scenes file that the user will never see or 206 // interact with. 207 BFile windowSettingsFile(&windowSettingsRef, B_READ_ONLY); 208 BMessage loadMessage; 209 if (loadMessage.Unflatten(&windowSettingsFile) == B_OK) 210 _LoadWindowSettings(loadMessage); 211 } 212 213 entry_ref keySetRef; 214 if (_GetSettingsFile(&keySetRef)) { 215 BMessage message(B_REFS_RECEIVED); 216 message.AddRef("refs", &keySetRef); 217 message.AddString("startupRef", "please"); 218 PostMessage(&message); 219 // tell ourselves to load this file if it exists 220 } else { 221 _AddNewSpec("/bin/setvolume -t", (B_HID_USAGE_PAGE_CONSUMER << 16) | B_HID_UID_CON_MUTE); 222 _AddNewSpec("/bin/setvolume -i", (B_HID_USAGE_PAGE_CONSUMER << 16) | B_HID_UID_CON_VOLUME_INCREMENT); 223 _AddNewSpec("/bin/setvolume -d", (B_HID_USAGE_PAGE_CONSUMER << 16) | B_HID_UID_CON_VOLUME_DECREMENT); 224 fLastSaved = BEntry(&keySetRef); 225 PostMessage(SAVE_KEYSET); 226 } 227 228 BLayoutBuilder::Group<>(this, B_VERTICAL, 0) 229 .Add(menuBar) 230 .AddGroup(B_VERTICAL) 231 .SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)) 232 .SetInsets(B_USE_WINDOW_SPACING) 233 .Add(fColumnListView) 234 .AddGroup(B_HORIZONTAL) 235 .AddGroup(B_HORIZONTAL) 236 .SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, B_ALIGN_TOP)) 237 .Add(fAddButton) 238 .Add(fRemoveButton) 239 .End() 240 .AddGroup(B_HORIZONTAL) 241 .SetExplicitAlignment(BAlignment(B_ALIGN_RIGHT, B_ALIGN_TOP)) 242 .Add(fSaveButton) 243 .End() 244 .End() 245 .End(); 246 247 Show(); 248 } 249 250 251 ShortcutsWindow::~ShortcutsWindow() 252 { 253 delete fSavePanel; 254 delete fOpenPanel; 255 delete fSelectPanel; 256 be_app->PostMessage(B_QUIT_REQUESTED); 257 } 258 259 260 bool 261 ShortcutsWindow::QuitRequested() 262 { 263 bool result = true; 264 265 if (fKeySetModified) { 266 BAlert* alert = new BAlert(WARNING, 267 B_TRANSLATE("Save changes before closing?"), 268 B_TRANSLATE("Cancel"), B_TRANSLATE("Don't save"), 269 B_TRANSLATE("Save")); 270 alert->SetShortcut(0, B_ESCAPE); 271 alert->SetShortcut(1, 'd'); 272 alert->SetShortcut(2, 's'); 273 switch (alert->Go()) { 274 case 0: 275 result = false; 276 break; 277 278 case 1: 279 result = true; 280 break; 281 282 case 2: 283 // Save: automatically if possible, otherwise go back and open 284 // up the file requester 285 if (fLastSaved.InitCheck() == B_OK) { 286 if (_SaveKeySet(fLastSaved) == false) { 287 BAlert* alert = new BAlert(ERROR, 288 B_TRANSLATE("Shortcuts was unable to save your " 289 "KeySet file!"), 290 B_TRANSLATE("Oh no")); 291 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 292 alert->Go(); 293 result = true; // quit anyway 294 } 295 } else { 296 PostMessage(SAVE_KEYSET); 297 result = false; 298 } 299 break; 300 } 301 } 302 303 if (result) { 304 fColumnListView->DeselectAll(); 305 306 // Save the window position. 307 entry_ref ref; 308 if (_GetWindowSettingsFile(&ref)) { 309 BEntry entry(&ref); 310 _SaveWindowSettings(entry); 311 } 312 } 313 314 return result; 315 } 316 317 318 bool 319 ShortcutsWindow::_GetSettingsFile(entry_ref* eref) 320 { 321 BPath path; 322 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 323 return false; 324 else 325 path.Append(SHORTCUTS_SETTING_FILE_NAME); 326 BEntry entry(path.Path(), true); 327 entry.GetRef(eref); 328 return entry.Exists(); 329 } 330 331 332 // Saves a settings file to (saveEntry). Returns true iff successful. 333 bool 334 ShortcutsWindow::_SaveKeySet(BEntry& saveEntry) 335 { 336 BFile saveTo(&saveEntry, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 337 if (saveTo.InitCheck() != B_OK) 338 return false; 339 340 BMessage saveMessage; 341 for (int i = 0; i < fColumnListView->CountRows(); i++) { 342 BMessage next; 343 if (((ShortcutsSpec*)fColumnListView->RowAt(i))->Archive(&next) 344 == B_OK) { 345 saveMessage.AddMessage("spec", &next); 346 } else 347 printf("Error archiving ShortcutsSpec #%i!\n", i); 348 } 349 350 bool result = (saveMessage.Flatten(&saveTo) == B_OK); 351 352 if (result) { 353 fKeySetModified = false; 354 fSaveButton->SetEnabled(false); 355 } 356 357 return result; 358 } 359 360 361 // Appends new entries from the file specified in the "spec" entry of 362 // (loadMessage). Returns true iff successful. 363 bool 364 ShortcutsWindow::_LoadKeySet(const BMessage& loadMessage) 365 { 366 int i = 0; 367 BMessage message; 368 while (loadMessage.FindMessage("spec", i++, &message) == B_OK) { 369 ShortcutsSpec* spec 370 = (ShortcutsSpec*)ShortcutsSpec::Instantiate(&message); 371 if (spec != NULL) 372 fColumnListView->AddRow(spec); 373 else 374 printf("_LoadKeySet: Error parsing spec!\n"); 375 } 376 377 return true; 378 } 379 380 381 // Gets the filesystem location of the "Shortcuts_window_settings" file. 382 bool 383 ShortcutsWindow::_GetWindowSettingsFile(entry_ref* eref) 384 { 385 BPath path; 386 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 387 return false; 388 else 389 path.Append(WINDOW_SETTINGS_FILE_NAME); 390 391 return BEntry(path.Path(), true).GetRef(eref) == B_OK; 392 } 393 394 395 // Saves the application settings file to (saveEntry). Because this is a 396 // non-essential file, errors are ignored when writing the settings. 397 void 398 ShortcutsWindow::_SaveWindowSettings(BEntry& saveEntry) 399 { 400 BFile saveTo(&saveEntry, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 401 if (saveTo.InitCheck() != B_OK) 402 return; 403 404 BMessage saveMsg; 405 saveMsg.AddRect("window frame", Frame()); 406 407 BMessage columnsState; 408 fColumnListView->SaveState(&columnsState); 409 saveMsg.AddMessage ("columns state", &columnsState); 410 411 saveMsg.Flatten(&saveTo); 412 } 413 414 415 // Loads the application settings file from (loadMessage) and resizes 416 // the interface to match the previously saved settings. Because this 417 // is a non-essential file, errors are ignored when loading the settings. 418 void 419 ShortcutsWindow::_LoadWindowSettings(const BMessage& loadMessage) 420 { 421 BRect frame; 422 if (loadMessage.FindRect("window frame", &frame) == B_OK) { 423 // ensure the frame does not resize below the computed minimum. 424 float width = max_c(Bounds().right, frame.right - frame.left); 425 float height = max_c(Bounds().bottom, frame.bottom - frame.top); 426 ResizeTo(width, height); 427 428 // ensure the frame is not placed outside of the screen. 429 BScreen screen(this); 430 float left = min_c(screen.Frame().right - width, frame.left); 431 float top = min_c(screen.Frame().bottom - height, frame.top); 432 MoveTo(left, top); 433 } 434 435 BMessage columnsStateMessage; 436 if (loadMessage.FindMessage ("columns state", &columnsStateMessage) == B_OK) 437 fColumnListView->LoadState(&columnsStateMessage); 438 } 439 440 441 // Creates a new entry and adds it to the GUI. (defaultCommand) will be the 442 // text in the entry, or NULL if no text is desired. 443 void 444 ShortcutsWindow::_AddNewSpec(const char* defaultCommand, uint32 keyCode) 445 { 446 _MarkKeySetModified(); 447 448 ShortcutsSpec* spec; 449 BRow* curSel = fColumnListView->CurrentSelection(); 450 if (curSel) 451 spec = new ShortcutsSpec(*((ShortcutsSpec*)curSel)); 452 else { 453 spec = new ShortcutsSpec(""); 454 for (int i = 0; i < fColumnListView->CountColumns(); i++) 455 spec->SetField(new BStringField(""), i); 456 } 457 458 fColumnListView->AddRow(spec); 459 fColumnListView->AddToSelection(spec); 460 fColumnListView->ScrollTo(spec); 461 if (defaultCommand) 462 spec->SetCommand(defaultCommand); 463 if (keyCode != 0) { 464 spec->ProcessColumnTextString(ShortcutsSpec::KEY_COLUMN_INDEX, 465 GetFallbackKeyName(keyCode).String()); 466 } 467 } 468 469 470 void 471 ShortcutsWindow::MessageReceived(BMessage* message) 472 { 473 switch (message->what) { 474 case OPEN_KEYSET: 475 case APPEND_KEYSET: 476 fLastOpenWasAppend = (message->what == APPEND_KEYSET); 477 if (fOpenPanel) 478 fOpenPanel->Show(); 479 else { 480 BMessenger messenger(this); 481 fOpenPanel = new BFilePanel(B_OPEN_PANEL, &messenger, NULL, 482 0, false); 483 fOpenPanel->Show(); 484 } 485 fOpenPanel->SetButtonLabel(B_DEFAULT_BUTTON, fLastOpenWasAppend ? 486 B_TRANSLATE("Append") : B_TRANSLATE("Open")); 487 break; 488 489 // send a message to myself, to get me to reload the settings file 490 case REVERT_KEYSET: 491 { 492 fLastOpenWasAppend = false; 493 BMessage reload(B_REFS_RECEIVED); 494 entry_ref eref; 495 _GetSettingsFile(&eref); 496 reload.AddRef("refs", &eref); 497 reload.AddString("startupRef", "yeah"); 498 PostMessage(&reload); 499 break; 500 } 501 502 // respond to drag-and-drop messages here 503 case B_SIMPLE_DATA: 504 { 505 int i = 0; 506 507 entry_ref ref; 508 while (message->FindRef("refs", i++, &ref) == B_OK) { 509 BEntry entry(&ref); 510 if (entry.InitCheck() == B_OK) { 511 BPath path(&entry); 512 513 if (path.InitCheck() == B_OK) { 514 // Add a new item with the given path. 515 BString str(path.Path()); 516 DoStandardEscapes(str); 517 _AddNewSpec(str.String()); 518 } 519 } 520 } 521 break; 522 } 523 524 // respond to FileRequester's messages here 525 case B_REFS_RECEIVED: 526 { 527 // Find file ref 528 entry_ref ref; 529 bool isStartMsg = message->HasString("startupRef"); 530 if (message->FindRef("refs", &ref) == B_OK) { 531 // load the file into (fileMsg) 532 BMessage fileMsg; 533 { 534 BFile file(&ref, B_READ_ONLY); 535 if ((file.InitCheck() != B_OK) 536 || (fileMsg.Unflatten(&file) != B_OK)) { 537 if (isStartMsg) { 538 // use this to save to anyway 539 fLastSaved = BEntry(&ref); 540 break; 541 } else { 542 BAlert* alert = new BAlert(ERROR, 543 B_TRANSLATE("Shortcuts was couldn't open your " 544 "KeySet file!"), B_TRANSLATE("OK")); 545 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 546 alert->Go(NULL); 547 break; 548 } 549 } 550 } 551 552 if (fLastOpenWasAppend == false) { 553 // Clear the menu... 554 while (fColumnListView->CountRows()) { 555 ShortcutsSpec* row = 556 static_cast<ShortcutsSpec*>(fColumnListView->RowAt(0)); 557 fColumnListView->RemoveRow(row); 558 delete row; 559 } 560 } 561 562 if (_LoadKeySet(fileMsg)) { 563 if (isStartMsg) fLastSaved = BEntry(&ref); 564 fSaveButton->SetEnabled(isStartMsg == false); 565 566 // If we just loaded in the Shortcuts settings file, then 567 // no need to tell the user to save on exit. 568 entry_ref eref; 569 _GetSettingsFile(&eref); 570 if (ref == eref) fKeySetModified = false; 571 } else { 572 BAlert* alert = new BAlert(ERROR, 573 B_TRANSLATE("Shortcuts was unable to parse your " 574 "KeySet file!"), B_TRANSLATE("OK")); 575 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 576 alert->Go(NULL); 577 break; 578 } 579 } 580 break; 581 } 582 583 // these messages come from the pop-up menu of the Applications column 584 case SELECT_APPLICATION: 585 { 586 ShortcutsSpec* row = 587 static_cast<ShortcutsSpec*>(fColumnListView->CurrentSelection()); 588 if (row != NULL) { 589 entry_ref aref; 590 if (message->FindRef("refs", &aref) == B_OK) { 591 BEntry ent(&aref); 592 if (ent.InitCheck() == B_OK) { 593 BPath path; 594 if ((ent.GetPath(&path) == B_OK) 595 && (row-> 596 ProcessColumnTextString(ShortcutsSpec::STRING_COLUMN_INDEX, 597 path.Path()))) { 598 _MarkKeySetModified(); 599 } 600 } 601 } 602 } 603 break; 604 } 605 606 case SAVE_KEYSET: 607 { 608 bool showSaveError = false; 609 610 const char* name; 611 entry_ref entry; 612 if ((message->FindString("name", &name) == B_OK) 613 && (message->FindRef("directory", &entry) == B_OK)) { 614 BDirectory dir(&entry); 615 BEntry saveTo(&dir, name, true); 616 showSaveError = ((saveTo.InitCheck() != B_OK) 617 || (_SaveKeySet(saveTo) == false)); 618 } else if (fLastSaved.InitCheck() == B_OK) { 619 // We've saved this before, save over previous file. 620 showSaveError = (_SaveKeySet(fLastSaved) == false); 621 } else 622 PostMessage(SAVE_KEYSET_AS); 623 // open the save requester... 624 625 if (showSaveError) { 626 BAlert* alert = new BAlert(ERROR, 627 B_TRANSLATE("Shortcuts wasn't able to save your keyset."), 628 B_TRANSLATE("OK")); 629 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 630 alert->Go(NULL); 631 } 632 break; 633 } 634 635 case SAVE_KEYSET_AS: 636 { 637 if (fSavePanel) 638 fSavePanel->Show(); 639 else { 640 BMessage message(SAVE_KEYSET); 641 BMessenger messenger(this); 642 fSavePanel = new BFilePanel(B_SAVE_PANEL, &messenger, NULL, 0, 643 false, &message); 644 fSavePanel->Show(); 645 } 646 break; 647 } 648 649 case ADD_HOTKEY_ITEM: 650 _AddNewSpec(NULL); 651 break; 652 653 case REMOVE_HOTKEY_ITEM: 654 { 655 BRow* item = fColumnListView->CurrentSelection(); 656 if (item) { 657 int index = fColumnListView->IndexOf(item); 658 fColumnListView->RemoveRow(item); 659 delete item; 660 _MarkKeySetModified(); 661 662 // Rules for new selection: If there is an item at (index), 663 // select it. Otherwise, if there is an item at (index-1), 664 // select it. Otherwise, select nothing. 665 int num = fColumnListView->CountRows(); 666 if (num > 0) { 667 if (index < num) 668 fColumnListView->AddToSelection( 669 fColumnListView->RowAt(index)); 670 else { 671 if (index > 0) 672 index--; 673 if (index < num) 674 fColumnListView->AddToSelection( 675 fColumnListView->RowAt(index)); 676 } 677 } 678 } 679 break; 680 } 681 682 // Received when the user clicks on the ColumnListView 683 case HOTKEY_ITEM_SELECTED: 684 { 685 if (fColumnListView->CountRows() > 0) 686 fRemoveButton->SetEnabled(true); 687 else 688 fRemoveButton->SetEnabled(false); 689 break; 690 } 691 692 // Received when an entry is to be modified in response to GUI activity 693 case HOTKEY_ITEM_MODIFIED: 694 { 695 int32 row, column; 696 697 if ((message->FindInt32("row", &row) == B_OK) 698 && (message->FindInt32("column", &column) == B_OK)) { 699 int32 key; 700 const char* bytes; 701 702 if (row >= 0) { 703 ShortcutsSpec* item = (ShortcutsSpec*) 704 fColumnListView->RowAt(row); 705 bool repaintNeeded = false; // default 706 707 if (message->HasInt32("mouseClick")) { 708 repaintNeeded = item->ProcessColumnMouseClick(column); 709 } else if ((message->FindString("bytes", &bytes) == B_OK) 710 && (message->FindInt32("key", &key) == B_OK)) { 711 repaintNeeded = item->ProcessColumnKeyStroke(column, 712 bytes, key); 713 } else if (message->FindInt32("unmappedkey", &key) == 714 B_OK) { 715 repaintNeeded = ((column == item->KEY_COLUMN_INDEX) 716 && ((key > 0xFF) || (GetKeyName(key) != NULL)) 717 && (item->ProcessColumnKeyStroke(column, NULL, 718 key))); 719 } else if (message->FindString("text", &bytes) == B_OK) { 720 if ((bytes[0] == '(')&&(bytes[1] == 'C')) { 721 if (fSelectPanel) 722 fSelectPanel->Show(); 723 else { 724 BMessage message(SELECT_APPLICATION); 725 BMessenger m(this); 726 fSelectPanel = new BFilePanel(B_OPEN_PANEL, &m, 727 NULL, 0, false, &message); 728 fSelectPanel->Show(); 729 } 730 fSelectPanel->SetButtonLabel(B_DEFAULT_BUTTON, 731 B_TRANSLATE("Select")); 732 } else 733 repaintNeeded = item->ProcessColumnTextString( 734 column, bytes); 735 } 736 737 if (repaintNeeded) { 738 fColumnListView->Invalidate(row); 739 _MarkKeySetModified(); 740 } 741 } 742 } 743 break; 744 } 745 746 default: 747 BWindow::MessageReceived(message); 748 break; 749 } 750 } 751 752 753 void 754 ShortcutsWindow::_MarkKeySetModified() 755 { 756 if (fKeySetModified == false) { 757 fKeySetModified = true; 758 fSaveButton->SetEnabled(true); 759 } 760 } 761 762 763 void 764 ShortcutsWindow::Quit() 765 { 766 BWindow::Quit(); 767 } 768 769 770 void 771 ShortcutsWindow::DispatchMessage(BMessage* message, BHandler* handler) 772 { 773 switch (message->what) { 774 case B_SIMPLE_DATA: 775 MessageReceived(message); 776 break; 777 778 case B_COPY: 779 case B_CUT: 780 if (be_clipboard->Lock()) { 781 ShortcutsSpec* row = 782 static_cast<ShortcutsSpec*>(fColumnListView->CurrentSelection()); 783 if (row) { 784 BMessage* data = be_clipboard->Data(); 785 data->RemoveName("text/plain"); 786 data->AddData("text/plain", B_MIME_TYPE, 787 row->GetCellText(ShortcutsSpec::STRING_COLUMN_INDEX), 788 strlen(row->GetCellText(ShortcutsSpec::STRING_COLUMN_INDEX))); 789 be_clipboard->Commit(); 790 791 if (message->what == B_CUT) { 792 row->ProcessColumnTextString( 793 ShortcutsSpec::STRING_COLUMN_INDEX, ""); 794 _MarkKeySetModified(); 795 } 796 } 797 be_clipboard->Unlock(); 798 } 799 break; 800 801 case B_PASTE: 802 if (be_clipboard->Lock()) { 803 BMessage* data = be_clipboard->Data(); 804 const char* text; 805 ssize_t textLen; 806 if (data->FindData("text/plain", B_MIME_TYPE, (const void**) 807 &text, &textLen) == B_OK) { 808 ShortcutsSpec* row = 809 static_cast<ShortcutsSpec*>(fColumnListView->CurrentSelection()); 810 if (row) { 811 for (ssize_t i = 0; i < textLen; i++) { 812 char buf[2] = {text[i], 0x00}; 813 row->ProcessColumnKeyStroke( 814 ShortcutsSpec::STRING_COLUMN_INDEX, buf, 0); 815 } 816 } 817 _MarkKeySetModified(); 818 } 819 be_clipboard->Unlock(); 820 } 821 break; 822 823 case B_KEY_DOWN: 824 case B_UNMAPPED_KEY_DOWN: 825 { 826 ShortcutsSpec* selected; 827 int32 modifiers = message->GetInt32("modifiers", 0); 828 // These should not block key detection here: 829 modifiers &= ~(B_CAPS_LOCK | B_SCROLL_LOCK | B_NUM_LOCK); 830 if (modifiers != 0) 831 BWindow::DispatchMessage(message, handler); 832 else if (handler == fColumnListView 833 && (selected = 834 static_cast<ShortcutsSpec*>(fColumnListView->CurrentSelection()))) { 835 uint32 keyCode = message->GetInt32("key", 0); 836 const char* keyName = GetKeyName(keyCode); 837 selected->ProcessColumnTextString( 838 ShortcutsSpec::KEY_COLUMN_INDEX, 839 keyName != NULL ? keyName : GetFallbackKeyName(keyCode).String()); 840 _MarkKeySetModified(); 841 } 842 break; 843 } 844 default: 845 BWindow::DispatchMessage(message, handler); 846 break; 847 } 848 } 849