xref: /haiku/src/preferences/shortcuts/ShortcutsSpec.cpp (revision c90684742e7361651849be4116d0e5de3a817194)
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_TRANSLATE_CONTEXT
36 #define B_TRANSLATE_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_COLOR_8_BIT
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 	float textWidth = sViewFont.StringWidth(text);
364 	BPoint point;
365 	rgb_color lowColor = color;
366 
367 	if (column_index == STRING_COLUMN_INDEX) {
368 		// left justified
369 		point.Set(item_column_rect.left + STRING_COLUMN_LEFT_MARGIN,
370 			item_column_rect.top + fTextOffset);
371 
372 		item_column_rect.left = point.x;
373 			// keep text from drawing into icon area
374 
375 		// scroll if too wide
376 		float rectWidth = item_column_rect.Width() - STRING_COLUMN_LEFT_MARGIN;
377 		float extra = textWidth - rectWidth;
378 		if (extra > 0.0f)
379 			point.x -= extra;
380 	} else {
381 		if ((column_index < NUM_META_COLUMNS) && (text[0] == '('))
382 			return; // don't draw for this ...
383 
384 		if ((column_index <= NUM_META_COLUMNS) && (text[0] == '\0'))
385 			return; // don't draw for this ...
386 
387 		// centered
388 		point.Set((item_column_rect.left + item_column_rect.right) / 2.0,
389 			item_column_rect.top + fTextOffset);
390 		_CacheViewFont(owner);
391 		point.x -= textWidth / 2.0f;
392 	}
393 
394 	BRegion Region;
395 	Region.Include(item_column_rect);
396 	owner->ConstrainClippingRegion(&Region);
397 	if (column_index != STRING_COLUMN_INDEX) {
398 		const float KEY_MARGIN = 3.0f;
399 		const float CORNER_RADIUS = 3.0f;
400 		_CacheViewFont(owner);
401 
402 		// How about I draw a nice "key" background for this one?
403 		BRect textRect(point.x - KEY_MARGIN, (point.y-sFontHeight) - KEY_MARGIN
404 			, point.x + textWidth + KEY_MARGIN - 2.0f, point.y + KEY_MARGIN);
405 
406 		if (column_index == KEY_COLUMN_INDEX)
407 			lowColor = ReallyLightPurple;
408 		else
409 			lowColor = LightYellow;
410 
411 		owner->SetHighColor(lowColor);
412 		owner->FillRoundRect(textRect, CORNER_RADIUS, CORNER_RADIUS);
413 		owner->SetHighColor(Black);
414 		owner->StrokeRoundRect(textRect, CORNER_RADIUS, CORNER_RADIUS);
415 	}
416 
417 	owner->SetHighColor(Black);
418 	owner->SetLowColor(lowColor);
419 	owner->DrawString(text, point);
420 	// with a cursor at the end if highlighted
421 	if (column_index == STRING_COLUMN_INDEX) {
422 		// Draw cursor
423 		if ((columnSelected) && (selected)) {
424 			point.x += textWidth;
425 			point.y += (fTextOffset / 4.0f);
426 
427 			BPoint pt2 = point;
428 			pt2.y -= fTextOffset;
429 			owner->StrokeLine(point, pt2);
430 
431 			fCursorPt1 = point;
432 			fCursorPt2 = pt2;
433 			fCursorPtsValid = true;
434 		}
435 
436 		BRegion bitmapRegion;
437 		item_column_rect.left	-= (STRING_COLUMN_LEFT_MARGIN - 4.0f);
438 		item_column_rect.right	= item_column_rect.left + 16.0f;
439 		item_column_rect.top	+= 3.0f;
440 		item_column_rect.bottom	= item_column_rect.top + 16.0f;
441 
442 		bitmapRegion.Include(item_column_rect);
443 		owner->ConstrainClippingRegion(&bitmapRegion);
444 		owner->SetDrawingMode(B_OP_OVER);
445 
446 		if ((fCommand != NULL) && (fCommand[0] == '*'))
447 			owner->DrawBitmap(sActuatorBitmaps[fBitmapValid ? 1 : 0],
448 				ICON_BITMAP_RECT, item_column_rect);
449 		else
450 			// Draw icon, if any
451 			if (fBitmapValid)
452 				owner->DrawBitmap(&fBitmap, ICON_BITMAP_RECT,
453 					item_column_rect);
454 	}
455 
456 	owner->SetDrawingMode(B_OP_COPY);
457 	owner->ConstrainClippingRegion(NULL);
458 }
459 
460 
461 void
462 ShortcutsSpec::Update(BView* owner, const BFont* font)
463 {
464 	CLVListItem::Update(owner, font);
465 	font_height FontAttributes;
466 	be_plain_font->GetHeight(&FontAttributes);
467 	float fontHeight = ceil(FontAttributes.ascent) +
468 		ceil(FontAttributes.descent);
469 	fTextOffset = ceil(FontAttributes.ascent) + (Height() - fontHeight) / 2.0;
470 }
471 
472 
473 const char*
474 ShortcutsSpec::GetCellText(int whichColumn) const
475 {
476 	const char* temp = ""; // default
477 	switch(whichColumn) {
478 		case KEY_COLUMN_INDEX:
479 		{
480 			if ((fKey > 0) && (fKey <= 0xFF)) {
481 				temp = GetKeyName(fKey);
482 				if (temp == NULL)
483 					temp = "";
484 			} else if (fKey > 0xFF) {
485 				sprintf(fScratch, "#%lx", fKey);
486 				return fScratch;
487 			}
488 			break;
489 		}
490 
491 		case STRING_COLUMN_INDEX:
492 			temp = fCommand;
493 			break;
494 
495 		default:
496 			if ((whichColumn >= 0) && (whichColumn < NUM_META_COLUMNS))
497 				temp = sMetaMaps[whichColumn].GetNthStateDesc(
498 							fMetaCellStateIndex[whichColumn]);
499 			break;
500 	}
501 	return temp;
502 }
503 
504 
505 bool
506 ShortcutsSpec::ProcessColumnMouseClick(int whichColumn)
507 {
508 	if ((whichColumn >= 0) && (whichColumn < NUM_META_COLUMNS)) {
509 		// same as hitting space for these columns: cycle entry
510 		const char temp = B_SPACE;
511 
512 		// 3rd arg isn't correct but it isn't read for this case anyway
513 		return ProcessColumnKeyStroke(whichColumn, &temp, 0);
514 	}
515 	return false;
516 }
517 
518 
519 bool
520 ShortcutsSpec::ProcessColumnTextString(int whichColumn, const char* string)
521 {
522 	switch(whichColumn) {
523 		case STRING_COLUMN_INDEX:
524 			SetCommand(string);
525 			return true;
526 			break;
527 
528 		case KEY_COLUMN_INDEX:
529 		{
530 			fKey = FindKeyCode(string);
531 			return true;
532 			break;
533 		}
534 
535 		default:
536 			return ProcessColumnKeyStroke(whichColumn, string, 0);
537 	}
538 }
539 
540 
541 bool
542 ShortcutsSpec::_AttemptTabCompletion()
543 {
544 	bool ret = false;
545 
546 	int32 argc;
547 	char** argv = ParseArgvFromString(fCommand, argc);
548 	if (argc > 0) {
549 		// Try to complete the path partially expressed in the last argument!
550 		char* arg = argv[argc - 1];
551 		char* fileFragment = strrchr(arg, '/');
552 		if (fileFragment) {
553 			const char* directoryName = (fileFragment == arg) ? "/" : arg;
554 			*fileFragment = '\0';
555 			fileFragment++;
556 			int fragLen = strlen(fileFragment);
557 
558 			BDirectory dir(directoryName);
559 			if (dir.InitCheck() == B_NO_ERROR) {
560 				BEntry nextEnt;
561 				BPath nextPath;
562 				BList matchList;
563 				int maxEntryLen = 0;
564 
565 				// Read in all the files in the directory whose names start
566 				// with our fragment.
567 				while (dir.GetNextEntry(&nextEnt) == B_NO_ERROR) {
568 					if (nextEnt.GetPath(&nextPath) == B_NO_ERROR) {
569 						char* filePath = strrchr(nextPath.Path(), '/') + 1;
570 						if (strncmp(filePath, fileFragment, fragLen) == 0) {
571 							int len = strlen(filePath);
572 							if (len > maxEntryLen)
573 								maxEntryLen = len;
574 							char* newStr = new char[len + 1];
575 							strcpy(newStr, filePath);
576 							matchList.AddItem(newStr);
577 						}
578 					}
579 				}
580 
581 				// Now slowly extend our keyword to its full length, counting
582 				// numbers of matches at each step. If the match list length
583 				// is 1, we can use that whole entry. If it's greater than one
584 				// , we can use just the match length.
585 				int matchLen = matchList.CountItems();
586 				if (matchLen > 0) {
587 					int i;
588 					BString result(fileFragment);
589 					for (i = fragLen; i < maxEntryLen; i++) {
590 						// See if all the matching entries have the same letter
591 						// in the next position... if so, we can go farther.
592 						char commonLetter = '\0';
593 						for (int j = 0; j < matchLen; j++) {
594 							char nextLetter = GetLetterAt(
595 								(char*)matchList.ItemAt(j), i);
596 							if (commonLetter == '\0')
597 								commonLetter = nextLetter;
598 
599 							if ((commonLetter != '\0')
600 								&& (commonLetter != nextLetter)) {
601 								commonLetter = '\0';// failed;
602 								beep();
603 								break;
604 							}
605 						}
606 						if (commonLetter == '\0')
607 							break;
608 						else
609 							result.Append(commonLetter, 1);
610 					}
611 
612 					// Free all the strings we allocated
613 					for (int k = 0; k < matchLen; k++)
614 						delete [] ((char*)matchList.ItemAt(k));
615 
616 					DoStandardEscapes(result);
617 
618 					BString wholeLine;
619 					for (int l = 0; l < argc - 1; l++) {
620 						wholeLine += argv[l];
621 						wholeLine += " ";
622 					}
623 
624 					BString file(directoryName);
625 					DoStandardEscapes(file);
626 
627 					if (directoryName[strlen(directoryName) - 1] != '/')
628 						file += "/";
629 
630 					file += result;
631 
632 					// Remove any trailing slash...
633 					const char* fileStr = file.String();
634 					if (fileStr[strlen(fileStr)-1] == '/')
635 						file.RemoveLast("/");
636 
637 					// And re-append it iff the file is a dir.
638 					BDirectory testFileAsDir(file.String());
639 					if ((strcmp(file.String(), "/") != 0)
640 						&& (testFileAsDir.InitCheck() == B_NO_ERROR))
641 						file.Append("/");
642 
643 					wholeLine += file;
644 
645 					SetCommand(wholeLine.String());
646 					ret = true;
647 				}
648 			}
649 			*(fileFragment - 1) = '/';
650 		}
651 	}
652 	FreeArgv(argv);
653 	return ret;
654 }
655 
656 
657 bool
658 ShortcutsSpec::ProcessColumnKeyStroke(int whichColumn, const char* bytes,
659 	int32 key)
660 {
661 	bool ret = false;
662 	switch(whichColumn) {
663 		case KEY_COLUMN_INDEX:
664 			if (key > -1) {
665 				if ((int32)fKey != key) {
666 					fKey = key;
667 					ret = true;
668 				}
669 			}
670 			break;
671 
672 		case STRING_COLUMN_INDEX:
673 		{
674 			switch(bytes[0]) {
675 				case B_BACKSPACE:
676 				case B_DELETE:
677 					if (fCommandNul > 0) {
678 						// trim a char off the string
679 						fCommand[fCommandNul - 1] = '\0';
680 						fCommandNul--;	// note new nul position
681 						ret = true;
682 						_UpdateIconBitmap();
683 					}
684 				break;
685 
686 				case B_TAB:
687 					if (_AttemptTabCompletion()) {
688 						_UpdateIconBitmap();
689 						ret = true;
690 					} else
691 						beep();
692 				break;
693 
694 				default:
695 				{
696 					uint32 newCharLen = strlen(bytes);
697 					if ((newCharLen > 0) && (bytes[0] >= ' ')) {
698 						bool reAllocString = false;
699 						// Make sure we have enough room in our command string
700 						// to add these chars...
701 						while (fCommandLen - fCommandNul <= newCharLen) {
702 							reAllocString = true;
703 							// enough for a while...
704 							fCommandLen = (fCommandLen + 10) * 2;
705 						}
706 
707 						if (reAllocString) {
708 							char* temp = new char[fCommandLen];
709 							strcpy(temp, fCommand);
710 							delete [] fCommand;
711 							fCommand = temp;
712 							// fCommandNul is still valid since it's an offset
713 							// and the string length is the same for now
714 						}
715 
716 						// Here we should be guaranteed enough room.
717 						strncat(fCommand, bytes, fCommandLen);
718 						fCommandNul += newCharLen;
719 						ret = true;
720 						_UpdateIconBitmap();
721 					}
722 				}
723 			}
724 			break;
725 		}
726 
727 		default:
728 			if ((whichColumn >= 0) && (whichColumn < NUM_META_COLUMNS)) {
729 				MetaKeyStateMap * map = &sMetaMaps[whichColumn];
730 				int curState = fMetaCellStateIndex[whichColumn];
731 				int origState = curState;
732 				int numStates = map->GetNumStates();
733 
734 				switch(bytes[0])
735 				{
736 					case B_RETURN:
737 						// cycle to the previous state
738 						curState = (curState + numStates - 1) % numStates;
739 						break;
740 
741 					case B_SPACE:
742 						// cycle to the next state
743 						curState = (curState + 1) % numStates;
744 						break;
745 
746 					default:
747 					{
748 						// Go to the state starting with the given letter, if
749 						// any
750 						char letter = bytes[0];
751 						if (islower(letter))
752 							letter = toupper(letter); // convert to upper case
753 
754 						if ((letter == B_BACKSPACE) || (letter == B_DELETE))
755 							letter = '(';
756 								// so space bar will blank out an entry
757 
758 						for (int i = 0; i < numStates; i++) {
759 							const char* desc = map->GetNthStateDesc(i);
760 
761 							if (desc) {
762 								if (desc[0] == letter) {
763 									curState = i;
764 									break;
765 								}
766 							} else
767 								printf(B_TRANSLATE("Error, NULL state description?\n"));
768 						}
769 						break;
770 					}
771 				}
772 				fMetaCellStateIndex[whichColumn] = curState;
773 
774 				if (curState != origState)
775 					ret = true;
776 			}
777 			break;
778 	}
779 
780 	return ret;
781 }
782 
783 
784 int
785 ShortcutsSpec::MyCompare(const CLVListItem* a_Item1, const CLVListItem* a_Item2,
786 	int32 KeyColumn)
787 {
788 	ShortcutsSpec* left = (ShortcutsSpec*) a_Item1;
789 	ShortcutsSpec* right = (ShortcutsSpec*) a_Item2;
790 
791 	int ret = strcmp(left->GetCellText(KeyColumn),
792 		right->GetCellText(KeyColumn));
793 	return (ret > 0) ? 1 : ((ret == 0) ? 0 : -1);
794 }
795 
796 
797 void
798 ShortcutsSpec::Pulse(BView* owner)
799 {
800 	if ((fCursorPtsValid)&&(owner->Window()->IsActive())) {
801 		rgb_color prevColor = owner->HighColor();
802 		rgb_color backgroundColor = (GetSelectedColumn() ==
803 			STRING_COLUMN_INDEX) ? BeBackgroundGrey : BeListSelectGrey;
804 		rgb_color barColor = ((GetSelectedColumn() == STRING_COLUMN_INDEX)
805 			&& ((system_time() % 1000000) > 500000)) ? Black : backgroundColor;
806 		owner->SetHighColor(barColor);
807 		owner->StrokeLine(fCursorPt1, fCursorPt2);
808 		owner->SetHighColor(prevColor);
809 	}
810 }
811 
812 
813 void
814 ShortcutsSpec::_UpdateIconBitmap()
815 {
816 	BString firstWord = ParseArgvZeroFromString(fCommand);
817 
818 	// Only need to change if the first word has changed...
819 	if (fLastBitmapName == NULL || firstWord.Length() == 0
820 		|| firstWord.Compare(fLastBitmapName)) {
821 		if (firstWord.ByteAt(0) == '*')
822 			fBitmapValid = IsValidActuatorName(&firstWord.String()[1]);
823 		else {
824 			fBitmapValid = false; // default till we prove otherwise!
825 
826 			if (firstWord.Length() > 0) {
827 				delete [] fLastBitmapName;
828 				fLastBitmapName = new char[firstWord.Length() + 1];
829 				strcpy(fLastBitmapName, firstWord.String());
830 
831 				BEntry progEntry(fLastBitmapName, true);
832 				if ((progEntry.InitCheck() == B_NO_ERROR)
833 					&& (progEntry.Exists())) {
834 					BNode progNode(&progEntry);
835 					if (progNode.InitCheck() == B_NO_ERROR) {
836 						BNodeInfo progNodeInfo(&progNode);
837 						if ((progNodeInfo.InitCheck() == B_NO_ERROR)
838 						&& (progNodeInfo.GetTrackerIcon(&fBitmap, B_MINI_ICON)
839 							== B_NO_ERROR)) {
840 							fBitmapValid = fBitmap.IsValid();
841 						}
842 					}
843 				}
844 			}
845 		}
846 	}
847 }
848 
849 
850 /*static*/ void
851 ShortcutsSpec::_InitModifierNames()
852 {
853 	sShiftName = B_TRANSLATE_COMMENT("Shift",
854 		"Name for modifier on keyboard");
855 	sControlName = B_TRANSLATE_COMMENT("Control",
856 		"Name for modifier on keyboard");
857 // TODO: Wrapping in __INTEL__ define probably won't work to extract catkeys?
858 #if __INTEL__
859 	sOptionName = B_TRANSLATE_COMMENT("Window",
860 		"Name for modifier on keyboard");
861 	sCommandName = B_TRANSLATE_COMMENT("Alt",
862 		"Name for modifier on keyboard");
863 #else
864 	sOptionName = B_TRANSLATE_COMMENT("Option",
865 		"Name for modifier on keyboard");
866 	sCommandName = B_TRANSLATE_COMMENT("Command",
867 		"Name for modifier on keyboard");
868 #endif
869 }
870 
871