xref: /haiku/src/preferences/shortcuts/ShortcutsWindow.cpp (revision 83b1a68c52ba3e0e8796282759f694b7fdddf06d)
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