xref: /haiku/src/preferences/shortcuts/ShortcutsWindow.cpp (revision a5a3b2d9a3d95cbae71eaf371708c73a1780ac0d)
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 	float spacing = be_control_look->DefaultItemSpacing();
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 	BRect tableBounds = Bounds();
139 	tableBounds.top = menuBar->Bounds().bottom + 1;
140 	tableBounds.right -= B_V_SCROLL_BAR_WIDTH;
141 	tableBounds.bottom -= B_H_SCROLL_BAR_HEIGHT;
142 
143 	fColumnListView = new BColumnListView(NULL,
144 		B_WILL_DRAW | B_FRAME_EVENTS, B_FANCY_BORDER);
145 
146 	float cellWidth = be_plain_font->StringWidth("Either") + 20;
147 		// ShortcutsSpec does not seem to translate the string "Either".
148 
149 	for (int i = 0; i < ShortcutsSpec::NUM_META_COLUMNS; i++) {
150 		const char* name = ShortcutsSpec::GetColumnName(i);
151 		float headerWidth = be_plain_font->StringWidth(name) + 20;
152 		float width = max_c(headerWidth, cellWidth);
153 
154 		fColumnListView->AddColumn(new PopUpColumn(CreateMetaPopUp(i), name,
155 				width, width - 1, width * 1.5, B_TRUNCATE_END, false, true, 1),
156 			fColumnListView->CountColumns());
157 	}
158 
159 	float keyCellWidth = be_plain_font->StringWidth("Caps Lock") + 20;
160 	fColumnListView->AddColumn(new PopUpColumn(CreateKeysPopUp(),
161 			B_TRANSLATE("Key"), keyCellWidth, keyCellWidth - 10,
162 			keyCellWidth + 30, B_TRUNCATE_END),
163 		fColumnListView->CountColumns());
164 	BPopUpMenu* popup = new BPopUpMenu(NULL, false);
165 	popup->AddItem(new BMenuItem(
166 		B_TRANSLATE("(Choose application with file requester)"), NULL));
167 	popup->AddItem(new BMenuItem(
168 		B_TRANSLATE("*InsertString \"Your Text Here\""), NULL));
169 	popup->AddItem(new BMenuItem(
170 		B_TRANSLATE("*MoveMouse +20 +0"), NULL));
171 	popup->AddItem(new BMenuItem(B_TRANSLATE("*MoveMouseTo 50% 50%"), NULL));
172 	popup->AddItem(new BMenuItem(B_TRANSLATE("*MouseButton 1"), NULL));
173 	popup->AddItem(new BMenuItem(
174 		B_TRANSLATE("*LaunchHandler text/html"), NULL));
175 	popup->AddItem(new BMenuItem(
176 		B_TRANSLATE("*Multi \"*MoveMouseTo 100% 0\" \"*MouseButton 1\""),
177 		NULL));
178 	popup->AddItem(new BMenuItem(B_TRANSLATE("*MouseDown"), NULL));
179 	popup->AddItem(new BMenuItem(B_TRANSLATE("*MouseUp"), NULL));
180 	popup->AddItem(new BMenuItem(
181 		B_TRANSLATE("*SendMessage application/x-vnd.Be-TRAK 'Tfnd'"), NULL));
182 	popup->AddItem(new BMenuItem(B_TRANSLATE("*Beep"), NULL));
183 	fColumnListView->AddColumn(new PopUpColumn(popup, B_TRANSLATE("Application"),
184 			300.0, 223.0, 324.0, B_TRUNCATE_END, true),
185 		fColumnListView->CountColumns());
186 
187 	fColumnListView->SetSelectionMessage(new BMessage(HOTKEY_ITEM_SELECTED));
188 	fColumnListView->SetSelectionMode(B_SINGLE_SELECTION_LIST);
189 	fColumnListView->SetTarget(this);
190 
191 	fAddButton = new BButton(BRect(0, 0, 0, 0), "add",
192 		B_TRANSLATE("Add new shortcut"), new BMessage(ADD_HOTKEY_ITEM),
193 		B_FOLLOW_BOTTOM);
194 	fAddButton->ResizeToPreferred();
195 	fAddButton->MoveBy(spacing,
196 		Bounds().bottom - fAddButton->Bounds().bottom - spacing);
197 
198 	fRemoveButton = new BButton(BRect(0, 0, 0, 0), "remove",
199 		B_TRANSLATE("Remove selected shortcut"),
200 		new BMessage(REMOVE_HOTKEY_ITEM), B_FOLLOW_BOTTOM);
201 	fRemoveButton->ResizeToPreferred();
202 	fRemoveButton->MoveBy(fAddButton->Frame().right + spacing,
203 		Bounds().bottom - fRemoveButton->Bounds().bottom - spacing);
204 
205 	fRemoveButton->SetEnabled(false);
206 
207 	fSaveButton = new BButton(BRect(0, 0, 0, 0), "save",
208 		B_TRANSLATE("Save & apply"), new BMessage(SAVE_KEYSET),
209 		B_FOLLOW_BOTTOM | B_FOLLOW_RIGHT);
210 	fSaveButton->ResizeToPreferred();
211 	fSaveButton->MoveBy(Bounds().right - fSaveButton->Bounds().right - spacing,
212 		Bounds().bottom - fSaveButton->Bounds().bottom - spacing);
213 
214 	fSaveButton->SetEnabled(false);
215 
216 	CenterOnScreen();
217 
218 	entry_ref windowSettingsRef;
219 	if (_GetWindowSettingsFile(&windowSettingsRef)) {
220 		// The window settings file is not accepted via B_REFS_RECEIVED; this
221 		// is a behind-the-scenes file that the user will never see or
222 		// interact with.
223 		BFile windowSettingsFile(&windowSettingsRef, B_READ_ONLY);
224 		BMessage loadMessage;
225 		if (loadMessage.Unflatten(&windowSettingsFile) == B_OK)
226 			_LoadWindowSettings(loadMessage);
227 	}
228 
229 	entry_ref keySetRef;
230 	if (_GetSettingsFile(&keySetRef)) {
231 		BMessage message(B_REFS_RECEIVED);
232 		message.AddRef("refs", &keySetRef);
233 		message.AddString("startupRef", "please");
234 		PostMessage(&message);
235 			// tell ourselves to load this file if it exists
236 	}
237 
238 	fColumnListView->ResizeAllColumnsToPreferred();
239 
240 	BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
241 		.Add(menuBar)
242 		.AddGroup(B_VERTICAL)
243 			.SetInsets(B_USE_WINDOW_SPACING)
244 			.Add(fColumnListView)
245 			.AddGroup(B_HORIZONTAL)
246 				.AddGroup(B_HORIZONTAL)
247 				.SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, B_ALIGN_TOP))
248 				.Add(fAddButton)
249 				.Add(fRemoveButton)
250 				.End()
251 				.AddGroup(B_HORIZONTAL)
252 					.SetExplicitAlignment(BAlignment(B_ALIGN_RIGHT, B_ALIGN_TOP))
253 					.Add(fSaveButton)
254 				.End()
255 			.End()
256 		.End();
257 
258 	Show();
259 }
260 
261 
262 ShortcutsWindow::~ShortcutsWindow()
263 {
264 	delete fSavePanel;
265 	delete fOpenPanel;
266 	delete fSelectPanel;
267 	be_app->PostMessage(B_QUIT_REQUESTED);
268 }
269 
270 
271 bool
272 ShortcutsWindow::QuitRequested()
273 {
274 	bool result = true;
275 
276 	if (fKeySetModified) {
277 		BAlert* alert = new BAlert(WARNING,
278 			B_TRANSLATE("Save changes before closing?"),
279 			B_TRANSLATE("Cancel"), B_TRANSLATE("Don't save"),
280 			B_TRANSLATE("Save"));
281 		alert->SetShortcut(0, B_ESCAPE);
282 		alert->SetShortcut(1, 'd');
283 		alert->SetShortcut(2, 's');
284 		switch (alert->Go()) {
285 			case 0:
286 				result = false;
287 				break;
288 
289 			case 1:
290 				result = true;
291 				break;
292 
293 			case 2:
294 				// Save: automatically if possible, otherwise go back and open
295 				// up the file requester
296 				if (fLastSaved.InitCheck() == B_OK) {
297 					if (_SaveKeySet(fLastSaved) == false) {
298 						BAlert* alert = new BAlert(ERROR,
299 							B_TRANSLATE("Shortcuts was unable to save your "
300 								"KeySet file!"),
301 							B_TRANSLATE("Oh no"));
302 						alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
303 						alert->Go();
304 						result = true; // quit anyway
305 					}
306 				} else {
307 					PostMessage(SAVE_KEYSET);
308 					result = false;
309 				}
310 				break;
311 		}
312 	}
313 
314 	if (result) {
315 		fColumnListView->DeselectAll();
316 
317 		// Save the window position.
318 		entry_ref ref;
319 		if (_GetWindowSettingsFile(&ref)) {
320 			BEntry entry(&ref);
321 			_SaveWindowSettings(entry);
322 		}
323 	}
324 
325 	return result;
326 }
327 
328 
329 bool
330 ShortcutsWindow::_GetSettingsFile(entry_ref* eref)
331 {
332 	BPath path;
333 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
334 		return false;
335 	else
336 		path.Append(SHORTCUTS_SETTING_FILE_NAME);
337 
338 	if (BEntry(path.Path(), true).GetRef(eref) == B_OK)
339 		return true;
340 	else
341 		return false;
342 }
343 
344 
345 // Saves a settings file to (saveEntry). Returns true iff successful.
346 bool
347 ShortcutsWindow::_SaveKeySet(BEntry& saveEntry)
348 {
349 	BFile saveTo(&saveEntry, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
350 	if (saveTo.InitCheck() != B_OK)
351 		return false;
352 
353 	BMessage saveMessage;
354 	for (int i = 0; i < fColumnListView->CountRows(); i++) {
355 		BMessage next;
356 		if (((ShortcutsSpec*)fColumnListView->RowAt(i))->Archive(&next)
357 				== B_OK) {
358 			saveMessage.AddMessage("spec", &next);
359 		} else
360 			printf("Error archiving ShortcutsSpec #%i!\n", i);
361 	}
362 
363 	bool result = (saveMessage.Flatten(&saveTo) == B_OK);
364 
365 	if (result) {
366 		fKeySetModified = false;
367 		fSaveButton->SetEnabled(false);
368 	}
369 
370 	return result;
371 }
372 
373 
374 // Appends new entries from the file specified in the "spec" entry of
375 // (loadMessage). Returns true iff successful.
376 bool
377 ShortcutsWindow::_LoadKeySet(const BMessage& loadMessage)
378 {
379 	int i = 0;
380 	BMessage message;
381 	while (loadMessage.FindMessage("spec", i++, &message) == B_OK) {
382 		ShortcutsSpec* spec
383 			= (ShortcutsSpec*)ShortcutsSpec::Instantiate(&message);
384 		if (spec != NULL)
385 			fColumnListView->AddRow(spec);
386 		else
387 			printf("_LoadKeySet: Error parsing spec!\n");
388 	}
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 		BColumn* 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 (loadMessage) and resizes
430 // the interface to match the previously saved settings. Because this
431 // is a non-essential file, errors are ignored when loading the settings.
432 void
433 ShortcutsWindow::_LoadWindowSettings(const BMessage& loadMessage)
434 {
435 	BRect frame;
436 	if (loadMessage.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 		BColumn* column = fColumnListView->ColumnAt(i);
451 		float columnWidth;
452 		if (loadMessage.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 	BRow* curSel = fColumnListView->CurrentSelection();
467 	if (curSel)
468 		spec = new ShortcutsSpec(*((ShortcutsSpec*)curSel));
469 	else {
470 		spec = new ShortcutsSpec("");
471 		for (int i = 0; i < fColumnListView->CountColumns(); i++)
472 			spec->SetField(new BStringField(""), i);
473 	}
474 
475 	fColumnListView->AddRow(spec);
476 	fColumnListView->AddToSelection(spec);
477 	fColumnListView->ScrollTo(spec);
478 	if (defaultCommand)
479 		spec->SetCommand(defaultCommand);
480 }
481 
482 
483 void
484 ShortcutsWindow::MessageReceived(BMessage* message)
485 {
486 	switch (message->what) {
487 		case OPEN_KEYSET:
488 		case APPEND_KEYSET:
489 			fLastOpenWasAppend = (message->what == APPEND_KEYSET);
490 			if (fOpenPanel)
491 				fOpenPanel->Show();
492 			else {
493 				BMessenger messenger(this);
494 				fOpenPanel = new BFilePanel(B_OPEN_PANEL, &messenger, NULL,
495 					0, false);
496 				fOpenPanel->Show();
497 			}
498 			fOpenPanel->SetButtonLabel(B_DEFAULT_BUTTON, fLastOpenWasAppend ?
499 				B_TRANSLATE("Append") : B_TRANSLATE("Open"));
500 			break;
501 
502 		// send a message to myself, to get me to reload the settings file
503 		case REVERT_KEYSET:
504 		{
505 			fLastOpenWasAppend = false;
506 			BMessage reload(B_REFS_RECEIVED);
507 			entry_ref eref;
508 			_GetSettingsFile(&eref);
509 			reload.AddRef("refs", &eref);
510 			reload.AddString("startupRef", "yeah");
511 			PostMessage(&reload);
512 			break;
513 		}
514 
515 		// respond to drag-and-drop messages here
516 		case B_SIMPLE_DATA:
517 		{
518 			int i = 0;
519 
520 			entry_ref ref;
521 			while (message->FindRef("refs", i++, &ref) == B_OK) {
522 				BEntry entry(&ref);
523 				if (entry.InitCheck() == B_OK) {
524 					BPath path(&entry);
525 
526 					if (path.InitCheck() == B_OK) {
527 						// Add a new item with the given path.
528 						BString str(path.Path());
529 						DoStandardEscapes(str);
530 						_AddNewSpec(str.String());
531 					}
532 				}
533 			}
534 			break;
535 		}
536 
537 		// respond to FileRequester's messages here
538 		case B_REFS_RECEIVED:
539 		{
540 			// Find file ref
541 			entry_ref ref;
542 			bool isStartMsg = message->HasString("startupRef");
543 			if (message->FindRef("refs", &ref) == B_OK) {
544 				// load the file into (fileMsg)
545 				BMessage fileMsg;
546 				{
547 					BFile file(&ref, B_READ_ONLY);
548 					if ((file.InitCheck() != B_OK)
549 						|| (fileMsg.Unflatten(&file) != B_OK)) {
550 						if (isStartMsg) {
551 							// use this to save to anyway
552 							fLastSaved = BEntry(&ref);
553 							break;
554 						} else {
555 							BAlert* alert = new BAlert(ERROR,
556 								B_TRANSLATE("Shortcuts was couldn't open your "
557 								"KeySet file!"), B_TRANSLATE("OK"));
558 							alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
559 							alert->Go(NULL);
560 							break;
561 						}
562 					}
563 				}
564 
565 				if (fLastOpenWasAppend == false) {
566 					// Clear the menu...
567 					while (fColumnListView->CountRows()) {
568 						ShortcutsSpec* row =
569 							static_cast<ShortcutsSpec*>(fColumnListView->RowAt(0));
570 						fColumnListView->RemoveRow(row);
571 						delete row;
572 					}
573 				}
574 
575 				if (_LoadKeySet(fileMsg)) {
576 					if (isStartMsg) fLastSaved = BEntry(&ref);
577 					fSaveButton->SetEnabled(isStartMsg == false);
578 
579 					// If we just loaded in the Shortcuts settings file, then
580 					// no need to tell the user to save on exit.
581 					entry_ref eref;
582 					_GetSettingsFile(&eref);
583 					if (ref == eref) fKeySetModified = false;
584 				} else {
585 					BAlert* alert = new BAlert(ERROR,
586 						B_TRANSLATE("Shortcuts was unable to parse your "
587 						"KeySet file!"), B_TRANSLATE("OK"));
588 					alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
589 					alert->Go(NULL);
590 					break;
591 				}
592 			}
593 			break;
594 		}
595 
596 		// these messages come from the pop-up menu of the Applications column
597 		case SELECT_APPLICATION:
598 		{
599 			ShortcutsSpec* row =
600 				static_cast<ShortcutsSpec*>(fColumnListView->CurrentSelection());
601 			if (row != NULL) {
602 				entry_ref aref;
603 				if (message->FindRef("refs", &aref) == B_OK) {
604 					BEntry ent(&aref);
605 					if (ent.InitCheck() == B_OK) {
606 						BPath path;
607 						if ((ent.GetPath(&path) == B_OK)
608 							&& (row->
609 								ProcessColumnTextString(ShortcutsSpec::STRING_COLUMN_INDEX,
610 									path.Path()))) {
611 							_MarkKeySetModified();
612 						}
613 					}
614 				}
615 			}
616 			break;
617 		}
618 
619 		case SAVE_KEYSET:
620 		{
621 			bool showSaveError = false;
622 
623 			const char* name;
624 			entry_ref entry;
625 			if ((message->FindString("name", &name) == B_OK)
626 				&& (message->FindRef("directory", &entry) == B_OK)) {
627 				BDirectory dir(&entry);
628 				BEntry saveTo(&dir, name, true);
629 				showSaveError = ((saveTo.InitCheck() != B_OK)
630 					|| (_SaveKeySet(saveTo) == false));
631 			} else if (fLastSaved.InitCheck() == B_OK) {
632 				// We've saved this before, save over previous file.
633 				showSaveError = (_SaveKeySet(fLastSaved) == false);
634 			} else
635 				PostMessage(SAVE_KEYSET_AS);
636 					// open the save requester...
637 
638 			if (showSaveError) {
639 				BAlert* alert = new BAlert(ERROR,
640 					B_TRANSLATE("Shortcuts wasn't able to save your keyset."),
641 					B_TRANSLATE("OK"));
642 				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
643 				alert->Go(NULL);
644 			}
645 			break;
646 		}
647 
648 		case SAVE_KEYSET_AS:
649 		{
650 			if (fSavePanel)
651 				fSavePanel->Show();
652 			else {
653 				BMessage message(SAVE_KEYSET);
654 				BMessenger messenger(this);
655 				fSavePanel = new BFilePanel(B_SAVE_PANEL, &messenger, NULL, 0,
656 					false, &message);
657 				fSavePanel->Show();
658 			}
659 			break;
660 		}
661 
662 		case ADD_HOTKEY_ITEM:
663 			_AddNewSpec(NULL);
664 			break;
665 
666 		case REMOVE_HOTKEY_ITEM:
667 		{
668 			BRow* item = fColumnListView->CurrentSelection();
669 			if (item) {
670 				int index = fColumnListView->IndexOf(item);
671 				fColumnListView->RemoveRow(item);
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->CountRows();
679 				if (num > 0) {
680 					if (index < num)
681 						fColumnListView->AddToSelection(
682 							fColumnListView->RowAt(index));
683 					else {
684 						if (index > 0)
685 							index--;
686 						if (index < num)
687 							fColumnListView->AddToSelection(
688 								fColumnListView->RowAt(index));
689 					}
690 				}
691 			}
692 			break;
693 		}
694 
695 		// Received when the user clicks on the ColumnListView
696 		case HOTKEY_ITEM_SELECTED:
697 		{
698 			if (fColumnListView->CountRows() > 0)
699 				fRemoveButton->SetEnabled(true);
700 			else
701 				fRemoveButton->SetEnabled(false);
702 			break;
703 		}
704 
705 		// Received when an entry is to be modified in response to GUI activity
706 		case HOTKEY_ITEM_MODIFIED:
707 		{
708 			int32 row, column;
709 
710 			if ((message->FindInt32("row", &row) == B_OK)
711 				&& (message->FindInt32("column", &column) == B_OK)) {
712 				int32 key;
713 				const char* bytes;
714 
715 				if (row >= 0) {
716 					ShortcutsSpec* item = (ShortcutsSpec*)
717 						fColumnListView->RowAt(row);
718 					bool repaintNeeded = false; // default
719 
720 					if (message->HasInt32("mouseClick")) {
721 						repaintNeeded = item->ProcessColumnMouseClick(column);
722 					} else if ((message->FindString("bytes", &bytes) == B_OK)
723 						&& (message->FindInt32("key", &key) == B_OK)) {
724 						repaintNeeded = item->ProcessColumnKeyStroke(column,
725 							bytes, key);
726 					} else if (message->FindInt32("unmappedkey", &key) ==
727 						B_OK) {
728 						repaintNeeded = ((column == item->KEY_COLUMN_INDEX)
729 							&& ((key > 0xFF) || (GetKeyName(key) != NULL))
730 							&& (item->ProcessColumnKeyStroke(column, NULL,
731 							key)));
732 					} else if (message->FindString("text", &bytes) == B_OK) {
733 						if ((bytes[0] == '(')&&(bytes[1] == 'C')) {
734 							if (fSelectPanel)
735 								fSelectPanel->Show();
736 							else {
737 								BMessage message(SELECT_APPLICATION);
738 								BMessenger m(this);
739 								fSelectPanel = new BFilePanel(B_OPEN_PANEL, &m,
740 									NULL, 0, false, &message);
741 								fSelectPanel->Show();
742 							}
743 							fSelectPanel->SetButtonLabel(B_DEFAULT_BUTTON,
744 								B_TRANSLATE("Select"));
745 						} else
746 							repaintNeeded = item->ProcessColumnTextString(
747 								column, bytes);
748 					}
749 
750 					if (repaintNeeded) {
751 						fColumnListView->Invalidate(row);
752 						_MarkKeySetModified();
753 					}
754 				}
755 			}
756 			break;
757 		}
758 
759 		default:
760 			BWindow::MessageReceived(message);
761 			break;
762 	}
763 }
764 
765 
766 void
767 ShortcutsWindow::_MarkKeySetModified()
768 {
769 	if (fKeySetModified == false) {
770 		fKeySetModified = true;
771 		fSaveButton->SetEnabled(true);
772 	}
773 }
774 
775 
776 void
777 ShortcutsWindow::Quit()
778 {
779 	BWindow::Quit();
780 }
781 
782 
783 void
784 ShortcutsWindow::DispatchMessage(BMessage* message, BHandler* handler)
785 {
786 	switch (message->what) {
787 		case B_SIMPLE_DATA:
788 			MessageReceived(message);
789 			break;
790 
791 		case B_COPY:
792 		case B_CUT:
793 			if (be_clipboard->Lock()) {
794 				ShortcutsSpec* row =
795 					static_cast<ShortcutsSpec*>(fColumnListView->CurrentSelection());
796 				if (row) {
797 					BMessage* data = be_clipboard->Data();
798 					data->RemoveName("text/plain");
799 					data->AddData("text/plain", B_MIME_TYPE,
800 						row->GetCellText(ShortcutsSpec::STRING_COLUMN_INDEX),
801 						strlen(row->GetCellText(ShortcutsSpec::STRING_COLUMN_INDEX)));
802 					be_clipboard->Commit();
803 
804 					if (message->what == B_CUT) {
805 						row->ProcessColumnTextString(
806 							ShortcutsSpec::STRING_COLUMN_INDEX, "");
807 						_MarkKeySetModified();
808 					}
809 				}
810 				be_clipboard->Unlock();
811 			}
812 			break;
813 
814 		case B_PASTE:
815 			if (be_clipboard->Lock()) {
816 				BMessage* data = be_clipboard->Data();
817 				const char* text;
818 				ssize_t textLen;
819 				if (data->FindData("text/plain", B_MIME_TYPE, (const void**)
820 					&text, &textLen) == B_OK) {
821 					ShortcutsSpec* row =
822 					static_cast<ShortcutsSpec*>(fColumnListView->CurrentSelection());
823 					if (row) {
824 						for (ssize_t i = 0; i < textLen; i++) {
825 							char buf[2] = {text[i], 0x00};
826 							row->ProcessColumnKeyStroke(
827 								ShortcutsSpec::STRING_COLUMN_INDEX, buf, 0);
828 						}
829 					}
830 					_MarkKeySetModified();
831 				}
832 				be_clipboard->Unlock();
833 			}
834 			break;
835 
836 		case B_KEY_DOWN:
837 			ShortcutsSpec* selected;
838 			if (message->GetInt32("modifiers", 0) != 0)
839 				BWindow::DispatchMessage(message, handler);
840 			else if (handler == fColumnListView
841 				&& (selected =
842 					static_cast<ShortcutsSpec*>(fColumnListView->CurrentSelection()))) {
843 				selected->ProcessColumnTextString(
844 						ShortcutsSpec::KEY_COLUMN_INDEX,
845 						GetKeyName(message->GetInt32("key", 0)));
846 				_MarkKeySetModified();
847 			}
848 			break;
849 
850 		default:
851 			BWindow::DispatchMessage(message, handler);
852 			break;
853 	}
854 }
855