xref: /haiku/src/preferences/shortcuts/ShortcutsWindow.cpp (revision 1a76488fc88584bf66b9751d7fb9b6527ac20d87)
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 -m", (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