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