1 (* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 {$INCLUDE ../../shared/a_modes.inc}
25 xstreams
, xparser
, utils
, hashtable
;
29 TStyleValue
= packed record
31 type TType
= (Empty
, Bool
, Int
, Color
);
34 constructor Create (v
: Boolean; okToInherit
: Boolean=true);
35 constructor Create (v
: Integer; okToInherit
: Boolean=true);
36 constructor Create (ar
, ag
, ab
: Integer; okToInherit
: Boolean=true);
37 constructor Create (ar
, ag
, ab
, aa
: Integer; okToInherit
: Boolean=true);
38 constructor Create (const v
: TGxRGBA
; okToInherit
: Boolean=true);
40 function isEmpty (): Boolean; inline;
41 function canInherit (): Boolean; inline;
43 function toString (): AnsiString;
44 function asRGBA
: TGxRGBA
; inline;
45 function asRGBADef (const def
: TGxRGBA
): TGxRGBA
; inline;
46 function asIntDef (const def
: Integer): Integer; inline;
47 function asBoolDef (const def
: Boolean): Boolean; inline;
51 allowInherit
: Boolean;
53 TType
.Bool
: (bval
: Boolean);
54 TType
.Int
: (ival
: Integer);
55 TType
.Color
: (r
, g
, b
, a
: Byte);
58 TStyleSection
= class;
60 THashStrStyleVal
= specialize THashBase
<AnsiString, TStyleValue
, THashKeyStrAnsiCI
>;
61 THashStrSection
= specialize THashBase
<AnsiString, TStyleSection
, THashKeyStrAnsiCI
>;
65 mVals
: THashStrStyleVal
;
66 mHashVals
: THashStrSection
; // "#..."
67 mCtlVals
: THashStrSection
;
70 // "text-color#inactive@label"
71 function getValue (const path
: AnsiString): TStyleValue
;
72 procedure putValue (const path
: AnsiString; const val
: TStyleValue
);
75 constructor Create ();
76 destructor Destroy (); override;
79 property value
[const path
: AnsiString]: TStyleValue read getValue write putValue
; default
;
84 mId
: AnsiString; // style name ('default', for example)
88 procedure parse (par
: TTextParser
);
90 function getValue (const path
: AnsiString): TStyleValue
; inline;
91 procedure putValue (const path
: AnsiString; const val
: TStyleValue
); inline;
94 constructor Create (const aid
: AnsiString);
95 constructor Create (st
: TStream
); // parse from stream
96 constructor CreateFromFile (const fname
: AnsiString);
97 destructor Destroy (); override;
100 property id
: AnsiString read mId
;
101 property value
[const path
: AnsiString]: TStyleValue read getValue write putValue
; default
;
105 procedure uiLoadStyles (const fname
: AnsiString);
106 procedure uiLoadStyles (st
: TStream
);
108 // will return "default" (or raise an exception if there is no "default")
109 function uiFindStyle (const stname
: AnsiString): TUIStyle
;
115 // ////////////////////////////////////////////////////////////////////////// //
117 styles
: array of TUIStyle
= nil;
120 function createDefaultStyle (): TUIStyle
;
122 result
:= TUIStyle
.Create('default');
124 result
['back-color'] := TStyleValue
.Create(TGxRGBA
.Create(0, 0, 128));
125 result
['text-color'] := TStyleValue
.Create(TGxRGBA
.Create(255, 255, 255));
126 result
['frame-color'] := TStyleValue
.Create(TGxRGBA
.Create(255, 255, 255));
127 result
['frame-text-color'] := TStyleValue
.Create(TGxRGBA
.Create(255, 255, 255));
128 result
['frame-icon-color'] := TStyleValue
.Create(TGxRGBA
.Create(0, 255, 0));
130 // disabled is always inactive too
131 result
['back-color#disabled'] := TStyleValue
.Create(TGxRGBA
.Create(0, 0, 128));
132 result
['text-color#disabled'] := TStyleValue
.Create(TGxRGBA
.Create(127, 127, 127));
133 result
['frame-text-color#disabled'] := TStyleValue
.Create(TGxRGBA
.Create(127, 127, 127));
134 result
['frame-icon-color#disabled'] := TStyleValue
.Create(TGxRGBA
.Create(0, 127, 0));
135 result
['darken#disabled'] := TStyleValue
.Create(128, false); // darken inactive windows, no-inherit
136 result
['darken#inactive'] := TStyleValue
.Create(128, false); // darken inactive windows, no-inherit
138 result
['text-color@label'] := TStyleValue
.Create(TGxRGBA
.Create(255, 255, 255));
139 result
['text-color#disabled@label'] := TStyleValue
.Create(TGxRGBA
.Create(127, 127, 127));
141 result
['frame-color@box'] := TStyleValue
.Create(TGxRGBA
.Create(255, 255, 0));
142 result
['frame-text-color@box'] := TStyleValue
.Create(TGxRGBA
.Create(255, 255, 0));
143 result
['frame-icon-color@box'] := TStyleValue
.Create(TGxRGBA
.Create(0, 255, 0));
145 result
['frame-color#disabled@box'] := TStyleValue
.Create(TGxRGBA
.Create(127, 127, 127));
146 result
['frame-text-color#disabled@box'] := TStyleValue
.Create(TGxRGBA
.Create(127, 127, 127));
147 result
['frame-icon-color#disabled@box'] := TStyleValue
.Create(TGxRGBA
.Create(127, 127, 127));
151 function uiFindStyle (const stname
: AnsiString): TUIStyle
;
155 if (Length(stname
) > 0) then
157 for stl
in styles
do if (strEquCI1251(stl
.mId
, stname
)) then begin result
:= stl
; exit
; end;
159 for stl
in styles
do if (strEquCI1251(stl
.mId
, 'default')) then begin result
:= stl
; exit
; end;
160 stl
:= createDefaultStyle();
161 SetLength(styles
, Length(styles
)+1);
162 styles
[High(styles
)] := stl
;
167 procedure uiLoadStyles (const fname
: AnsiString);
171 st
:= openDiskFileRO(fname
);
180 procedure uiLoadStyles (st
: TStream
);
186 if (st
= nil) then raise Exception
.Create('cannot load UI styles from nil stream');
187 par
:= TFileTextParser
.Create(st
, false, [par
.TOption
.SignedNumbers
, par
.TOption
.DollarIsId
, par
.TOption
.DashIsId
]);
190 while (not par
.isEOF
) do
192 stl
:= TUIStyle
.Create('');
194 //writeln('new style: <', stl.mId, '>');
196 while (f
< Length(styles
)) do begin if (strEquCI1251(styles
[f
].mId
, stl
.mId
)) then break
; Inc(f
); end;
197 if (f
< Length(styles
)) then
199 FreeAndNil(styles
[f
]);
204 SetLength(styles
, f
+1);
213 // we should have "default" style
214 for f
:= 0 to High(styles
) do if (strEquCI1251(styles
[f
].mId
, 'default')) then exit
;
215 stl
:= createDefaultStyle();
216 SetLength(styles
, Length(styles
)+1);
217 styles
[High(styles
)] := stl
;
221 // ////////////////////////////////////////////////////////////////////////// //
222 constructor TStyleValue
.Create (v
: Boolean; okToInherit
: Boolean=true); begin vtype
:= TType
.Bool
; allowInherit
:= okToInherit
; bval
:= v
; end;
223 constructor TStyleValue
.Create (v
: Integer; okToInherit
: Boolean=true); begin vtype
:= TType
.Int
; allowInherit
:= okToInherit
; ival
:= v
; end;
225 constructor TStyleValue
.Create (ar
, ag
, ab
: Integer; okToInherit
: Boolean=true);
227 vtype
:= TType
.Color
;
228 allowInherit
:= okToInherit
;
229 r
:= nmax(0, nmin(ar
, 255));
230 g
:= nmax(0, nmin(ag
, 255));
231 b
:= nmax(0, nmin(ab
, 255));
235 constructor TStyleValue
.Create (ar
, ag
, ab
, aa
: Integer; okToInherit
: Boolean=true);
237 vtype
:= TType
.Color
;
238 allowInherit
:= okToInherit
;
239 r
:= nmax(0, nmin(ar
, 255));
240 g
:= nmax(0, nmin(ag
, 255));
241 b
:= nmax(0, nmin(ab
, 255));
242 a
:= nmax(0, nmin(aa
, 255));
245 constructor TStyleValue
.Create (const v
: TGxRGBA
; okToInherit
: Boolean=true);
247 vtype
:= TType
.Color
;
248 allowInherit
:= okToInherit
;
255 function TStyleValue
.isEmpty (): Boolean; inline; begin result
:= (vtype
= TType
.Empty
); end;
256 function TStyleValue
.canInherit (): Boolean; inline; begin result
:= allowInherit
; end;
257 function TStyleValue
.asRGBA
: TGxRGBA
; inline; begin if (vtype
= TType
.Color
) then result
:= TGxRGBA
.Create(r
, g
, b
, a
) else result
:= TGxRGBA
.Create(0, 0, 0, 0); end;
258 function TStyleValue
.asRGBADef (const def
: TGxRGBA
): TGxRGBA
; inline; begin if (vtype
= TType
.Color
) then result
:= TGxRGBA
.Create(r
, g
, b
, a
) else result
:= def
; end;
259 function TStyleValue
.asIntDef (const def
: Integer): Integer; inline; begin if (vtype
= TType
.Int
) then result
:= ival
else if (vtype
= TType
.Bool
) then begin if (bval
) then result
:= 1 else result
:= 0; end else result
:= def
; end;
260 function TStyleValue
.asBoolDef (const def
: Boolean): Boolean; inline; begin if (vtype
= TType
.Bool
) then result
:= bval
else if (vtype
= TType
.Int
) then result
:= (ival
<> 0) else result
:= def
; end;
263 function TStyleValue
.toString (): AnsiString;
266 TType
.Empty
: result
:= '<empty>';
267 TType
.Bool
: if bval
then result
:= 'true' else result
:= 'false';
268 TType
.Int
: result
:= formatstrf('%s', [ival
]);
269 TType
.Color
: if (a
= 255) then result
:= formatstrf('rgb(%s,%s,%s)', [r
, g
, b
]) else result
:= formatstrf('rgba(%s,%s,%s)', [r
, g
, b
, a
]);
270 else result
:= '<invalid>';
275 // ////////////////////////////////////////////////////////////////////////// //
276 procedure freeSectionCB (var v
: TStyleSection
); begin FreeAndNil(v
); end;
279 function splitPath (const path
: AnsiString; out name
, hash
, ctl
: AnsiString): Boolean;
281 hashPos
, atPos
: Integer;
287 hashPos
:= pos('#', path
);
288 atPos
:= pos('@', path
);
292 // has ctl, and (possible) hash
293 if (hashPos
> 0) then
296 if (atPos
< hashPos
) then exit
; // alas
297 if (hashPos
> 1) then name
:= Copy(path
, 1, hashPos
-1);
298 Inc(hashPos
); // skip hash
299 if (hashPos
< atPos
) then hash
:= Copy(path
, hashPos
, atPos
-hashPos
);
304 if (atPos
> 1) then name
:= Copy(path
, 1, atPos
-1);
306 Inc(atPos
); // skip "at"
307 if (atPos
<= Length(path
)) then ctl
:= Copy(path
, atPos
, Length(path
)-atPos
+1);
309 else if (hashPos
> 0) then
312 if (hashPos
> 1) then name
:= Copy(path
, 1, hashPos
-1);
313 Inc(hashPos
); // skip hash
314 if (hashPos
<= Length(path
)) then hash
:= Copy(path
, hashPos
, Length(path
)-hashPos
+1);
325 // ////////////////////////////////////////////////////////////////////////// //
326 constructor TStyleSection
.Create ();
328 mVals
:= THashStrStyleVal
.Create();
329 mHashVals
:= THashStrSection
.Create();
330 mCtlVals
:= THashStrSection
.Create(freeSectionCB
);
334 destructor TStyleSection
.Destroy ();
337 FreeAndNil(mHashVals
);
338 FreeAndNil(mCtlVals
);
343 // "text-color#inactive@label"
344 function TStyleSection
.getValue (const path
: AnsiString): TStyleValue
;
346 name
, hash
, ctl
: AnsiString;
347 sect
: TStyleSection
= nil;
348 s1
: TStyleSection
= nil;
349 checkInheritance
: Boolean = false;
351 result
.vtype
:= result
.TType
.Empty
;
352 if (not splitPath(path
, name
, hash
, ctl
)) then exit
; // alas
353 //writeln('name:<', name, '>; hash:<', hash, '>; ctl:<', ctl, '>');
354 if (Length(name
) = 0) then exit
; // alas
356 if (Length(ctl
) > 0) then
359 if not mCtlVals
.get(ctl
, sect
) then
362 checkInheritance
:= true;
370 if (Length(hash
) > 0) then
372 if sect
.mHashVals
.get(hash
, s1
) then
374 if s1
.mVals
.get(name
, result
) then
376 //writeln('hash: <', hash, '>: val=', result.toString);
377 if (not result
.isEmpty
) and ((not checkInheritance
) or (result
.canInherit
)) then exit
;
380 //writeln('NO hash: <', hash, '>: val=', result.toString);
381 checkInheritance
:= true;
384 if sect
.mVals
.get(name
, result
) then
386 if (not result
.isEmpty
) and ((not checkInheritance
) or (result
.canInherit
)) then exit
;
389 result
.vtype
:= result
.TType
.Empty
;
393 procedure TStyleSection
.putValue (const path
: AnsiString; const val
: TStyleValue
);
395 name
, hash
, ctl
: AnsiString;
396 sect
: TStyleSection
= nil;
397 s1
: TStyleSection
= nil;
399 if (not splitPath(path
, name
, hash
, ctl
)) then exit
; // alas
401 if (Length(name
) = 0) then exit
; // no name -> nothing to do
403 if (Length(ctl
) > 0) then
405 if not mCtlVals
.get(ctl
, sect
) then
407 // create new section
408 sect
:= TStyleSection
.Create();
409 mCtlVals
.put(ctl
, sect
);
414 // no ctl, use default section
418 if (Length(hash
) > 0) then
420 if not sect
.mHashVals
.get(hash
, s1
) then
422 // create new section
423 s1
:= TStyleSection
.Create();
424 mHashVals
.put(hash
, s1
);
429 // no hash, use default section
432 s1
.mVals
.put(name
, val
);
436 // ////////////////////////////////////////////////////////////////////////// //
437 constructor TUIStyle
.Create (const aid
: AnsiString);
440 mMain
:= TStyleSection
.Create();
444 constructor TUIStyle
.Create (st
: TStream
); // parse from stream
449 mMain
:= TStyleSection
.Create();
450 if (st
= nil) then exit
;
451 par
:= TFileTextParser
.Create(st
, false, [par
.TOption
.SignedNumbers
, par
.TOption
.DollarIsId
, par
.TOption
.DashIsId
]);
460 constructor TUIStyle
.CreateFromFile (const fname
: AnsiString);
466 mMain
:= TStyleSection
.Create();
467 st
:= openDiskFileRO(fname
);
469 par
:= TFileTextParser
.Create(st
, false, [par
.TOption
.SignedNumbers
, par
.TOption
.DollarIsId
, par
.TOption
.DashIsId
]);
481 destructor TUIStyle
.Destroy ();
488 function TUIStyle
.getValue (const path
: AnsiString): TStyleValue
; inline;
490 result
:= mMain
[path
];
493 procedure TUIStyle
.putValue (const path
: AnsiString; const val
: TStyleValue
); inline;
495 mMain
.putValue(path
, val
);
499 procedure TUIStyle
.parse (par
: TTextParser
);
500 function getByte (): Byte;
502 if (par
.tokType
<> par
.TTInt
) then par
.expectInt();
503 if (par
.tokInt
< 0) or (par
.tokInt
> 255) then par
.error('invalid byte value');
504 result
:= Byte(par
.tokInt
);
508 procedure parseSection (sect
: TStyleSection
; ctlAllowed
: Boolean; hashAllowed
: Boolean);
511 sc
: TStyleSection
= nil;
514 par
.expectDelim('{');
515 while (not par
.isDelim('}')) do
517 while (par
.eatDelim(';')) do begin end;
519 if hashAllowed
and (par
.eatDelim('#')) then
521 s
:= par
.expectIdOrStr();
522 //writeln('hash: <', s, '>');
523 par
.eatDelim(':'); // optional
524 if not sect
.mHashVals
.get(s
, sc
) then
526 // create new section
527 sc
:= TStyleSection
.Create();
528 sect
.mHashVals
.put(s
, sc
);
530 parseSection(sc
, false, false);
534 if ctlAllowed
and (par
.eatDelim('@')) then
536 s
:= par
.expectIdOrStr();
537 //writeln('ctl: <', s, '>');
538 par
.eatDelim(':'); // optional
539 if not sect
.mCtlVals
.get(s
, sc
) then
541 // create new section
542 sc
:= TStyleSection
.Create();
543 sect
.mCtlVals
.put(s
, sc
);
545 parseSection(sc
, false, true);
549 s
:= par
.expectIdOrStr();
550 //writeln('name: <', s, '>');
551 v
.allowInherit
:= true;
552 par
.expectDelim(':');
553 if (par
.eatId('rgb')) or (par
.eatId('rgba')) then
556 par
.expectDelim('(');
557 v
.vtype
:= v
.TType
.Color
;
558 v
.r
:= getByte(); par
.eatDelim(','); // optional
559 v
.g
:= getByte(); par
.eatDelim(','); // optional
560 v
.b
:= getByte(); par
.eatDelim(','); // optional
561 if (par
.tokType
= par
.TTInt
) then
563 v
.a
:= getByte(); par
.eatDelim(','); // optional
567 v
.a
:= 255; // opaque
569 par
.expectDelim(')');
571 else if (par
.eatId('true')) or (par
.eatId('tan')) then
573 v
.vtype
:= v
.TType
.Bool
;
576 else if (par
.eatId('false')) or (par
.eatId('ona')) then
578 v
.vtype
:= v
.TType
.Bool
;
584 v
.vtype
:= v
.TType
.Int
;
585 v
.ival
:= par
.expectInt();
588 while (par
.eatDelim('!')) do
590 if (par
.eatId('no-inherit')) then v
.allowInherit
:= false
591 else par
.error('unknown flag');
593 par
.expectDelim(';');
594 sect
.mVals
.put(s
, v
);
596 par
.expectDelim('}');
601 if (not par
.isIdOrStr
) then
603 if (Length(mId
) = 0) then par
.error('style name expected');
609 if (Length(mId
) = 0) then mId
:= 'default';
611 parseSection(mMain
, true, true);