libroot/posix/stdio: Remove unused portions.
[haiku.git] / src / preferences / shortcuts / ShortcutsSpec.cpp
blob3bb63e22634ce27338fcc70f69e84b6e7e9d981b
1 /*
2 * Copyright 1999-2009 Jeremy Friesner
3 * Copyright 2009-2010 Haiku, Inc. All rights reserved.
4 * Distributed under the terms of the MIT License.
6 * Authors:
7 * Jeremy Friesner
8 */
10 #include "ShortcutsSpec.h"
12 #include <ctype.h>
13 #include <stdio.h>
15 #include <Beep.h>
16 #include <Catalog.h>
17 #include <ColumnTypes.h>
18 #include <Directory.h>
19 #include <Locale.h>
20 #include <NodeInfo.h>
21 #include <Path.h>
22 #include <Region.h>
23 #include <Window.h>
25 #include "ColumnListView.h"
27 #include "BitFieldTesters.h"
28 #include "CommandActuators.h"
29 #include "KeyInfos.h"
30 #include "MetaKeyStateMap.h"
31 #include "ParseCommandLine.h"
34 #define CLASS "ShortcutsSpec : "
36 #undef B_TRANSLATION_CONTEXT
37 #define B_TRANSLATION_CONTEXT "ShortcutsSpec"
39 const float _height = 20.0f;
41 static MetaKeyStateMap sMetaMaps[ShortcutsSpec::NUM_META_COLUMNS];
43 static bool sFontCached = false;
44 static BFont sViewFont;
45 static float sFontHeight;
47 const char* ShortcutsSpec::sShiftName;
48 const char* ShortcutsSpec::sControlName;
49 const char* ShortcutsSpec::sOptionName;
50 const char* ShortcutsSpec::sCommandName;
53 #define ICON_BITMAP_RECT BRect(0.0f, 0.0f, 15.0f, 15.0f)
54 #define ICON_BITMAP_SPACE B_RGBA32
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)
62 for (int i = 0; i < pos; i++) {
63 if (str[i] == '\0')
64 return '\0';
66 return str[pos];
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)
75 map.SetInfo(name);
77 // In this state, neither key may be pressed.
78 map.AddState("(None)", new HasBitsFieldTester(0, both));
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));
84 // Here, only the left may be pressed
85 map.AddState("Left", new HasBitsFieldTester(left, right));
87 // Here, only the right may be pressed
88 map.AddState("Right", new HasBitsFieldTester(right, left));
90 // Here, both must be pressed.
91 map.AddState("Both", new HasBitsFieldTester(left | right));
95 MetaKeyStateMap&
96 GetNthKeyMap(int which)
98 return sMetaMaps[which];
102 /*static*/ void
103 ShortcutsSpec::InitializeMetaMaps()
105 static bool metaMapsInitialized = false;
106 if (metaMapsInitialized)
107 return;
108 metaMapsInitialized = true;
110 _InitModifierNames();
112 SetupStandardMap(sMetaMaps[ShortcutsSpec::SHIFT_COLUMN_INDEX], sShiftName,
113 B_SHIFT_KEY, B_LEFT_SHIFT_KEY, B_RIGHT_SHIFT_KEY);
115 SetupStandardMap(sMetaMaps[ShortcutsSpec::CONTROL_COLUMN_INDEX],
116 sControlName, B_CONTROL_KEY, B_LEFT_CONTROL_KEY, B_RIGHT_CONTROL_KEY);
118 SetupStandardMap(sMetaMaps[ShortcutsSpec::COMMAND_COLUMN_INDEX],
119 sCommandName, B_COMMAND_KEY, B_LEFT_COMMAND_KEY, B_RIGHT_COMMAND_KEY);
121 SetupStandardMap(sMetaMaps[ShortcutsSpec::OPTION_COLUMN_INDEX], sOptionName
122 , B_OPTION_KEY, B_LEFT_OPTION_KEY, B_RIGHT_OPTION_KEY);
126 ShortcutsSpec::ShortcutsSpec(const char* cmd)
128 BRow(),
129 fCommand(NULL),
130 fBitmap(ICON_BITMAP_RECT, ICON_BITMAP_SPACE),
131 fLastBitmapName(NULL),
132 fBitmapValid(false),
133 fKey(0),
134 fCursorPtsValid(false)
136 for (int i = 0; i < NUM_META_COLUMNS; i++)
137 fMetaCellStateIndex[i] = 0;
138 SetCommand(cmd);
142 ShortcutsSpec::ShortcutsSpec(const ShortcutsSpec& from)
144 BRow(),
145 fCommand(NULL),
146 fBitmap(ICON_BITMAP_RECT, ICON_BITMAP_SPACE),
147 fLastBitmapName(NULL),
148 fBitmapValid(false),
149 fKey(from.fKey),
150 fCursorPtsValid(false)
152 for (int i = 0; i < NUM_META_COLUMNS; i++)
153 fMetaCellStateIndex[i] = from.fMetaCellStateIndex[i];
155 SetCommand(from.fCommand);
156 SetSelectedColumn(from.GetSelectedColumn());
158 for (int i = 0; i < from.CountFields(); i++)
159 SetField(new BStringField(
160 static_cast<const BStringField*>(from.GetField(i))->String()), i);
164 ShortcutsSpec::ShortcutsSpec(BMessage* from)
166 BRow(),
167 fCommand(NULL),
168 fBitmap(ICON_BITMAP_RECT, ICON_BITMAP_SPACE),
169 fLastBitmapName(NULL),
170 fBitmapValid(false),
171 fCursorPtsValid(false)
173 const char* temp;
174 if (from->FindString("command", &temp) != B_NO_ERROR) {
175 printf(CLASS);
176 printf(" Error, no command string in archive BMessage!\n");
177 temp = "";
180 SetCommand(temp);
182 if (from->FindInt32("key", (int32*) &fKey) != B_NO_ERROR) {
183 printf(CLASS);
184 printf(" Error, no key int32 in archive BMessage!\n");
187 for (int i = 0; i < NUM_META_COLUMNS; i++)
188 if (from->FindInt32("mcidx", i, (int32*)&fMetaCellStateIndex[i])
189 != B_NO_ERROR) {
190 printf(CLASS);
191 printf(" Error, no modifiers int32 in archive BMessage!\n");
194 for (int i = 0; i <= STRING_COLUMN_INDEX; i++)
195 SetField(new BStringField(GetCellText(i)), i);
199 void
200 ShortcutsSpec::SetCommand(const char* command)
202 delete[] fCommand;
203 // out with the old (if any)...
204 fCommandLen = strlen(command) + 1;
205 fCommandNul = fCommandLen - 1;
206 fCommand = new char[fCommandLen];
207 strcpy(fCommand, command);
208 SetField(new BStringField(command), STRING_COLUMN_INDEX);
212 const char*
213 ShortcutsSpec::GetColumnName(int i)
215 return sMetaMaps[i].GetName();
219 status_t
220 ShortcutsSpec::Archive(BMessage* into, bool deep) const
222 status_t ret = BArchivable::Archive(into, deep);
223 if (ret != B_NO_ERROR)
224 return ret;
226 into->AddString("class", "ShortcutsSpec");
228 // These fields are for our prefs panel's benefit only
229 into->AddString("command", fCommand);
230 into->AddInt32("key", fKey);
232 // Assemble a BitFieldTester for the input_server add-on to use...
233 MinMatchFieldTester test(NUM_META_COLUMNS, false);
234 for (int i = 0; i < NUM_META_COLUMNS; i++) {
235 // for easy parsing by prefs applet on load-in
236 into->AddInt32("mcidx", fMetaCellStateIndex[i]);
237 test.AddSlave(sMetaMaps[i].GetNthStateTester(fMetaCellStateIndex[i]));
240 BMessage testerMsg;
241 ret = test.Archive(&testerMsg);
242 if (ret != B_NO_ERROR)
243 return ret;
245 into->AddMessage("modtester", &testerMsg);
247 // And also create a CommandActuator for the input_server add-on to execute
248 CommandActuator* act = CreateCommandActuator(fCommand);
249 BMessage actMsg;
250 ret = act->Archive(&actMsg);
251 if (ret != B_NO_ERROR)
252 return ret;
253 delete act;
255 into->AddMessage("act", &actMsg);
257 return ret;
261 BArchivable*
262 ShortcutsSpec::Instantiate(BMessage* from)
264 bool validateOK = false;
265 if (validate_instantiation(from, "ShortcutsSpec"))
266 validateOK = true;
267 else // test the old one.
268 if (validate_instantiation(from, "SpicyKeysSpec"))
269 validateOK = true;
271 if (!validateOK)
272 return NULL;
274 return new ShortcutsSpec(from);
278 ShortcutsSpec::~ShortcutsSpec()
280 delete[] fCommand;
281 delete[] fLastBitmapName;
285 void
286 ShortcutsSpec::_CacheViewFont(BView* owner)
288 if (sFontCached == false) {
289 sFontCached = true;
290 owner->GetFont(&sViewFont);
291 font_height fh;
292 sViewFont.GetHeight(&fh);
293 sFontHeight = fh.ascent - fh.descent;
298 const char*
299 ShortcutsSpec::GetCellText(int whichColumn) const
301 const char* temp = ""; // default
302 switch (whichColumn) {
303 case KEY_COLUMN_INDEX:
305 if ((fKey > 0) && (fKey <= 0xFF)) {
306 temp = GetKeyName(fKey);
307 if (temp == NULL)
308 temp = "";
309 } else if (fKey > 0xFF) {
310 sprintf(fScratch, "#%" B_PRIx32, fKey);
311 return fScratch;
313 break;
316 case STRING_COLUMN_INDEX:
317 temp = fCommand;
318 break;
320 default:
321 if ((whichColumn >= 0) && (whichColumn < NUM_META_COLUMNS))
322 temp = sMetaMaps[whichColumn].GetNthStateDesc(
323 fMetaCellStateIndex[whichColumn]);
324 if (temp[0] == '(')
325 temp = "";
326 break;
328 return temp;
332 bool
333 ShortcutsSpec::ProcessColumnMouseClick(int whichColumn)
335 if ((whichColumn >= 0) && (whichColumn < NUM_META_COLUMNS)) {
336 // same as hitting space for these columns: cycle entry
337 const char temp = B_SPACE;
339 // 3rd arg isn't correct but it isn't read for this case anyway
340 return ProcessColumnKeyStroke(whichColumn, &temp, 0);
342 return false;
346 bool
347 ShortcutsSpec::ProcessColumnTextString(int whichColumn, const char* string)
349 switch (whichColumn) {
350 case STRING_COLUMN_INDEX:
351 SetCommand(string);
352 return true;
353 break;
355 case KEY_COLUMN_INDEX:
357 fKey = FindKeyCode(string);
358 SetField(new BStringField(GetCellText(whichColumn)),
359 KEY_COLUMN_INDEX);
360 return true;
361 break;
364 default:
365 return ProcessColumnKeyStroke(whichColumn, string, 0);
370 bool
371 ShortcutsSpec::_AttemptTabCompletion()
373 bool result = false;
375 int32 argc;
376 char** argv = ParseArgvFromString(fCommand, argc);
377 if (argc > 0) {
378 // Try to complete the path partially expressed in the last argument!
379 char* arg = argv[argc - 1];
380 char* fileFragment = strrchr(arg, '/');
381 if (fileFragment != NULL) {
382 const char* directoryName = (fileFragment == arg) ? "/" : arg;
383 *fileFragment = '\0';
384 fileFragment++;
385 int fragmentLength = strlen(fileFragment);
387 BDirectory dir(directoryName);
388 if (dir.InitCheck() == B_NO_ERROR) {
389 BEntry nextEnt;
390 BPath nextPath;
391 BList matchList;
392 int maxEntryLen = 0;
394 // Read in all the files in the directory whose names start
395 // with our fragment.
396 while (dir.GetNextEntry(&nextEnt) == B_NO_ERROR) {
397 if (nextEnt.GetPath(&nextPath) == B_NO_ERROR) {
398 char* filePath = strrchr(nextPath.Path(), '/') + 1;
399 if (strncmp(filePath, fileFragment, fragmentLength) == 0) {
400 int len = strlen(filePath);
401 if (len > maxEntryLen)
402 maxEntryLen = len;
403 char* newStr = new char[len + 1];
404 strcpy(newStr, filePath);
405 matchList.AddItem(newStr);
410 // Now slowly extend our keyword to its full length, counting
411 // numbers of matches at each step. If the match list length
412 // is 1, we can use that whole entry. If it's greater than one,
413 // we can use just the match length.
414 int matchLen = matchList.CountItems();
415 if (matchLen > 0) {
416 int i;
417 BString result(fileFragment);
418 for (i = fragmentLength; i < maxEntryLen; i++) {
419 // See if all the matching entries have the same letter
420 // in the next position... if so, we can go farther.
421 char commonLetter = '\0';
422 for (int j = 0; j < matchLen; j++) {
423 char nextLetter = GetLetterAt(
424 (char*)matchList.ItemAt(j), i);
425 if (commonLetter == '\0')
426 commonLetter = nextLetter;
428 if ((commonLetter != '\0')
429 && (commonLetter != nextLetter)) {
430 commonLetter = '\0';// failed;
431 beep();
432 break;
435 if (commonLetter == '\0')
436 break;
437 else
438 result.Append(commonLetter, 1);
441 // free all the strings we allocated
442 for (int k = 0; k < matchLen; k++)
443 delete [] ((char*)matchList.ItemAt(k));
445 DoStandardEscapes(result);
447 BString wholeLine;
448 for (int l = 0; l < argc - 1; l++) {
449 wholeLine += argv[l];
450 wholeLine += " ";
453 BString file(directoryName);
454 DoStandardEscapes(file);
456 if (directoryName[strlen(directoryName) - 1] != '/')
457 file += "/";
459 file += result;
461 // Remove any trailing slash...
462 const char* fileStr = file.String();
463 if (fileStr[strlen(fileStr) - 1] == '/')
464 file.RemoveLast("/");
466 // and re-append it iff the file is a dir.
467 BDirectory testFileAsDir(file.String());
468 if ((strcmp(file.String(), "/") != 0)
469 && (testFileAsDir.InitCheck() == B_NO_ERROR))
470 file.Append("/");
472 wholeLine += file;
474 SetCommand(wholeLine.String());
475 result = true;
478 *(fileFragment - 1) = '/';
481 FreeArgv(argv);
483 return result;
487 bool
488 ShortcutsSpec::ProcessColumnKeyStroke(int whichColumn, const char* bytes,
489 int32 key)
491 bool result = false;
493 switch (whichColumn) {
494 case KEY_COLUMN_INDEX:
495 if (key > -1) {
496 if ((int32)fKey != key) {
497 fKey = key;
498 result = true;
501 break;
503 case STRING_COLUMN_INDEX:
505 switch (bytes[0]) {
506 case B_BACKSPACE:
507 case B_DELETE:
508 if (fCommandNul > 0) {
509 // trim a char off the string
510 fCommand[fCommandNul - 1] = '\0';
511 fCommandNul--; // note new nul position
512 result = true;
514 break;
516 case B_TAB:
517 if (_AttemptTabCompletion()) {
518 result = true;
519 } else
520 beep();
521 break;
523 default:
525 uint32 newCharLen = strlen(bytes);
526 if ((newCharLen > 0) && (bytes[0] >= ' ')) {
527 bool reAllocString = false;
528 // Make sure we have enough room in our command string
529 // to add these chars...
530 while (fCommandLen - fCommandNul <= newCharLen) {
531 reAllocString = true;
532 // enough for a while...
533 fCommandLen = (fCommandLen + 10) * 2;
536 if (reAllocString) {
537 char* temp = new char[fCommandLen];
538 strcpy(temp, fCommand);
539 delete [] fCommand;
540 fCommand = temp;
541 // fCommandNul is still valid since it's an offset
542 // and the string length is the same for now
545 // Here we should be guaranteed enough room.
546 strncat(fCommand, bytes, fCommandLen);
547 fCommandNul += newCharLen;
548 result = true;
552 break;
555 default:
556 if (whichColumn < 0 || whichColumn >= NUM_META_COLUMNS)
557 break;
559 MetaKeyStateMap * map = &sMetaMaps[whichColumn];
560 int curState = fMetaCellStateIndex[whichColumn];
561 int origState = curState;
562 int numStates = map->GetNumStates();
564 switch(bytes[0]) {
565 case B_RETURN:
566 // cycle to the previous state
567 curState = (curState + numStates - 1) % numStates;
568 break;
570 case B_SPACE:
571 // cycle to the next state
572 curState = (curState + 1) % numStates;
573 break;
575 default:
577 // Go to the state starting with the given letter, if
578 // any
579 char letter = bytes[0];
580 if (islower(letter))
581 letter = toupper(letter); // convert to upper case
583 if ((letter == B_BACKSPACE) || (letter == B_DELETE))
584 letter = '(';
585 // so space bar will blank out an entry
587 for (int i = 0; i < numStates; i++) {
588 const char* desc = map->GetNthStateDesc(i);
590 if (desc) {
591 if (desc[0] == letter) {
592 curState = i;
593 break;
595 } else {
596 puts(B_TRANSLATE(
597 "Error, NULL state description?"));
602 fMetaCellStateIndex[whichColumn] = curState;
604 if (curState != origState)
605 result = true;
608 SetField(new BStringField(GetCellText(whichColumn)), whichColumn);
610 return result;
614 /*static*/ void
615 ShortcutsSpec::_InitModifierNames()
617 sShiftName = B_TRANSLATE_COMMENT("Shift",
618 "Name for modifier on keyboard");
619 sControlName = B_TRANSLATE_COMMENT("Control",
620 "Name for modifier on keyboard");
621 // TODO: Wrapping in __INTEL__ define probably won't work to extract catkeys?
622 #if __INTEL__
623 sOptionName = B_TRANSLATE_COMMENT("Option",
624 "Name for modifier on keyboard");
625 sCommandName = B_TRANSLATE_COMMENT("Alt",
626 "Name for modifier on keyboard");
627 #else
628 sOptionName = B_TRANSLATE_COMMENT("Option",
629 "Name for modifier on keyboard");
630 sCommandName = B_TRANSLATE_COMMENT("Command",
631 "Name for modifier on keyboard");
632 #endif