egra: styles can store strings and integers now
[iv.d.git] / egra / gui / style.d
blob3d607c5942f21a0d09e37c1b4b529fa7cfe4f052
1 /*
2 * Simple Framebuffer GUI
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.egra.gui.style;
21 import iv.egra.gfx.base;
23 import iv.strex;
24 import iv.xcolornames;
27 // ////////////////////////////////////////////////////////////////////////// //
28 static immutable defaultStyleText = `
29 SubWindow {
30 frame: rgb(255, 255, 255);
31 title-back: @frame;
32 title-text: rgb(0, 0, 0);
33 shadow-color: rgba(0, 0, 0, 127);
34 shadow-size: 8;
36 .inactive {
37 frame: rgb(192, 192, 192);
38 title-back: @frame;
39 title-text: rgb(0, 0, 0);
42 back: rgb(0, 0, 180);
43 text: rgb(255, 255, 255);
46 .focused {
47 back: rgb(0, 0, 0);
48 text: rgb(255, 255, 255);
52 .disabled {
53 text: rgb(128, 128, 128);
58 YesNoWindow {
59 frame: rgb(0x40, 0x70, 0xcf);
60 title-back: rgb(0x73, 0x96, 0xdc);
61 title-text: rgb(0xff, 0xff, 0xff);
63 .inactive {
64 frame: rgb(192, 192, 192);
65 title-back: @frame;
66 title-text: rgb(0, 0, 0);
69 back: rgb(0xbf, 0xcf, 0xef);
70 text: rgb(0x16, 0x2d, 0x59);
74 Widget {
75 back: @parent:;
76 text: @parent:;
78 .focused {
79 back: @parent:.null;
80 text: @parent:.null;
83 .disabled {
84 back: @parent:;
85 text: @parent:;
90 LabelWidget {
91 back: transparent;
93 .focused {
94 back: transparent;
97 .disabled {
98 back: transparent;
103 ProgressBarWidget {
104 rect: gray20;
105 back: rgb(17, 17, 0);
106 text: white;
107 stripe: rgb(52, 52, 38);
109 .hishade {
110 back: rgb(52, 52, 38);
111 stripe: rgb(82, 82, 70);
114 .full {
115 back: rgb(159, 71, 0);
116 stripe: rgb(173, 98, 38);
119 .full-hishade {
120 back: rgb(173, 98, 38);
121 stripe: rgb(203, 128, 70);
126 ButtonWidget {
127 back: rgb(180, 180, 180);
128 text: rgb(0, 0, 0);
129 hotline: @text;
131 .focused {
132 back: rgb(250, 250, 250);
133 text: rgb(0, 0, 60);
134 hotline: @text;
137 .disabled {
138 back: rgb(142, 142, 142);
139 text: rgb(32, 32, 32);
140 hotline: @text;
145 ButtonExWidget {
146 back: rgb(0x73, 0x96, 0xdc);
147 text: rgb(0xff, 0xff, 0xff);
148 rect: rgb(0x40, 0x70, 0xcf);
149 shadowline: rgb(0x83, 0xa6, 0xec);
151 .focused {
152 back: rgb(0x93, 0xb6, 0xfc);
153 shadowline: rgb(0xa3, 0xc6, 0xff);
158 CheckboxWidget {
159 back: transparent;
160 text: rgb(192, 192, 192);
161 mark: rgb(0, 192, 0);
163 .focused {
164 back: rgb(0, 0, 64);
165 text: @SubWindow:;
166 mark: rgb(0, 255, 0);
169 .disabled {
170 back: @SubWindow:;
171 text: @SubWindow:;
172 mark: @SubWindow:text;
177 SimpleListBoxWidget {
178 back: rgb(0, 0, 60);
179 text: rgb(255, 255, 0);
180 cursor-back: rgb(0, 110, 110);
181 cursor-text: rgb(255, 255, 255);
183 .focused {
184 back:@.null;
185 text:@.null;
186 cursor-back: rgb(0, 60, 60);
187 cursor-text: rgb(192, 192, 192);
192 EditorWidget {
193 back: #007;
195 status-back: white;
196 status-color: black;
198 text: rgb(220, 220, 0);
199 quote0-text: rgb(128, 128, 0);
200 quote1-text: rgb(0, 128, 128);
201 wrap-mark-text: rgb(0, 90, 220);
203 attach-file-text: rgb(0x6e, 0x00, 0xff);
204 attach-bad-text: red;
206 // marked block
207 mark-back: rgb(0, 160, 160);
208 mark-text: white;
212 LineEditWidget : EditorWidget {
213 title-back: transparent;
214 title-text: white;
216 back: black;
220 // ////////////////////////////////////////////////////////////////////////// //
221 struct FuiSimpleParser {
222 private:
223 const(char)[] text;
224 const(char)[] str; // text left
226 public:
227 this (const(char)[] atext) nothrow @safe @nogc { pragma(inline, true); setText(atext); }
229 int getCurrentLine () pure const nothrow @safe @nogc {
230 int res = 0;
231 foreach (immutable char ch; text[0..$-str.length]) if (ch == '\n') ++res;
232 return res;
235 void error (string msg) const {
236 import std.conv : to;
237 version(none) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "===\n%.*s\n===\n", cast(uint)str.length, str.ptr); }
238 throw new Exception("parse error around line "~getCurrentLine.to!string~": "~msg);
241 void setText (const(char)[] atext) nothrow @safe @nogc { pragma(inline, true); text = atext; str = atext; }
243 bool isEOT () {
244 skipBlanks();
245 return (str.length == 0);
248 void skipBlanks () {
249 while (str.length) {
250 if (str[0] <= ' ') { str = str.xstripleft; continue; }
251 if (str.length < 2 || str[0] != '/') break;
252 // single-line comment?
253 if (str[1] == '/') {
254 str = str[2..$];
255 while (str.length && str[0] != '\n') str = str[1..$];
256 continue;
258 // multiline comment?
259 if (str[1] == '*') {
260 bool endFound = false;
261 auto svs = str;
262 str = str[2..$];
263 while (str.length) {
264 if (str.length > 1 && str[0] == '*' && str[1] == '/') {
265 endFound = true;
266 str = str[2..$];
267 break;
269 str = str[1..$];
271 if (!endFound) { str = svs; error("unfinished comment"); }
272 continue;
274 // multiline nested comment?
275 if (str[1] == '+') {
276 bool endFound = false;
277 auto svs = str;
278 int level = 0;
279 while (str.length) {
280 if (str.length > 1) {
281 if (str[0] == '/' && str[1] == '+') { str = str[2..$]; ++level; continue; }
282 if (str[0] == '+' && str[1] == '/') { str = str[2..$]; if (--level == 0) { endFound = true; break;} continue; }
284 str = str[1..$];
286 if (!endFound) { str = svs; error("unfinished comment"); }
287 continue;
289 break;
293 bool checkNoEat (const(char)[] tk) {
294 assert(tk.length);
295 skipBlanks();
296 return (str.length >= tk.length && str[0..tk.length] == tk);
299 bool checkDigitNoEat () {
300 skipBlanks();
301 return (str.length > 0 && isdigit(str[0]));
304 bool checkNoEat (in char ch) {
305 skipBlanks();
306 return (str.length > 0 && str[0] == ch);
309 bool check (const(char)[] tk) {
310 if (!checkNoEat(tk)) return false;
311 str = str[tk.length..$];
312 skipBlanks();
313 return true;
316 bool check (in char ch) {
317 if (!checkNoEat(ch)) return false;
318 str = str[1..$];
319 skipBlanks();
320 return true;
323 void expect (const(char)[] tk) {
324 skipBlanks();
325 auto svs = str;
326 if (!check(tk)) { str = svs; error("`"~tk.idup~"` expected"); }
329 void expect (in char ch) {
330 skipBlanks();
331 auto svs = str;
332 if (!check(ch)) { str = svs; error("`"~ch~"` expected"); }
335 const(char)[] expectIdNoCopy () {
336 skipBlanks();
337 if (str.length == 0) error("identifier expected");
338 if (!isalpha(str[0]) && str[0] != '_' && str[0] != '-') error("identifier expected");
339 usize pos = 1;
340 while (pos < str.length) {
341 if (!isalnum(str[pos]) && str[pos] != '_' && str[pos] != '-') break;
342 ++pos;
344 const(char)[] res = str[0..pos];
345 str = str[pos..$];
346 skipBlanks();
347 return res;
350 string expectId () {
351 pragma(inline, true);
352 return expectIdNoCopy().idup;
355 uint parseColor () {
356 skipBlanks();
357 if (str.length == 0) error("color expected");
359 // html-like color?
360 if (check('#')) return parseHtmlColor();
362 auto svs = str;
363 auto id = expectIdNoCopy();
365 if (id.strEquCI("transparent")) return gxTransparent;
367 // `rgb()` or `rgba()`?
368 bool allowAlpha;
369 if (id.strEquCI("rgba")) allowAlpha = true;
370 else if (id.strEquCI("rgb")) allowAlpha = false;
371 else {
372 auto xc = xFindColorByName(id);
373 if (xc is null) { str = svs; error("invalid color definition"); }
374 return gxrgb(xc.r, xc.g, xc.b);
377 skipBlanks();
378 if (!check('(')) { str = svs; error("invalid color definition"); }
379 immutable uint clr = parseColorRGB(allowAlpha);
380 if (!check(')')) { str = svs; error("invalid color definition"); }
381 return clr;
384 // open quote already eaten
385 string parseString (in char qch) {
386 auto epos = str.indexOf('"');
387 if (epos < 0) error("invalid string");
388 auto svs = str;
389 char[] res = null;
390 res.reserve(epos);
391 scope(failure) delete res;
392 usize pos = 0;
393 while (pos < str.length) {
394 immutable char ch = str.ptr[pos++];
395 if (ch == 0) { str = svs; error("invalid string"); }
396 if (ch == qch) {
397 str = str[pos..$];
398 skipBlanks();
399 if (res.length == 0) {
400 delete res;
401 return "";
403 return cast(string)res; // it is safe to cast here
405 if (ch != '\\') {
406 if (ch == '\n') { str = svs; error("unterminated string"); }
407 res ~= ch;
408 continue;
410 if (pos >= str.length) { str = svs; error("unterminated string"); }
411 switch (str.ptr[pos++]) {
412 case 't': res ~= '\t'; break;
413 case 'n': res ~= '\n'; break;
414 case 'r': res ~= '\r'; break;
415 case '\n': break;
416 case '\r': if (pos < str.length && str.ptr[pos] == '\n') ++pos; break;
417 case '"': case '\'': case '\\': res ~= str.ptr[pos-1]; break;
418 default: str = svs; error("invalid string escape");
421 str = svs;
422 error("unterminated string");
423 assert(0);
426 int parseInt () {
427 skipBlanks();
428 if (str.length == 0) error("number expected");
429 auto svs = str;
430 bool neg = false;
431 if (check('+')) {}
432 else if (check('-')) neg = true;
433 if (str.length == 0 || !isdigit(str[0])) { str = svs; error("number expected"); }
434 int base = 0;
435 // check bases
436 if (str.length > 1 && str[0] == '0') {
437 switch (str[1]) {
438 case 'x': case 'X': base = 16; break;
439 case 'b': case 'B': base = 2; break;
440 case 'o': case 'O': base = 8; break;
441 case 'd': case 'D': base = 10; break;
442 default: break;
444 if (base) {
445 str = str[2..$];
446 while (str.length && str[0] == '_') str = str[1..$];
449 if (!base) base = 10;
450 else if (str.length == 0 || digitInBase(str[0], base) < 0) { str = svs; error("number expected"); }
451 immutable long vmax = (neg ? -cast(long)int.min : cast(long)int.max);
452 long n = 0;
453 while (str.length) {
454 if (str[0] != '_') {
455 immutable int dg = digitInBase(str[0], base);
456 if (dg < 0) break;
457 n = n*base+dg;
458 if (n > vmax) { str = svs; error("integer overflow"); }
460 str = str[1..$];
462 if (str.length && isalpha(str[0])) { str = svs; error("number expected"); }
463 skipBlanks();
464 return cast(int)n;
467 private:
468 // '#' skipped
469 uint parseHtmlColor () {
470 auto svs = str;
471 skipBlanks();
472 ubyte[3] rgb = 0;
473 // first 3 digits
474 foreach (immutable n; 0..3) {
475 while (str.length && str[0] == '_') str = str[1..$];
476 if (str.length == 0) { str = svs; error("invalid color"); }
477 immutable int dg = digitInBase(str[0], 16);
478 if (dg < 0) { str = svs; error("invalid color"); }
479 rgb[n] = cast(ubyte)dg;
480 str = str[1..$];
482 while (str.length && str[0] == '_') str = str[1..$];
483 // second 3 digits?
484 if (str.length && digitInBase(str[0], 16) >= 0) {
485 foreach (immutable n; 0..3) {
486 while (str.length && str[0] == '_') str = str[1..$];
487 if (str.length == 0) { str = svs; error("invalid color"); }
488 immutable int dg = digitInBase(str[0], 16);
489 if (dg < 0) { str = svs; error("invalid color"); }
490 rgb[n] = cast(ubyte)(rgb[n]*16+dg);
491 str = str[1..$];
493 while (str.length && str[0] == '_') str = str[1..$];
494 } else {
495 foreach (immutable n; 0..3) rgb[n] = cast(ubyte)(rgb[n]*16+rgb[n]);
497 skipBlanks();
498 return gxrgb(rgb[0], rgb[1], rgb[2]);
501 // "(" skipped
502 uint parseColorRGB (bool allowAlpha) {
503 auto svs = str;
504 ubyte[4] rgba = 0;
505 foreach (immutable n; 0..3+(allowAlpha ? 1 : 0)) {
506 if (n && !check(',')) { str = svs; error("invalid color"); }
507 skipBlanks();
508 if (str.length == 0 || !isdigit(str[0])) { str = svs; error("invalid color"); }
509 uint val = 0;
510 uint base = 10;
511 if (str[0] == '0' && str.length >= 2 && (str[1] == 'x' || str[1] == 'X')) {
512 str = str[2..$];
513 if (str.length == 0 || digitInBase(str[0], 16) < 0) { str = svs; error("invalid color"); }
514 base = 16;
516 while (str.length) {
517 if (str[0] != '_') {
518 immutable int dg = digitInBase(str[0], cast(int)base);
519 if (dg < 0) break;
520 val = val*base+cast(uint)dg;
521 if (val > 255) { str = svs; error("invalid color"); }
523 str = str[1..$];
525 while (str.length && str[0] == '_') str = str[1..$];
526 rgba[n] = cast(ubyte)val;
528 skipBlanks();
529 if (allowAlpha) return gxrgba(rgba[0], rgba[1], rgba[2], rgba[3]);
530 return gxrgb(rgba[0], rgba[1], rgba[2]);
535 // ////////////////////////////////////////////////////////////////////////// //
536 class WidgetStyle {
537 public:
538 static struct Value {
539 enum Type {
540 Str,
541 Int,
542 Color,
544 Type type = Type.Color;
545 string sval;
546 union {
547 uint color;
548 int ival;
551 this(T:const(char)[]) (T str) @safe nothrow {
552 pragma(inline, true);
553 static if (is(T == typeof(null))) sval = null;
554 else static if (is(T == immutable(char))) sval = str;
555 else sval = (str.length ? str.idup : "");
556 type = Type.Str;
559 this (in int val) @safe nothrow { pragma(inline, true); ival = val; type = Type.Int; }
560 this (in uint val) @safe nothrow { pragma(inline, true); color = val; type = Type.Color; }
562 @property bool isString () pure const @safe nothrow { pragma(inline, true); return (type == Type.Str); }
563 @property bool isColor () pure const @safe nothrow { pragma(inline, true); return (type == Type.Color); }
564 @property bool isInteger () pure const @safe nothrow { pragma(inline, true); return (type == Type.Int); }
566 static String(T:const(char)[]) (T str) @safe nothrow { pragma(inline, true); return Value(str); }
567 static Color (in uint clr) @safe nothrow { pragma(inline, true); return Value(clr); }
568 static Integer (in int val) @safe nothrow { pragma(inline, true); return Value(val); }
571 static struct Item {
572 string widget; // "Widget"
573 string type; // "text"
574 string mod; // "inactive", "disabled", etc.
575 bool redirect;
576 Value value;
577 // redirect
578 string rewidget; // null means "this"
579 string retype; // null means "this"
580 string remod; // null means "this"
581 // loop protection
582 uint seenCount;
585 protected:
586 Item[string] colors;
587 string[string] parents;
588 uint seenCount;
590 Value[string] styleCache;
591 char[] tempccstr;
593 protected:
594 final void clearStyleCache () {
595 pragma(inline, true);
596 styleCache = null;
599 final const(char)[] buildcc (const(char)[] defparent, const(char)[] ctname, const(char)[] type, const(char)[] mod) {
600 pragma(inline, true);
601 immutable usize strsize = defparent.length+1+ctname.length+1+type.length+1+mod.length;
602 if (tempccstr.length < strsize) tempccstr.length = (strsize|0xff)+1;
603 // default parent
604 usize spos = defparent.length;
605 tempccstr[0..spos] = defparent;
606 tempccstr[spos++] = 0;
607 // class name
608 tempccstr[spos..spos+ctname.length] = ctname;
609 spos += ctname.length;
610 tempccstr[spos++] = 0;
611 // type
612 tempccstr[spos..spos+type.length] = type;
613 spos += type.length;
614 tempccstr[spos++] = 0;
615 // modifier
616 tempccstr[spos..spos+mod.length] = mod;
617 // done
618 return tempccstr[0..spos+mod.length];
621 final const(Value)* findCachedValue (in TypeInfo_Class defpar, in TypeInfo_Class ct, const(char)[] type, const(char)[] mod) {
622 pragma(inline, true);
623 return (buildcc((defpar !is null ? defpar.name : null), (ct !is null ? ct.name : null), type, mod) in styleCache);
626 final void cacheValue (in Value *val, in TypeInfo_Class defpar, in TypeInfo_Class ct, const(char)[] type, const(char)[] mod) {
627 pragma(inline, true);
628 auto key = buildcc((defpar !is null ? defpar.name : null), (ct !is null ? ct.name : null), type, mod);
629 if (auto cp = key in styleCache) {
630 *cp = *val;
631 } else {
632 styleCache[key.idup] = *val;
636 protected:
637 final void resetSeenCount () {
638 seenCount = 1;
639 foreach (ref Item ci; colors.byValue) ci.seenCount = 0;
642 final void incSeenCount () {
643 pragma(inline, true);
644 if (++seenCount == 0) resetSeenCount();
647 protected:
648 void addColorItem (in Item ci) {
649 clearStyleCache();
650 auto nm = buildcc(null, ci.widget, ci.type, ci.mod);
651 if (auto cptr = nm in colors) {
652 cptr.redirect = ci.redirect;
653 cptr.value = ci.value;
654 cptr.rewidget = ci.rewidget;
655 cptr.retype = ci.retype;
656 cptr.remod = ci.remod;
657 cptr.seenCount = 0;
658 } else {
659 colors[nm.idup] = ci;
663 public:
664 void parseStyle (const(char)[] str) {
665 auto par = FuiSimpleParser(str);
666 while (!par.isEOT) {
667 if (par.check('!')) {
668 auto cmd = par.expectIdNoCopy();
669 if (cmd.strEquCI("clear-style")) {
670 clear();
671 } else {
672 par.error("invalid command: '"~cmd.idup~"'");
674 par.expect(';');
675 continue;
677 string widget = par.expectId();
678 string parent = null;
679 if (par.check(':')) {
680 parent = par.expectId();
681 if (parent == widget) par.error("invalid parent `"~parent~"` for `"~widget~"`");
682 parents[widget] = parent;
684 par.expect('{');
685 string mod = null;
686 for (;;) {
687 if (par.check('}')) {
688 if (mod is null) break;
689 mod = null;
690 continue;
692 //{ import std.stdio; writeln("WIDGET: <", widget, ">"); }
693 if ((mod is null) && par.check('.')) {
694 mod = par.expectId();
695 //{ import std.stdio; writeln("NEW MOD: <", mod, ">"); }
696 par.expect('{');
697 continue;
699 string type = par.expectId();
700 //{ import std.stdio; writeln("TYPE: <", type, ">"); }
701 par.expect(':');
702 Item ci;
703 ci.widget = widget;
704 ci.type = type;
705 ci.mod = mod;
706 // redirect?
707 if (par.check('@')) {
708 // redirect
709 ci.redirect = true;
710 if (par.checkNoEat('.')) {
711 // .mod
712 ci.rewidget = null;
713 ci.retype = type;
714 } else {
715 string rd0 = par.expectId();
716 if (par.check(':')) {
717 // widget:...
718 ci.rewidget = rd0;
719 if (par.checkNoEat('.')) {
720 ci.retype = type;
721 } else {
722 // has type?
723 if (par.checkNoEat(';')) {
724 ci.retype = type;
725 } else {
726 ci.retype = par.expectId();
729 } else {
730 // type...
731 ci.rewidget = null;
732 ci.retype = rd0;
735 if (par.check('.')) {
736 ci.remod = par.expectId();
737 } else {
738 ci.remod = mod;
740 } else if (par.check('"')) {
741 // string
742 ci.redirect = false;
743 ci.value = Value.String(par.parseString('"'));
744 } else if (par.check('\'')) {
745 // string
746 ci.redirect = false;
747 ci.value = Value.String(par.parseString('\''));
748 } else if (par.checkDigitNoEat() || par.checkNoEat('+') || par.checkNoEat('-')) {
749 // number
750 ci.redirect = false;
751 ci.value = Value.Integer(par.parseInt());
752 } else {
753 // color
754 ci.redirect = false;
755 ci.value = Value.Color(par.parseColor());
757 par.expect(';');
758 if (ci.mod.strEquCI("none") || ci.mod.strEquCI("null")) ci.mod = null;
759 if (ci.remod.strEquCI("none") || ci.remod.strEquCI("null")) ci.remod = null;
760 version(none) {
761 import std.stdio;
762 write("SRC: <", ci.widget, ":", ci.type, ".", ci.mod, ">");
763 if (ci.redirect) {
764 writeln(" --> <", ci.rewidget, ":", ci.retype, ".", ci.remod, ">");
765 } else {
766 writeln(" : rgba(", gxGetRed(ci.color), ",", gxGetGreen(ci.color), ",", gxGetBlue(ci.color), ",", gxGetAlpha(ci.color), ")");
769 addColorItem(ci);
774 public:
775 this () {}
777 void cloneFrom (WidgetStyle st) {
778 if (st is null || st is this) return;
779 colors = null;
780 foreach (const ref Item ci; st.colors.byValue) addColorItem(ci);
781 parents = null;
782 foreach (auto s; st.parents.byKeyValue) parents[s.key] = s.value;
783 styleCache = null;
784 resetSeenCount();
785 seenCount = 0;
788 void clear () {
789 colors = null;
790 parents = null;
791 styleCache = null;
792 seenCount = 0;
795 protected static struct BaseInfo {
796 TypeInfo_Class defaultParent = void;
797 TypeInfo_Class ctsrc = void;
800 protected const(Value)* findValueIntr (ref BaseInfo nfo, TypeInfo_Class ctwdt,
801 const(char)[] type, const(char)[] mod=null)
803 if (ctwdt is null) {
804 ctwdt = nfo.defaultParent;
805 if (ctwdt is null) return null;
808 string sname = classShortName(ctwdt);
810 Item *mcit = void;
811 auto nm = buildcc(null, sname, type, mod);
812 mcit = (nm in colors);
813 // if there is a modifier, try without it
814 if (mcit is null && mod.length) {
815 nm = buildcc(null, sname, type, null);
816 mcit = (nm in colors);
819 if (mcit !is null) {
820 if (!mcit.redirect) {
821 // not a redirect
822 mcit.seenCount = seenCount;
823 return &mcit.value;
826 // check for infinite loop
827 if (mcit.seenCount == seenCount) {
828 // infinite loop
829 return null;
831 mcit.seenCount = seenCount;
833 // empty redirect widget name means "source widget"
834 if (mcit.rewidget.length == 0) {
835 return findValueIntr(ref nfo, nfo.ctsrc, mcit.retype, mcit.remod);
838 // special "parent" redirect widget name
839 if (mcit.rewidget == "parent") {
840 if (auto pp = sname in parents) {
841 return findValueIntr(ref nfo, findWidgetClass(*pp), mcit.retype, mcit.remod);
842 } else {
843 return findValueIntr(ref nfo, ctwdt.base, mcit.retype, mcit.remod);
847 // normal-named redirect
848 return findValueIntr(ref nfo, findWidgetClass(mcit.rewidget), mcit.retype, mcit.remod);
851 // try parent
852 if (auto pp = sname in parents) {
853 return findValueIntr(ref nfo, findWidgetClass(*pp), type, mod);
856 // try inheritance parent
857 return findValueIntr(ref nfo, ctwdt.base, type, mod);
860 final const(Value)* findValue (TypeInfo_Class defparent, TypeInfo_Class ctsrc, const(char)[] type, const(char)[] mod=null) {
861 if (defparent is ctsrc) defparent = null;
863 if (ctsrc is null) {
864 if (defparent is null) return null;
865 ctsrc = defparent;
866 defparent = null;
869 if (auto fv = findCachedValue(defparent, ctsrc, type, mod)) return fv;
871 incSeenCount();
872 BaseInfo nfo;
873 nfo.defaultParent = defparent;
874 nfo.ctsrc = ctsrc;
875 if (auto fv = findValueIntr(ref nfo, ctsrc, type, mod)) {
876 cacheValue(fv, defparent, ctsrc, type, mod);
877 return fv;
880 auto v = Value.Color(gxUnknown);
881 cacheValue(&v, defparent, ctsrc, type, mod);
883 return null;
886 final uint findColor (TypeInfo_Class defparent, TypeInfo_Class ctsrc, const(char)[] type, const(char)[] mod=null, bool* foundp=null) {
887 if (auto val = findValue(defparent, ctsrc, type, mod)) {
888 if (val.isColor) {
889 if (foundp) *foundp = true;
890 return val.color;
894 if (foundp) *foundp = false;
895 return gxUnknown;
898 final uint findColor (in Object defpar, in Object obj, const(char)[] type, const(char)[] mod=null, bool* foundp=null) {
899 pragma(inline, true);
900 TypeInfo_Class dp = (defpar !is null ? cast(TypeInfo_Class)typeid(defpar) : null);
901 TypeInfo_Class ct = (obj !is null ? cast(TypeInfo_Class)typeid(obj) : null);
902 return findColor(dp, ct, type, mod, foundp);
905 // returns `null` if not found
906 final string findString (in Object defpar, in Object obj, const(char)[] type, const(char)[] mod=null) {
907 pragma(inline, true);
908 TypeInfo_Class dp = (defpar !is null ? cast(TypeInfo_Class)typeid(defpar) : null);
909 TypeInfo_Class ct = (obj !is null ? cast(TypeInfo_Class)typeid(obj) : null);
910 if (auto val = findValue(dp, ct, type, mod)) {
911 if (val.isString) return (val.sval.length ? val.sval : "");
913 return null;
916 // returns 0 if not found
917 final int findInt (in Object defpar, in Object obj, const(char)[] type, const(char)[] mod=null, bool* foundp=null) {
918 pragma(inline, true);
919 TypeInfo_Class dp = (defpar !is null ? cast(TypeInfo_Class)typeid(defpar) : null);
920 TypeInfo_Class ct = (obj !is null ? cast(TypeInfo_Class)typeid(obj) : null);
921 if (auto val = findValue(dp, ct, type, mod)) {
922 if (val.isInteger) {
923 if (foundp) *foundp = true;
924 return val.ival;
927 if (foundp) *foundp = false;
928 return 0;
931 static:
932 __gshared TypeInfo_Class[string] classNameCache;
934 static bool isGoodClassType (TypeInfo_Class ct) pure nothrow @trusted @nogc {
935 while (ct !is null) {
936 if (ct.name == "iv.egra.gui.subwindows.SubWindow" ||
937 ct.name == "iv.egra.gui.widgets.Widget")
939 return true;
941 ct = ct.base;
943 return false;
946 static string classShortName (in TypeInfo_Class ct) nothrow @trusted @nogc {
947 pragma(inline, true);
948 if (ct is null) return null;
949 string name = ct.name;
950 auto dpos = name.lastIndexOf('.');
951 return (dpos < 0 ? name : name[dpos+1..$]);
954 static TypeInfo_Class findWidgetClass (string cname) {
955 if (cname.length == 0) return null;
956 if (auto ctp = cname in classNameCache) {
957 version(none) { import core.stdc.stdio : printf;
958 printf("findWidgetClass<%.*s>: CACHE HIT! %p\n", cast(uint)cname.length, cname.ptr, *ctp);
960 return *ctp;
962 foreach (ModuleInfo* m; ModuleInfo) {
963 string mname = m.name;
964 if (mname.startsWith("std.") ||
965 mname.startsWith("core.") ||
966 mname.startsWith("rt.") ||
967 mname.startsWith("gc.") ||
968 mname.startsWith("arsd.") ||
969 false)
971 continue;
973 foreach (TypeInfo_Class ct; m.localClasses()) {
974 if (!ct.name.endsWith(cname)) continue;
975 version(none) { import core.stdc.stdio : printf;
976 printf("findWidgetClass<%.*s>: checking <%.*s>\n",
977 cast(uint)cname.length, cname.ptr,
978 cast(uint)ct.name.length, ct.name.ptr);
980 if (ct.name.length == cname.length || ct.name[$-cname.length-1] == '.') {
981 version(none) { import core.stdc.stdio : printf;
982 printf("findWidgetClass<%.*s>: found <%.*s>\n",
983 cast(uint)cname.length, cname.ptr,
984 cast(uint)ct.name.length, ct.name.ptr);
986 // final check
987 if (isGoodClassType(ct)) {
988 // cache it
989 classNameCache[cname.idup] = ct;
990 return ct;
995 version(none) { import core.stdc.stdio : printf;
996 printf("findWidgetClass<%.*s>: NOT FOUND!\n", cast(uint)cname.length, cname.ptr);
998 // cache "unknown"
999 classNameCache[cname] = null;
1000 return null;
1005 __gshared WidgetStyle defaultColorStyle;
1007 shared static this () {
1008 defaultColorStyle = new WidgetStyle;
1009 defaultColorStyle.parseStyle(defaultStyleText);