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