egra: cosmetix
[iv.d.git] / egtui / parser.d
bloba4a9734532d200a3cb43d06463a87affb9f5a5e8
1 /* Invisible Vector Library
2 * simple FlexBox-based TUI engine
4 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
5 * Understanding is not required. Only obedience.
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, version 3 of the License ONLY.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 module iv.egtui.parser /*is aliced*/;
21 import iv.alice;
22 import iv.strex;
23 import iv.rawtty;
25 import iv.egtui.tty;
26 import iv.egtui.tui;
27 import iv.egtui.types;
30 // ////////////////////////////////////////////////////////////////////////// //
31 void parse(Vars...) (ref FuiContext ctx, const(char)[] text) {
32 ctx.clearControls();
34 auto rmd = ctx.maxDimensions;
35 if (rmd.w < 1) rmd.w = ttyw;
36 if (rmd.h < 1) rmd.h = ttyh;
37 ctx.maxDimensions = rmd;
39 // stupid "reusable string" thingy
40 static struct DynStr {
41 char[] buf;
43 @disable this (this); // no copies
44 ~this () nothrow { delete buf; buf = null; }
46 void reset () nothrow {
47 if (buf.length > 0) {
48 buf.length = 0;
49 buf.assumeSafeAppend;
53 @property int pos () const pure nothrow @safe @nogc { pragma(inline, true); return cast(int)buf.length; }
54 @property void pos (int v) nothrow {
55 if (v < buf.length) {
56 buf.length = (v > 0 ? v : 0);
57 buf.assumeSafeAppend;
61 const(char)[] opSlice () const pure nothrow @safe @nogc { pragma(inline, true); return buf[]; }
62 const(char)[] opSlice (usize lo, usize hi) const pure nothrow @safe @nogc { pragma(inline, true); return buf[lo..hi]; }
64 char opIndex (usize idx) const pure nothrow @safe @nogc { pragma(inline, true); return buf[idx]; }
66 @property usize length () const pure nothrow @safe @nogc { pragma(inline, true); return buf.length; }
67 alias opDollar = length;
69 void put (const(char)[] s...) nothrow @safe { pragma(inline, true); buf ~= s; }
71 @property bool empty () const pure nothrow @safe @nogc { pragma(inline, true); return (buf.length == 0); }
72 @property const(char)[] getz () const pure nothrow @safe @nogc { pragma(inline, true); return buf[]; }
75 DynStr tokenbuf;
76 bool eof;
77 bool hasValue;
78 bool tkstr;
80 void skipBlanks () {
81 while (text.length > 0) {
82 if (text.length > 1 && text.ptr[0] == '/' && text.ptr[1] == '/') {
83 while (text.length && text.ptr[0] != '\n') text = text[1..$];
84 } else if (text.length > 1 && text.ptr[0] == '#') {
85 while (text.length && text.ptr[0] != '\n') text = text[1..$];
86 } else if (text.length > 1 && text.ptr[0] == '/' && text.ptr[1] == '*') {
87 text = text[2..$];
88 while (text.length) {
89 if (text.length < 2) { text = text[$..$]; break; }
90 if (text.ptr[0] == '*' && text.ptr[1] == '/') { text = text[2..$]; break; }
91 text = text[1..$];
93 } else if (text.length > 1 && text.ptr[0] == '/' && text.ptr[1] == '+') {
94 text = text[2..$];
95 int level = 1;
96 while (text.length) {
97 if (text.length < 2) { text = text[$..$]; break; }
98 if (text.ptr[0] == '+' && text.ptr[1] == '/') {
99 text = text[2..$];
100 if (--level == 0) break;
101 continue;
103 if (text.ptr[0] == '/' && text.ptr[1] == '+') {
104 text = text[2..$];
105 ++level;
106 continue;
108 text = text[1..$];
110 } else if (text.ptr[0] <= ' ') {
111 text = text[1..$];
112 } else {
113 break;
118 void nextToken () {
119 bool processVarSubst () {
120 if (text.length < 2) return false;
121 if (text.ptr[0] == '\\' && text.ptr[1] == '$') {
122 tokenbuf.put('$');
123 text = text[2..$];
124 return true;
126 if (text.ptr[0] != '$') return false;
127 if (text.ptr[1] == '$') {
128 tokenbuf.put('$');
129 text = text[2..$];
130 return true;
132 auto stpos = tokenbuf.pos;
133 if (text.ptr[1].isalpha || text.ptr[1].isalpha == '_') {
134 text = text[1..$];
135 while (text.length > 0) {
136 char ch = text.ptr[0];
137 if (ch != '_' && ch != '-') {
138 if (ch <= ' ' || !isalnum(ch)) break;
140 tokenbuf.put(ch);
141 text = text[1..$];
143 } else if (text.ptr[1] == '{') {
144 usize pos = 2;
145 while (pos < text.length) {
146 if (text.ptr[pos] == '\n') return false;
147 if (text.ptr[pos] == '}') break;
148 ++pos;
150 if (pos >= text.length) return false;
151 tokenbuf.put(text[2..pos]);
152 text = text[pos+1..$];
153 } else {
154 return false;
156 auto name = tokenbuf[stpos..$];
157 //{ import iv.vfs.io; VFile("z00.log", "a").writeln(name.quote); }
158 foreach (immutable idx, /*auto*/ var; Vars) {
159 static if (!is(typeof(var) == function) && !is(typeof(var) == delegate)) {
160 import std.conv : to;
161 static if (is(typeof({auto s = var.to!string;}))) {
162 if (Vars[idx].stringof == name) {
163 tokenbuf.pos = stpos;
164 tokenbuf.put(Vars[idx].to!string);
169 tkstr = true;
170 return true;
173 skipBlanks();
174 tokenbuf.reset();
175 hasValue = false;
176 tkstr = false;
177 eof = false;
178 if (text.length == 0) { eof = true; return; }
179 if (text.ptr[0] == '"' || text.ptr[0] == '\'' || text.ptr[0] == '`') {
180 char ech = text.ptr[0];
181 text = text[1..$];
182 char ch = ' ';
183 tkstr = true;
184 while (text.length > 0) {
185 ch = text.ptr[0];
186 if (processVarSubst()) continue;
187 text = text[1..$];
188 if (ch == ech) break;
189 if (ch == '\\' && text.length > 0) {
190 ch = text.ptr[0];
191 text = text[1..$];
192 switch (ch) {
193 case 't': ch = '\t'; break;
194 case 'n': ch = '\n'; break;
195 case 'L': ch = '\x01'; break;
196 case 'R': ch = '\x02'; break;
197 case 'C': ch = '\x03'; break;
198 default:
199 if (ch == 'x' || ch == 'X') {
200 if (text.length == 0) throw new Exception("invalid escape");
201 int n = digitInBase(text.ptr[0], 16);
202 if (n < 0) throw new Exception("invalid escape");
203 text = text[1..$];
204 if (text.length && digitInBase(text.ptr[0], 16) >= 0) {
205 n = n*16+digitInBase(text.ptr[0], 16);
206 text = text[1..$];
208 ch = cast(char)n;
209 } else if (ch.isalnum) {
210 throw new Exception("invalid escape");
212 break;
215 tokenbuf.put(ch);
217 if (ch != ech) throw new Exception("unterminated string");
218 skipBlanks();
219 if (text.length && text.ptr[0] == ':') {
220 text = text[1..$];
221 hasValue = true;
223 } else if (processVarSubst()) {
224 skipBlanks();
225 if (text.length && text.ptr[0] == ':') {
226 text = text[1..$];
227 hasValue = true;
229 } else if (text.ptr[0].isalnum || text.ptr[0] == '_' || text.ptr[0] == '-') {
230 while (text.length > 0) {
231 char ch = text.ptr[0];
232 if (ch != '_' && ch != '-') {
233 if (ch <= ' ' || !isalnum(ch)) break;
235 tokenbuf.put(ch);
236 text = text[1..$];
238 skipBlanks();
239 if (text.length && text.ptr[0] == ':') {
240 text = text[1..$];
241 hasValue = true;
243 } else {
244 assert(text.length > 0);
245 tokenbuf.put(text.ptr[0]);
246 text = text[1..$];
250 const(char)[] token () { pragma(inline, true); return tokenbuf.getz; }
252 DynStr widname;
254 // "{" eaten
255 void skipGroup () {
256 for (;;) {
257 nextToken();
258 if (eof) break;
259 if (tkstr) continue;
260 if (token == "{") skipGroup();
261 if (token == "}") break;
265 int getInt () {
266 import std.conv : to;
267 if (!hasValue) throw new Exception("number value expected");
268 nextToken();
269 if (/*tkstr ||*/ eof) throw new Exception("number expected");
270 if (hasValue) throw new Exception("number expected");
271 auto t = token;
272 if (t.length == 0 || !t.ptr[0].isdigit) throw new Exception("number expected");
273 return t.to!int;
276 int getBool () {
277 if (!hasValue) return 1;
278 nextToken();
279 if (/*tkstr ||*/ eof) throw new Exception("boolean expected");
280 if (hasValue) throw new Exception("boolean expected");
281 auto t = token;
282 if (t.strEquCI("yes") || t.strEquCI("tan") || t.strEquCI("true") || t.strEquCI("y") || t.strEquCI("t")) return 1;
283 if (t.strEquCI("no") || t.strEquCI("ona") || t.strEquCI("false") || t.strEquCI("n") || t.strEquCI("f") || t.strEquCI("o")) return 0;
284 throw new Exception("boolean expected, but got '"~t.idup~"'");
287 void getStr (ref DynStr dest) {
288 if (!hasValue) throw new Exception("string value expected");
289 nextToken();
290 if (eof) throw new Exception("string value expected");
291 dest.reset();
292 dest.put(token);
295 int getClickMask () {
296 if (!hasValue) throw new Exception("clickmask value expected");
297 nextToken();
298 if (hasValue || eof) throw new Exception("clickmask expected");
299 auto t = token.xstrip;
300 int res;
301 while (t.length > 0) {
302 int pos = 0;
303 while (pos < t.length && t.ptr[0] > ' ' && t.ptr[0] != ',') ++pos;
304 if (pos > 0) {
305 auto n = t[0..pos];
306 t = t[pos..$];
307 if (n.strEquCI("left")) res |= FuiLayoutProps.Buttons.Left;
308 else if (n.strEquCI("right")) res |= FuiLayoutProps.Buttons.Right;
309 else if (n.strEquCI("middle")) res |= FuiLayoutProps.Buttons.Middle;
310 else throw new Exception("invalid button name: '"~n.idup~"'");
312 while (t.length && (t.ptr[0] <= ' ' || t.ptr[0] == ',')) t = t[1..$];
314 return res;
317 struct Props {
318 // properties
319 int paddingLeft = -1;
320 int paddingRight = -1;
321 int paddingTop = -1;
322 int paddingBottom = -1;
323 int flex = -1, spacing = -1;
324 int clickMask = -1, doubleMask = -1;
325 int linebreak = -1;
326 int canbefocused = -1;
327 int hv = -1;
328 int def = -1;
329 int smallframe = -1;
330 int wpalign = -1;
331 int minw = -1, minh = -1;
332 int maxw = -1, maxh = -1;
333 int disabled = -1;
334 int alert = -1;
335 int utfuck = -1;
336 int enterclose = -1;
337 int resetsize = -1;
338 DynStr id;
339 DynStr caption;
340 DynStr hgroup;
341 DynStr vgroup;
342 DynStr dest;
343 DynStr bindvar;
344 DynStr bindfuncAction;
345 DynStr bindfuncDraw;
346 DynStr bindfuncEvent;
348 void reset () {
349 foreach (ref v; this.tupleof) {
350 static if (is(typeof(v) == DynStr)) v.reset;
351 else static if (is(typeof(v) == int)) v = -1;
355 // token: prop name
356 bool parseProp () {
357 auto ts = token;
358 if (ts.strEquCI("flex")) { flex = getInt(); return true; }
359 if (ts.strEquCI("vertical")) { if (getBool) hv = 1; else hv = 0; return true; }
360 if (ts.strEquCI("horizontal")) { if (getBool) hv = 0; else hv = 1; return true; }
361 if (ts.strEquCI("orientation")) {
362 if (!hasValue) throw new Exception("value expected");
363 nextToken();
364 if (eof) throw new Exception("value expected");
365 if (token.strEquCI("horizontal")) hv = 0;
366 else if (token.strEquCI("vertical")) hv = 1;
367 else throw new Exception("invalid orientation: '"~token.idup~"'");
368 return true;
370 if (ts.strEquCI("align")) {
371 if (!hasValue) throw new Exception("value expected");
372 nextToken();
373 if (eof) throw new Exception("value expected");
374 if (token.strEquCI("center")) wpalign = FuiLayoutProps.Align.Center;
375 else if (token.strEquCI("start")) wpalign = FuiLayoutProps.Align.Start;
376 else if (token.strEquCI("end")) wpalign = FuiLayoutProps.Align.End;
377 else if (token.strEquCI("stretch")) wpalign = FuiLayoutProps.Align.Stretch;
378 else if (token.strEquCI("expand")) wpalign = FuiLayoutProps.Align.Stretch;
379 else throw new Exception("invalid align: '"~token.idup~"'");
380 return true;
382 if (ts.strEquCI("lineBreak") || ts.strEquCI("line-break")) { linebreak = getBool; return true; }
383 if (ts.strEquCI("canBeFocused") || ts.strEquCI("can-be-focused")) { canbefocused = getBool; return true; }
384 if (ts.strEquCI("id")) { getStr(id); return true; }
385 if (ts.strEquCI("caption")) { getStr(caption); return true; }
386 if (ts.strEquCI("text")) { getStr(caption); return true; } // alias for "caption"
387 if (ts.strEquCI("dest")) { getStr(dest); return true; }
388 if (ts.strEquCI("hgroup")) { getStr(hgroup); return true; }
389 if (ts.strEquCI("vgroup")) { getStr(vgroup); return true; }
390 if (ts.strEquCI("bindVar") || ts.strEquCI("bind-var")) { getStr(bindvar); return true; }
391 //if (ts.strEquCI("bindFunc") || ts.strEquCI("bind-func")) { getStr(bindfuncAction); return true; }
392 if (ts.strEquCI("onaction") || ts.strEquCI("on-action")) { getStr(bindfuncAction); return true; }
393 if (ts.strEquCI("ondraw") || ts.strEquCI("on-draw")) { getStr(bindfuncDraw); return true; }
394 if (ts.strEquCI("onevent") || ts.strEquCI("on-event")) { getStr(bindfuncEvent); return true; }
395 if (ts.strEquCI("paddingLeft") || ts.strEquCI("padding-left")) { paddingLeft = getInt(); return true; }
396 if (ts.strEquCI("paddingTop") || ts.strEquCI("padding-top")) { paddingTop = getInt(); return true; }
397 if (ts.strEquCI("paddingRight") || ts.strEquCI("padding-right")) { paddingRight = getInt(); return true; }
398 if (ts.strEquCI("paddingBottom") || ts.strEquCI("padding-bottom")) { paddingBottom = getInt(); return true; }
399 if (ts.strEquCI("spacing")) { spacing = getInt(); return true; }
400 if (ts.strEquCI("width")) { minw = maxw = getInt(); return true; }
401 if (ts.strEquCI("height")) { minh = maxh = getInt(); return true; }
402 if (ts.strEquCI("min-width")) { minw = getInt(); return true; }
403 if (ts.strEquCI("min-height")) { minh = getInt(); return true; }
404 if (ts.strEquCI("max-width")) { maxw = getInt(); return true; }
405 if (ts.strEquCI("max-height")) { maxh = getInt(); return true; }
406 if (ts.strEquCI("default")) { def = getBool(); return true; }
407 if (ts.strEquCI("disabled")) { disabled = getBool(); return true; }
408 if (ts.strEquCI("enabled")) { disabled = 1-getBool(); return true; }
409 if (ts.strEquCI("smallFrame") || ts.strEquCI("small-frame")) { smallframe = getBool(); return true; }
410 if (ts.strEquCI("clickMask") || ts.strEquCI("click-mask")) { clickMask = getClickMask(); return true; }
411 if (ts.strEquCI("doubleMask") || ts.strEquCI("double-mask")) { doubleMask = getClickMask(); return true; }
412 if (ts.strEquCI("alert")) { alert = getBool(); return true; }
413 if (ts.strEquCI("utfuck")) { utfuck = getBool(); return true; }
414 if (ts.strEquCI("enter-close")) { enterclose = getBool(); return true; }
415 if (ts.strEquCI("reset-size")) { resetsize = getBool(); return true; }
416 return false;
420 Props props;
422 bool isGoodWidgetName (const(char)[] s) {
423 return
424 s.strEquCI("span") || s.strEquCI("hspan") || s.strEquCI("vspan") ||
425 s.strEquCI("hline") ||
426 s.strEquCI("spacer") ||
427 s.strEquCI("box") || s.strEquCI("hbox") || s.strEquCI("vbox") ||
428 s.strEquCI("panel") || s.strEquCI("hpanel") || s.strEquCI("vpanel") ||
429 s.strEquCI("label") ||
430 s.strEquCI("button") ||
431 s.strEquCI("checkbox") ||
432 s.strEquCI("radio") ||
433 s.strEquCI("editline") || s.strEquCI("edittext") ||
434 s.strEquCI("textview") ||
435 s.strEquCI("listbox") ||
436 s.strEquCI("custombox");
439 int bindFuncTo(string type) (int item) {
440 static assert(type == "Action" || type == "Draw" || type == "Event", "invalid type");
441 static if (type == "Action") bool bfe = props.bindfuncAction.empty;
442 else static if (type == "Draw") bool bfe = props.bindfuncDraw.empty;
443 else static if (type == "Event") bool bfe = props.bindfuncEvent.empty;
444 if (!bfe) {
445 foreach (immutable idx, /*auto*/ var; Vars) {
446 static if (is(typeof(var) == function) || is(typeof(var) == delegate)) {
447 static if (type == "Action") {
448 static if (is(typeof((dg){int n = dg(FuiContext.init, 666);}(&Vars[idx])))) {
449 if ((&Vars[idx]).stringof[1..$].xstrip == props.bindfuncAction.getz) {
450 ctx.setActionCB(item, &Vars[idx]);
451 return item;
454 } else static if (type == "Draw") {
455 static if (is(typeof((dg){dg(FuiContext.init, 666, FuiRect.init);}(&Vars[idx])))) {
456 if ((&Vars[idx]).stringof[1..$].xstrip == props.bindfuncDraw.getz) {
457 ctx.setDrawCB(item, &Vars[idx]);
458 return item;
461 } else static if (type == "Event") {
462 static if (is(typeof((dg){bool b = dg(FuiContext.init, 666, FuiEvent.init);}(&Vars[idx])))) {
463 if ((&Vars[idx]).stringof[1..$].xstrip == props.bindfuncEvent.getz) {
464 ctx.setEventCB(item, &Vars[idx]);
465 return item;
472 return item;
475 int bindFuncs (int item) {
476 bindFuncTo!"Action"(item);
477 bindFuncTo!"Draw"(item);
478 bindFuncTo!"Event"(item);
479 return item;
482 int createWidget (int parent) {
483 auto widn = widname.getz;
484 if (widn.strEquCI("span")) return bindFuncs(ctx.span(parent, (props.hv <= 0)));
485 if (widn.strEquCI("hspan")) return bindFuncs(ctx.hspan(parent));
486 if (widn.strEquCI("vspan")) return bindFuncs(ctx.vspan(parent));
487 if (widn.strEquCI("spacer")) return bindFuncs(ctx.spacer(parent));
488 if (widn.strEquCI("hline")) return bindFuncs(ctx.hline(parent));
489 if (widn.strEquCI("box")) return bindFuncs(ctx.box(parent, (props.hv <= 0), props.id.getz));
490 if (widn.strEquCI("hbox")) return bindFuncs(ctx.hbox(parent, props.id.getz));
491 if (widn.strEquCI("vbox")) return bindFuncs(ctx.vbox(parent, props.id.getz));
492 if (widn.strEquCI("panel")) return bindFuncs(ctx.panel(parent, props.caption.getz, (props.hv <= 0), props.id.getz));
493 if (widn.strEquCI("hpanel")) return bindFuncs(ctx.hpanel(parent, props.caption.getz, props.id.getz));
494 if (widn.strEquCI("vpanel")) return bindFuncs(ctx.vpanel(parent, props.caption.getz, props.id.getz));
495 if (widn.strEquCI("label")) return bindFuncs(ctx.label(parent, props.id.getz, props.caption.getz, props.dest.getz));
496 if (widn.strEquCI("button")) return bindFuncs(ctx.button(parent, props.id.getz, props.caption.getz));
497 if (widn.strEquCI("editline")) return bindFuncs(ctx.editline(parent, props.id.getz, props.caption.getz, props.utfuck > 0));
498 if (widn.strEquCI("edittext")) return bindFuncs(ctx.edittext(parent, props.id.getz, props.caption.getz, props.utfuck > 0));
499 if (widn.strEquCI("textview")) return bindFuncs(ctx.textview(parent, props.id.getz, props.caption.getz));
500 if (widn.strEquCI("listbox")) return bindFuncs(ctx.listbox(parent, props.id.getz));
501 if (widn.strEquCI("custombox")) return bindFuncs(ctx.custombox(parent, props.id.getz));
502 if (widn.strEquCI("checkbox")) {
503 if (!props.bindvar.empty) {
504 foreach (int idx, /*auto*/ var; Vars) {
505 static if (is(typeof(var) == bool)) {
506 if (Vars[idx].stringof == props.bindvar.getz) {
507 return bindFuncs(ctx.checkbox(parent, props.id.getz, props.caption.getz, &Vars[idx]));
512 return bindFuncs(ctx.checkbox(parent, props.id.getz, props.caption.getz, null));
514 if (widn.strEquCI("radio")) {
515 if (!props.bindvar.empty) {
516 foreach (int idx, /*auto*/ var; Vars) {
517 static if (is(typeof(var) == int)) {
518 if (Vars[idx].stringof == props.bindvar.getz) {
519 return bindFuncs(ctx.radio(parent, props.id.getz, props.caption.getz, &Vars[idx]));
524 return bindFuncs(ctx.radio(parent, props.id.getz, props.caption.getz, null));
526 throw new Exception("unknown widget: '"~widn.idup~"'");
529 // at widget name
530 void readProps(bool doWidgetCreation=true) (int parent) {
531 bool readGroup = true;
532 int item = 0;
534 widname.reset;
535 if (eof) return;
536 static if (doWidgetCreation) {
537 if (!isGoodWidgetName(token)) throw new Exception("widget name expected");
538 widname.put(token);
539 readGroup = hasValue;
542 void setProps () {
543 // set properties
544 auto lp = ctx.layprops(item);
545 if (props.paddingLeft >= 0) lp.padding.left = props.paddingLeft;
546 if (props.paddingTop >= 0) lp.padding.top = props.paddingTop;
547 if (props.paddingRight >= 0) lp.padding.right = props.paddingRight;
548 if (props.paddingBottom >= 0) lp.padding.bottom = props.paddingBottom;
549 if (props.flex >= 0) lp.flex = props.flex;
550 if (props.spacing >= 0) lp.spacing = props.spacing;
551 if (props.clickMask >= 0) lp.clickMask = cast(ubyte)props.clickMask;
552 if (props.doubleMask >= 0) lp.doubleMask = cast(ubyte)props.doubleMask;
553 if (props.linebreak >= 0) lp.lineBreak = (props.linebreak != 0);
554 if (props.canbefocused >= 0) lp.canBeFocused = (props.canbefocused != 0);
555 if (props.hv >= 0) lp.vertical = (props.hv != 0);
556 if (props.def >= 0) lp.userFlags = (lp.userFlags&~cast(uint)FuiCtlUserFlags.Default)|(props.def > 0 ? FuiCtlUserFlags.Default : 0);
557 if (props.wpalign >= 0) lp.aligning = cast(FuiLayoutProps.Align)props.wpalign;
558 if (props.resetsize > 0) {
559 lp.minSize = FuiSize(1, 1);
560 lp.maxSize = FuiSize(int.max-1024, int.max-1024);
562 if (props.minw >= 0) lp.minSize.w = props.minw;
563 if (props.minh >= 0) lp.minSize.h = props.minh;
564 if (props.maxw >= 0) lp.maxSize.w = props.maxw;
565 if (props.maxh >= 0) lp.maxSize.h = props.maxh;
566 if (props.disabled >= 0) lp.disabled = (props.disabled > 0);
567 if (!props.hgroup.empty) {
568 auto id = ctx[props.hgroup.getz];
569 if (id < 0) throw new Exception("no widget with id '"~props.hgroup.getz.idup~"'");
570 lp.hgroup = id;
572 if (!props.vgroup.empty) {
573 auto id = ctx[props.vgroup.getz];
574 if (id < 0) throw new Exception("no widget with id '"~props.vgroup.getz.idup~"'");
575 lp.vgroup = id;
577 if (item == 0) {
578 ctx.dialogCaption(props.caption.getz);
579 if (props.smallframe >= 0) ctx.dialogFrame(props.smallframe > 0 ? FuiDialogFrameType.Small : FuiDialogFrameType.Normal);
580 if (props.alert >= 0) ctx.dialogPalette(props.alert > 0 ? TuiPaletteError : TuiPaletteNormal);
581 if (props.enterclose >= 0) ctx.dialogEnterClose(props.enterclose > 0);
585 props.reset();
586 if (readGroup) {
587 if (doWidgetCreation) {
588 nextToken();
589 if (eof || tkstr || token != "{") throw new Exception("group expected");
591 auto stpos = text;
592 // collect properties
593 for (;;) {
594 nextToken();
595 if (eof) break;
596 auto ts = token;
597 if (ts == "}") break;
598 if (!hasValue && !tkstr && ts == ",") continue;
599 if (ts == "{") {
600 skipGroup();
601 if (eof) throw new Exception("unclosed group");
602 if (token != "}") throw new Exception("unclosed group");
603 continue;
605 if (props.parseProp()) continue;
606 if (ts.strEquCI("items")) {
607 if (widname.getz != "listbox") throw new Exception("'"~widname.getz.idup~"' can't have items");
608 if (hasValue) {
609 nextToken();
610 if (eof || token != "{") throw new Exception("items expected");
611 skipGroup();
613 continue;
615 if (!isGoodWidgetName(ts)) throw new Exception("invalid token: '"~ts.idup~"'");
617 static if (doWidgetCreation) item = createWidget(parent);
618 setProps();
619 // second pass: process children, add items
620 text = stpos;
621 for (;;) {
622 nextToken();
623 if (eof) break;
624 if (!tkstr && token == "}") break;
625 if (!hasValue && !tkstr && token == ",") continue;
626 if (isGoodWidgetName(token)) {
627 readProps(item);
628 } else if (token.strEquCI("items")) {
629 //if (widname.getz != "listbox") throw new Exception("'"~widname.getz.idup~"' can't have items");
630 if (hasValue) {
631 nextToken();
632 if (eof || token != "{") throw new Exception("items expected");
633 for (;;) {
634 nextToken();
635 if (eof) throw new Exception("unclosed item group");
636 if (token == "}") break;
637 if (!hasValue && !tkstr && token == ",") continue;
638 if (hasValue) throw new Exception("item can't have any value");
639 ctx.listboxItemAdd(item, token);
641 auto lp = ctx.layprops(item);
642 if (lp.minSize.w <= 3) lp.minSize.w = ctx.listboxMaxItemWidth(item);
643 if (lp.minSize.h < 1) {
644 int fh = (ctx.dialogFrame == FuiDialogFrameType.Normal ? 4 : 2);
645 auto mh = ctx.listboxItemCount(item);
646 if (mh > ttyh-fh) mh = ttyh-fh;
647 lp.minSize.h = mh;
650 } else {
651 if (!tkstr && token == "{") throw new Exception("orphaned group");
654 } else {
655 if (doWidgetCreation) item = createWidget(parent);
659 //{ import iv.vfs.io; writeln("text: ", text); }
660 readProps!false(0);