xref: /haiku/src/preferences/shortcuts/ShortcutsSpec.cpp (revision 1026b0a1a76dc88927bb8175c470f638dc5464ee)
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  */
8 
9 
10 #include "ShortcutsSpec.h"
11 
12 #include <ctype.h>
13 #include <stdio.h>
14 
15 #include <Beep.h>
16 #include <Catalog.h>
17 #include <Directory.h>
18 #include <Locale.h>
19 #include <NodeInfo.h>
20 #include <Path.h>
21 #include <Region.h>
22 #include <Window.h>
23 
24 #include "ColumnListView.h"
25 
26 #include "BitFieldTesters.h"
27 #include "Colors.h"
28 #include "CommandActuators.h"
29 #include "MetaKeyStateMap.h"
30 #include "ParseCommandLine.h"
31 
32 
33 #define CLASS "ShortcutsSpec : "
34 
35 #undef B_TRANSLATION_CONTEXT
36 #define B_TRANSLATION_CONTEXT "ShortcutsSpec"
37 
38 const float _height = 20.0f;
39 
40 static MetaKeyStateMap sMetaMaps[ShortcutsSpec::NUM_META_COLUMNS];
41 
42 static bool sFontCached = false;
43 static BFont sViewFont;
44 static float sFontHeight;
45 static BBitmap* sActuatorBitmaps[2];
46 
47 const char* ShortcutsSpec::sShiftName;
48 const char* ShortcutsSpec::sControlName;
49 const char* ShortcutsSpec::sOptionName;
50 const char* ShortcutsSpec::sCommandName;
51 
52 
53 #define ICON_BITMAP_RECT BRect(0.0f, 0.0f, 15.0f, 15.0f)
54 #define ICON_BITMAP_SPACE B_RGBA32
55 
56 
57 // Returns the (pos)'th char in the string, or '\0' if (pos) if off the end of
58 // the string
59 static char
60 GetLetterAt(const char* str, int pos)
61 {
62 	for (int i = 0; i < pos; i++) {
63 		if (str[i] == '\0')
64 			return '\0';
65 	}
66 	return str[pos];
67 }
68 
69 
70 // Setup the states in a standard manner for a pair of meta-keys.
71 static void
72 SetupStandardMap(MetaKeyStateMap& map, const char* name, uint32 both,
73 	uint32 left, uint32 right)
74 {
75 	map.SetInfo(name);
76 
77 	// In this state, neither key may be pressed.
78 	map.AddState("(None)", new HasBitsFieldTester(0, both));
79 
80 	// Here, either may be pressed. (Remember both is NOT a 2-bit chord, it's
81 	// another bit entirely)
82 	map.AddState("Either", new HasBitsFieldTester(both));
83 
84 	// Here, only the left may be pressed
85 	map.AddState("Left", new HasBitsFieldTester(left, right));
86 
87 	// Here, only the right may be pressed
88 	map.AddState("Right", new HasBitsFieldTester(right, left));
89 
90 	// Here, both must be pressed.
91 	map.AddState("Both", new HasBitsFieldTester(left | right));
92 }
93 
94 
95 MetaKeyStateMap&
96 GetNthKeyMap(int which)
97 {
98 	return sMetaMaps[which];
99 }
100 
101 
102 static BBitmap*
103 MakeActuatorBitmap(bool lit)
104 {
105 	BBitmap* map = new BBitmap(ICON_BITMAP_RECT, ICON_BITMAP_SPACE, true);
106 	const rgb_color yellow = {255, 255, 0};
107 	const rgb_color red = {200, 200, 200};
108 	const rgb_color black = {0, 0, 0};
109 	const BPoint points[10] = {
110 		BPoint(8, 0), BPoint(9.8, 5.8), BPoint(16, 5.8),
111 		BPoint(11, 9.0), BPoint(13, 16), BPoint(8, 11),
112 		BPoint(3, 16), BPoint(5, 9.0), BPoint(0, 5.8),
113 		BPoint(6.2, 5.8) };
114 
115 	BView* view = new BView(BRect(0, 0, 16, 16), NULL, B_FOLLOW_ALL_SIDES, 0L);
116 	map->AddChild(view);
117 	map->Lock();
118 	view->SetHighColor(B_TRANSPARENT_32_BIT);
119 	view->FillRect(ICON_BITMAP_RECT);
120 	view->SetHighColor(lit ? yellow : red);
121 	view->FillPolygon(points, 10);
122 	view->SetHighColor(black);
123 	view->StrokePolygon(points, 10);
124 	map->Unlock();
125 	map->RemoveChild(view);
126 	delete view;
127 	return map;
128 }
129 
130 
131 /*static*/ void
132 ShortcutsSpec::InitializeMetaMaps()
133 {
134 	static bool metaMapsInitialized = false;
135 	if (metaMapsInitialized)
136 		return;
137 	metaMapsInitialized = true;
138 
139 	_InitModifierNames();
140 
141 	SetupStandardMap(sMetaMaps[ShortcutsSpec::SHIFT_COLUMN_INDEX], sShiftName,
142 		B_SHIFT_KEY, B_LEFT_SHIFT_KEY, B_RIGHT_SHIFT_KEY);
143 
144 	SetupStandardMap(sMetaMaps[ShortcutsSpec::CONTROL_COLUMN_INDEX],
145 		sControlName, B_CONTROL_KEY, B_LEFT_CONTROL_KEY, B_RIGHT_CONTROL_KEY);
146 
147 	SetupStandardMap(sMetaMaps[ShortcutsSpec::COMMAND_COLUMN_INDEX],
148 		sCommandName, B_COMMAND_KEY, B_LEFT_COMMAND_KEY, B_RIGHT_COMMAND_KEY);
149 
150 	SetupStandardMap(sMetaMaps[ShortcutsSpec::OPTION_COLUMN_INDEX], sOptionName
151 		, B_OPTION_KEY, B_LEFT_OPTION_KEY, B_RIGHT_OPTION_KEY);
152 
153 	sActuatorBitmaps[0] = MakeActuatorBitmap(false);
154 	sActuatorBitmaps[1] = MakeActuatorBitmap(true);
155 }
156 
157 
158 ShortcutsSpec::ShortcutsSpec(const char* cmd)
159 	:
160 	CLVListItem(0, false, false, _height),
161 	fCommand(NULL),
162 	fTextOffset(0),
163 	fBitmap(ICON_BITMAP_RECT, ICON_BITMAP_SPACE),
164 	fLastBitmapName(NULL),
165 	fBitmapValid(false),
166 	fKey(0),
167 	fCursorPtsValid(false)
168 {
169 	for (int i = 0; i < NUM_META_COLUMNS; i++)
170 		fMetaCellStateIndex[i] = 0;
171 	SetCommand(cmd);
172 }
173 
174 
175 ShortcutsSpec::ShortcutsSpec(const ShortcutsSpec& from)
176 	:
177 	CLVListItem(0, false, false, _height),
178 	fCommand(NULL),
179 	fTextOffset(from.fTextOffset),
180 	fBitmap(ICON_BITMAP_RECT, ICON_BITMAP_SPACE),
181 	fLastBitmapName(NULL),
182 	fBitmapValid(false),
183 	fKey(from.fKey),
184 	fCursorPtsValid(false)
185 {
186 	for (int i = 0; i < NUM_META_COLUMNS; i++)
187 		fMetaCellStateIndex[i] = from.fMetaCellStateIndex[i];
188 
189 	SetCommand(from.fCommand);
190 	SetSelectedColumn(from.GetSelectedColumn());
191 }
192 
193 
194 ShortcutsSpec::ShortcutsSpec(BMessage* from)
195 	:
196 	CLVListItem(0, false, false, _height),
197 	fCommand(NULL),
198 	fTextOffset(0),
199 	fBitmap(ICON_BITMAP_RECT, ICON_BITMAP_SPACE),
200 	fLastBitmapName(NULL),
201 	fBitmapValid(false),
202 	fCursorPtsValid(false)
203 {
204 	const char* temp;
205 	if (from->FindString("command", &temp) != B_NO_ERROR) {
206 		printf(CLASS);
207 		printf(" Error, no command string in archive BMessage!\n");
208 		temp = "";
209 	}
210 
211 	SetCommand(temp);
212 
213 	if (from->FindInt32("key", (int32*) &fKey) != B_NO_ERROR) {
214 		printf(CLASS);
215 		printf(" Error, no key int32 in archive BMessage!\n");
216 	}
217 
218 	for (int i = 0; i < NUM_META_COLUMNS; i++)
219 		if (from->FindInt32("mcidx", i, (int32*)&fMetaCellStateIndex[i])
220 			!= B_NO_ERROR) {
221 			printf(CLASS);
222 			printf(" Error, no modifiers int32 in archive BMessage!\n");
223 		}
224 }
225 
226 
227 void
228 ShortcutsSpec::SetCommand(const char* command)
229 {
230 	delete[] fCommand;	// out with the old (if any)...
231 	fCommandLen = strlen(command) + 1;
232 	fCommandNul = fCommandLen - 1;
233 	fCommand = new char[fCommandLen];
234 	strcpy(fCommand, command);
235 	_UpdateIconBitmap();
236 }
237 
238 
239 const char*
240 ShortcutsSpec::GetColumnName(int i)
241 {
242 	return sMetaMaps[i].GetName();
243 }
244 
245 
246 status_t
247 ShortcutsSpec::Archive(BMessage* into, bool deep) const
248 {
249 	status_t ret = BArchivable::Archive(into, deep);
250 	if (ret != B_NO_ERROR)
251 		return ret;
252 
253 	into->AddString("class", "ShortcutsSpec");
254 
255 	// These fields are for our prefs panel's benefit only
256 	into->AddString("command", fCommand);
257 	into->AddInt32("key", fKey);
258 
259 	// Assemble a BitFieldTester for the input_server add-on to use...
260 	MinMatchFieldTester test(NUM_META_COLUMNS, false);
261 	for (int i = 0; i < NUM_META_COLUMNS; i++) {
262 		// for easy parsing by prefs applet on load-in
263 		into->AddInt32("mcidx", fMetaCellStateIndex[i]);
264 		test.AddSlave(sMetaMaps[i].GetNthStateTester(fMetaCellStateIndex[i]));
265 	}
266 
267 	BMessage testerMsg;
268 	ret = test.Archive(&testerMsg);
269 	if (ret != B_NO_ERROR)
270 		return ret;
271 
272 	into->AddMessage("modtester", &testerMsg);
273 
274 	// And also create a CommandActuator for the input_server add-on to execute
275 	CommandActuator* act = CreateCommandActuator(fCommand);
276 	BMessage actMsg;
277 	ret = act->Archive(&actMsg);
278 	if (ret != B_NO_ERROR)
279 		return ret;
280 	delete act;
281 
282 	into->AddMessage("act", &actMsg);
283 	return ret;
284 }
285 
286 
287 static bool IsValidActuatorName(const char* c);
288 static bool
289 IsValidActuatorName(const char* c)
290 {
291 	return (strcmp(c, B_TRANSLATE("InsertString")) == 0
292 		|| strcmp(c, B_TRANSLATE("MoveMouse")) == 0
293 		|| strcmp(c, B_TRANSLATE("MoveMouseTo")) == 0
294 		|| strcmp(c, B_TRANSLATE("MouseButton")) == 0
295 		|| strcmp(c, B_TRANSLATE("LaunchHandler")) == 0
296 		|| strcmp(c, B_TRANSLATE("Multi")) == 0
297 		|| strcmp(c, B_TRANSLATE("MouseDown")) == 0
298 		|| strcmp(c, B_TRANSLATE("MouseUp")) == 0
299 		|| strcmp(c, B_TRANSLATE("SendMessage")) == 0
300 		|| strcmp(c, B_TRANSLATE("Beep")) == 0);
301 }
302 
303 
304 BArchivable*
305 ShortcutsSpec::Instantiate(BMessage* from)
306 {
307 	bool validateOK = false;
308 	if (validate_instantiation(from, "ShortcutsSpec"))
309 		validateOK = true;
310 	else // test the old one.
311 		if (validate_instantiation(from, "SpicyKeysSpec"))
312 			validateOK = true;
313 
314 	if (!validateOK)
315 		return NULL;
316 
317 	return new ShortcutsSpec(from);
318 }
319 
320 
321 ShortcutsSpec::~ShortcutsSpec()
322 {
323 	delete[] fCommand;
324 	delete[] fLastBitmapName;
325 }
326 
327 
328 void
329 ShortcutsSpec::_CacheViewFont(BView* owner)
330 {
331 	if (sFontCached == false) {
332 		sFontCached = true;
333 		owner->GetFont(&sViewFont);
334 		font_height fh;
335 		sViewFont.GetHeight(&fh);
336 		sFontHeight = fh.ascent - fh.descent;
337 	}
338 }
339 
340 
341 void
342 ShortcutsSpec::DrawItemColumn(BView* owner, BRect item_column_rect,
343 	int32 column_index, bool columnSelected, bool complete)
344 {
345 	const float STRING_COLUMN_LEFT_MARGIN = 25.0f; // 16 for the icon,+9 empty
346 
347 	rgb_color color;
348 	bool selected = IsSelected();
349 	if (selected)
350 		color = columnSelected ? BeBackgroundGrey : BeListSelectGrey;
351 	else
352 		color = BeInactiveControlGrey;
353 	owner->SetLowColor(color);
354 	owner->SetDrawingMode(B_OP_COPY);
355 	owner->SetHighColor(color);
356 	owner->FillRect(item_column_rect);
357 
358 	const char* text = GetCellText(column_index);
359 
360 	if (text == NULL)
361 		return;
362 
363 	_CacheViewFont(owner);
364 		// Ensure that sViewFont is configured before using it to calculate
365 		// widths.  The lack of this call was causing the initial display of
366 		// columns to be incorrect, with a "jump" as all the columns correct
367 		// themselves upon the first column resize.
368 
369 	float textWidth = sViewFont.StringWidth(text);
370 	BPoint point;
371 	rgb_color lowColor = color;
372 
373 	if (column_index == STRING_COLUMN_INDEX) {
374 		// left justified
375 		point.Set(item_column_rect.left + STRING_COLUMN_LEFT_MARGIN,
376 			item_column_rect.top + fTextOffset);
377 
378 		item_column_rect.left = point.x;
379 			// keep text from drawing into icon area
380 
381 		// scroll if too wide
382 		float rectWidth = item_column_rect.Width() - STRING_COLUMN_LEFT_MARGIN;
383 		float extra = textWidth - rectWidth;
384 		if (extra > 0.0f)
385 			point.x -= extra;
386 	} else {
387 		if ((column_index < NUM_META_COLUMNS) && (text[0] == '('))
388 			return; // don't draw for this ...
389 
390 		if ((column_index <= NUM_META_COLUMNS) && (text[0] == '\0'))
391 			return; // don't draw for this ...
392 
393 		// centered
394 		point.Set((item_column_rect.left + item_column_rect.right) / 2.0,
395 			item_column_rect.top + fTextOffset);
396 		_CacheViewFont(owner);
397 		point.x -= textWidth / 2.0f;
398 	}
399 
400 	BRegion Region;
401 	Region.Include(item_column_rect);
402 	owner->ConstrainClippingRegion(&Region);
403 	if (column_index != STRING_COLUMN_INDEX) {
404 		const float KEY_MARGIN = 3.0f;
405 		const float CORNER_RADIUS = 3.0f;
406 		_CacheViewFont(owner);
407 
408 		// How about I draw a nice "key" background for this one?
409 		BRect textRect(point.x - KEY_MARGIN, (point.y-sFontHeight) - KEY_MARGIN
410 			, point.x + textWidth + KEY_MARGIN - 2.0f, point.y + KEY_MARGIN);
411 
412 		if (column_index == KEY_COLUMN_INDEX)
413 			lowColor = ReallyLightPurple;
414 		else
415 			lowColor = LightYellow;
416 
417 		owner->SetHighColor(lowColor);
418 		owner->FillRoundRect(textRect, CORNER_RADIUS, CORNER_RADIUS);
419 		owner->SetHighColor(Black);
420 		owner->StrokeRoundRect(textRect, CORNER_RADIUS, CORNER_RADIUS);
421 	}
422 
423 	owner->SetHighColor(Black);
424 	owner->SetLowColor(lowColor);
425 	owner->DrawString(text, point);
426 	// with a cursor at the end if highlighted
427 	if (column_index == STRING_COLUMN_INDEX) {
428 		// Draw cursor
429 		if ((columnSelected) && (selected)) {
430 			point.x += textWidth;
431 			point.y += (fTextOffset / 4.0f);
432 
433 			BPoint pt2 = point;
434 			pt2.y -= fTextOffset;
435 			owner->StrokeLine(point, pt2);
436 
437 			fCursorPt1 = point;
438 			fCursorPt2 = pt2;
439 			fCursorPtsValid = true;
440 		}
441 
442 		BRegion bitmapRegion;
443 		item_column_rect.left	-= (STRING_COLUMN_LEFT_MARGIN - 4.0f);
444 		item_column_rect.right	= item_column_rect.left + 16.0f;
445 		item_column_rect.top	+= 3.0f;
446 		item_column_rect.bottom	= item_column_rect.top + 16.0f;
447 
448 		bitmapRegion.Include(item_column_rect);
449 		owner->ConstrainClippingRegion(&bitmapRegion);
450 		owner->SetDrawingMode(B_OP_ALPHA);
451 
452 		if ((fCommand != NULL) && (fCommand[0] == '*'))
453 			owner->DrawBitmap(sActuatorBitmaps[fBitmapValid ? 1 : 0],
454 				ICON_BITMAP_RECT, item_column_rect);
455 		else
456 			// Draw icon, if any
457 			if (fBitmapValid)
458 				owner->DrawBitmap(&fBitmap, ICON_BITMAP_RECT,
459 					item_column_rect);
460 	}
461 
462 	owner->SetDrawingMode(B_OP_COPY);
463 	owner->ConstrainClippingRegion(NULL);
464 }
465 
466 
467 void
468 ShortcutsSpec::Update(BView* owner, const BFont* font)
469 {
470 	CLVListItem::Update(owner, font);
471 	font_height FontAttributes;
472 	be_plain_font->GetHeight(&FontAttributes);
473 	float fontHeight = ceil(FontAttributes.ascent) +
474 		ceil(FontAttributes.descent);
475 	fTextOffset = ceil(FontAttributes.ascent) + (Height() - fontHeight) / 2.0;
476 }
477 
478 
479 const char*
480 ShortcutsSpec::GetCellText(int whichColumn) const
481 {
482 	const char* temp = ""; // default
483 	switch(whichColumn) {
484 		case KEY_COLUMN_INDEX:
485 		{
486 			if ((fKey > 0) && (fKey <= 0xFF)) {
487 				temp = GetKeyName(fKey);
488 				if (temp == NULL)
489 					temp = "";
490 			} else if (fKey > 0xFF) {
491 				sprintf(fScratch, "#%lx", fKey);
492 				return fScratch;
493 			}
494 			break;
495 		}
496 
497 		case STRING_COLUMN_INDEX:
498 			temp = fCommand;
499 			break;
500 
501 		default:
502 			if ((whichColumn >= 0) && (whichColumn < NUM_META_COLUMNS))
503 				temp = sMetaMaps[whichColumn].GetNthStateDesc(
504 							fMetaCellStateIndex[whichColumn]);
505 			break;
506 	}
507 	return temp;
508 }
509 
510 
511 bool
512 ShortcutsSpec::ProcessColumnMouseClick(int whichColumn)
513 {
514 	if ((whichColumn >= 0) && (whichColumn < NUM_META_COLUMNS)) {
515 		// same as hitting space for these columns: cycle entry
516 		const char temp = B_SPACE;
517 
518 		// 3rd arg isn't correct but it isn't read for this case anyway
519 		return ProcessColumnKeyStroke(whichColumn, &temp, 0);
520 	}
521 	return false;
522 }
523 
524 
525 bool
526 ShortcutsSpec::ProcessColumnTextString(int whichColumn, const char* string)
527 {
528 	switch(whichColumn) {
529 		case STRING_COLUMN_INDEX:
530 			SetCommand(string);
531 			return true;
532 			break;
533 
534 		case KEY_COLUMN_INDEX:
535 		{
536 			fKey = FindKeyCode(string);
537 			return true;
538 			break;
539 		}
540 
541 		default:
542 			return ProcessColumnKeyStroke(whichColumn, string, 0);
543 	}
544 }
545 
546 
547 bool
548 ShortcutsSpec::_AttemptTabCompletion()
549 {
550 	bool ret = false;
551 
552 	int32 argc;
553 	char** argv = ParseArgvFromString(fCommand, argc);
554 	if (argc > 0) {
555 		// Try to complete the path partially expressed in the last argument!
556 		char* arg = argv[argc - 1];
557 		char* fileFragment = strrchr(arg, '/');
558 		if (fileFragment) {
559 			const char* directoryName = (fileFragment == arg) ? "/" : arg;
560 			*fileFragment = '\0';
561 			fileFragment++;
562 			int fragLen = strlen(fileFragment);
563 
564 			BDirectory dir(directoryName);
565 			if (dir.InitCheck() == B_NO_ERROR) {
566 				BEntry nextEnt;
567 				BPath nextPath;
568 				BList matchList;
569 				int maxEntryLen = 0;
570 
571 				// Read in all the files in the directory whose names start
572 				// with our fragment.
573 				while (dir.GetNextEntry(&nextEnt) == B_NO_ERROR) {
574 					if (nextEnt.GetPath(&nextPath) == B_NO_ERROR) {
575 						char* filePath = strrchr(nextPath.Path(), '/') + 1;
576 						if (strncmp(filePath, fileFragment, fragLen) == 0) {
577 							int len = strlen(filePath);
578 							if (len > maxEntryLen)
579 								maxEntryLen = len;
580 							char* newStr = new char[len + 1];
581 							strcpy(newStr, filePath);
582 							matchList.AddItem(newStr);
583 						}
584 					}
585 				}
586 
587 				// Now slowly extend our keyword to its full length, counting
588 				// numbers of matches at each step. If the match list length
589 				// is 1, we can use that whole entry. If it's greater than one
590 				// , we can use just the match length.
591 				int matchLen = matchList.CountItems();
592 				if (matchLen > 0) {
593 					int i;
594 					BString result(fileFragment);
595 					for (i = fragLen; i < maxEntryLen; i++) {
596 						// See if all the matching entries have the same letter
597 						// in the next position... if so, we can go farther.
598 						char commonLetter = '\0';
599 						for (int j = 0; j < matchLen; j++) {
600 							char nextLetter = GetLetterAt(
601 								(char*)matchList.ItemAt(j), i);
602 							if (commonLetter == '\0')
603 								commonLetter = nextLetter;
604 
605 							if ((commonLetter != '\0')
606 								&& (commonLetter != nextLetter)) {
607 								commonLetter = '\0';// failed;
608 								beep();
609 								break;
610 							}
611 						}
612 						if (commonLetter == '\0')
613 							break;
614 						else
615 							result.Append(commonLetter, 1);
616 					}
617 
618 					// Free all the strings we allocated
619 					for (int k = 0; k < matchLen; k++)
620 						delete [] ((char*)matchList.ItemAt(k));
621 
622 					DoStandardEscapes(result);
623 
624 					BString wholeLine;
625 					for (int l = 0; l < argc - 1; l++) {
626 						wholeLine += argv[l];
627 						wholeLine += " ";
628 					}
629 
630 					BString file(directoryName);
631 					DoStandardEscapes(file);
632 
633 					if (directoryName[strlen(directoryName) - 1] != '/')
634 						file += "/";
635 
636 					file += result;
637 
638 					// Remove any trailing slash...
639 					const char* fileStr = file.String();
640 					if (fileStr[strlen(fileStr)-1] == '/')
641 						file.RemoveLast("/");
642 
643 					// And re-append it iff the file is a dir.
644 					BDirectory testFileAsDir(file.String());
645 					if ((strcmp(file.String(), "/") != 0)
646 						&& (testFileAsDir.InitCheck() == B_NO_ERROR))
647 						file.Append("/");
648 
649 					wholeLine += file;
650 
651 					SetCommand(wholeLine.String());
652 					ret = true;
653 				}
654 			}
655 			*(fileFragment - 1) = '/';
656 		}
657 	}
658 	FreeArgv(argv);
659 	return ret;
660 }
661 
662 
663 bool
664 ShortcutsSpec::ProcessColumnKeyStroke(int whichColumn, const char* bytes,
665 	int32 key)
666 {
667 	bool ret = false;
668 	switch(whichColumn) {
669 		case KEY_COLUMN_INDEX:
670 			if (key > -1) {
671 				if ((int32)fKey != key) {
672 					fKey = key;
673 					ret = true;
674 				}
675 			}
676 			break;
677 
678 		case STRING_COLUMN_INDEX:
679 		{
680 			switch(bytes[0]) {
681 				case B_BACKSPACE:
682 				case B_DELETE:
683 					if (fCommandNul > 0) {
684 						// trim a char off the string
685 						fCommand[fCommandNul - 1] = '\0';
686 						fCommandNul--;	// note new nul position
687 						ret = true;
688 						_UpdateIconBitmap();
689 					}
690 				break;
691 
692 				case B_TAB:
693 					if (_AttemptTabCompletion()) {
694 						_UpdateIconBitmap();
695 						ret = true;
696 					} else
697 						beep();
698 				break;
699 
700 				default:
701 				{
702 					uint32 newCharLen = strlen(bytes);
703 					if ((newCharLen > 0) && (bytes[0] >= ' ')) {
704 						bool reAllocString = false;
705 						// Make sure we have enough room in our command string
706 						// to add these chars...
707 						while (fCommandLen - fCommandNul <= newCharLen) {
708 							reAllocString = true;
709 							// enough for a while...
710 							fCommandLen = (fCommandLen + 10) * 2;
711 						}
712 
713 						if (reAllocString) {
714 							char* temp = new char[fCommandLen];
715 							strcpy(temp, fCommand);
716 							delete [] fCommand;
717 							fCommand = temp;
718 							// fCommandNul is still valid since it's an offset
719 							// and the string length is the same for now
720 						}
721 
722 						// Here we should be guaranteed enough room.
723 						strncat(fCommand, bytes, fCommandLen);
724 						fCommandNul += newCharLen;
725 						ret = true;
726 						_UpdateIconBitmap();
727 					}
728 				}
729 			}
730 			break;
731 		}
732 
733 		default:
734 			if ((whichColumn >= 0) && (whichColumn < NUM_META_COLUMNS)) {
735 				MetaKeyStateMap * map = &sMetaMaps[whichColumn];
736 				int curState = fMetaCellStateIndex[whichColumn];
737 				int origState = curState;
738 				int numStates = map->GetNumStates();
739 
740 				switch(bytes[0])
741 				{
742 					case B_RETURN:
743 						// cycle to the previous state
744 						curState = (curState + numStates - 1) % numStates;
745 						break;
746 
747 					case B_SPACE:
748 						// cycle to the next state
749 						curState = (curState + 1) % numStates;
750 						break;
751 
752 					default:
753 					{
754 						// Go to the state starting with the given letter, if
755 						// any
756 						char letter = bytes[0];
757 						if (islower(letter))
758 							letter = toupper(letter); // convert to upper case
759 
760 						if ((letter == B_BACKSPACE) || (letter == B_DELETE))
761 							letter = '(';
762 								// so space bar will blank out an entry
763 
764 						for (int i = 0; i < numStates; i++) {
765 							const char* desc = map->GetNthStateDesc(i);
766 
767 							if (desc) {
768 								if (desc[0] == letter) {
769 									curState = i;
770 									break;
771 								}
772 							} else
773 								printf(B_TRANSLATE("Error, NULL state description?\n"));
774 						}
775 						break;
776 					}
777 				}
778 				fMetaCellStateIndex[whichColumn] = curState;
779 
780 				if (curState != origState)
781 					ret = true;
782 			}
783 			break;
784 	}
785 
786 	return ret;
787 }
788 
789 
790 int
791 ShortcutsSpec::MyCompare(const CLVListItem* a_Item1, const CLVListItem* a_Item2,
792 	int32 KeyColumn)
793 {
794 	ShortcutsSpec* left = (ShortcutsSpec*) a_Item1;
795 	ShortcutsSpec* right = (ShortcutsSpec*) a_Item2;
796 
797 	int ret = strcmp(left->GetCellText(KeyColumn),
798 		right->GetCellText(KeyColumn));
799 	return (ret > 0) ? 1 : ((ret == 0) ? 0 : -1);
800 }
801 
802 
803 void
804 ShortcutsSpec::Pulse(BView* owner)
805 {
806 	if ((fCursorPtsValid)&&(owner->Window()->IsActive())) {
807 		rgb_color prevColor = owner->HighColor();
808 		rgb_color backgroundColor = (GetSelectedColumn() ==
809 			STRING_COLUMN_INDEX) ? BeBackgroundGrey : BeListSelectGrey;
810 		rgb_color barColor = ((GetSelectedColumn() == STRING_COLUMN_INDEX)
811 			&& ((system_time() % 1000000) > 500000)) ? Black : backgroundColor;
812 		owner->SetHighColor(barColor);
813 		owner->StrokeLine(fCursorPt1, fCursorPt2);
814 		owner->SetHighColor(prevColor);
815 	}
816 }
817 
818 
819 void
820 ShortcutsSpec::_UpdateIconBitmap()
821 {
822 	BString firstWord = ParseArgvZeroFromString(fCommand);
823 
824 	// Only need to change if the first word has changed...
825 	if (fLastBitmapName == NULL || firstWord.Length() == 0
826 		|| firstWord.Compare(fLastBitmapName)) {
827 		if (firstWord.ByteAt(0) == '*')
828 			fBitmapValid = IsValidActuatorName(&firstWord.String()[1]);
829 		else {
830 			fBitmapValid = false; // default till we prove otherwise!
831 
832 			if (firstWord.Length() > 0) {
833 				delete [] fLastBitmapName;
834 				fLastBitmapName = new char[firstWord.Length() + 1];
835 				strcpy(fLastBitmapName, firstWord.String());
836 
837 				BEntry progEntry(fLastBitmapName, true);
838 				if ((progEntry.InitCheck() == B_NO_ERROR)
839 					&& (progEntry.Exists())) {
840 					BNode progNode(&progEntry);
841 					if (progNode.InitCheck() == B_NO_ERROR) {
842 						BNodeInfo progNodeInfo(&progNode);
843 						if ((progNodeInfo.InitCheck() == B_NO_ERROR)
844 						&& (progNodeInfo.GetTrackerIcon(&fBitmap, B_MINI_ICON)
845 							== B_NO_ERROR)) {
846 							fBitmapValid = fBitmap.IsValid();
847 						}
848 					}
849 				}
850 			}
851 		}
852 	}
853 }
854 
855 
856 /*static*/ void
857 ShortcutsSpec::_InitModifierNames()
858 {
859 	sShiftName = B_TRANSLATE_COMMENT("Shift",
860 		"Name for modifier on keyboard");
861 	sControlName = B_TRANSLATE_COMMENT("Control",
862 		"Name for modifier on keyboard");
863 // TODO: Wrapping in __INTEL__ define probably won't work to extract catkeys?
864 #if __INTEL__
865 	sOptionName = B_TRANSLATE_COMMENT("Window",
866 		"Name for modifier on keyboard");
867 	sCommandName = B_TRANSLATE_COMMENT("Alt",
868 		"Name for modifier on keyboard");
869 #else
870 	sOptionName = B_TRANSLATE_COMMENT("Option",
871 		"Name for modifier on keyboard");
872 	sCommandName = B_TRANSLATE_COMMENT("Command",
873 		"Name for modifier on keyboard");
874 #endif
875 }
876 
877