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