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