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