xref: /haiku/src/preferences/shortcuts/ShortcutsWindow.cpp (revision c9ad965c81b08802fed0827fd1dd16f45297928a)
1 /*
2  * Copyright 1999-2009 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 
14 #include <math.h>
15 #include <stdio.h>
16 
17 
18 #include <Alert.h>
19 #include <Application.h>
20 #include <Clipboard.h>
21 #include <MessageFilter.h>
22 #include <Menu.h>
23 #include <MenuItem.h>
24 #include <MenuBar.h>
25 #include <ScrollBar.h>
26 #include <ScrollView.h>
27 #include <String.h>
28 #include <Input.h>
29 #include <PopUpMenu.h>
30 #include <File.h>
31 #include <Path.h>
32 #include <FindDirectory.h>
33 
34 #include "ColumnListView.h"
35 
36 
37 #include "KeyInfos.h"
38 #include "ShortcutsSpec.h"
39 #include "ParseCommandLine.h"
40 #include "MetaKeyStateMap.h"
41 #include "ShortcutsFilterConstants.h"
42 
43 // Window sizing constraints
44 #define MIN_WIDTH	600
45 #define MIN_HEIGHT	130
46 #define MAX_WIDTH	65535
47 #define MAX_HEIGHT	65535
48 
49 // Default window position
50 #define WINDOW_START_X 30
51 #define WINDOW_START_Y 100
52 
53 #define ERROR "Shortcuts Error"
54 #define WARNING "Shortcuts warning"
55 
56 // Global constants for Shortcuts
57 #define V_SPACING 5 // vertical spacing between GUI components
58 
59 
60 // Creates a pop-up-menu that reflects the possible states of the specified
61 // meta-key.
62 static BPopUpMenu* CreateMetaPopUp(int col);
63 static BPopUpMenu* CreateMetaPopUp(int col)
64 {
65 	MetaKeyStateMap& map = GetNthKeyMap(col);
66 	BPopUpMenu * popup = new BPopUpMenu(NULL, false);
67 	int numStates = map.GetNumStates();
68 
69 	for (int i = 0; i < numStates; i++)
70 		popup->AddItem(new BMenuItem(map.GetNthStateDesc(i), NULL));
71 
72 	return popup;
73 }
74 
75 // Creates a pop-up that allows the user to choose a key-cap visually
76 static BPopUpMenu* CreateKeysPopUp();
77 static BPopUpMenu* CreateKeysPopUp()
78 {
79 	BPopUpMenu* popup = new BPopUpMenu(NULL, false);
80 	int numKeys = GetNumKeyIndices();
81 	for (int i = 0; i < numKeys; i++) {
82 		const char* next = GetKeyName(i);
83 
84 		if (next)
85 			popup->AddItem(new BMenuItem(next, NULL));
86 	}
87 	return popup;
88 }
89 
90 
91 ShortcutsWindow::ShortcutsWindow()
92 	:
93 	BWindow(BRect(WINDOW_START_X, WINDOW_START_Y, WINDOW_START_X + MIN_WIDTH,
94 		WINDOW_START_Y + MIN_HEIGHT * 2), "Shortcuts", B_DOCUMENT_WINDOW, 0L),
95 		fSavePanel(NULL),
96 		fOpenPanel(NULL),
97 		fSelectPanel(NULL),
98 		fKeySetModified(false),
99 		fLastOpenWasAppend(false)
100 {
101 	InitializeMetaMaps();
102 	SetSizeLimits(MIN_WIDTH, MAX_WIDTH, MIN_HEIGHT, MAX_HEIGHT);
103 	BMenuBar* menuBar = new BMenuBar(BRect(0, 0, 0, 0), "Menu Bar");
104 
105 	BMenu* fileMenu = new BMenu("File");
106 	fileMenu->AddItem(new BMenuItem("Open KeySet...",
107 		new BMessage(OPEN_KEYSET), 'O'));
108 	fileMenu->AddItem(new BMenuItem("Append KeySet...",
109 		new BMessage(APPEND_KEYSET), 'A'));
110 	fileMenu->AddItem(new BMenuItem("Revert to Saved",
111 		new BMessage(REVERT_KEYSET), 'A'));
112 	fileMenu->AddItem(new BSeparatorItem);
113 	fileMenu->AddItem(new BMenuItem("Save KeySet As...",
114 		new BMessage(SAVE_KEYSET_AS), 'S'));
115 	fileMenu->AddItem(new BSeparatorItem);
116 	fileMenu->AddItem(new BMenuItem("About Shortcuts", new BMessage(B_ABOUT_REQUESTED)));
117 	fileMenu->AddItem(new BSeparatorItem);
118 	fileMenu->AddItem(new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED),
119 		'Q'));
120 	menuBar->AddItem(fileMenu);
121 
122 	AddChild(menuBar);
123 
124 	font_height fh;
125 	be_plain_font->GetHeight(&fh);
126 	float vButtonHeight = ceil(fh.ascent) + ceil(fh.descent) + 5.0f;
127 
128 	BRect tableBounds = Bounds();
129 	tableBounds.top = menuBar->Bounds().bottom + 1;
130 	tableBounds.right -= B_V_SCROLL_BAR_WIDTH;
131 	tableBounds.bottom -= (B_H_SCROLL_BAR_HEIGHT + V_SPACING + vButtonHeight +
132 		V_SPACING * 2);
133 
134 	BScrollView* containerView;
135 	fColumnListView = new ColumnListView(tableBounds, &containerView, NULL,
136 		B_FOLLOW_ALL_SIDES, B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE,
137 		B_SINGLE_SELECTION_LIST, true, true, true, B_NO_BORDER);
138 
139 	fColumnListView->SetEditMessage(new BMessage(HOTKEY_ITEM_MODIFIED),
140 		BMessenger(this));
141 
142 	const float metaWidth = 50.0f;
143 
144 	for (int i = 0; i < ShortcutsSpec::NUM_META_COLUMNS; i++)
145 		fColumnListView->AddColumn(
146 			new CLVColumn(ShortcutsSpec::GetColumnName(i), CreateMetaPopUp(i),
147 			metaWidth, CLV_SORT_KEYABLE));
148 
149 	fColumnListView->AddColumn(new CLVColumn("Key", CreateKeysPopUp(), 60,
150 		CLV_SORT_KEYABLE));
151 
152 	BPopUpMenu* popup = new BPopUpMenu(NULL, false);
153 	popup->AddItem(new BMenuItem("(Choose App with File Requester)", NULL));
154 	popup->AddItem(new BMenuItem("*InsertString \"Your Text Here\"", NULL));
155 	popup->AddItem(new BMenuItem("*MoveMouse +20 +0", NULL));
156 	popup->AddItem(new BMenuItem("*MoveMouseTo 50% 50%", NULL));
157 	popup->AddItem(new BMenuItem("*MouseButton 1", NULL));
158 	popup->AddItem(new BMenuItem("*LaunchHandler text/html", NULL));
159 	popup->AddItem(new BMenuItem(
160 		"*Multi \"*MoveMouseTo 100% 0\" \"*MouseButton 1\"", NULL));
161 	popup->AddItem(new BMenuItem("*MouseDown", NULL));
162 	popup->AddItem(new BMenuItem("*MouseUp", NULL));
163 	popup->AddItem(new BMenuItem(
164 		"*SendMessage application/x-vnd.Be-TRAK 'Tfnd'", NULL));
165 	popup->AddItem(new BMenuItem("*Beep", NULL));
166 	fColumnListView->AddColumn(new CLVColumn("Application", popup, 323.0,
167 		CLV_SORT_KEYABLE));
168 
169 	fColumnListView->SetSortFunction(ShortcutsSpec::MyCompare);
170 	AddChild(containerView);
171 
172 	fColumnListView->SetSelectionMessage(new BMessage(HOTKEY_ITEM_SELECTED));
173 	fColumnListView->SetTarget(this);
174 
175 	BRect buttonBounds = Bounds();
176 	buttonBounds.left += V_SPACING;
177 	buttonBounds.right = ((buttonBounds.right - buttonBounds.left) / 2.0f) +
178 		buttonBounds.left;
179 	buttonBounds.bottom -= V_SPACING * 2;
180 	buttonBounds.top = buttonBounds.bottom - vButtonHeight;
181 	buttonBounds.right -= B_V_SCROLL_BAR_WIDTH;
182 	float origRight = buttonBounds.right;
183 	buttonBounds.right = (buttonBounds.left + origRight) * 0.40f -
184 		(V_SPACING / 2);
185 	AddChild(fAddButton = new ResizableButton(Bounds(), buttonBounds, "add",
186 		"Add New Shortcut", new BMessage(ADD_HOTKEY_ITEM)));
187 	buttonBounds.left = buttonBounds.right + V_SPACING;
188 	buttonBounds.right = origRight;
189 	AddChild(fRemoveButton = new ResizableButton(Bounds(), buttonBounds,
190 		"remove", "Remove Selected Shortcut",
191 		new BMessage(REMOVE_HOTKEY_ITEM)));
192 
193 	fRemoveButton->SetEnabled(false);
194 
195 	float offset = (buttonBounds.right - buttonBounds.left) / 2.0f;
196 	BRect saveButtonBounds = buttonBounds;
197 	saveButtonBounds.right = Bounds().right - B_V_SCROLL_BAR_WIDTH - offset;
198 	saveButtonBounds.left = buttonBounds.right + V_SPACING + offset;
199 	AddChild(fSaveButton = new ResizableButton(Bounds(), saveButtonBounds,
200 		"save", "Save & Apply", new BMessage(SAVE_KEYSET)));
201 
202 	fSaveButton->SetEnabled(false);
203 
204 	entry_ref ref;
205 	if (_GetSettingsFile(&ref)) {
206 		BMessage msg(B_REFS_RECEIVED);
207 		msg.AddRef("refs", &ref);
208 		msg.AddString("startupRef", "please");
209 		PostMessage(&msg); // Tell ourself to load this file if it exists.
210 	}
211 	Show();
212 }
213 
214 
215 ShortcutsWindow::~ShortcutsWindow()
216 {
217 	delete fSavePanel;
218 	delete fOpenPanel;
219 	delete fSelectPanel;
220 	be_app->PostMessage(B_QUIT_REQUESTED);
221 }
222 
223 
224 bool
225 ShortcutsWindow::QuitRequested()
226 {
227 	bool ret = true;
228 
229 	if (fKeySetModified) {
230 		BAlert* alert = new BAlert(WARNING,
231 			"Really quit without saving your changes?", "Don't Save", "Cancel",
232 			"Save");
233 		switch(alert->Go()) {
234 			case 1:
235 				ret = false;
236 			break;
237 
238 			case 2:
239 				// Save: automatically if possible, otherwise go back and open
240 				// up the file requester
241 				if (fLastSaved.InitCheck() == B_NO_ERROR) {
242 					if (_SaveKeySet(fLastSaved) == false) {
243 						(new BAlert(ERROR,
244 							"Shortcuts was unable to save your KeySet file!",
245 							"Oh no"))->Go();
246 						ret = true; //quit anyway
247 					}
248 				} else {
249 					PostMessage(SAVE_KEYSET);
250 					ret = false;
251 				}
252 			break;
253 			default:
254 				ret = true;
255 			break;
256 		}
257 	}
258 
259 	if (ret)
260 		fColumnListView->DeselectAll(); // avoid mysterious crash on PPC!?
261 	return ret;
262 }
263 
264 
265 bool
266 ShortcutsWindow::_GetSettingsFile(entry_ref* eref)
267 {
268 	BPath path;
269 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
270 		return false;
271 	else
272 		path.Append(SHORTCUTS_SETTING_FILE_NAME);
273 
274 	if (BEntry(path.Path(), true).GetRef(eref) == B_NO_ERROR)
275 		return true;
276 	else
277 		return false;
278 }
279 
280 
281 // Saves a settings file to (saveEntry). Returns true iff successful.
282 bool
283 ShortcutsWindow::_SaveKeySet(BEntry& saveEntry)
284 {
285 	BFile saveTo(&saveEntry, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
286 	if (saveTo.InitCheck() != B_NO_ERROR)
287 		return false;
288 
289 	BMessage saveMsg;
290 	for (int i = 0; i < fColumnListView->CountItems(); i++) {
291 		BMessage next;
292 		if (((ShortcutsSpec*)fColumnListView->ItemAt(i))->Archive(&next)
293 			== B_NO_ERROR)
294 			saveMsg.AddMessage("spec", &next);
295 		else
296 			printf("Error archiving ShortcutsSpec #%i!\n",i);
297 	}
298 
299 	bool ret = (saveMsg.Flatten(&saveTo) == B_NO_ERROR);
300 
301 	if (ret) {
302 		fKeySetModified = false;
303 		fSaveButton->SetEnabled(false);
304 	}
305 
306 	return ret;
307 }
308 
309 
310 // Appends new entries from the file specified in the "spec" entry of
311 // (loadMsg). Returns true iff successful.
312 bool
313 ShortcutsWindow::_LoadKeySet(const BMessage& loadMsg)
314 {
315 	int i = 0;
316 	BMessage msg;
317 	while (loadMsg.FindMessage("spec", i++, &msg) == B_NO_ERROR) {
318 		ShortcutsSpec* spec = (ShortcutsSpec*)ShortcutsSpec::Instantiate(&msg);
319 		if (spec != NULL)
320 			fColumnListView->AddItem(spec);
321 		else
322 			printf("_LoadKeySet: Error parsing spec!\n");
323 	}
324 	return true;
325 }
326 
327 
328 // Creates a new entry and adds it to the GUI. (defaultCommand) will be the
329 // text in the entry, or NULL if no text is desired.
330 void
331 ShortcutsWindow::_AddNewSpec(const char* defaultCommand)
332 {
333 	_MarkKeySetModified();
334 
335 	ShortcutsSpec* spec;
336 	int curSel = fColumnListView->CurrentSelection();
337 	if (curSel >= 0) {
338 		spec = new ShortcutsSpec(*((ShortcutsSpec*)
339 			fColumnListView->ItemAt(curSel)));
340 
341 		if (defaultCommand)
342 			spec->SetCommand(defaultCommand);
343 	} else
344 		spec = new ShortcutsSpec(defaultCommand ? defaultCommand : "");
345 
346 	fColumnListView->AddItem(spec);
347 	fColumnListView->Select(fColumnListView->CountItems() - 1);
348 	fColumnListView->ScrollToSelection();
349 }
350 
351 
352 void
353 ShortcutsWindow::MessageReceived(BMessage* msg)
354 {
355 	switch(msg->what) {
356 		case OPEN_KEYSET:
357 		case APPEND_KEYSET:
358 			fLastOpenWasAppend = (msg->what == APPEND_KEYSET);
359 			if (fOpenPanel)
360 				fOpenPanel->Show();
361 			else {
362 				BMessenger m(this);
363 				fOpenPanel = new BFilePanel(B_OPEN_PANEL, &m, NULL, 0, false);
364 				fOpenPanel->Show();
365 			}
366 			fOpenPanel->SetButtonLabel(B_DEFAULT_BUTTON, fLastOpenWasAppend ?
367 				"Append" : "Open");
368 		break;
369 
370 		case REVERT_KEYSET:
371 		{
372 			// Send a message to myself, to get me to reload the settings file
373 			fLastOpenWasAppend = false;
374 			BMessage reload(B_REFS_RECEIVED);
375 			entry_ref eref;
376 			_GetSettingsFile(&eref);
377 			reload.AddRef("refs", &eref);
378 			reload.AddString("startupRef", "yeah");
379 			PostMessage(&reload);
380 		}
381 		break;
382 
383 		// Respond to drag-and-drop messages here
384 		case B_SIMPLE_DATA:
385 		{
386 			int i = 0;
387 
388 			entry_ref ref;
389 			while (msg->FindRef("refs", i++, &ref) == B_NO_ERROR) {
390 				BEntry entry(&ref);
391 				if (entry.InitCheck() == B_NO_ERROR) {
392 					BPath path(&entry);
393 
394 					if (path.InitCheck() == B_NO_ERROR) {
395 						// Add a new item with the given path.
396 						BString str(path.Path());
397 						DoStandardEscapes(str);
398 						_AddNewSpec(str.String());
399 					}
400 				}
401 			}
402 		}
403 		break;
404 
405 		// Respond to FileRequester's messages here
406 		case B_REFS_RECEIVED:
407 		{
408 			// Find file ref
409 			entry_ref ref;
410 			bool isStartMsg = msg->HasString("startupRef");
411 			if (msg->FindRef("refs", &ref) == B_NO_ERROR) {
412 				// load the file into (fileMsg)
413 				BMessage fileMsg;
414 				{
415 					BFile file(&ref, B_READ_ONLY);
416 					if ((file.InitCheck() != B_NO_ERROR)
417 						|| (fileMsg.Unflatten(&file) != B_NO_ERROR)) {
418 						if (isStartMsg) {
419 							// use this to save to anyway
420 							fLastSaved = BEntry(&ref);
421 							break;
422 						} else {
423 							(new BAlert(ERROR,
424 								"Shortcuts was couldn't open your KeySet file!"
425 								, "Okay"))->Go(NULL);
426 							break;
427 						}
428 					}
429 				}
430 
431 				if (fLastOpenWasAppend == false) {
432 					// Clear the menu...
433 					ShortcutsSpec * item;
434 					do {
435 						delete (item = ((ShortcutsSpec*)
436 							fColumnListView->RemoveItem(int32(0))));
437 					} while (item);
438 				}
439 
440 				if (_LoadKeySet(fileMsg)) {
441 					if (isStartMsg) fLastSaved = BEntry(&ref);
442 					fSaveButton->SetEnabled(isStartMsg == false);
443 
444 					// If we just loaded in the Shortcuts settings file, then
445 					// no need to tell the user to save on exit.
446 					entry_ref eref;
447 					_GetSettingsFile(&eref);
448 					if (ref == eref) fKeySetModified = false;
449 				} else {
450 					(new BAlert(ERROR,
451 						"Shortcuts was unable to parse your KeySet file!",
452 						"Okay"))->Go(NULL);
453 					break;
454 				}
455 			}
456 		}
457 		break;
458 
459 		// These messages come from the pop-up menu of the Applications column
460 		case SELECT_APPLICATION:
461 		{
462 			int csel = fColumnListView->CurrentSelection();
463 			if (csel >= 0) {
464 				entry_ref aref;
465 				if (msg->FindRef("refs", &aref) == B_NO_ERROR) {
466 					BEntry ent(&aref);
467 					if (ent.InitCheck() == B_NO_ERROR) {
468 						BPath path;
469 						if ((ent.GetPath(&path) == B_NO_ERROR)
470 							&& (((ShortcutsSpec *)
471 							fColumnListView->ItemAt(csel))->
472 							ProcessColumnTextString(ShortcutsSpec::
473 							STRING_COLUMN_INDEX, path.Path()))) {
474 
475 							fColumnListView->InvalidateItem(csel);
476 							_MarkKeySetModified();
477 						}
478 					}
479 				}
480 			}
481 		}
482 		break;
483 
484 		case SAVE_KEYSET:
485 		{
486 			bool showSaveError = false;
487 
488 			const char * name;
489 			entry_ref entry;
490 			if ((msg->FindString("name", &name) == B_NO_ERROR)
491 				&& (msg->FindRef("directory", &entry) == B_NO_ERROR)) {
492 				BDirectory dir(&entry);
493 				BEntry saveTo(&dir, name, true);
494 				showSaveError = ((saveTo.InitCheck() != B_NO_ERROR)
495 				|| (_SaveKeySet(saveTo) == false));
496 			} else if (fLastSaved.InitCheck() == B_NO_ERROR) {
497 				// We've saved this before, save over previous file.
498 				showSaveError = (_SaveKeySet(fLastSaved) == false);
499 			} else PostMessage(SAVE_KEYSET_AS); // open the save requester...
500 
501 			if (showSaveError) {
502 				(new BAlert(ERROR, "Shortcuts wasn't able to save your keyset."
503 				, "Okay"))->Go(NULL);
504 			}
505 		}
506 		break;
507 
508 		case SAVE_KEYSET_AS:
509 		{
510 			if (fSavePanel)
511 				fSavePanel->Show();
512 			else {
513 				BMessage msg(SAVE_KEYSET);
514 				BMessenger messenger(this);
515 				fSavePanel = new BFilePanel(B_SAVE_PANEL, &messenger, NULL, 0,
516 					false, &msg);
517 				fSavePanel->Show();
518 			}
519 		}
520 		break;
521 
522 		case B_ABOUT_REQUESTED:
523 			be_app_messenger.SendMessage(B_ABOUT_REQUESTED);
524 		break;
525 
526 		case ADD_HOTKEY_ITEM:
527 			_AddNewSpec(NULL);
528 		break;
529 
530 		case REMOVE_HOTKEY_ITEM:
531 		{
532 			int index = fColumnListView->CurrentSelection();
533 			if (index >= 0) {
534 				CLVListItem* item = (CLVListItem*)
535 					fColumnListView->ItemAt(index);
536 				fColumnListView->RemoveItem(index);
537 				delete item;
538 				_MarkKeySetModified();
539 
540 				// Rules for new selection: If there is an item at (index),
541 				// select it. Otherwise, if there is an item at (index-1),
542 				// select it. Otherwise, select nothing.
543 				int num = fColumnListView->CountItems();
544 				if (num > 0) {
545 					if (index < num)
546 						fColumnListView->Select(index);
547 					else {
548 						if (index > 0)
549 							index--;
550 						if (index < num)
551 							fColumnListView->Select(index);
552 					}
553 				}
554 			}
555 		}
556 		break;
557 
558 		// Received when the user clicks on the ColumnListView
559 		case HOTKEY_ITEM_SELECTED:
560 		{
561 			int32 index = -1;
562 			msg->FindInt32("index", &index);
563 			bool validItem = (index >= 0);
564 			fRemoveButton->SetEnabled(validItem);
565 		}
566 		break;
567 
568 		// Received when an entry is to be modified in response to GUI activity
569 		case HOTKEY_ITEM_MODIFIED:
570 		{
571 			int32 row, column;
572 
573 			if ((msg->FindInt32("row", &row) == B_NO_ERROR)
574 				&& (msg->FindInt32("column", &column) == B_NO_ERROR)) {
575 				int32 key;
576 				const char* bytes;
577 
578 				if (row >= 0) {
579 					ShortcutsSpec* item = (ShortcutsSpec*)
580 						fColumnListView->ItemAt(row);
581 					bool repaintNeeded = false; // default
582 
583 					if (msg->HasInt32("mouseClick")) {
584 						repaintNeeded = item->ProcessColumnMouseClick(column);
585 					} else if ((msg->FindString("bytes", &bytes) == B_NO_ERROR)
586 						&& (msg->FindInt32("key", &key) == B_NO_ERROR)) {
587 						repaintNeeded = item->ProcessColumnKeyStroke(column,
588 							bytes, key);
589 					} else if (msg->FindInt32("unmappedkey", &key) ==
590 						B_NO_ERROR) {
591 						repaintNeeded = ((column == item->KEY_COLUMN_INDEX)
592 							&& ((key > 0xFF) || (GetKeyName(key) != NULL))
593 							&& (item->ProcessColumnKeyStroke(column, NULL,
594 							key)));
595 					} else if (msg->FindString("text", &bytes) == B_NO_ERROR) {
596 						if ((bytes[0] == '(')&&(bytes[1] == 'C')) {
597 							if (fSelectPanel)
598 								fSelectPanel->Show();
599 							else {
600 								BMessage msg(SELECT_APPLICATION);
601 								BMessenger m(this);
602 								fSelectPanel = new BFilePanel(B_OPEN_PANEL, &m,
603 									NULL, 0, false, &msg);
604 								fSelectPanel->Show();
605 							}
606 							fSelectPanel->SetButtonLabel(B_DEFAULT_BUTTON,
607 								"Select");
608 						} else
609 							repaintNeeded = item->ProcessColumnTextString(
610 								column, bytes);
611 					}
612 
613 					if (repaintNeeded) {
614 						fColumnListView->InvalidateItem(row);
615 						_MarkKeySetModified();
616 					}
617 				}
618 			}
619 		}
620 		break;
621 
622 		default:
623 			BWindow::MessageReceived(msg);
624 		break;
625 	}
626 }
627 
628 
629 void
630 ShortcutsWindow::_MarkKeySetModified()
631 {
632 	if (fKeySetModified == false) {
633 		fKeySetModified = true;
634 		fSaveButton->SetEnabled(true);
635 	}
636 }
637 
638 
639 void
640 ShortcutsWindow::Quit()
641 {
642 	for (int i = fColumnListView->CountItems() - 1; i >= 0; i--)
643 		delete (ShortcutsSpec*)fColumnListView->ItemAt(i);
644 
645 	fColumnListView->MakeEmpty();
646 	BWindow::Quit();
647 }
648 
649 
650 void
651 ShortcutsWindow::FrameResized(float w, float h)
652 {
653 	fAddButton->ChangeToNewSize(w, h);
654 	fRemoveButton->ChangeToNewSize(w, h);
655 	fSaveButton->ChangeToNewSize(w, h);
656 }
657 
658 
659 void
660 ShortcutsWindow::DispatchMessage(BMessage* msg, BHandler* handler)
661 {
662 	switch(msg->what) {
663 		case B_COPY:
664 		case B_CUT:
665 			if (be_clipboard->Lock()) {
666 				int32 row = fColumnListView->CurrentSelection();
667 				int32 column = fColumnListView->GetSelectedColumn();
668 				if ((row >= 0)
669 					&& (column == ShortcutsSpec::STRING_COLUMN_INDEX)) {
670 					ShortcutsSpec* spec = (ShortcutsSpec*)
671 						fColumnListView->ItemAt(row);
672 					if (spec) {
673 						BMessage* data = be_clipboard->Data();
674 						data->RemoveName("text/plain");
675 						data->AddData("text/plain", B_MIME_TYPE,
676 							spec->GetCellText(column),
677 							strlen(spec->GetCellText(column)));
678 						be_clipboard->Commit();
679 
680 						if (msg->what == B_CUT) {
681 							spec->ProcessColumnTextString(column, "");
682 							_MarkKeySetModified();
683 							fColumnListView->InvalidateItem(row);
684 						}
685 					}
686 				}
687 				be_clipboard->Unlock();
688 			}
689 		break;
690 
691 		case B_PASTE:
692 			if (be_clipboard->Lock()) {
693 				BMessage* data = be_clipboard->Data();
694 				const char* text;
695 				ssize_t textLen;
696 				if (data->FindData("text/plain", B_MIME_TYPE, (const void**)
697 					&text, &textLen) == B_NO_ERROR) {
698 					int32 row = fColumnListView->CurrentSelection();
699 					int32 column = fColumnListView->GetSelectedColumn();
700 					if ((row >= 0)
701 						&& (column == ShortcutsSpec::STRING_COLUMN_INDEX)) {
702 						ShortcutsSpec* spec = (ShortcutsSpec*)
703 							fColumnListView->ItemAt(row);
704 						if (spec) {
705 							for (ssize_t i = 0; i < textLen; i++) {
706 								char buf[2] = {text[i], 0x00};
707 								spec->ProcessColumnKeyStroke(column, buf, 0);
708 							}
709 						}
710 						fColumnListView->InvalidateItem(row);
711 						_MarkKeySetModified();
712 					}
713 				}
714 				be_clipboard->Unlock();
715 			}
716 		break;
717 
718 		default:
719 			BWindow::DispatchMessage(msg, handler);
720 		break;
721 	}
722 }
723