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
38const float _height = 20.0f;
39
40static MetaKeyStateMap sMetaMaps[ShortcutsSpec::NUM_META_COLUMNS];
41
42static bool sFontCached = false;
43static BFont sViewFont;
44static float sFontHeight;
45static BBitmap* sActuatorBitmaps[2];
46
47const char* ShortcutsSpec::sShiftName;
48const char* ShortcutsSpec::sControlName;
49const char* ShortcutsSpec::sOptionName;
50const 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
59static char
60GetLetterAt(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.
71static void
72SetupStandardMap(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
95MetaKeyStateMap&
96GetNthKeyMap(int which)
97{
98	return sMetaMaps[which];
99}
100
101
102static BBitmap*
103MakeActuatorBitmap(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
132ShortcutsSpec::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
158ShortcutsSpec::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
175ShortcutsSpec::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
194ShortcutsSpec::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
227void
228ShortcutsSpec::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
239const char*
240ShortcutsSpec::GetColumnName(int i)
241{
242	return sMetaMaps[i].GetName();
243}
244
245
246status_t
247ShortcutsSpec::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
287static bool IsValidActuatorName(const char* c);
288static bool
289IsValidActuatorName(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
304BArchivable*
305ShortcutsSpec::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
321ShortcutsSpec::~ShortcutsSpec()
322{
323	delete[] fCommand;
324	delete[] fLastBitmapName;
325}
326
327
328void
329ShortcutsSpec::_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
341void
342ShortcutsSpec::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
467void
468ShortcutsSpec::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
479const char*
480ShortcutsSpec::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
511bool
512ShortcutsSpec::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
525bool
526ShortcutsSpec::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
547bool
548ShortcutsSpec::_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
663bool
664ShortcutsSpec::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
790int
791ShortcutsSpec::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
803void
804ShortcutsSpec::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
819void
820ShortcutsSpec::_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
857ShortcutsSpec::_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("Option",
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