xref: /haiku/src/preferences/shortcuts/ShortcutsWindow.cpp (revision 820dca4df6c7bf955c46e8f6521b9408f50b2900)
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("Save changes before closing?"),
287 			B_TRANSLATE("Cancel"), B_TRANSLATE("Don't save"),
288 			B_TRANSLATE("Save"));
289 		alert->SetShortcut(0, B_ESCAPE);
290 		alert->SetShortcut(1, 'd');
291 		alert->SetShortcut(2, 's');
292 		switch(alert->Go()) {
293 			case 0:
294 				ret = false;
295 				break;
296 
297 			case 1:
298 				ret = true;
299 				break;
300 
301 			case 2:
302 				// Save: automatically if possible, otherwise go back and open
303 				// up the file requester
304 				if (fLastSaved.InitCheck() == B_OK) {
305 					if (_SaveKeySet(fLastSaved) == false) {
306 						BAlert* alert = new BAlert(ERROR,
307 							B_TRANSLATE("Shortcuts was unable to save your "
308 								"KeySet file!"),
309 							B_TRANSLATE("Oh no"));
310 						alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
311 						alert->Go();
312 						ret = true; //quit anyway
313 					}
314 				} else {
315 					PostMessage(SAVE_KEYSET);
316 					ret = false;
317 				}
318 				break;
319 		}
320 	}
321 
322 	if (ret) {
323 		fColumnListView->DeselectAll();
324 
325 		// Save the window position.
326 		entry_ref ref;
327 		if (_GetWindowSettingsFile(&ref)) {
328 			BEntry entry(&ref);
329 			_SaveWindowSettings(entry);
330 		}
331 	}
332 
333 	return ret;
334 }
335 
336 
337 bool
338 ShortcutsWindow::_GetSettingsFile(entry_ref* eref)
339 {
340 	BPath path;
341 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
342 		return false;
343 	else
344 		path.Append(SHORTCUTS_SETTING_FILE_NAME);
345 
346 	if (BEntry(path.Path(), true).GetRef(eref) == B_OK)
347 		return true;
348 	else
349 		return false;
350 }
351 
352 
353 // Saves a settings file to (saveEntry). Returns true iff successful.
354 bool
355 ShortcutsWindow::_SaveKeySet(BEntry& saveEntry)
356 {
357 	BFile saveTo(&saveEntry, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
358 	if (saveTo.InitCheck() != B_OK)
359 		return false;
360 
361 	BMessage saveMsg;
362 	for (int i = 0; i < fColumnListView->CountItems(); i++) {
363 		BMessage next;
364 		if (((ShortcutsSpec*)fColumnListView->ItemAt(i))->Archive(&next)
365 			== B_OK)
366 			saveMsg.AddMessage("spec", &next);
367 		else
368 			printf("Error archiving ShortcutsSpec #%i!\n", i);
369 	}
370 
371 	bool ret = (saveMsg.Flatten(&saveTo) == B_OK);
372 
373 	if (ret) {
374 		fKeySetModified = false;
375 		fSaveButton->SetEnabled(false);
376 	}
377 
378 	return ret;
379 }
380 
381 
382 // Appends new entries from the file specified in the "spec" entry of
383 // (loadMsg). Returns true iff successful.
384 bool
385 ShortcutsWindow::_LoadKeySet(const BMessage& loadMsg)
386 {
387 	int i = 0;
388 	BMessage msg;
389 	while (loadMsg.FindMessage("spec", i++, &msg) == B_OK) {
390 		ShortcutsSpec* spec = (ShortcutsSpec*)ShortcutsSpec::Instantiate(&msg);
391 		if (spec != NULL)
392 			fColumnListView->AddItem(spec);
393 		else
394 			printf("_LoadKeySet: Error parsing spec!\n");
395 	}
396 	return true;
397 }
398 
399 
400 // Gets the filesystem location of the "Shortcuts_window_settings" file.
401 bool
402 ShortcutsWindow::_GetWindowSettingsFile(entry_ref* eref)
403 {
404 	BPath path;
405 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
406 		return false;
407 	else
408 		path.Append(WINDOW_SETTINGS_FILE_NAME);
409 
410 	return BEntry(path.Path(), true).GetRef(eref) == B_OK;
411 }
412 
413 
414 // Saves the application settings file to (saveEntry).  Because this is a
415 // non-essential file, errors are ignored when writing the settings.
416 void
417 ShortcutsWindow::_SaveWindowSettings(BEntry& saveEntry)
418 {
419 	BFile saveTo(&saveEntry, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
420 	if (saveTo.InitCheck() != B_OK)
421 		return;
422 
423 	BMessage saveMsg;
424 	saveMsg.AddRect("window frame", Frame());
425 
426 	for (int i = 0; i < fColumnListView->CountColumns(); i++) {
427 		CLVColumn* column = fColumnListView->ColumnAt(i);
428 		saveMsg.AddFloat("column width", column->Width());
429 	}
430 
431 	saveMsg.Flatten(&saveTo);
432 }
433 
434 
435 // Loads the application settings file from (loadMsg) and resizes the interface
436 // to match the previously saved settings.  Because this is a non-essential
437 // file, errors are ignored when loading the settings.
438 void
439 ShortcutsWindow::_LoadWindowSettings(const BMessage& loadMsg)
440 {
441 	BRect frame;
442 	if (loadMsg.FindRect("window frame", &frame) == B_OK) {
443 		// Ensure the frame does not resize below the computed minimum.
444 		float width = max_c(Bounds().right, frame.right - frame.left);
445 		float height = max_c(Bounds().bottom, frame.bottom - frame.top);
446 		ResizeTo(width, height);
447 
448 		// Ensure the frame is not placed outside of the screen.
449 		BScreen screen(this);
450 		float left = min_c(screen.Frame().right - width, frame.left);
451 		float top = min_c(screen.Frame().bottom - height, frame.top);
452 		MoveTo(left, top);
453 	}
454 
455 	for (int i = 0; i < fColumnListView->CountColumns(); i++) {
456 		CLVColumn* column = fColumnListView->ColumnAt(i);
457 		float columnWidth;
458 		if (loadMsg.FindFloat("column width", i, &columnWidth) == B_OK)
459 			column->SetWidth(max_c(column->Width(), columnWidth));
460 	}
461 }
462 
463 
464 // Creates a new entry and adds it to the GUI. (defaultCommand) will be the
465 // text in the entry, or NULL if no text is desired.
466 void
467 ShortcutsWindow::_AddNewSpec(const char* defaultCommand)
468 {
469 	_MarkKeySetModified();
470 
471 	ShortcutsSpec* spec;
472 	int curSel = fColumnListView->CurrentSelection();
473 	if (curSel >= 0) {
474 		spec = new ShortcutsSpec(*((ShortcutsSpec*)
475 			fColumnListView->ItemAt(curSel)));
476 
477 		if (defaultCommand)
478 			spec->SetCommand(defaultCommand);
479 	} else
480 		spec = new ShortcutsSpec(defaultCommand ? defaultCommand : "");
481 
482 	fColumnListView->AddItem(spec);
483 	fColumnListView->Select(fColumnListView->CountItems() - 1);
484 	fColumnListView->ScrollToSelection();
485 }
486 
487 
488 void
489 ShortcutsWindow::MessageReceived(BMessage* msg)
490 {
491 	switch(msg->what) {
492 		case OPEN_KEYSET:
493 		case APPEND_KEYSET:
494 			fLastOpenWasAppend = (msg->what == APPEND_KEYSET);
495 			if (fOpenPanel)
496 				fOpenPanel->Show();
497 			else {
498 				BMessenger m(this);
499 				fOpenPanel = new BFilePanel(B_OPEN_PANEL, &m, NULL, 0, false);
500 				fOpenPanel->Show();
501 			}
502 			fOpenPanel->SetButtonLabel(B_DEFAULT_BUTTON, fLastOpenWasAppend ?
503 				B_TRANSLATE("Append") : B_TRANSLATE("Open"));
504 			break;
505 
506 		case REVERT_KEYSET:
507 		{
508 			// Send a message to myself, to get me to reload the settings file
509 			fLastOpenWasAppend = false;
510 			BMessage reload(B_REFS_RECEIVED);
511 			entry_ref eref;
512 			_GetSettingsFile(&eref);
513 			reload.AddRef("refs", &eref);
514 			reload.AddString("startupRef", "yeah");
515 			PostMessage(&reload);
516 			break;
517 		}
518 
519 		// Respond to drag-and-drop messages here
520 		case B_SIMPLE_DATA:
521 		{
522 			int i = 0;
523 
524 			entry_ref ref;
525 			while (msg->FindRef("refs", i++, &ref) == B_OK) {
526 				BEntry entry(&ref);
527 				if (entry.InitCheck() == B_OK) {
528 					BPath path(&entry);
529 
530 					if (path.InitCheck() == B_OK) {
531 						// Add a new item with the given path.
532 						BString str(path.Path());
533 						DoStandardEscapes(str);
534 						_AddNewSpec(str.String());
535 					}
536 				}
537 			}
538 			break;
539 		}
540 
541 		// Respond to FileRequester's messages here
542 		case B_REFS_RECEIVED:
543 		{
544 			// Find file ref
545 			entry_ref ref;
546 			bool isStartMsg = msg->HasString("startupRef");
547 			if (msg->FindRef("refs", &ref) == B_OK) {
548 				// load the file into (fileMsg)
549 				BMessage fileMsg;
550 				{
551 					BFile file(&ref, B_READ_ONLY);
552 					if ((file.InitCheck() != B_OK)
553 						|| (fileMsg.Unflatten(&file) != B_OK)) {
554 						if (isStartMsg) {
555 							// use this to save to anyway
556 							fLastSaved = BEntry(&ref);
557 							break;
558 						} else {
559 							BAlert* alert = new BAlert(ERROR,
560 								B_TRANSLATE("Shortcuts was couldn't open your "
561 								"KeySet file!"), B_TRANSLATE("OK"));
562 							alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
563 							alert->Go(NULL);
564 							break;
565 						}
566 					}
567 				}
568 
569 				if (fLastOpenWasAppend == false) {
570 					// Clear the menu...
571 					while (ShortcutsSpec* item
572 						= ((ShortcutsSpec*)fColumnListView->RemoveItem(0L))) {
573 						delete item;
574 					}
575 				}
576 
577 				if (_LoadKeySet(fileMsg)) {
578 					if (isStartMsg) fLastSaved = BEntry(&ref);
579 					fSaveButton->SetEnabled(isStartMsg == false);
580 
581 					// If we just loaded in the Shortcuts settings file, then
582 					// no need to tell the user to save on exit.
583 					entry_ref eref;
584 					_GetSettingsFile(&eref);
585 					if (ref == eref) fKeySetModified = false;
586 				} else {
587 					BAlert* alert = new BAlert(ERROR,
588 						B_TRANSLATE("Shortcuts was unable to parse your "
589 						"KeySet file!"), B_TRANSLATE("OK"));
590 					alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
591 					alert->Go(NULL);
592 					break;
593 				}
594 			}
595 			break;
596 		}
597 
598 		// These messages come from the pop-up menu of the Applications column
599 		case SELECT_APPLICATION:
600 		{
601 			int csel = fColumnListView->CurrentSelection();
602 			if (csel >= 0) {
603 				entry_ref aref;
604 				if (msg->FindRef("refs", &aref) == B_OK) {
605 					BEntry ent(&aref);
606 					if (ent.InitCheck() == B_OK) {
607 						BPath path;
608 						if ((ent.GetPath(&path) == B_OK)
609 							&& (((ShortcutsSpec *)
610 							fColumnListView->ItemAt(csel))->
611 							ProcessColumnTextString(ShortcutsSpec::
612 							STRING_COLUMN_INDEX, path.Path()))) {
613 
614 							fColumnListView->InvalidateItem(csel);
615 							_MarkKeySetModified();
616 						}
617 					}
618 				}
619 			}
620 			break;
621 		}
622 
623 		case SAVE_KEYSET:
624 		{
625 			bool showSaveError = false;
626 
627 			const char * name;
628 			entry_ref entry;
629 			if ((msg->FindString("name", &name) == B_OK)
630 				&& (msg->FindRef("directory", &entry) == B_OK)) {
631 				BDirectory dir(&entry);
632 				BEntry saveTo(&dir, name, true);
633 				showSaveError = ((saveTo.InitCheck() != B_OK)
634 				|| (_SaveKeySet(saveTo) == false));
635 			} else if (fLastSaved.InitCheck() == B_OK) {
636 				// We've saved this before, save over previous file.
637 				showSaveError = (_SaveKeySet(fLastSaved) == false);
638 			} else PostMessage(SAVE_KEYSET_AS); // open the save requester...
639 
640 			if (showSaveError) {
641 				BAlert* alert = new BAlert(ERROR,
642 					B_TRANSLATE("Shortcuts wasn't able to save your keyset."),
643 					B_TRANSLATE("OK"));
644 				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
645 				alert->Go(NULL);
646 			}
647 			break;
648 		}
649 
650 		case SAVE_KEYSET_AS:
651 		{
652 			if (fSavePanel)
653 				fSavePanel->Show();
654 			else {
655 				BMessage msg(SAVE_KEYSET);
656 				BMessenger messenger(this);
657 				fSavePanel = new BFilePanel(B_SAVE_PANEL, &messenger, NULL, 0,
658 					false, &msg);
659 				fSavePanel->Show();
660 			}
661 			break;
662 		}
663 
664 		case ADD_HOTKEY_ITEM:
665 			_AddNewSpec(NULL);
666 			break;
667 
668 		case REMOVE_HOTKEY_ITEM:
669 		{
670 			int index = fColumnListView->CurrentSelection();
671 			if (index >= 0) {
672 				CLVListItem* item = (CLVListItem*)
673 					fColumnListView->ItemAt(index);
674 				fColumnListView->RemoveItem(index);
675 				delete item;
676 				_MarkKeySetModified();
677 
678 				// Rules for new selection: If there is an item at (index),
679 				// select it. Otherwise, if there is an item at (index-1),
680 				// select it. Otherwise, select nothing.
681 				int num = fColumnListView->CountItems();
682 				if (num > 0) {
683 					if (index < num)
684 						fColumnListView->Select(index);
685 					else {
686 						if (index > 0)
687 							index--;
688 						if (index < num)
689 							fColumnListView->Select(index);
690 					}
691 				}
692 			}
693 			break;
694 		}
695 
696 		// Received when the user clicks on the ColumnListView
697 		case HOTKEY_ITEM_SELECTED:
698 		{
699 			int32 index = -1;
700 			msg->FindInt32("index", &index);
701 			bool validItem = (index >= 0);
702 			fRemoveButton->SetEnabled(validItem);
703 			break;
704 		}
705 
706 		// Received when an entry is to be modified in response to GUI activity
707 		case HOTKEY_ITEM_MODIFIED:
708 		{
709 			int32 row, column;
710 
711 			if ((msg->FindInt32("row", &row) == B_OK)
712 				&& (msg->FindInt32("column", &column) == B_OK)) {
713 				int32 key;
714 				const char* bytes;
715 
716 				if (row >= 0) {
717 					ShortcutsSpec* item = (ShortcutsSpec*)
718 						fColumnListView->ItemAt(row);
719 					bool repaintNeeded = false; // default
720 
721 					if (msg->HasInt32("mouseClick")) {
722 						repaintNeeded = item->ProcessColumnMouseClick(column);
723 					} else if ((msg->FindString("bytes", &bytes) == B_OK)
724 						&& (msg->FindInt32("key", &key) == B_OK)) {
725 						repaintNeeded = item->ProcessColumnKeyStroke(column,
726 							bytes, key);
727 					} else if (msg->FindInt32("unmappedkey", &key) ==
728 						B_OK) {
729 						repaintNeeded = ((column == item->KEY_COLUMN_INDEX)
730 							&& ((key > 0xFF) || (GetKeyName(key) != NULL))
731 							&& (item->ProcessColumnKeyStroke(column, NULL,
732 							key)));
733 					} else if (msg->FindString("text", &bytes) == B_OK) {
734 						if ((bytes[0] == '(')&&(bytes[1] == 'C')) {
735 							if (fSelectPanel)
736 								fSelectPanel->Show();
737 							else {
738 								BMessage msg(SELECT_APPLICATION);
739 								BMessenger m(this);
740 								fSelectPanel = new BFilePanel(B_OPEN_PANEL, &m,
741 									NULL, 0, false, &msg);
742 								fSelectPanel->Show();
743 							}
744 							fSelectPanel->SetButtonLabel(B_DEFAULT_BUTTON,
745 								B_TRANSLATE("Select"));
746 						} else {
747 							repaintNeeded = item->ProcessColumnTextString(
748 								column, bytes);
749 						}
750 					}
751 
752 					if (repaintNeeded) {
753 						fColumnListView->InvalidateItem(row);
754 						_MarkKeySetModified();
755 					}
756 				}
757 			}
758 			break;
759 		}
760 
761 		default:
762 			BWindow::MessageReceived(msg);
763 			break;
764 	}
765 }
766 
767 
768 void
769 ShortcutsWindow::_MarkKeySetModified()
770 {
771 	if (fKeySetModified == false) {
772 		fKeySetModified = true;
773 		fSaveButton->SetEnabled(true);
774 	}
775 }
776 
777 
778 void
779 ShortcutsWindow::Quit()
780 {
781 	for (int i = fColumnListView->CountItems() - 1; i >= 0; i--)
782 		delete (ShortcutsSpec*)fColumnListView->ItemAt(i);
783 
784 	fColumnListView->MakeEmpty();
785 	BWindow::Quit();
786 }
787 
788 
789 void
790 ShortcutsWindow::DispatchMessage(BMessage* msg, BHandler* handler)
791 {
792 	switch(msg->what) {
793 		case B_COPY:
794 		case B_CUT:
795 			if (be_clipboard->Lock()) {
796 				int32 row = fColumnListView->CurrentSelection();
797 				int32 column = fColumnListView->GetSelectedColumn();
798 				if ((row >= 0)
799 					&& (column == ShortcutsSpec::STRING_COLUMN_INDEX)) {
800 					ShortcutsSpec* spec = (ShortcutsSpec*)
801 						fColumnListView->ItemAt(row);
802 					if (spec) {
803 						BMessage* data = be_clipboard->Data();
804 						data->RemoveName("text/plain");
805 						data->AddData("text/plain", B_MIME_TYPE,
806 							spec->GetCellText(column),
807 							strlen(spec->GetCellText(column)));
808 						be_clipboard->Commit();
809 
810 						if (msg->what == B_CUT) {
811 							spec->ProcessColumnTextString(column, "");
812 							_MarkKeySetModified();
813 							fColumnListView->InvalidateItem(row);
814 						}
815 					}
816 				}
817 				be_clipboard->Unlock();
818 			}
819 			break;
820 
821 		case B_PASTE:
822 			if (be_clipboard->Lock()) {
823 				BMessage* data = be_clipboard->Data();
824 				const char* text;
825 				ssize_t textLen;
826 				if (data->FindData("text/plain", B_MIME_TYPE, (const void**)
827 					&text, &textLen) == B_OK) {
828 					int32 row = fColumnListView->CurrentSelection();
829 					int32 column = fColumnListView->GetSelectedColumn();
830 					if ((row >= 0)
831 						&& (column == ShortcutsSpec::STRING_COLUMN_INDEX)) {
832 						ShortcutsSpec* spec = (ShortcutsSpec*)
833 							fColumnListView->ItemAt(row);
834 						if (spec) {
835 							for (ssize_t i = 0; i < textLen; i++) {
836 								char buf[2] = {text[i], 0x00};
837 								spec->ProcessColumnKeyStroke(column, buf, 0);
838 							}
839 						}
840 						fColumnListView->InvalidateItem(row);
841 						_MarkKeySetModified();
842 					}
843 				}
844 				be_clipboard->Unlock();
845 			}
846 			break;
847 
848 		default:
849 			BWindow::DispatchMessage(msg, handler);
850 			break;
851 	}
852 }
853 
854