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