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.
71static BPopUpMenu*
72CreateMetaPopUp(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
86static BPopUpMenu*
87CreateKeysPopUp()
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
101ShortcutsWindow::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
270ShortcutsWindow::~ShortcutsWindow()
271{
272	delete fSavePanel;
273	delete fOpenPanel;
274	delete fSelectPanel;
275	be_app->PostMessage(B_QUIT_REQUESTED);
276}
277
278
279bool
280ShortcutsWindow::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
337bool
338ShortcutsWindow::_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.
354bool
355ShortcutsWindow::_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.
384bool
385ShortcutsWindow::_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.
401bool
402ShortcutsWindow::_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.
416void
417ShortcutsWindow::_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.
438void
439ShortcutsWindow::_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.
466void
467ShortcutsWindow::_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
488void
489ShortcutsWindow::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
768void
769ShortcutsWindow::_MarkKeySetModified()
770{
771	if (fKeySetModified == false) {
772		fKeySetModified = true;
773		fSaveButton->SetEnabled(true);
774	}
775}
776
777
778void
779ShortcutsWindow::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
789void
790ShortcutsWindow::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