From 313c905391ae6cfdcb15a0a341db2bd0951e489e Mon Sep 17 00:00:00 2001 From: Dmitry Kalyanov Date: Sun, 27 Sep 2009 09:56:32 +0400 Subject: [PATCH] Add embedded UI specification language --- doc/Makefile | 8 ++- doc/gtk.ref.texi | 102 ++++++++++++++++++++++++++++++++++ doc/gtk.widgets.texi | 14 +++++ doc/let-ui-glext.png | Bin 0 -> 17630 bytes doc/let-ui.png | Bin 0 -> 5849 bytes gtk-glext/demo.lisp | 42 +++++++------- gtk/cl-gtk2-gtk.asd | 2 + gtk/gtk.demo.lisp | 39 ++++++++++++- gtk/gtk.package.lisp | 3 +- gtk/ui-markup.lisp | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 333 insertions(+), 25 deletions(-) create mode 100644 doc/let-ui-glext.png create mode 100644 doc/let-ui.png create mode 100644 gtk/ui-markup.lisp diff --git a/doc/Makefile b/doc/Makefile index 7259322..099c2d6 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -1,4 +1,4 @@ -all: doc.html tutorial.html gobject/index.html gtk/index.html gobject/style.css gtk/style.css +all: doc.html tutorial.html gobject/index.html gtk/index.html gobject/style.css gtk/style.css gtk/let-ui.png gtk/let-ui-glext.png .PHONY: all archive @@ -27,6 +27,12 @@ gobject/index.html: gobject.texi gobject.ref.texi gtk/style.css: style.css ([ -x gtk ] || mkdir gtk) && cp $< $@ +gtk/let-ui.png: let-ui.png + cp $< $@ + +gtk/let-ui-glext.png: let-ui-glext.png + cp $< $@ + gtk/index.html: gtk.texi gtk.ref.texi gdk.ref.texi gobject.ref.texi glib.ref.texi gdk.enums.texi \ gtk.flags.texi gtk.objects.texi gdk.flags.texi gdk.structs.texi gtk.interfaces.texi gtk.widgets.texi gdk.objects.texi \ gtk.enums.texi gtk.main_loop.texi gtk.structs.texi diff --git a/doc/gtk.ref.texi b/doc/gtk.ref.texi index 4b159af..e3ed077 100644 --- a/doc/gtk.ref.texi +++ b/doc/gtk.ref.texi @@ -6,6 +6,7 @@ * Gtk+ Structs:: * Gtk+ Enums:: * Gtk+ Flags:: +* Gtk+ Embedded UI Mini-language:: @end menu All symbols of Gtk+ binding in cl-gtk2 reside in @code{gtk} package. @@ -44,3 +45,104 @@ All symbols of Gtk+ binding in cl-gtk2 reside in @code{gtk} package. @chapter Gtk+ Flags @include gtk.flags.texi + +@node Gtk+ Embedded UI Mini-language +@chapter Gtk+ Embedded UI Mini-language + +For convenience of specifying widgets hierarchy in Lisp code, the @ref{let-ui} macro is introduced. + +@RMacro let-ui +@lisp +(let-ui ui-description &body body) + +ui-description ::= widget +widget ::= (class properties child*) +properties ::= @{:prop-name prop-value@}* +child ::= widget properties +child ::= (:expr expr) properties +@end lisp + +@table @var +@item @var{class} +Name of class of a widget +@item @var{:prop-name} +Name of class's slot or a @code{:var} for specifying the variable name to which the object will be bound +@item @var{prop-value} +A Lisp expression that will be evaluated to obtain the initarg for slot of a class; or a symbol if @code{:prop-name} is @code{:var} +@item @var{expr} +An expression that will be evaluated to obtain the widget +@end table + +This macro creates widgets and evaluates the @var{body}. Widgets that have @code{:var} specified are bound to lexical variables with specified names. + +@var{ui-description} specifies the hierarchy of widgets in a window. It can specify either the entire top-level window or other kind of widgets. @var{ui-description} is a mini-language for specifying widgets. @ref{let-ui} creates specified widgets, lexically binds specified variables to widgets and evaluates the @var{body}. The @var{body} my refer to these widgets. + +@var{widget} is the specification of a single widget. It may specify some properties (slots of objects) and their values (the expressions to be evaluated), a variable name that will be bound to the widget (the @code{:var} property whose @var{prop-value} must be a symbol) and widget's children. + +@var{class} specifies the class of the widget (e.g., @ref{label}, @ref{button}, @ref{gtk-window}). @var{:prop-name} may be any slot of the class. If @var{:var} property is specified, then corresponding variable is accessible in @var{body} and its value is the widget on which it is specified as @var{:var}. + +Container widgets may specify their @var{children} along with their @var{child properties}. Child properties specify how @var{children} are used in @var{widget}. They are specific to the type of the container: +@itemize +@item @ref{box} specifies @code{:expand}, @code{:fill}. See @ref{box-pack-start} for information. +@item @ref{paned} specifies @code{:resize}, @code{:shrink}. See @ref{paned-pack-1} for information. +@item @ref{table} specifies @code{:left}, @code{:right}, @code{:top}, @code{:bottom}, @code{:x-options}, @code{:y-options}, @code{x-padding}, @code{y-padding}. Of these, @code{:left}, @code{:right}, @code{:top} and @code{:bottom} are mandatory. See @ref{table-attach} for information. +@end itemize + +An example: +@lisp +(let-ui (gtk-window :title "Hello" :position :center :var w + (v-box + (label :label "Hello, world!") + (button :label "gtk-ok" :use-stock t) :expand nil)) + (widget-show w)) +@end lisp +produces this output: + +@image{let-ui,,,,png} + +More complex example from demo of cl-gtk2-gtk-glext: +@lisp +(let-ui (v-paned :var v + (:expr (opengl-window-drawing-area window)) + :resize t :shrink nil + (v-box + (h-paned + (scrolled-window + :hscrollbar-policy :automatic + :vscrollbar-policy :automatic + (:expr (opengl-window-expose-fn-text-view window))) + :resize t :shrink nil + (scrolled-window + :hscrollbar-policy :automatic + :vscrollbar-policy :automatic + (:expr (opengl-window-resize-fn-text-view window))) + :resize t :shrink nil) + (h-box + (button :label "Update functions" :var update-fns-button) :expand nil + (button :label "Redraw" :var redraw-button) :expand nil) + :expand nil) + :resize t :shrink nil) + (container-add window v) + (connect-signal update-fns-button "clicked" + (lambda (b) + (declare (ignore b)) + (update-fns window))) + (connect-signal redraw-button "clicked" + (lambda (b) + (declare (ignore b)) + (widget-queue-draw (opengl-window-drawing-area window)))) + (let ((area (opengl-window-drawing-area window))) + (setf (gl-drawing-area-on-expose area) + (lambda (w e) + (declare (ignore w e)) + (opengl-interactive-on-expose window)) + (gl-drawing-area-on-resize area) + (lambda (widget w h) + (declare (ignore widget)) + (opengl-interactive-on-resize window w h))))) +@end lisp +produces this output: + +@image{let-ui-glext,,,,png} + +In this example, not top-level window, but a widget is created and then added to already existing window. This UI also uses some already created widgets: @code{(:expr (opengl-window-resize-fn-text-view window))}. diff --git a/doc/gtk.widgets.texi b/doc/gtk.widgets.texi index 0966de9..7c4604f 100644 --- a/doc/gtk.widgets.texi +++ b/doc/gtk.widgets.texi @@ -427,6 +427,10 @@ Signals: @itemize @end itemize +@RMethod box-pack-start +@lisp +box-pack-start +@end lisp @@ -3076,6 +3080,11 @@ Signals: @end itemize +@RMethod paned-pack-1 +@lisp +paned-pack-1 +@end lisp + @node plug @@ -3730,6 +3739,11 @@ Signals: @end itemize +@RMethod table-attach +@lisp +table-attach +@end lisp + @node tearoff-menu-item diff --git a/doc/let-ui-glext.png b/doc/let-ui-glext.png new file mode 100644 index 0000000000000000000000000000000000000000..509927775d35799b9c5752884b0045c3a2b51efd GIT binary patch literal 17630 zcmeIacTkhxw>KIqDo7ClQHoMTq$!~Yp(q^`klq9oq?gb`3sIEbr1vIGr1wswcL;>u zLhmH>k`Qv^_sl&r=braHzkBB1^T(aJPx4HXJ=trmy`HuA{;cvmLGM-M@7{TM2LJ%v zRaB5s2LP^I0037hZe1m|(9h%Si62*-)#arC#e)o+#2+_J-pb1W2!CH0O?lD8mfH>r zdd>g#8_#a5<$YBK03HJrW!`AIPw&n_ycx#og!k4( zfE8Lzx>06td7Z$uUzE%4cHYo1K{{@D+|GO&LwZHZ-oEyF)E{=XuWaAH-+N3vJzZOF zB11_#JLjAt3$4d?f$NR1er+rEzL>`baPWnr+`-_^PU51lMVbhx4QMgQ1^{e4IwF^( zIH~-p1^|#h0^#*GrC}e6BLa&>90Aj}01Ku2f0m#!mCZfL_pr-O(1{dXDVe)>k1bgN zU#>+3x6(52nUO2sz7$$-^3;y9G#UcY9;2V1>Q3s9WhPau$%wy$w8IJ%F&y8RF{<9axH79$~1nhi6 zfQ*m{1>j3FD`loAb5UXFg##K;BV5s-gPry{9iKP7Nsn(_5o#XrSUze=8)qKm0sksl z%|_nH8TG$Qf`;p8YEn`GPrgb=+$nI?`aRGhf3*JY(k%{AAVL1;jq9O~wj6JI8W&IB z#PlpagfBe}w@#y+7s@YoVK-=_vv-lBjv8q`eRjdX1{B?&ZP zeHghtmUxJUXJ%ttV3&(lykZ*}JDc~ot2{~y)lFsryrb}OirC7PtLIvhDlwX2MU@4z zRdV;YQ=(g>okTQn4=I#KN1k#S{9-SB#}faIw8G>4(?{A}cds|4P|C=V2NZBXjlgoW zYSEO#+3(U$jhV;XfXh=FA@zyzfjd(}Y5Mom+0TU;*TT@g`=oX6I17qnrkib?mt0;8 zyC@?C43RE|6Rw$7^~5*Q!7Yu>({KrAp$uOSk8Hf?Fuu%=tDSt@v66#Vj(zm2>tURoje5>tf@N42;!a^FuGJ??lM{n@ATQ)z@&;D zCPYb<*v?h0FI&vR4tEqMQ5T2wJRw(M7gGwbRv{IDz1+OBEFosEYjLrum* zNB<6ywxNFdkhkRN_2?}zj4R}lb9Wm9#|NLaA_s9p6gE2^e}6gW(on0dqu7R$yALN> zMjCCbhX1U@nwrS=O0Z4#ZJ8Tw@YF6x^k<8>|AI}G9n2wm_o;QiQ2UyXU5`5MCtFzh`J_}g~m(wqVli$?kD&3r$Ub#2!%ZQq8 z3Hqiyclp7wcaC(8OxPD^U4t15k=IOdv2i5qSP-zP5TV^)N~njKudxxxGERokT1lY5 zda<(#Jw|<($4jE%_Nxk|yzta&x8<#Y5v1d_mc<9y{z7u=`D_HnetsK$fyCDs4=Vz{=M!8IMvq>+aEw4xosa6iuVS0bZaGZ^!OiRO`^6yp zol$nNCJtPv$0_Q4IpNr~Ay`%1DccBcd(H|!udFi}Iff_9qTUD)c5v&-V?&Z&7%=1r zv^*IyNNJ>kyzdXHs6#U3Yuf}2l65e0nja`iEiBD4>Ms4RhwB-(f zB}%R5)}Dxh2PBq%Z4WZNtevFFN8wxg2Bi5?Vk%eY(te5IIfU=)NS;NY_y+6tT9{QW z&-X5w?W_;ZD|ij}{^aNx&*F#R&5TU7dkHHwMhzaeUmf_VB5A5m*7_46{SNZDBnS+$ zy)|d|{IH!G+=vex&QpC@{n=_a^v0%s*62Os&wMFy=w%nn;MC^V&TBhUT@kK+kV226 zuD3cmUW_X>PUU^&eNMIfvyRu^lu+Q^u|eN9HY=1sC5%%&J7?kW$GeT|j2fPVU7g92 zci}i37{1hKssw_Ae+tDC7mAzNvq$pc zr)eI=OSt=|j<00ZN(RZo!Y!uXihkB<2fm_De1{tS8w0*2>ZI4aV<2PgaYp z@WQR>Ll3ulZ)x=<4Rl;=P=giyUy#U2)}Mm<<>FH`LNO$fF|;q$7Dr2vWqZBOhz zRsF^5+H=_ssK!o@rPqkwjEms50XvHXs17;{q753FdDgS_80S3|7HMIXimjiXC~>TK zO&Qm@+l0Xm9(PT4b37Wy1a0`q(p)2ptrlFl={T~F!YnOk3(0DbI!>*$l2J|Wk0jj{ zq$asVrT+E3#z45CT@1scNkfXeB(XkaeSn{bjr(`u89PFm| zlqUG{*PPhz9_XB^B&%6^m!We|!Z%&<8^#pfVWUm>cr!kjO4|on9bN|S(a8v-r%%RFH;WwntIed=(ng~&t?VLRDeDH;&K=b33%2NA zuB!#`W*&qUah=hcS1aQug$RPDS6?`*uI>JQHu`Bkd^MHVoYuD^HExKh^QpQ ztMlfyY&J#Oq|$7^0a!QleSsW1pHKL>Mft(AMS7s3pG`F$mj^E5U@5M-QolgOJ?zD4d0J ztWrwK+6`gbIc;IOEG^fxWSnKP$S!51+c3MCQJ0mvWKBX!Jo`YsqaVPKVOfRAd;}#2u7T=mp?m!j}lY zr`J^ll=Brh-|7Y5u;Jz|M<69*Jh5@p+BEY6J5Hpl%8eN@64<@83W;tWE(ReBY~4CV zlAF~0$wS$dZq7c$DDM{)}2tsKpPQ^Sj88jZ$i zccO&PrHWH=bo~rOn^Y0UtF#PoTR*aKQJ&b1OiIe#9JW$M-VI=Nw3OO7-9khte`a{O zJT0jlL>W0%zt)noZDR2yu8`iPt@V(xYUaeh@f&}NtsgW_R)+14YJzi4&2xSi%S2GE z^H>@_bHs$T{AJk>tq~Lq`Oy<7TTyVOnj4$&dMj2k(B8_&=ASB(6Dk*^5zMx#?Q~-v|ELrrZ21Ra|6J3& zou5@(=THh93Ki$7v3mDQ0iBvaLeQh$r~tzj;NU}v zbwZdxaE$GFXnWR!$Hf7(ySBK{Ln4KOGxfxcDgwvX3?Gy zebDCGfU+{1uVeM-0Yz!QreklpYfv*0(T`a#@CK)=hVPUd+j!XNb=BB+J5`tuwSG_^ zEkDjNFtTjiNk~KnbIJX#7N0?Rt~E`dc)MwSru62DQ}sc zIJuEn7tVS(hD}|h&2t=m+{PGaJ82>0ulc=ub)@$|CR*2FN%`xj^~71r_IBc8yNg@P0xWu#@1}JcFK&O?A>|_b{u~dbDuOXp-O+v)d>2 zl_OGFv!&Wy>t^ayHU_TQ<%ZVwMcwbmik>%23bLoOUUtXnS9F$ew|Ja~2jOL&S@p=% z?B^N=`TbDW^7@uu_1q%&@Yh%Uo79nau%gY!3!$S|AnIcn3NVV#fTdk@)m+4%&N?x! z?&(pg-lhTTz+Z(bX;5-j!9~iOhNqd;)l0kBgPo2J7zXTer(PqmHsm|?O|i($^X2mC zzP$Cdm1EFCNb>0f-a{4{+ePOmbqRJfVmZKFgPp%RinVWBa1m8QN?w0;)WeGWP?j$G zTw82U^r_aXs1dPp(FdR~*A01cp>m|%rvWpzxF2G>0^tl+Y^(8ywI3|hsoGvXA-<14 zh`i*Z0v#=WJ6bGb?&?GhZa<^Y(!N07R3+t5M`( zhsqV4-N0KJ&&!3|L*9Egs+j?QKJmf!uBQgt6J`Pi?h4Ap7zBce!3_rcq^(mbwcYvN z0A?SB3=seTOa5H~0FYt^04T8lz!wAw0Pwfr@zE6kAd>_hTvVhcZLuFvoEVB%e5Eyu$(@f8l?gJQW zAl!u45uhV#f!q#P0C6|ihPr|n(im7-ha4=nipf5`@-x4stf9o&7kVA~)ql>Ci+KGQ z$YUy7?ZT*xD=eou$nwlz=>fkZbZ_NkSH9!AZ&^I^&Hw6mi9d^fFsEK(Ozb>dJMobj zIaY<6{Yg-Pm_^*_v@yvc3x5uT_~BUbIVX43e3c z4^T=`mc^JG?D|xa{gqJM6z-h5wnxdy!jkiI4y(IBeTb$}wNi;-WmSc^>mn8NOH1}taa#I)fGgOllZ_~E7I#`tF+SCI@UF0E?%SSntWZ-am>xeT09^mJj)6a9$kwz7U z&b5N^6Al*iXl}B35~7x1`W>N%Nn8_2PmG;+)5;YO$-uM~o!o(mK|w)*)OD)u{cegJ z*oVp%PjolhxpO)qDMQ*FrC!aF54?}dFAOa2-zj%gQdF18J}H8-=2>R6CEK`{1B1DQ z;D^tuZxFTCtk)JU2{+$;WH%5dBXG9im$djf69|laY_7s4(RJD7mIMU8q$c$Wj!{>? z7s=8SwcKaHa_=EE7p)R9Hi|7rJvioh;)|hX1L?8f@kBw*SLs9=>*}PRf(=hbbuQj7 z4>cwR^h%jFtPk3%MpL%?>XlOV`|ADpX!6EQSkmoYMhuI3b~e6?=~qd%x?6l2 z8z&9(PPA?_LU*IkO3JcuQwrFdmu~E~|SjqPPP?eKP7bkGBNf7m}@Ib}PugKvKSyjZV{iV>KPg>$c?+u$kSvF(^u$)x* z;a(M`eU5pQj@zk`n!q--BQwabG@iNWF%@Y!1t4iKiPy#AJd_mg11~Rx+88s79fL?D zJdc^TIG-U?>02Gj5sIAjxBU$sO`q6J?YEFlB8cloM&pvxfWuUc;*ZE5^ao{#M*Pif z|Cu{g6(1YqhooNEaDT^lh5Ov|{Z0$I_k!J8EJcA3W2JO%c46{`vh1=sZghXb5B9j;UvMa7fk;6I2e2p_L zY%{rrMI}zFTgt@gMlM`Yx~v-+3Tn4W6eLQ@-sbe=-}SfU`kUJm z^%P{Z*>NOsaf2#ey zR!WNAUpySX9jvvm+agqE-vEMrY@_STs={rMDk#29YFCPiD&&re5=`~$l)9DKMGhbu zZQIxJrbmbV%E~=%u@8@)+o8KiPF`@3Q`)Y(;_3QG<(cwC#1irHl$=(2N=Zuk{-BCO z-$r`9dXUBjc_w|K)yWML`rCE}`>H#Sz90hH*f%%^TtBZ67dZrjl<>|Kq5Vrx$Vy}k zLxXj1kgf98hndyXGhJM}C4;Pzezt;jaBN5GAhbv7hj^pBr9X%Zb~ZemMfx&QQ@vB= zc8vP-9UJnh(HjOfAzcG1;+*fZHT#N>>Dk-9qd&3)r}^EQbD(8@nI2O~!KI|cu52b7 zIN7Y`%pcKJ&>)tC{yg_lxn^5!grBwf=?>dp=~@uWgF=mUQjlA7lu{PTrVXcz%HHAR zscNa{AX@xQZmRsr_YY-dPW+}OT_?yHHocOMx3v={k;@g|yiwG`NjGRd%x@(hnmnoV!`nq%T)CkkJ84mgMp1 z{?dkXDd0|4eCYMz0pe=PaB!2+BN$ll8e(BZ9*-bv*tVkx(@}~nj}h4Atrn2hewAGq z7?WH6j-AIWafE-DMe%@{i{XC#w`Jq}uny?XgFj^<DxXYWAcsKitYiisem ztFCRA+Fu3QCJl&CD8O?meP@twjKO}ufpqS1&dxKP?Ea4p^tQljyV`7)sQbt}Bt;Id z<$Om%6}WyD23E3+gto`8^>U8P=}AFkFn;tr*MrVvH9=9P-}X!$(_J1YEV!#0mgDh- z?sC-m+1n(~PqtKa+_iR6WL!U#|6GAs(C*(+mD3JK4+`u^XF%_dy3k4`MV=(>PJa3; zkC41vTI!jMl3eRZ@b{TyDZ9Jor94YEL1)P-+0;Zrm?QRuRugRX>pXno;Z2XA8EDO z0^fj$Rw8Y1z7qX??W2j6e-3^4S#n!*?rT%6jQnY`>eCk+%TCz4l=Urz?7WLn3bT>{ zpwP^B=OW{qD%2CR$Wx41ZQk~%>gK*YX6D7t*hPv*_?)x=_uS%>OyU+(Fa1DeV9_KqGG^BmRMtks zy5x4f{5fWkfeCMnYnwPH)em#1(*wURvQaTE(R0Ogq0{05UBitLyPAuN`TIw;-N@xu zl>i?fr#;}*a!g9%4Q>I@QKI0dSAuP_-2Dy!VM8MHV>rmBQ5(^WEA6VGB*mFuGnP^r z{?eYxAKhF`(?G9cTdM4&DtQT;wNrphZDTG!BFg$uR+-P!&XhVR)CwE;6*jo918wS~ zbxRSSUj;VZpu{>n`0k^d^a*EfL4<>O6N^13RaHJgva*LSCCby5#u!z61n%UF@7HPk zoRM}Ok!fUEDi%E$M$KIF9=R~BKeF(0>{sf*;iMoIo5k-;SjH3dHvFS#U)D_6x6NNg zvCNuLH#2E>^9__n(NdAlAq?sLIdE^eIe;ALuJVNue6Kw2n{U}sMWLfmS|n*mSvTxs zj98@TYmLaQo~^ENWU-t`N+D<`6Lb>!hpbWyddYM``+B483Ez@UoOxi332m#xmP!PA zu%CxlazlK?!O7dwoCO^X=j{s$FmyE8QyFncy%rCCC4$mgmzH8VHAVGo?QIofP>Jp` ztfT{h z)XW{PHqa-RjNpIOi;yZ{Yc`P8qC`cc!Ftt{Ccn8UaOQBE1fLnyG~oo?Xo!-2YQ1w_ z=V=rL;rrL9Qnap>`^$4ot2Ra z4?Y^x)`m7;Z6JAkwCJ#G13nzHrx2RZ{{)Yswed$EJwU&+kemN=n}fmqvK^VP?lq7< z2N|m)q(9qq8KcG|U>RXKnn}yD0xenxj&C}A9qzM4_&F%Qg`oz0-7 zN>B9V^;6(ZFu0|x%1D$MLG)PV72`5!8mxK=@$${eyAy=;BPR(O9R>|k`wrHj&PAR- z-<I?H-tRfDpc& zLy0Gv-w~&vfzCS4fd8$$2>$^C{kasil<;?Sl)0|6C(#q)8Yrc=MK!J2u@IdO&Kx$b z91{p%NT&oxUQ=*Rop`QM$2Kr1ePH>8abx>e!`&1gN*R)bgFu^NRUvq^0^A|FnG)!72Bs%xdoa2vmG zVFnU`rxl6n&XU7<+WXsO!I}aU%Q{|l(wqvBTMlijqZu`P*SmZa=qK7{U(Qk!9%7CP z-0iv~=t`NB(jK6)*xZN_4e9jdQ{07tj>V{LA8-1?AeXB-jkSND3d}4~U0+GRzE5*R zUFms_ZjY`wM}BgNrfsQ8rLY}EbC4X&_LfQ^9dmMoDXP4RZ`$n^HhZaA$)nT5i6a#> z3X788n3=<_z_Vzc0Ld(QbN4cYn_iH{&Lem)S8gg((vxKpPwr6&FRT zsHh|d!VS-UR`-qO5__&aqE=>8TxhbXD#P%|k2++>9VI#3l9wGRIh>?=L4Gj|AL)P= zhNo+;t0{7@?DM(EPJB@0^4%rB1>HbSCq0@;LUH{3861@6%Wt8bNelz{94U3oazTpR zN6;AOLijtPkEZmJ^NkYwmgxPGwVC0fSEAV*KIX6YnWO#JCmAnv5i{40v)D6M9zE*{ zxMU-L5R{-wFaOZ6N7o|#C!;reY!v^!PFmu~0K`~QwP2;4vfCR)x#9Qn+C;B(C^E7n zyRzXzNndHRFgx|QEQx}vqb#J@?TCwo2Gi8lb>8*-{Q2b#UK;pUgUUhb3JMr<=4aMJ9cciu&_Uob(Nt-1}}NC>6u-`ByE2o*hb5iY%Fd3G9)j5qE+_RU5wy^$&Ne zY952;yo__|T2#2VO<^5AF~b`*4d+Vwz2z}x=!*?ATjjr@o7Y}8&bh?3>N`U54h?*Y zANckuZJ8q|Dhg9vs1W%vN+w10lYBiJ?Qrrzd#8D4!$p3LdloOcIyZ`U^{L#Zj16eG zHeN}ump@h@$IL|IsuXiZKq?+k@xrgTLYN8L&yY$h6Q$d}C77MC%L}j6NK^PdxxI6n zm?_EPAQ=wG=jhxoaG^>!uhLw4tGmDa&K404#p^w{QnXU+Bdfu5xn2J*@jBuR??XX# zJ1jv^+Iok@oQCJ1oy6uIDNB!%On~k*(Z*(@gL4}Ll}lMv9$2?}DWER!dohRW$*xU>uU@_kdS*3cBjSX5 zV)reeouQ%3ymA?~z`^WBjr|)cT*K%Z>RV{Z;_S2k^yW?bL zQ^Vj}BSAsC1_Gszwfm(ysEwboP~jf(c3tLf9FAuRTbOSBZj8RTa2b1f^mkGQbNGy5 zS0{~#=6Ck{!Ux{9d>d!r0(^(&wxX8;E)Da!N?ZCP$Y}K|Az+Gej!cEfP}hs-gVgBy zi|nOgRF$r<g0JuX9&8?9Hhue7G@Cvs%fU0wfl_oYrN4$QdFCS};3R(x zl#KFWg`AY>lO6f83Weuq*_+cdkWXtH@sNhTi)_$UJ#BCOBr2|*khA1%E@6{Le@P_= z`EZ#S508=MAyQi%o(=qnYf0)X>{v1~4VROg>iTl#4g;;~O~TcNTf`V|E)=v!1bsSl zTS`9p=xvrr1#|vYRh*M~Gx;Xb9#9{Tyu?1s`o^IVWO;#da1BdO^(7n5prMD(jgX;P zt5)*UcE-cyq0O*)Zjs0{qGJXuEL3PmmBUafw&cW_bdgto!4zel&~|0O;r1LS**8Dl z@1045f4$&LpnQS&@Zx#dU^5gD7=~YOM-Fjr4(pV$5*@;k&}Uq>9L=HMPbM@?xQHIb zfn8~y)6r$GIrWTXejwsf;-rvEmj9(4N{(l|p{CV` z7-qF;!P5|3xxL-|%cW~9z05D(LCh+(z}0Wh*}fylGB<;aBtvv3(t@t%F#t^ba2o^s z)UdaGm91B}R!*yGe&!Ql2Esr9J!h>-jH%tTou{)0M@3RnQB@#Z#>v(!^U|`~grj{z z=agG6?RoMyWO}kSlHCfer52fp+H2Ath|@Zear*>Qod-ZJznmo5?F!C_!ufq@CV1B@ z$!ls*l;Gh72b*tDF2AC_G#M}ALa~5As2k6z8!N{gq~L(h+ByG+XBQ~!vCLP_*XjkZ)O+}!kayk0%I8I5C9+}j{*@= zc(xE-*yz(_adm;RRoKY2r-?#hdiMRd>MzoMj8#RrZO@+T)wdY#S)Xc4T>S0F^S-t)GeT~y@*vj z83M!(*;bps0@#oy??6OoHhJJ07dvh&^LqG{v_AcARD|^-Rju9gv)(C3wIcvB0qkG4{0ogHpoZHa0E*7Gp& zim8c1j|z?t7g-@3p9y(x#~Z&N`g0W!&TdFTt>hOZr`~G~@g#Y)#EZASDcRjzXF3fz z-S)07BIpX7N%|C1+7{n-?^7xluoXXx=t=cCmB--+VoOl(&pJ~f&DD8bP)5v;DN+lH z{Yw(a@$4o9Ho5_x#X__Va=|e4_gaJ=D28SorA{56e#k zdIHw$;7w?3yb?W#KOTWnv8eNGUt+(R1|0SAJ!5j26&ym7QAg?#TCT-20ke&bh66n$ zee9DZR|nc=(i+!raJ}jsVT(}7gD8#~doT0hqv#U&W$TniS45J_cu5xcy2E?Mt+bsJ zZh<)mx_6>hEf1PJA<{2)!;{Qm;c*ZaB9V_$R{tD|pR_MW4dR8NrVaH?zhyP(QpAmR zrmL5c@+B!EKiT;595(t3mmb? zkD1^y0Gwc!d?g8AS^>q4Cdh1Ff?~al&ZZbkiagJMoBC__*P;ieU3R07no&)dO%hOL ztQKLI^;Gi2%5-G-aqZ=)MT+-Ku=&q=;bH~@w~=p;q(0ljOonF*PzsC!W`aH!e`Gao zt{gvt-gH{{C}q472Ew=AFfEr2*hD+bJ5J2=Ll5zU(=|FSg3H9xfsMU{S%!-Cac3`; zq|ZJ+0(FTx(1E$3yl|Z5%XKsMu!f~#ih>jF26oJpfzPF9RW|f3>{O=h24P;!7>+}7 z6sIPYL~0#x!BBgrq1&igy^77Ca{~-+Q?mMd)ck;&W6L#Iv;A8Cb~<4vx{LRh!;dOV z$Q-M^&wkq&cs2>&3mvi}|CHR?aw(5ko}0J5+)33D+GdrHtwo}LrJk-o$XfE{n9w2~ zyWSXK(k_OVUW31O!0tK}T0WJZL-f?Oe*lmAz2>Kkj8D&5oogib4lQc@cYn;*d|EGM zhBu!q=fPf>y_=@fEW#p=n{L}atF}#R$O7xE?VG6NM4%PfiB-RzKeOT#LiUfQ8gz+HgO^-_rb*2-$H2K)mim55}Kg) zFZ1TqK$sy$QlaGZi%sxJn%lZE$NgZiwGQ}lVbFsHAENa1((*V`c$OcwQc7PD@o;09 zLAHxW6sCaW0443lA1BvawovK$c7w7*=op}z9ThjtkJI^2e|^pAO0D_mZ8X@O=zQ}` zNV{tID*?27aHH&08L8bF*k^Qk$O_iiwkys*H?bK{ah4Y-nJ$PD&hwfNOMus(x&>h!jpQAP&cc*Uq9Q&6F!0^9)g!GE(* zeIVwEK2|LRM@Cj-{hKhTCWobjm!_SeBKx`Qcljx;_3+!&cU%o8NZceaw1Z?3-+~l2 zZuXUOmMJej`T<(jDbSZ5NKTV5=EkoFH$_&0eotA!R16hx7O=YAC#atd#p0p?n~!G3 zDxMNH@=qu1C-s_eS(3)0Lcm5`Si?}dg!#t8lQxUFi*X}Zw^8BR;Gmn0+f)^f&XOD= z=jP+zmNu$28MCP~|Bg4&_1O2!gaVD061KU@noHYR>Xwx zQ(wYoubP7)UflD%{T&O-9PaM!R%@i*`_YoeSYyk!V4TnS-~z!17deFw>&+W%IvY3( zy2iY4V{T0j8(YGHKDl8HS%QM_HH7w`Z#Ii{8u zy6B!+RP2iAMnl>azZIl=+d66?;P@)IL<8(-w#;MvG7QsPQ}*&*c>l$R66qHqORRL^ z#h2R{T zwdeDy;k)ZqcN4rYTb%={sN-zcdAs$9UdHpenZb3~EdEsa&B#p^aiOC<#czG8wOtUj zp)gateCH1!Y+teqTx+F7xB~Q1gJ9<*L3Ns0@5exJF)t~rH}FpugaU$@aQcU*l~?lS zJy4Kq6e*&Gk4p$=NWy7VFQEFKca~@Q498Nj!?edCV_&+vPTHu4{BEOUm$}W{+1eu; zp=Qs%!o^kMk)?uPmFF>TD`qM-I`2oJ<~K2G?6SxbG2!hp=Wq%8aXms!nXw)d^AYJt z`cbywxi2Rw^(|bota2f+&v=4jZ*!zlD~Ys@Si30qj_F4vv5J$Qk1jOf!&VQyM4hxF z3AtSF-HAq=F??;l+Z7J?j)`V5Ux)tI zOkUk$(#wmHfXfu~hYK(K=a{e#9>jVeavM%@sJZr*&6&MGd@Al}$$7Yzc#F~I|D`DC zzg7P-j3DTv3&{Q-lKD?1OaH0ie_zA&@8bS<`~1&(GF%d8Y+NPA2iO04m8Gt|oOvz@ zfDMp~z?CCGB8HFR_zTLP5~H(!K9R2e4PPBSd4_pyOvLR+BF``YPb>g>tM_mCX+!$` z{MTl(RmKfxBA!1wx`HL5e4;uK0U3?gp!B^U#5(Z{@hbi$KxCFd@y3G$%O3;D7$SZ_ zApapi%*nlG_9fCbBQi-RdI*0#jlTql{++~iBJDmR6A2QcAgcdHfT+yB{1Ne)L?+}! zLDK$>05RzDmwyJIG?57dQ4sNeBk=!)0sO0KxPP~#zsB|jAy2#`kT~4`qI~W@boi&j z?!W2#5ijrmoie?D=kq^o;{WjZpPd%}Xz5k|Uzb;SG($F{q{W^BDtu&TAY|u_nPabV z6^ygW{92#6sjsHi;|NrFAmQY~XWLX+Kmxdhzu)j2(u`l*CLI!k>>)lS6=!{||CG2k zxNAVb&jMBA8@v$<^W&bWIuw8mQ)O5W2%P3Re@??HXI(Fsy_c6PnN+M5`oLq{W1Y`q zT{(SdVn)B@aO(=dWhZQWX9o0f;c*UvUtI9xLPiqQYi4Gq$p}p3y)xF;c@@y4q5Nk= zAnXTLJl9IU(Hli-z~^xc0E~>L(mdIRi&ZiK7)&xFMzO*96aa)xP$hJ%w2X&F)J>a(1r3wNt^vOLtdFX) z$$iF2@`@a9(G_IlO&yk5*J10OHP&9Vg2D9{6VMVv*YOCWl1;@m;vUeScMW!MP+x-) zuS;n5K4OQPu5~*%LpnG5OSm+(bc*DU`|J}1f7&7t2boz<8DPVVeD;S0C9^KZj>G|E zqFmRy?y*)O-6_TU(a}X5q_?ZaZ_MIJf$bB+%V8?Dz2V7T9}>484SDATmgmhMEKL_} zqr8@Ib#GyWKTFo-B>BV0RF;1k{EMzBg7@r4hB*{FKgkE$4%|n@m9S<69 zm<{3VtBk^p`)$r8uqJJ)8wGK51i~7lp!r2*bnzv!x8C6>%T+(adgkn?$bt!IwN%U5 z#dEP~)6kYW!)CY5$#O5ryw<6UQPSeOwoa9#Ow7q)u9c`3{y@vZ$Q!q-!)U>gEe5rQlIcjY+`LJ!X)>L>EfDChh`!w;^v*ZLedDQlO*Yl$1}?i2^Y-UnQvtf zV6Q5CnJ!*A8H<-}I_Yg9?RAN*^ej7muQYB~^3ds=m9Oz=MJo{_cF}o~!GFqgIX09& z!FSe&vyt#QjkJm2dj8g@EAhrK#l!jIPCkqW7@<5=g5BFJC^v?0UqWmOk4Os*&nKO6 zbeCow9NY6SV$xi-n}$(;<8=|8)l{v?z8h3QHnXiz@z4V)oT^@K--m9IbU&z~V-kby zl#&np3jIm7YTcEalmQ-E*NUrfT|U8=drTy)rLgvvH|YOW98B-VvP!DpY@2>dy9hEg zj0;jJX#b(CX62|Nm*!Pd+j=U z2TRFMQzvzXVbIU@Yb`xt#MS8JXS9@(x>bIoO|AJtT9r`TdumXAHOWNBF-r|Db1Si+&=N`8tq0 zGUrA$8f;r^gDeN8QW-y!N&-pFsL|cVN>zf-P2u>qx9MZw6>b=$bPe9U>7Drz5oOM{ zKzgGekJ~ye!nU2RN&cpath^dNtf8+jT~|g!di`DB$`8K#8Kq79Z;YS4Ui_%5AL)Rg zyW^jYe!&fU_BxI;mi`vjAjvxU6|jp3n)!Msnr%Mv*+gaH-scz_)Nic<7CR65bnTOA_;|75p-0AE zr4P&Iw`%|wW@1__T}W(7Z?w9Ey*2q-uPhWAE&+h+a(|x-0IuH@H#dJuE7RX! z=;iE$Rc*lP5Na~S>`r46cj@?TlB9od(g34rP6F6u2|Yga8n(UN-2*W^cZ;ad@f%BB zsF&E=iLT&qoob=Vau&nC-dJyz(=cI%+x%nW zl2myOhSPurVjO1d06;w-X41-n(8DZZ`W{&@#63TH@3vqu>^za8|E0XnXW&b2nm=}_yDhs{-tfCT@7VSF|53ed4HfmlR zxZhkqeyi-+14$d*IZ~KcUb$h^X2GTR?zsQ0yuxepI*LO)JTJK>Fvroh`f!(y>XS*h z`bbDnpa^)ja50Qe;&MYm)VtKjlsEp3kQz zG@s0p^3|S{*`2eCxS<3g{l3c^`yr|cDEe8!2g;P$uJqs3 z=R1G==R8^^aZ&g9pvy^aeJHtlGirhj3XF=A{QNEmD{DNeoqA#ZmxFn&exjl_P86(7fUJoqF|OU_YxuYP&o@(d)|k}-&F zaBI9Afm;luVvgNC4Ypm0d+(R8n4+(HgKe8~fE!)^UUcTvcgqtABipWJ>et zlVb*k4l*Oo$4BAP>uM@VG{huR~`V>bbzgpAXJt|m7F7#_8j#)&PH0H&7S-UeD=sS&1_UPKWiK!) zz>~NEHcc*vGWFm`^y-cG6C3Pi$bVQ35}$=Z zR5H!I`{gg1I^^$kkVQDJgwTo}jF~d}t{yx_DJscElFC0|(1@`QdS?7JxjuOPkkr`x z)4n3cy9tr9P)3%q%L{Qv9qjZoDd4AuJ{%g@;Qq8S$-#5aoxlx9XMk&2^skq_H4m)Z zH})qfo#)0teb(6nWxd5AMn?;Cn4zwgw@j%N$Hcggx()JUeGsd6LPcfa&G5zfRMAhu zhNI5LB^<`Ydd{}VQw2~bmKf4IkDdTAQybV#LDkLBp(j7W*YmQj^ly|Ae-$vPeTB#} z?l}X^lj04{F?=Kdt%|b>fBGDTh%0T_=IIt>g7XD}PP#n!w~7E@(oR+*GOe@)<70&W z6#{tO*tb#ZA1anx24_n58(ZOQU3+X@2l9jZ(Mu%JOAzi~gmYP-LO1t8)Y!{tPOS+=BPQ{y98Wr8YLdECF_ALP8I z4VhrCvx|BWh8D@U0|4nc8%`g!r8uy(+|Hx%ssLAFaw2<$Tg7;HI`bp0oU&VK9aQ85 znsl1II>W!-yS+MGyzAj9=kHmZ69(?r8Hr$WUBLFJoYG3|Pu6RbTjLm@YxPBw8#_W8 zVQ8Jra<>l%kBo#d=j28x@WjJ%fP_XE2$=7(tIcv_Sp4Yl4bzcF-gh$rb$9y~dA zl=0Q^$QSOB3vlVmYvI60ddVv2gvp>a_cta`%dP7=RyJq&{pK99V~*%U9*(ap-(9dC zC>lhDNM41J{$IXKHi4;D$E}$0A2;^si_NFaGiepApJQKjAs=qaa;J~E7p)^G_6brb zv<%ei^U9#W%vc=-V9*=J#XW2ab?U#gG*-4Ce3yU!4 zifXyweOTLa5=NBH`VER*HyLI7!tyLQv(!F^7 z;bvGAT(j|&UI7Sa5@l`DV-l3Wi1ko>9~1F2NkPrs_>JR4A9-%VP|K_W@HuPhJWmyF zwl@AbpEiYpKa0)KAcrM0V1;dRe{O4JgWt<@{h+hqhy-{Lkx81lPFU|JU0}Xv6iljE zvasbxolB=cCC?|8V;_;3und+;y)#r2@;vot;U!bZ!-BGeQrX?wNWst=O1nZmw(O+n zrX?nX*zLOs_0eF&!j4{E&3FZV2$==w@YkHzy9lnmk2wo|pd_QA4ly-i7ReO}rqhsC zV$>enT>Et97i2L2<@NC&?A^ZH5qQ&+n1bb1Ur;i<1UbR zcf=C&O-z8F&LQz7qbJU!?Z|@STd>F+(+E;rvj*{;1H-Z`1OMd4VX?YRCmoYkPqE#?D{iy*6EK~nTIHMKhW z@5!&!O#{9&=v7qu16?<9RNV#N#2fanzZx#Ptmy}tPx#Tis2w<8!YxB?ZAhQ20sk#1 z<3;ez#c{@&QZ@+tX8kiC>pQVRA=Z^&yW{jq?;HAz$IhBkR&7nz8kmp5o_I|BMl7dq z6}4p4CEQ%T!ShWdL6ukTSa3wsk8IRsJn^=L`rIYjuO+< zTu#O!*9iG4f%NLKQDKKwd=zXc<>BT|#P>(eo0o7Pz{}`E4f#jkj#L+H+B;sw5=Vym zr(ss*Iy>G&?<(K6o!;%@f%*&BQgEXLL0ddkLoS%3{Sb?&A$cuG`Q$jsOG1uQXaL?} z>{zMOYZLoxf|B;IO7?!wW_pL-^6Iu|w!X|xaT>*b@g1={G$OYFw)Vf*$D8?pS5pw0 zZph)0PR8nlO0zn9Lp(y);r#v3_xs$ko}!)L4|RC%CKXJaO$p<6G0}JCpPi%?XBo8* zBkXMOER?v{AE1Ndwek0ls7Y$TR-JLW1-%Og>*6%$MRFeBj zi~eW+h$)N1D_V1%S7-aiaBno-6IwEX+i;baIQB-AK?Tr?59uma@V$wrDVRG6>DmQo zTlSx8cM58e>4DXo{OvY|I~eHS$_u?7OCcl%tI>L}KOI64Kb@faSL6Ri|J(on9r&Lv z|AU(oH0Q-ZJ8_|AI^rF3ptQjJJYUXbv4k#HTVTdTkBExm%c0>JM@V;>RtM z3J2(QN?kICU+>oxQ5L-?(QH&ch`+yoCeq7$4&VhuUm#{R9;t@5tx!59%}5P=<+ZuAO0G1W zt5$SZfIz*Is>zc4_sk|!7-z=)B)`^}43Bq34$7aF36j;eV3x;Y;nF5*6FaHr^yujI z*AUK9c^%{J@j(4TUwoGpZf7TzUl?^agz@V);$Tl1Y$VhJx$Zq|bAU&#Itl zZ&#^7%9iv#x8WeQl91-py!erk_%f#H$Mi{_tJ5V3-}r1i{<2u4`s_w+U~;16s$tk< z*0)V()%jV!)ex+cceM0pvl_egqe0~m_61Rr95>kRpW@n3RV)AuA9u7=(>&=J>}^K) z?tP!L(g$=Bm9qRPBe!DYrxJY9P-o1_01#yys4Zi@uw~p)0P}iw5u&J#j&;G;I@8zt zVWz}i52g{J$gnGoV#iOaNZ%zr97|eQdc8mZ%r+7=R$o6%pAeV)X#dPej=W!QDWohi zD#8+F1^~OmZb0jXg2Fqq=EKGF$63cW9xC*7V-+x?vmbmZPr_dfs0%JP&Poh?qK!f* zO=T+JF^6utx<7sl_cq}v8)YZ<2`$~~L+=7gY%?~er}m72K~o9N{4x@|juL+_n51A^ zYHaPZoDwXEaBWPv&BO{}a1V*prM%l?OpjN_$tcuV+!-5k&oL>x$Y*?~KRlvn=>`|r zQoQu))&3Q0N&rR5+{|%3tM4_1+1Wyvx7@bdmSfu2{^qQ% ztsTSWR)a}W!Io*$1Kk8E2gA1LdTUGMGulmhUB&KS_p)6+M|-JA?oOcIOHhp`*OBEp zkD;8V!vZtP9?yH8u$ogs)x1n$MhC*%bCq&3FFUm1yfoquqRt(D?5BS)Pl?n}s&8~0 z2w2vIQAaR{GJgyi#Tq9Gfj=xAX{eF!;N}`qNZH!C$m^rI)@6e}W66U`?_C@J)%joO ze;WVd`)@A)WcWAqzbNGgq|YsH=~wf0b&R&;%WyJXrm*OZaT$y;3HbkBUEpC}=|g6S z;^6aE+S;;b`0y>(WCqjD(L08e(Q8fb?tPLPjOcDK)oB3%Zrk;%;~P-E+FSHA$n-;P z*LO*5!mqYt3WM)zN@;UW;XBf@bJKpWRmqfR9qiSwt`@Y1-b7?bmKnV=Bb?82sJ5i?QXj-+#CDc2Qc z(Q!JuWsgRsI*IjFE3lBdY7JMb?OZk>6>3EMsjpT%JO10^eW2Qpxe6-I=PXg&yPO|=I5x&)sVP+53kt4fK0H*-~V<;C+L5NuhQ80DT|GLPOJh2k=` zM2mh3#NQ3Z-llJ5dgb!)253$%50OED9VtU2NRoZbknAmzTkpULghZt8MLVy5Qj7v( z&ncdZJ*<45K&xfIEhq`w$ti{XtoMhBkfLE12VL`Jw?ElIH_2&>u$3ANiO>0U4Z3?C zmUAhk--j;`K2L0RTCXpY}*_h zb8MdFQnQvH8^l02Na7#AEM}#fto3#-c|yR2Y%`l_(!Yfy1iGH$>=*ai&GOc!#}M3z ziJ332Gn@{NJ@Yat_~h$Q1Bc-wqh&2@R)Zy8M5F?^<>7*nTGC$@r{g1rIcn~3xPpQL zr}~Ic<4P9Q6#j_wyW(629^RGB#tpM0o15~(^vvdz^5p>E~T;jpkrGkFbP9L@K(zDJ2cN zHT{ESI8G5-L*%c1J2fc|b??h5$y;H`VggiwIur>2Evd5I=X|arEu++I5k`Z~z_onS zI80TCqi}!UYm}9z9P=ZN$Jq&XamAi_>9VP;a|ICaVBsN*)czYY(0pn?wy+lRfScP~ zASraJo)VTZdXzmw0+c(gxML~wZmX!=(ody$rJJH+-KdnJ@=LUbf$61T1|a)(oj7~x zDG#8}tTW_zl9o!6b-6u0s~2^9U;ldTBMv+8{hBWYvtt`AbPP4@2xLBjnI>MFao20xQdPT>fG<|aSNGcHPJWr3oMe1xq@5x! zak_68d0!!Ye8g|BwmBI(ay(*`ATe5$DP#6UFK3cmeS=uR-tMDvxlO)I5l z8b0wvDJ<7$@Z{x4ZdB^tVBiWTRrt{5)!O^X3!nFfIJ$cw!k2#zy2HXkbueJBJ~_W~ zKxFyU8mfb%pfw)+ewZ^pa{DSN(Og`prlz;M@1Rz;S?>(x5afpmMUulNS{;)8ArmLU zUkB&#t~!x!Q;*8sK8D}mjC!+Rk0k+s&AC_0vvmbamT8wgG0xwz_`6*O*)gEURM(SX zi%aPV0cZ<2n8P^6^~?44s3{YcYqlE8V81K^U4h_ zpgT^$^uB2vqp2<T!%JNp{}K_|LR3A#3xe_DH0g;GG1$UbloL>C82mXb^Gc@Sj`p$r-xT;(xXp z%f0OFJ0fE5sS21Ls;vAo#@O3+04C|>x+7?_N+S4PU!_W%ZBK+ikKTw5CY@kWMy9T? zP*1}
Tq>@A4+DIKw)oz}ewwl2eobdw9WET1d?3rQCf5XWsJW_8!a68eb}!`Dmo z)%osqyYgj){N-}M6N8x0Y~EHT+BP?~F1L=(>*UQrHW#@P;<5!XQr>bgsd;h{mS;*q2wABN?V{ff{9klI zbHjR}_qz@%JNgZ(uL7TXg(u_OPS1xx#o0VO+^1(}+mp=-JUkOKefU(43*`2!jN86} z@XL>j_bl%Ni8v1L;2xCS#SKf%u~^a7gQ7rG`U>mb_EI(S}k zuGZeJh`^PT$`VhSR*J~VLY6p06=&Ikd|~2{ubo#k+U1I!t++Sej~#BV&TaQ{JS+_zRV}v z#b`D}VOO;*){Rfb?cP;<` literal 0 HcmV?d00001 diff --git a/gtk-glext/demo.lisp b/gtk-glext/demo.lisp index ed1cd6d..1efbde7 100644 --- a/gtk-glext/demo.lisp +++ b/gtk-glext/demo.lisp @@ -141,29 +141,27 @@ (text-buffer-text (text-view-buffer (opengl-window-resize-fn-text-view window))) ";; Resize-fn. Parameters: w h ") - (let ((v (make-instance 'v-paned)) - (lower-v-box (make-instance 'v-box)) - (h (make-instance 'h-paned)) - (buttons (make-instance 'h-box)) - (update-fns-button (make-instance 'button :label "Update functions")) - (redraw-button (make-instance 'button :label "Redraw"))) + (let-ui (v-paned :var v + (:expr (opengl-window-drawing-area window)) + :resize t :shrink nil + (v-box + (h-paned + (scrolled-window + :hscrollbar-policy :automatic + :vscrollbar-policy :automatic + (:expr (opengl-window-expose-fn-text-view window))) + :resize t :shrink nil + (scrolled-window + :hscrollbar-policy :automatic + :vscrollbar-policy :automatic + (:expr (opengl-window-resize-fn-text-view window))) + :resize t :shrink nil) + (h-box + (button :label "Update functions" :var update-fns-button) :expand nil + (button :label "Redraw" :var redraw-button) :expand nil) + :expand nil) + :resize t :shrink nil) (container-add window v) - (paned-pack-1 v (opengl-window-drawing-area window) :resize t :shrink nil) - (paned-pack-2 v lower-v-box :resize t :shrink nil) - (box-pack-start lower-v-box h) - (let ((scrolled (make-instance 'scrolled-window - :hscrollbar-policy :automatic - :vscrollbar-policy :automatic))) - (container-add scrolled (opengl-window-expose-fn-text-view window)) - (paned-pack-1 h scrolled :resize t :shrink nil)) - (let ((scrolled (make-instance 'scrolled-window - :hscrollbar-policy :automatic - :vscrollbar-policy :automatic))) - (container-add scrolled (opengl-window-resize-fn-text-view window)) - (paned-pack-2 h scrolled :resize t :shrink nil)) - (box-pack-start lower-v-box buttons :expand nil) - (box-pack-start buttons update-fns-button :expand nil) - (box-pack-start buttons redraw-button :expand nil) (connect-signal update-fns-button "clicked" (lambda (b) (declare (ignore b)) diff --git a/gtk/cl-gtk2-gtk.asd b/gtk/cl-gtk2-gtk.asd index 98e72a1..6dd4847 100644 --- a/gtk/cl-gtk2-gtk.asd +++ b/gtk/cl-gtk2-gtk.asd @@ -56,6 +56,8 @@ (:file "gtk.high-level") + (:file "ui-markup") + (:file "gtk.dialog.example") (:file "gtk.demo") diff --git a/gtk/gtk.demo.lisp b/gtk/gtk.demo.lisp index e9bb59c..14ef3ec 100644 --- a/gtk/gtk.demo.lisp +++ b/gtk/gtk.demo.lisp @@ -27,7 +27,8 @@ #:demo-treeview-tree #:test-custom-window #:test-assistant - #:test-entry-completion)) + #:test-entry-completion + #:test-ui-markup)) (in-package :gtk-demo) @@ -922,3 +923,39 @@ (funcall fn)))) (box-pack-start v-box-buttons button :expand nil)) (widget-show window)))) + +(defun test-ui-markup () + (within-main-loop + (let ((label (make-instance 'label :label "Hello!"))) + (let-ui (gtk-window :type :toplevel + :position :center + :title "Hello, world!" + :default-width 300 + :default-height 400 + :var w + (v-box + (:expr label) :expand nil + (scrolled-window + :hscrollbar-policy :automatic + :vscrollbar-policy :automatic + :shadow-type :etched-in + (text-view :var tv)) + (h-box + (label :label "Insert:") :expand nil + (entry :var entry) + (button :label "gtk-ok" :use-stock t :var btn) :expand nil) + :expand nil + (label :label "Table packing") + :expand nil + (table + :n-columns 2 + :n-rows 2 + (label :label "2 x 1") :left 0 :right 2 :top 0 :bottom 1 + (label :label "1 x 1") :left 0 :right 1 :top 1 :bottom 2 + (label :label "1 x 1") :left 1 :right 2 :top 1 :bottom 2))) + (connect-signal btn "clicked" + (lambda (b) + (declare (ignore b)) + (text-buffer-insert (text-view-buffer tv) + (entry-text entry)))) + (widget-show w))))) diff --git a/gtk/gtk.package.lisp b/gtk/gtk.package.lisp index 228a76f..7f07f35 100644 --- a/gtk/gtk.package.lisp +++ b/gtk/gtk.package.lisp @@ -23,7 +23,8 @@ #:tree-lisp-store-add-column #:gtk-main-add-timeout #:gtk-call-aborted - #:gtk-call-aborted-condition)) + #:gtk-call-aborted-condition + #:let-ui)) (defpackage :gtk-examples (:use :cl :gtk :gdk :gobject) diff --git a/gtk/ui-markup.lisp b/gtk/ui-markup.lisp new file mode 100644 index 0000000..8bb13bc --- /dev/null +++ b/gtk/ui-markup.lisp @@ -0,0 +1,148 @@ +(in-package :gtk) + +(defstruct ui-d class props children expansion var initform initializer) + +(defstruct ui-prop name value) + +(defstruct ui-child v props) + +(defun parse-ui-props (list) + ;; list is ({:prop value}* rest) + (iter (for x first list then (cddr x)) + (while (keywordp (first x))) + (for (name value) = x) + (collect (make-ui-prop :name name :value value) into props) + (finally (return (values props x))))) + +(defun parse-ui-children (list) + ;; list is (child*) + ;; child is {ui {:prop value}*} + (iter (while list) + (for child = (if (eq :expr (first (first list))) + (make-ui-d :var (second (first list))) + (parse-ui-description (first list)))) + (for (values props rest) = (parse-ui-props (rest list))) + (setf list rest) + (collect (make-ui-child :v child :props props)))) + +(defun parse-ui-description (description) + ;; description is (class {:prop value}* child*) + ;; child is {ui {:prop value}*} + (let ((class (first description))) + (multiple-value-bind (props rest) (parse-ui-props (rest description)) + (let ((children (parse-ui-children rest))) + (make-ui-d :class class :props props :children children))))) + +(defun get-ui-d-var (d) + (let ((prop (find :var (ui-d-props d) :key #'ui-prop-name))) + (if prop + (ui-prop-value prop) + (gensym (format nil "~A-" (symbol-name (ui-d-class d))))))) + +(defun get-ui-d-initform (d) + `(make-instance ',(ui-d-class d) + ,@(iter (for prop in (ui-d-props d)) + (unless (eq (ui-prop-name prop) :var) + (appending (list (ui-prop-name prop) (ui-prop-value prop))))))) + +(defvar *ui-child-packers* (make-hash-table)) + +(defmacro def-ui-child-packer (class (var child-def child) &body body) + `(setf (gethash ',class *ui-child-packers*) + (lambda (,var ,child-def ,child) ,@body))) + +(def-ui-child-packer container (w d child) + (declare (ignore d)) + `(container-add ,w ,child)) + +(defun get-ui-child-prop-value (d name required-p context) + (let ((prop (find name (ui-child-props d) :key #'ui-prop-name))) + (if (and required-p (null prop)) + (error "~A is a mandatory child property for ~A" name context) + (when prop (ui-prop-value prop))))) + +(def-ui-child-packer box (b d child) + (let ((expand-prop (find :expand (ui-child-props d) :key #'ui-prop-name)) + (fill-prop (find :fill (ui-child-props d) :key #'ui-prop-name)) + (padding-prop (find :padding (ui-child-props d) :key #'ui-prop-name))) + `(box-pack-start ,b ,child + ,@(when expand-prop (list :expand (ui-prop-value expand-prop))) + ,@(when fill-prop (list :fill (ui-prop-value fill-prop))) + ,@(when padding-prop (list :padding (ui-prop-value padding-prop)))))) + +(def-ui-child-packer paned (p d child) + (let ((resize-prop (find :resize (ui-child-props d) :key #'ui-prop-name)) + (shrink-prop (find :shrink (ui-child-props d) :key #'ui-prop-name))) + `(if (null (paned-child-1 ,p)) + (paned-pack-1 ,p ,child + ,@(when resize-prop (list :resize (ui-prop-value resize-prop))) + ,@(when shrink-prop (list :shrink (ui-prop-value shrink-prop)))) + (paned-pack-2 ,p ,child + ,@(when resize-prop (list :resize (ui-prop-value resize-prop))) + ,@(when shrink-prop (list :shrink (ui-prop-value shrink-prop))))))) + +(def-ui-child-packer table (table d child) + `(table-attach ,table ,child + ,(get-ui-child-prop-value d :left t "table packing") + ,(get-ui-child-prop-value d :right t "table packing") + ,(get-ui-child-prop-value d :top t "table packing") + ,(get-ui-child-prop-value d :bottom t "table packing") + ,@(let ((x-options (get-ui-child-prop-value d :x-options nil nil))) + (when x-options + (list :x-options x-options))) + ,@(let ((y-options (get-ui-child-prop-value d :y-options nil nil))) + (when y-options + (list :y-options y-options))) + ,@(let ((x-padding (get-ui-child-prop-value d :x-padding nil nil))) + (when x-padding + (list :x-padding x-padding))) + ,@(let ((y-padding (get-ui-child-prop-value d :y-padding nil nil))) + (when y-padding + (list :y-padding y-padding))))) + +(defun get-child-packer-fn (d) + (iter (for class first (find-class (ui-d-class d)) then (first (c2mop:class-direct-superclasses class))) + (while class) + (for packer = (gethash (class-name class) *ui-child-packers*)) + (when packer (return packer)))) + +(defun get-child-packer (d var) + (let ((fn (get-child-packer-fn d))) + (when fn + (let ((forms (iter (for child in (ui-d-children d)) + (for child-var = (ui-d-var (ui-child-v child))) + (collect (funcall fn var child child-var))))) + (when forms (cons 'progn forms)))))) + +(defun get-ui-d-initializer (d var) + (get-child-packer d var)) + +(defun set-ui-expansion-1 (d) + (when (ui-d-class d) + ;; only direct-vars do not have class + (setf (ui-d-var d) (get-ui-d-var d) + (ui-d-initform d) (get-ui-d-initform d)) + (setf (ui-d-initializer d) (get-ui-d-initializer d (ui-d-var d))))) + +(defun set-ui-expansion (description) + (iter (for child in (ui-d-children description)) + (set-ui-expansion (ui-child-v child))) + (set-ui-expansion-1 description)) + +(defun flattened-ui-descriptions (d) + (cons d + (iter (for child in (ui-d-children d)) + (when (ui-d-class (ui-child-v child)) + (appending (flattened-ui-descriptions (ui-child-v child))))))) + +(defmacro let-ui (ui-description &body body) + (let* ((description (parse-ui-description ui-description)) + (items (flattened-ui-descriptions description))) + (set-ui-expansion description) + `(let (,@(iter (for i in items) + (collect (list (ui-d-var i) + (ui-d-initform i))))) + ,@(iter (for i in items) + (when (ui-d-initializer i) + (collect (ui-d-initializer i)))) + ,@body))) -- 1.7.10.4