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*
CreateMetaPopUp(int column)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*
CreateKeysPopUp()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
ShortcutsWindow()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
~ShortcutsWindow()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
QuitRequested()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
_GetSettingsFile(entry_ref * eref)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
_SaveKeySet(BEntry & saveEntry)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
_LoadKeySet(const BMessage & loadMessage)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
_GetWindowSettingsFile(entry_ref * eref)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
_SaveWindowSettings(BEntry & saveEntry)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
_LoadWindowSettings(const BMessage & loadMessage)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
_AddNewSpec(const char * defaultCommand,uint32 keyCode)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
MessageReceived(BMessage * message)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
_MarkKeySetModified()754 ShortcutsWindow::_MarkKeySetModified()
755 {
756 if (fKeySetModified == false) {
757 fKeySetModified = true;
758 fSaveButton->SetEnabled(true);
759 }
760 }
761
762
763 void
Quit()764 ShortcutsWindow::Quit()
765 {
766 BWindow::Quit();
767 }
768
769
770 void
DispatchMessage(BMessage * message,BHandler * handler)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