2 * Copyright 1999-2009 Jeremy Friesner
3 * Copyright 2009-2010 Haiku, Inc. All rights reserved.
4 * Distributed under the terms of the MIT License.
10 #include "ShortcutsSpec.h"
17 #include <ColumnTypes.h>
18 #include <Directory.h>
25 #include "ColumnListView.h"
27 #include "BitFieldTesters.h"
28 #include "CommandActuators.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
60 GetLetterAt(const char* str
, int pos
)
62 for (int i
= 0; i
< pos
; i
++) {
70 // Setup the states in a standard manner for a pair of meta-keys.
72 SetupStandardMap(MetaKeyStateMap
& map
, const char* name
, uint32 both
,
73 uint32 left
, uint32 right
)
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
));
96 GetNthKeyMap(int which
)
98 return sMetaMaps
[which
];
103 ShortcutsSpec::InitializeMetaMaps()
105 static bool metaMapsInitialized
= false;
106 if (metaMapsInitialized
)
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
)
130 fBitmap(ICON_BITMAP_RECT
, ICON_BITMAP_SPACE
),
131 fLastBitmapName(NULL
),
134 fCursorPtsValid(false)
136 for (int i
= 0; i
< NUM_META_COLUMNS
; i
++)
137 fMetaCellStateIndex
[i
] = 0;
142 ShortcutsSpec::ShortcutsSpec(const ShortcutsSpec
& from
)
146 fBitmap(ICON_BITMAP_RECT
, ICON_BITMAP_SPACE
),
147 fLastBitmapName(NULL
),
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
)
168 fBitmap(ICON_BITMAP_RECT
, ICON_BITMAP_SPACE
),
169 fLastBitmapName(NULL
),
171 fCursorPtsValid(false)
174 if (from
->FindString("command", &temp
) != B_NO_ERROR
) {
176 printf(" Error, no command string in archive BMessage!\n");
182 if (from
->FindInt32("key", (int32
*) &fKey
) != B_NO_ERROR
) {
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
])
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
);
200 ShortcutsSpec::SetCommand(const char* command
)
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
);
213 ShortcutsSpec::GetColumnName(int i
)
215 return sMetaMaps
[i
].GetName();
220 ShortcutsSpec::Archive(BMessage
* into
, bool deep
) const
222 status_t ret
= BArchivable::Archive(into
, deep
);
223 if (ret
!= B_NO_ERROR
)
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
]));
241 ret
= test
.Archive(&testerMsg
);
242 if (ret
!= B_NO_ERROR
)
245 into
->AddMessage("modtester", &testerMsg
);
247 // And also create a CommandActuator for the input_server add-on to execute
248 CommandActuator
* act
= CreateCommandActuator(fCommand
);
250 ret
= act
->Archive(&actMsg
);
251 if (ret
!= B_NO_ERROR
)
255 into
->AddMessage("act", &actMsg
);
262 ShortcutsSpec::Instantiate(BMessage
* from
)
264 bool validateOK
= false;
265 if (validate_instantiation(from
, "ShortcutsSpec"))
267 else // test the old one.
268 if (validate_instantiation(from
, "SpicyKeysSpec"))
274 return new ShortcutsSpec(from
);
278 ShortcutsSpec::~ShortcutsSpec()
281 delete[] fLastBitmapName
;
286 ShortcutsSpec::_CacheViewFont(BView
* owner
)
288 if (sFontCached
== false) {
290 owner
->GetFont(&sViewFont
);
292 sViewFont
.GetHeight(&fh
);
293 sFontHeight
= fh
.ascent
- fh
.descent
;
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
);
309 } else if (fKey
> 0xFF) {
310 sprintf(fScratch
, "#%" B_PRIx32
, fKey
);
316 case STRING_COLUMN_INDEX
:
321 if ((whichColumn
>= 0) && (whichColumn
< NUM_META_COLUMNS
))
322 temp
= sMetaMaps
[whichColumn
].GetNthStateDesc(
323 fMetaCellStateIndex
[whichColumn
]);
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);
347 ShortcutsSpec::ProcessColumnTextString(int whichColumn
, const char* string
)
349 switch (whichColumn
) {
350 case STRING_COLUMN_INDEX
:
355 case KEY_COLUMN_INDEX
:
357 fKey
= FindKeyCode(string
);
358 SetField(new BStringField(GetCellText(whichColumn
)),
365 return ProcessColumnKeyStroke(whichColumn
, string
, 0);
371 ShortcutsSpec::_AttemptTabCompletion()
376 char** argv
= ParseArgvFromString(fCommand
, argc
);
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';
385 int fragmentLength
= strlen(fileFragment
);
387 BDirectory
dir(directoryName
);
388 if (dir
.InitCheck() == B_NO_ERROR
) {
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
)
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();
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;
435 if (commonLetter
== '\0')
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
);
448 for (int l
= 0; l
< argc
- 1; l
++) {
449 wholeLine
+= argv
[l
];
453 BString
file(directoryName
);
454 DoStandardEscapes(file
);
456 if (directoryName
[strlen(directoryName
) - 1] != '/')
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
))
474 SetCommand(wholeLine
.String());
478 *(fileFragment
- 1) = '/';
488 ShortcutsSpec::ProcessColumnKeyStroke(int whichColumn
, const char* bytes
,
493 switch (whichColumn
) {
494 case KEY_COLUMN_INDEX
:
496 if ((int32
)fKey
!= key
) {
503 case STRING_COLUMN_INDEX
:
508 if (fCommandNul
> 0) {
509 // trim a char off the string
510 fCommand
[fCommandNul
- 1] = '\0';
511 fCommandNul
--; // note new nul position
517 if (_AttemptTabCompletion()) {
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;
537 char* temp
= new char[fCommandLen
];
538 strcpy(temp
, fCommand
);
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
;
556 if (whichColumn
< 0 || whichColumn
>= NUM_META_COLUMNS
)
559 MetaKeyStateMap
* map
= &sMetaMaps
[whichColumn
];
560 int curState
= fMetaCellStateIndex
[whichColumn
];
561 int origState
= curState
;
562 int numStates
= map
->GetNumStates();
566 // cycle to the previous state
567 curState
= (curState
+ numStates
- 1) % numStates
;
571 // cycle to the next state
572 curState
= (curState
+ 1) % numStates
;
577 // Go to the state starting with the given letter, if
579 char letter
= bytes
[0];
581 letter
= toupper(letter
); // convert to upper case
583 if ((letter
== B_BACKSPACE
) || (letter
== B_DELETE
))
585 // so space bar will blank out an entry
587 for (int i
= 0; i
< numStates
; i
++) {
588 const char* desc
= map
->GetNthStateDesc(i
);
591 if (desc
[0] == letter
) {
597 "Error, NULL state description?"));
602 fMetaCellStateIndex
[whichColumn
] = curState
;
604 if (curState
!= origState
)
608 SetField(new BStringField(GetCellText(whichColumn
)), whichColumn
);
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?
623 sOptionName
= B_TRANSLATE_COMMENT("Option",
624 "Name for modifier on keyboard");
625 sCommandName
= B_TRANSLATE_COMMENT("Alt",
626 "Name for modifier on keyboard");
628 sOptionName
= B_TRANSLATE_COMMENT("Option",
629 "Name for modifier on keyboard");
630 sCommandName
= B_TRANSLATE_COMMENT("Command",
631 "Name for modifier on keyboard");