1 /*
2 * Copyright 2009, Axel Dörfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7 #include "KeyboardLayout.h"
8
9 #include <ctype.h>
10 #include <stdarg.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <vector>
14
15 #include <File.h>
16 #include <InterfaceDefs.h>
17
18
19 #undef TRACE
20
21 //#define TRACE_LAYOUT
22 #ifdef TRACE_LAYOUT
23 # define TRACE(x...) printf(x)
24 #else
25 # define TRACE(x...)
26 #endif
27
28
KeyboardLayout()29 KeyboardLayout::KeyboardLayout()
30 :
31 fKeys(NULL),
32 fKeyCount(0),
33 fKeyCapacity(0),
34 fIndicators(5, true),
35 fIsDefault(true)
36 {
37 SetDefault();
38 }
39
40
~KeyboardLayout()41 KeyboardLayout::~KeyboardLayout()
42 {
43 free(fKeys);
44 }
45
46
47 const char*
Name()48 KeyboardLayout::Name()
49 {
50 return fName.String();
51 }
52
53
54 int32
CountKeys()55 KeyboardLayout::CountKeys()
56 {
57 return fKeyCount;
58 }
59
60
61 Key*
KeyAt(int32 index)62 KeyboardLayout::KeyAt(int32 index)
63 {
64 if (index < 0 || index >= fKeyCount)
65 return NULL;
66
67 return &fKeys[index];
68 }
69
70
71 int32
CountIndicators()72 KeyboardLayout::CountIndicators()
73 {
74 return fIndicators.CountItems();
75 }
76
77
78 Indicator*
IndicatorAt(int32 index)79 KeyboardLayout::IndicatorAt(int32 index)
80 {
81 return fIndicators.ItemAt(index);
82 }
83
84
85 BRect
Bounds()86 KeyboardLayout::Bounds()
87 {
88 return fBounds;
89 }
90
91
92 BSize
DefaultKeySize()93 KeyboardLayout::DefaultKeySize()
94 {
95 return fDefaultKeySize;
96 }
97
98
99 int32
IndexForModifier(int32 modifier)100 KeyboardLayout::IndexForModifier(int32 modifier)
101 {
102 switch (modifier) {
103 case B_CAPS_LOCK:
104 return 58;
105 case B_NUM_LOCK:
106 return 33;
107 case B_SCROLL_LOCK:
108 return 14;
109 case B_LEFT_SHIFT_KEY:
110 return 74;
111 case B_RIGHT_SHIFT_KEY:
112 return 85;
113 case B_LEFT_CONTROL_KEY:
114 return 91;
115 case B_RIGHT_CONTROL_KEY:
116 return 98;
117 case B_LEFT_OPTION_KEY:
118 return 92;
119 case B_RIGHT_OPTION_KEY:
120 return 96;
121 case B_LEFT_COMMAND_KEY:
122 return 93;
123 case B_RIGHT_COMMAND_KEY:
124 return 95;
125 case B_MENU_KEY:
126 return 97;
127 }
128
129 return 0;
130 }
131
132
133 status_t
Load(const char * path)134 KeyboardLayout::Load(const char* path)
135 {
136 entry_ref ref;
137 get_ref_for_path(path, &ref);
138
139 return Load(ref);
140 }
141
142
143 status_t
Load(entry_ref & ref)144 KeyboardLayout::Load(entry_ref& ref)
145 {
146 BFile file;
147 status_t status = file.SetTo(&ref, B_READ_ONLY);
148 if (status != B_OK)
149 return status;
150
151 off_t size;
152 status = file.GetSize(&size);
153 if (status != B_OK)
154 return status;
155
156 if (size > 65536) {
157 // We don't accept files larger than this
158 return B_BAD_VALUE;
159 }
160
161 char* data = (char*)malloc(size + 1);
162 if (data == NULL)
163 return B_NO_MEMORY;
164
165 ssize_t bytesRead = file.Read(data, size);
166 if (bytesRead != size) {
167 free(data);
168
169 if (bytesRead < 0)
170 return bytesRead;
171
172 return B_IO_ERROR;
173 }
174
175 data[size] = '\0';
176
177 status = _InitFrom(data);
178 if (status == B_OK)
179 fIsDefault = false;
180
181 free(data);
182
183 return status;
184 }
185
186
187 void
SetDefault()188 KeyboardLayout::SetDefault()
189 {
190 // The keyboard layout description language defines the position,
191 // size, shape, and scancodes of the keys on the keyboard, row by
192 // row.
193 // You can define variables that are substituted in the row
194 // descriptions. Variables are always prefixed with a '$' symbol.
195
196 // On top level, only two different terms are accepted: value pairs,
197 // and row descriptions.
198 // Value pairs are in the form "<name> = <value>". There are only two
199 // predefined names at this moment "name" that specifies the name of
200 // a layout, and "default-size" that specifies the size of keys when
201 // no specific size is given. Also, variables can be specified this
202 // way.
203
204 // Row descriptions are embedded in the '[' and ']' brackets.
205 // The first term within a row is the position of the row, written as
206 // "<x>,<y>;" - the delimiter between terms/keys is usually the
207 // semi-colon. After the initial position, key descriptions are expected
208 // until the row is closed with a ']'.
209
210 // A key description is of the form "<shape>:<scancodes>", where <shape>
211 // is combined of the shape of the character, and its size, written as
212 // "<kind><width>,<height>[,<second-row-width>]".
213 // The kind can either be 'r' (the default, can also be omitted) for
214 // rectangular keys, or 'l' for the enter key. Additionally, you can use
215 // the 'd' character to mark dark keys. The size can be omitted completely,
216 // in which case the defined "default-size" is used. The default size is
217 // also used to determine the height of the first row of the enter key;
218 // the "<second-row-width>" specifier is only valid for enter keys, too.
219
220 // The scancodes can be written in different ways:
221 // 1) "+<count>": adds <count> keys, the scancodes will be used relative
222 // to the previous keys.
223 // 2) "<first>-<last>": keys are added for scancode <first> to scancode
224 // <last>.
225 // 3) "<first>+<count>": one key with the <first> scancode is added, plus
226 // <count> ones with relative scancode from that one.
227
228 // Finally, you can also define LED indicator. Those can be made instead
229 // of a key, it accepts a size, but no shape modifiers. Also, instead of
230 // a scancode, the name of the modifer is given, currently only the
231 // following are allowed: "led-num", "led-caps", and "led-scroll".
232
233 // See for examples in the default layout below.
234 #if 1
235 static const char* kDefaultLayout105 =
236 "name = Generic 105-key International\n"
237 // Size shortcuts
238 "default-size = 10,10\n"
239 "$b = 5,10\n"
240 "$c = 20,10\n"
241 "$d = 15,10\n"
242 "$e = l15,20,9\n"
243 "$f = 10,20\n"
244 "$g = 13,10\n"
245 // Key rows
246 "[ 0,0; d:0x01; :-; :+4; $b:-; d:+4; $b:-; :+4; $b:-; d:+3; $b:-; "
247 "$g:led-num; $g:led-caps; $g:led-scroll ]\n"
248 "[ 0,20; :+13; d$c:+; $b:-; d:+3; $b:-; d:+4 ]\n"
249 "[ 0,30; d$d:0x26; :+12; d$e:0x47; $b:-; d:0x34-0x36; $b:-; :+3; "
250 "d$f:+1 ]\n"
251 "[ 0,40; d19,10:0x3b; :+11; :0x33; 51,10:-; :0x48-0x4a ]\n"
252 "[ 0,50; d$g:0x4b; :0x69; :0x4c+9; d27,10:+1; 15,10:-; d:+1; "
253 " 15,10:-; :+3; d$f:+1 ]\n"
254 "[ 0,60; d$g:0x5c; d$g:0x66; d$g:0x5d; 59,10:+1; d$g:+1; d$g:0x67+1; "
255 "d$g:0x60; $b:-; d:+3; $b:-; $c:+1; :+1 ]\n";
256
257 _InitFrom(kDefaultLayout105);
258 #endif
259 #if 0
260 static const char* kDefaultLayout104 = "name = Generic 104-key\n"
261 // Size shortcuts
262 "default-size = 10,10\n"
263 "$b = 5,10\n"
264 "$c = 20,10\n"
265 "$d = 15,10\n"
266 "$enter = d20,10\n"
267 "$f = 10,20\n"
268 "$g = 13,10\n"
269 // Key rows
270 "[ 0,0; d:0x01; :-; :+4; $b:-; d:+4; $b:-; :+4; $b:-; d:+3; $b:-; "
271 "$g:led-num; $g:led-caps; $g:led-scroll ]\n"
272 "[ 0,20; :+13; d$c:+; $b:-; d:+3; $b:-; d:+4 ]\n"
273 "[ 0,30; d$d:0x26; :+12; $d:+1; $b:-; d:+3; $b:-; :+3; "
274 "d$f:+1 ]\n"
275 "[ 0,40; d$c:0x3b; :+11; $enter:+1; 40,10:-; :+3 ]\n"
276 "[ 0,50; d24,10:0x4b; :+10; d26,10:+1; 15,10:-; d:+1; "
277 " 15,10:-; :+3; d$f:+1 ]\n"
278 "[ 0,60; d$g:0x5c; d$g:0x66; d$g:0x5d; 59,10:+1; d$g:+1; d$g:0x67+1; "
279 "d$g:0x60; $b:-; d:+3; $b:-; $c:+1; :+1 ]\n";
280
281 _InitFrom(kDefaultLayout104);
282 #endif
283 #if 0
284 static const char* kIBMLaptop = "name = IBM Laptop International\n"
285 // Size shortcuts
286 "default-size = 18,18\n"
287 "$s = 17,10\n"
288 "$gap = 6,10\n"
289 "$sgap = 5,10\n"
290 "$backspace = 38,18\n"
291 "$tab = 28,18\n"
292 "$caps = 32,18\n"
293 "$enter = l28,36,22\n"
294 "$l-shift-ctrl = 23,18\n"
295 "$r-shift = 51,18\n"
296 "$option = 13,18\n"
297 "$space = 95,18\n"
298 // Key rows
299 "[ 0,0; $s:0x01; 148,10:-; $s:0x0e+2; $sgap:-; $s:0x1f+2; ]\n"
300 "[ 0,10; $s:0x02+3; $gap:-; $s:+4; $gap:-; $s:+4; $sgap:-; "
301 "$s:0x34+2; ]\n"
302 "[ 0,20; :0x11+12; $backspace:+1 ]\n"
303 "[ 0,38; $tab:0x26; :+12; $enter:0x47; ]\n"
304 "[ 0,56; $caps:0x3b; :+11; :0x33 ]\n"
305 "[ 0,74; $l-shift-ctrl:0x4b; :0x69; :0x4c+9; $r-shift:+1 ]\n"
306 "[ 0,92; :0x99; $l-shift-ctrl:0x5c; $option:0x66; :0x5d; $space:+1; "
307 ":+1; :0x68; :0x60; $s:0x9a; $s:0x57; $s:0x9b ]\n"
308 "[ 221,102; $s:0x61+2; ]\n";
309
310 _InitFrom(kIBMLaptop);
311 #endif
312 fIsDefault = true;
313 }
314
315
316 void
_FreeKeys()317 KeyboardLayout::_FreeKeys()
318 {
319 free(fKeys);
320 fKeys = NULL;
321 fKeyCount = 0;
322 fKeyCapacity = 0;
323 fBounds = BRect();
324
325 fIndicators.MakeEmpty();
326 }
327
328
329 void
_Error(const parse_state & state,const char * reason,...)330 KeyboardLayout::_Error(const parse_state& state, const char* reason, ...)
331 {
332 va_list args;
333 va_start(args, reason);
334
335 fprintf(stderr, "Syntax error in line %" B_PRId32 ": ", state.line);
336 vfprintf(stderr, reason, args);
337 fprintf(stderr, "\n");
338
339 va_end(args);
340 }
341
342
343 void
_AddAlternateKeyCode(Key * key,int32 modifier,int32 code)344 KeyboardLayout::_AddAlternateKeyCode(Key* key, int32 modifier, int32 code)
345 {
346 if (key == NULL)
347 return;
348
349 // TODO: search for free spot
350 }
351
352
353 bool
_AddKey(const Key & key)354 KeyboardLayout::_AddKey(const Key& key)
355 {
356 TRACE(" add %ld (%g,%g)\n", key.code, key.frame.left, key.frame.top);
357 if (fKeyCount + 1 > fKeyCapacity) {
358 // enlarge array
359 int32 newCapacity = fKeyCapacity + 32;
360 Key* newKeys = (Key*)realloc((void*)fKeys, newCapacity * sizeof(Key));
361 if (newKeys == NULL)
362 return false;
363
364 fKeys = newKeys;
365 fKeyCapacity = newCapacity;
366 }
367
368 fKeys[fKeyCount] = key;
369 fKeyCount++;
370
371 fBounds = key.frame | fBounds;
372
373 return true;
374 }
375
376
377 void
_SkipCommentsAndSpace(parse_state & state,const char * & data)378 KeyboardLayout::_SkipCommentsAndSpace(parse_state& state, const char*& data)
379 {
380 while (data[0] != '\0') {
381 while (isspace(data[0])) {
382 if (data[0] == '\n')
383 state.line++;
384 data++;
385 }
386
387 if (data[0] == '#') {
388 // skip comment
389 while (data[0] != '\0' && data[0] != '\n') {
390 data++;
391 }
392 } else
393 break;
394 }
395 }
396
397
398 void
_Trim(BString & string,bool stripComments)399 KeyboardLayout::_Trim(BString& string, bool stripComments)
400 {
401 // Strip leading spaces
402 int32 i = 0;
403 while (isspace(string[i])) {
404 i++;
405 }
406 if (i > 0)
407 string.Remove(0, i);
408
409 // Remove comments
410 if (stripComments) {
411 i = string.FindFirst('#');
412 if (i >= 0)
413 string.Truncate(i);
414 }
415
416 // Strip trailing spaces
417 i = string.Length() - 1;
418 while (i > 0 && isspace(string[i])) {
419 i--;
420 }
421 string.Truncate(i + 1);
422 }
423
424
425 bool
_GetPair(const parse_state & state,const char * & data,BString & name,BString & value)426 KeyboardLayout::_GetPair(const parse_state& state, const char*& data,
427 BString& name, BString& value)
428 {
429 // Get name
430 name = "";
431 while (data[0] != '\0' && data[0] != '=') {
432 name += data[0];
433 data++;
434 }
435
436 if (data[0] != '=') {
437 _Error(state, "no valid pair");
438 return false;
439 }
440
441 // Skip sign
442 data++;
443
444 // Get value
445 value = "";
446 while (data[0] != '\0' && data[0] != '\n') {
447 value += data[0];
448 data++;
449 }
450
451 _Trim(name, false);
452 _Trim(value, true);
453
454 return true;
455 }
456
457
458 bool
_AddKeyCodes(const parse_state & state,BPoint & rowLeftTop,Key & key,const char * data,int32 & lastCount)459 KeyboardLayout::_AddKeyCodes(const parse_state& state, BPoint& rowLeftTop,
460 Key& key, const char* data, int32& lastCount)
461 {
462 if (data[0] == '-') {
463 // no key, just free space
464 int32 num = strtoul(data + 1, NULL, 0);
465 if (num < 1)
466 num = 1;
467 else if (num > 32) {
468 _Error(state, "empty key count too large");
469 return false;
470 }
471
472 key.frame.OffsetTo(rowLeftTop);
473 rowLeftTop.x = key.frame.left + key.frame.Width() * num;
474 return true;
475 }
476
477 int32 modifier = 0;
478
479 if (isalpha(data[0])) {
480 bool led = false;
481 if (!strcmp("led-caps", data)) {
482 modifier = B_CAPS_LOCK;
483 led = true;
484 } else if (!strcmp("led-num", data)) {
485 modifier = B_NUM_LOCK;
486 led = true;
487 } else if (!strcmp("led-scroll", data)) {
488 modifier = B_SCROLL_LOCK;
489 led = true;
490 } else {
491 // TODO: get modifier (ie. "num")
492 }
493
494 if (led) {
495 key.frame.OffsetTo(rowLeftTop);
496 rowLeftTop.x = key.frame.right;
497 fBounds = key.frame | fBounds;
498
499 Indicator* indicator = new(std::nothrow) Indicator;
500 if (indicator != NULL) {
501 indicator->modifier = modifier;
502 indicator->frame = key.frame;
503
504 fIndicators.AddItem(indicator);
505 }
506 return true;
507 }
508 }
509
510 int32 first;
511 int32 last;
512 int32 num = 1;
513
514 if (data[0] == '+') {
515 num = strtoul(data + 1, NULL, 0);
516 if (num < 1)
517 num = 1;
518 else if (num > 32) {
519 _Error(state, "key count too large");
520 return false;
521 }
522
523 if (fKeyCount > 0)
524 first = fKeys[fKeyCount - 1].code + 1;
525 else
526 first = 1;
527
528 last = first + num - 1;
529 } else {
530 char* end;
531 first = strtoul(data, &end, 0);
532 last = first;
533
534 if (end[0] == '-') {
535 last = strtoul(end + 1, NULL, 0);
536 if (first > last) {
537 _Error(state, "invalid key code specifier");
538 return false;
539 }
540
541 num = last - first;
542 } else if (end[0] == '+') {
543 num = strtoul(end + 1, NULL, 0) + 1;
544 last = first + num - 1;
545 } else if (end[0] != '\0') {
546 _Error(state, "invalid key range");
547 return false;
548 }
549 }
550
551 if (lastCount != 0) {
552 // update existing keys
553 if (lastCount != num) {
554 _Error(state, "modifier key mismatch");
555 return false;
556 }
557
558 for (int32 i = fKeyCount - num; i < fKeyCount; i++, first++) {
559 Key* key = KeyAt(i);
560
561 _AddAlternateKeyCode(key, modifier, first);
562 }
563 } else {
564 // add new keys
565 for (int32 i = first; i <= last; i++) {
566 key.code = i;
567
568 // "layout"
569 key.frame.OffsetTo(rowLeftTop);
570 rowLeftTop.x = key.frame.right;
571
572 _AddKey(key);
573 }
574
575 lastCount = num;
576 }
577
578 return true;
579 }
580
581
582 bool
_GetSize(const parse_state & state,const char * data,float & x,float & y,float * _secondRow)583 KeyboardLayout::_GetSize(const parse_state& state, const char* data,
584 float& x, float& y, float* _secondRow)
585 {
586 if (data[0] == '\0') {
587 // default size
588 x = fDefaultKeySize.width;
589 y = fDefaultKeySize.height;
590 return true;
591 }
592
593 float secondRow = 0;
594 int num = sscanf(data, "%g,%g,%g", &x, &y, &secondRow);
595 if (num < 2) {
596 _Error(state, "invalid size");
597 return false;
598 }
599
600 if (_secondRow != NULL)
601 *_secondRow = secondRow;
602 return true;
603 }
604
605
606 bool
_GetShape(const parse_state & state,const char * data,Key & key)607 KeyboardLayout::_GetShape(const parse_state& state, const char* data, Key& key)
608 {
609 // the default
610 key.shape = kRectangleKeyShape;
611 key.dark = false;
612
613 while (isalpha(data[0])) {
614 switch (tolower(data[0])) {
615 case 'r':
616 key.shape = kRectangleKeyShape;
617 break;
618 case 'c':
619 key.shape = kCircleKeyShape;
620 break;
621 case 'l':
622 key.shape = kEnterKeyShape;
623 break;
624 case 'd':
625 key.dark = true;
626 break;
627
628 default:
629 _Error(state, "unknown shape specifier '%c'", data[0]);
630 return false;
631 }
632
633 data++;
634 }
635
636 float width, height;
637 if (!_GetSize(state, data, width, height, &key.second_row))
638 return false;
639
640 // don't accept second row with anything but kEnterKeyShape
641 if ((key.shape != kEnterKeyShape && key.second_row != 0)
642 || (key.shape == kEnterKeyShape && key.second_row == 0)) {
643 _Error(state, "shape size mismatch");
644 return false;
645 }
646
647 key.frame.left = 0;
648 key.frame.top = 0;
649 key.frame.right = width;
650 key.frame.bottom = height;
651
652 return true;
653 }
654
655
656 /*! Returns the term delimiter expected in a certain parse mode. */
657 const char*
_Delimiter(parse_mode mode)658 KeyboardLayout::_Delimiter(parse_mode mode)
659 {
660 switch (mode) {
661 default:
662 case kSize:
663 return "";
664 case kRowStart:
665 return ";";
666
667 case kKeyShape:
668 return ":";
669 case kKeyCodes:
670 return ";:";
671 }
672 }
673
674
675 bool
_GetTerm(const char * & data,const char * delimiter,BString & term,bool closingBracketAllowed)676 KeyboardLayout::_GetTerm(const char*& data, const char* delimiter,
677 BString& term, bool closingBracketAllowed)
678 {
679 // Get term
680 term = "";
681 while (data[0] != '\0' && strchr(delimiter, data[0]) == NULL
682 && data[0] != '\n' && data[0] != '#'
683 && (!closingBracketAllowed || data[0] != ']')) {
684 term += data[0];
685 data++;
686 }
687
688 if (data[0] == '\0' && delimiter[0])
689 return false;
690
691 _Trim(term, true);
692 return true;
693 }
694
695
696 bool
_SubstituteVariables(BString & term,VariableMap & variables,BString & unknown)697 KeyboardLayout::_SubstituteVariables(BString& term, VariableMap& variables,
698 BString& unknown)
699 {
700 while (true) {
701 int32 index = term.FindFirst('$');
702 if (index < 0)
703 break;
704
705 // find variable name
706
707 VariableMap::iterator iterator = variables.begin();
708 VariableMap::iterator best = variables.end();
709 int32 bestLength = 0;
710
711 for (; iterator != variables.end(); iterator++) {
712 const BString& name = iterator->first;
713 if (!term.CompareAt(index, name, name.Length())
714 && name.Length() > bestLength) {
715 best = iterator;
716 bestLength = name.Length();
717 }
718 }
719
720 if (best != variables.end()) {
721 // got one, replace it
722 term.Remove(index, bestLength);
723 term.Insert(best->second.String(), index);
724 } else {
725 // variable has not been found
726 int32 length = 1;
727 while (isalpha(term[length + index])) {
728 length++;
729 }
730 term.Truncate(length + index);
731 return false;
732 }
733 }
734
735 return true;
736 }
737
738
739 bool
_ParseTerm(const parse_state & state,const char * & data,BString & term,VariableMap & variables)740 KeyboardLayout::_ParseTerm(const parse_state& state, const char*& data,
741 BString& term, VariableMap& variables)
742 {
743 if (!_GetTerm(data, _Delimiter(state.mode), term,
744 state.mode == kKeyCodes)) {
745 _Error(state, state.mode == kRowStart
746 ? "no valid row start" : "invalid term");
747 return false;
748 }
749
750 BString unknown;
751 if (!_SubstituteVariables(term, variables, unknown)) {
752 _Error(state, "Unknown variable \"%s\"", unknown.String());
753 return false;
754 }
755
756 return true;
757 }
758
759
760 /*! Initializes the keyboard layout from the data given.
761 The string has to be a valid keyboard layout description, otherwise
762 an error is returned.
763 */
764 status_t
_InitFrom(const char * data)765 KeyboardLayout::_InitFrom(const char* data)
766 {
767 _FreeKeys();
768
769 VariableMap variables;
770 BPoint rowLeftTop;
771 int32 lastKeyCount = 0;
772 Key key;
773
774 parse_state state = {kPairs, 1};
775
776 while (data[0] != '\0') {
777 _SkipCommentsAndSpace(state, data);
778
779 if (data[0] == '[') {
780 state.mode = kRowStart;
781
782 rowLeftTop = BPoint(0, 0);
783 data++;
784 continue;
785 } else if (data[0] == '\0')
786 break;
787
788 switch (state.mode) {
789 case kPairs:
790 {
791 BString name;
792 BString value;
793 if (!_GetPair(state, data, name, value))
794 return B_BAD_VALUE;
795
796 TRACE("<%s> = <%s>\n", name.String(), value.String());
797 if (name == "name")
798 fName = value;
799 else if (name == "default-size") {
800 const char* valueString = value.String();
801 parse_state tempState = {kSize, state.line};
802 BString term;
803 if (!_ParseTerm(tempState, valueString, term, variables))
804 return B_BAD_VALUE;
805
806 TRACE(" size = %s\n", term.String());
807 if (!_GetSize(state, term.String(), fDefaultKeySize.width,
808 fDefaultKeySize.height))
809 return B_BAD_VALUE;
810 } else if (name[0] == '$')
811 variables[name] = value;
812 break;
813 }
814
815 case kRowStart:
816 case kKeyShape:
817 case kKeyCodes:
818 {
819 if (data[0] == ']') {
820 if (state.mode == kKeyShape) {
821 state.mode = kPairs;
822 data++;
823 continue;
824 }
825 _Error(state, "unexpected row closing bracket");
826 return B_BAD_VALUE;
827 }
828
829 BString term;
830 if (!_ParseTerm(state, data, term, variables))
831 return B_BAD_VALUE;
832
833 switch (state.mode) {
834 case kRowStart:
835 if (!_GetSize(state, term.String(), rowLeftTop.x,
836 rowLeftTop.y))
837 return B_BAD_VALUE;
838
839 TRACE("row: %s (%g:%g)\n", term.String(), rowLeftTop.x,
840 rowLeftTop.y);
841
842 state.mode = kKeyShape;
843 break;
844 case kKeyShape:
845 memset((void*)&key, 0, sizeof(Key));
846 if (!_GetShape(state, term.String(), key))
847 return B_BAD_VALUE;
848
849 TRACE(" shape: %s (%g:%g:%g)\n", term.String(),
850 key.frame.Width(), key.frame.Height(),
851 key.second_row);
852
853 lastKeyCount = 0;
854 state.mode = kKeyCodes;
855 break;
856 case kKeyCodes:
857 TRACE(" raw key: %s\n", term.String());
858
859 if (!_AddKeyCodes(state, rowLeftTop, key, term.String(),
860 lastKeyCount))
861 return B_BAD_VALUE;
862
863 if (data[0] != ':')
864 state.mode = kKeyShape;
865 break;
866
867 default:
868 break;
869 }
870 if (data[0] != ']' && data[0] != '\0')
871 data++;
872 break;
873 }
874
875 default:
876 return B_BAD_VALUE;
877 }
878 }
879
880 return B_OK;
881 }
882
883