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