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