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