webglade

JavaScript library to dynamically create XUL GUI from Glade XML files
git clone https://logand.com/git/webglade.git/
Log | Files | Refs | README | LICENSE

webglade.ls (28349B)


      1 ;; TODO remove & fix push/pop etc
      2 
      3 (defun member (item lst)
      4   (when lst
      5     (dolist (i lst)
      6       (when (= i item)
      7         (return true)))))
      8 
      9 (defun pop (place)
     10   (return (.pop place)))
     11 
     12 (defun push (item place)
     13   (.push place item))
     14 
     15 (defun pushnew (item place)
     16   (unless (member item place)
     17     (push item place))
     18   (return place))
     19 
     20 (defun object2alist (obj)
     21   (when (and obj
     22              (= "object" (typeof obj))
     23              (not (length obj)))
     24     (let ((result (list)))
     25       (doeach (i obj)
     26               (push (cons i (slot-value obj i)) result))
     27       (return result)))) ;; note push adds on the tail!
     28 
     29 (defun copy-seq (seq)
     30   (when seq
     31     (let ((result (list)))
     32       (dolist (i seq)
     33         (push i result)) ;; careful, push puts it at the end! (should be reverse)
     34       (return result))))
     35 
     36 (defun nreverse (seq)
     37   (when seq
     38     (return (.reverse seq))))
     39 
     40 (defun reverse (seq)
     41   (return (nreverse (copy-seq seq))))
     42 
     43 ;;(let ((lst (list 1 2 3 4))) (alert (+ lst #\newline (copy-seq lst))))
     44 ;;(let ((lst (list 1 2 3 4))) (alert (+ lst #\newline (reverse lst))))
     45 ;;(let ((lst (list 1 2 3 4))) (alert (+ lst #\newline (nreverse lst))))
     46 
     47 (defun sort (sequence predicate)
     48   ;; < relationship
     49   (.sort sequence
     50          (lambda (x y)
     51            (return (if (predicate x y) -1 1))))
     52   (return sequence))
     53 
     54 ;;;
     55 
     56 (defvar ns (create "xul" "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
     57                    "html" "http://www.w3.org/1999/xhtml"))
     58 
     59 (defun wget (id doc)
     60   (return (if doc
     61               (doc.get-element-by-id id) ;; why this doesn't work (on glade)?
     62               (document.get-element-by-id id))))
     63 
     64 (defvar *whandler* (create))
     65 
     66 (defun whandler (sig)
     67   (when sig
     68     (let ((h (aref *whandler* sig)))
     69       (if h
     70           (return h)
     71           (return (lambda (e)
     72                     (alert (+ "Unhandled event " sig "."))))))))
     73 
     74 (defun wgetp (w prop)
     75   ;;(return (w.get-attribute prop)))
     76   (return (slot-value w prop)))
     77 
     78 (defun wsetp (w prop value)
     79   (w.set-attribute prop value)
     80   ;;(setf (slot-value w prop) value)
     81   (return value))
     82 
     83 (defun wgets (w style)
     84   (return (slot-value w.style style)))
     85 
     86 (defun wsets (w style value)
     87   (setf (slot-value w.style style) value)
     88   ;;tempEl.style.setAttribute('cssText', 'left:150px; top:150px;', 0)
     89   ;;(w.set-style style value)
     90   (return value))
     91 
     92 (defun wsete (w event handler use-capture)
     93   (w.add-event-listener event handler (if use-capture true false)))
     94 
     95 (defun wunsete (w event handler use-capture)
     96   (w.remove-event-listener event handler (if use-capture true false)))
     97 
     98 (defun wmake (pw tag props sigs)
     99   (if (= tag "string")
    100       ;; text
    101       (let ((w (document.create-text-node props)))
    102         (when pw
    103           (pw.append-child w))
    104         (return w))
    105       ;; xul or html
    106       (let ((w (if (tag.match "^xul:")
    107                    (document.create-element-n-s ns.xul (tag.substring 4))
    108                    (document.create-element-n-s ns.html tag))))
    109         (when pw
    110           (pw.append-child w))
    111         (when props
    112           (doeach (i props)
    113                   (unless (= undefined (slot-value props i))
    114                     ;;(w.set-attribute i (slot-value props i))
    115                     (wsetp w i (slot-value props i)))))
    116         (when sigs
    117           (doeach (i sigs)
    118                   (unless (= undefined (slot-value sigs i))
    119                     #+nil(w.add-event-listener i (whandler (slot-value sigs i)) false)
    120                     (wsete w i (whandler (slot-value sigs i))))))
    121         ;;(pw.append-child w)
    122         (return w))))
    123 
    124 (defun wadda (w child)
    125   (.insert-before (.first-child w) child)
    126   (return child))
    127 
    128 (defun waddz (w child)
    129   (.append-child w child)
    130   (return child))
    131 
    132 ;; function insertAfter(newElement, targetElement)
    133 ;; {
    134 ;; 	var parent = targetElement.parentNode;
    135 ;; 	if(parent.lastChild == targetElement)
    136 ;; 	{
    137 ;; 		parent.appendChild(newElement);
    138 ;; 	}
    139 ;; 	else
    140 ;; 	{
    141 ;; 		parent.insertBefore(newElement, targetElement.nextSibling);
    142 ;; 	}
    143 ;; }
    144 
    145 ;; (defun winsert (w pid)
    146 ;;   (.insert-before (wget pid) id))
    147 
    148 (defun wreplace (w ow nw)
    149   (.replace-child w nw ow)
    150   (return nw))
    151 
    152 (defun wshow (w)
    153   (wsets w "visibility" "visible")
    154   (wsets w "display" ""))
    155 
    156 (defun whide (w)
    157   (wsets w "visibility" "hidden")
    158   (wsets w "display" "none"))
    159 
    160 (defun change-visibility (w show)
    161   (if show
    162       (wshow w)
    163       (whide w)))
    164 
    165 (defun toggle-visibility (w)
    166   (if (= "hidden" (wgets w "visibility"))
    167       (wshow w)
    168       (whide w)))
    169 
    170 ;; widget constructors
    171 
    172 (defun wempty (w)
    173   (when (w.has-child-nodes)
    174     (dolist (child w.child-nodes)
    175       (w.remove-child child))))
    176 
    177 (defun wremove (w)
    178   (w.parent-node.remove-child w))
    179 
    180 (defun xml-to-string (doc)
    181   (let ((s (new (*x-m-l-serializer))))
    182     (return (s.serialize-to-string doc))))
    183 
    184 (defun load-xml (url callback)
    185   ;; http://dean.edwards.name/weblog/2006/04/easy-xml/
    186   ;; http://www.w3schools.com/xml/xml_parser.asp
    187   (if window.*active-x-object
    188       (let ((doc (new (*active-x-object "Microsoft.XMLDOM"))))
    189         (setf doc.async false)
    190         (doc.load url)
    191         (callback doc))
    192       #+nil(let ((xml (wmake "xml")))
    193              (setf xml.src url)
    194              (waddz (wget "body") xml)
    195              (let ((doc xml.*x-m-l-document))
    196                (document.body.remove-child xml)
    197                (callback doc)))
    198       (if (and document.implementation
    199                document.implementation.create-document)
    200           (let ((doc (document.implementation.create-document "" "" nil)))
    201             (setf doc.onload (lambda () (callback doc)))
    202             (doc.load url))
    203           (alert "Your browser cannot handle this script."))))
    204 
    205 (defvar *wbuild* (create))
    206 
    207 (defvar *wpack* (create))
    208 
    209 (defun wclass (widget)
    210   (if (= widget.class "Custom")
    211       (return (get-property widget "creation_function"))
    212       (return widget.class)))
    213 
    214 (defun parse-glade (glade)
    215   ;;(netscape.security.*privilege-manager.enable-privilege "UniversalXPConnect")
    216   (let ((widgets (create)))
    217     (labels ((attrs (node)
    218                (let ((name (node.get-attribute "name"))
    219                      (value (when node.child-nodes[0]
    220                               node.child-nodes[0].node-value))
    221                      (result (create :name name)))
    222                  (when value
    223                    (setf result.value value))
    224                  (dolist (a node.attributes)
    225                    (setf (slot-value result a.node-name) a.node-value))
    226                  (return result)))
    227              (rec-widget (parent node)
    228                (let ((id (node.get-attribute "id"))
    229                      (class (node.get-attribute "class"))
    230                      (widget (create :id id
    231                                      :parent parent
    232                                      :children (list)
    233                                      :class class
    234                                      :property (create)
    235                                      :signal (create)
    236                                      :packing (create))))
    237                  (when parent
    238                    (pushnew widget parent.children)) ;; appends!
    239                  (setf (slot-value widgets id) widget)
    240                  (rec widget node.child-nodes)
    241                  ;; TODO return local to widget!
    242                  (return widget)))
    243              (rec-child (parent node)
    244                (let ((current nil))
    245                  (dolist (child node.child-nodes)
    246                    (case child.tag-name
    247                      ("widget" (setf current (rec-widget parent child)))
    248                      ("packing" (rec-packing parent child current))))))
    249              (rec-property (parent node)
    250                (let ((as (attrs node)))
    251                  (setf (slot-value parent.property as.name) as)))
    252              (rec-signal (parent node)
    253                (let ((as (attrs node)))
    254                  (setf (slot-value parent.signal as.name) as)))
    255              (rec-packing (parent node current)
    256                (dolist (child node.child-nodes)
    257                  (when (= "property" child.tag-name)
    258                    (let ((as (attrs child)))
    259                      (setf (slot-value current.packing as.name) as)))))
    260              (rec (parent nodes current)
    261                (dolist (node nodes)
    262                  (case node.tag-name
    263                    ("glade-interface" (rec parent node.child-nodes))
    264                    ("widget" (rec-widget parent node))
    265                    ("child" (rec-child parent node))
    266                    ("property" (rec-property parent node))
    267                    ("signal" (rec-signal parent node))
    268                    ("packing" (rec-packing parent node current))))))
    269       (rec nil (list glade.document-element)))
    270     (return widgets)))
    271 
    272 (defun wbuild (pw widgets)
    273   (let ((result nil))
    274     (dolist (widget widgets)
    275       (let ((fbuild (aref *wbuild* (wclass widget)))
    276             (fpack (when (and widget widget.parent)
    277                      (aref *wpack* (wclass widget.parent))))
    278             (w (if fbuild
    279                    (fbuild pw widget)
    280                    (wmake pw "string" (+ "{ " (wclass widget) " not implemented! }")))))
    281         (when fpack
    282           (fpack pw w widget))
    283         (setf result w)))
    284     (return result))) ;; return only one for whole container???
    285 
    286 (defun get-property (widget name)
    287   (awhen (slot-value widget.property name)
    288     (return (slot-value it 'value))))
    289 
    290 (defun get-signal (widget name)
    291   (awhen (slot-value widget.signal name)
    292     (return (slot-value it 'handler))))
    293 
    294 (defun get-packing (widget name)
    295   (awhen (slot-value widget.packing name)
    296     (return (slot-value it 'value))))
    297 
    298 (defbuild "GtkWindow" ()
    299   (with-properties (title)
    300     (if (= "window" pw.tag-name)
    301         (progn
    302           (wsetp pw "id" self.id)
    303           (wsetp pw "title" title)
    304           (wbuild pw self.children)
    305           (return pw)) ;; ???
    306         (let ((w (wmake pw "xul:window" (create :id self.id :title title))))
    307           (wbuild w self.children)
    308           (return w)))))
    309 
    310 ;; <property name="decorated">True</property>
    311 ;; <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
    312 
    313 (defpack "GtkWindow" ()
    314   (let ((resizable (= "True" (get-property self.parent "resizable"))))
    315     (when resizable
    316       (wsetp w "flex" 1))))
    317 
    318 (defbuild "GtkDialog" ()
    319   (return (funcall (aref *wbuild* "GtkWindow") pw self)))
    320 
    321 (defpack "GtkDialog" ()
    322   (return (funcall (aref *wpack* "GtkWindow") pw w self)))
    323 
    324 (defbuild "GtkButton" ()
    325   (with-properties (tooltip label sensitive)
    326     (with-signals (clicked)
    327       (let ((w (wmake pw "xul:button"
    328                       (create :id self.id
    329                               ;;:class-name (get-property self "class-name")
    330                               :tooltip tooltip
    331                               :disabled (= "False" sensitive)
    332                               :label label)
    333                       (create :command clicked))))
    334         (wbuild w self.children)
    335         (return w)))))
    336 
    337 (defbuild "GtkCheckButton" ()
    338   (with-properties (active label)
    339     (with-signals (toggled)
    340       (return (wmake pw "xul:checkbox"
    341                      (create :id self.id
    342                              :checked (when active true)
    343                              :label label)
    344                      (create :command toggled))))))
    345 
    346 (defbuild "GtkLabel" ()
    347   (with-properties (label)
    348     (return (wmake pw "xul:label" (create :id self.id :value label)))))
    349 
    350 (defbuild "GtkEntry" ()
    351   (with-properties (text tooltip editable)
    352     (with-signals (changed)
    353       (return (wmake pw "xul:textbox"
    354                      (create :id self.id
    355                              :value text
    356                              :tooltip tooltip
    357                              ;; ff3 cant be just (= "False" editable)
    358                              :readonly (if (= "False" editable)
    359                                            true
    360                                            undefined))
    361                      (create :change changed))))))
    362 
    363 (defbuild "GtkTextView" ()
    364   (with-properties (text tooltip editable)
    365     (with-signals (changed)
    366       (return (wmake pw "xul:textbox"
    367                      (create :id self.id
    368                              :value text
    369                              :tooltip tooltip
    370                              ;; ff3 cant be just (= "False" editable)
    371                              :readonly (if (= "False" editable)
    372                                            true
    373                                            undefined)
    374                              :multiline t)
    375                      (create :change changed))))))
    376 
    377 (defbuild "GtkFrame" ()
    378   (let ((w (wmake pw "xul:groupbox" (create :id self.id)))
    379         (w2 (wmake w "xul:caption"))
    380         (label (list))
    381         (body (list)))
    382     (dolist (child self.children)
    383       ;; TODO fix label_item... (alert (+ (object2alist child) #\newline "---" #\newline child.packing.type))
    384       (if (= "label_item" (get-packing child "type"))
    385           (push child label)
    386           (push child body)))
    387     (wbuild w2 label)
    388     (wbuild w body)))
    389 
    390 ;; TODO do something meaningful
    391 (defbuild "GtkAlignment" ()
    392   (wbuild pw self.children)
    393   #+nil(return ???))
    394 
    395 (defbuild "GtkComboBox" ()
    396   ;; why must be created first and then inserted?
    397   (with-properties (items)
    398     (with-signals (changed)
    399       (let ((w (wmake nil "xul:menulist"
    400                       (create :id self.id)
    401                       (create :command changed)))
    402             (w2 (wmake w "xul:menupopup"))
    403             (n 0))
    404         ;;(alert (get-property self "active"))
    405         (when items
    406           (dolist (item (.split items #\newline))
    407             (let ((pair (.split item "|")))
    408               (wmake w2 "xul:menuitem"
    409                      (create :value (aref pair 0)
    410                              :label (or (aref pair 1) (aref pair 0))
    411                              ;; TODO :selected (when (= n (get-property self "active")) t)
    412                              )))
    413             (incf n)))
    414         (waddz pw w)
    415         (return w)))))
    416 
    417 (defbuild "GtkMenuBar" ()
    418   (let ((w (wmake nil "xul:menubar" (create :id self.id))))
    419     (wbuild w self.children)
    420     (waddz pw w)
    421     (return w)))
    422 
    423 (defbuild "GtkMenu" ()
    424   (let ((w (wmake pw "xul:menupopup" (create :id self.id))))
    425     (wbuild w self.children)
    426     (return w)))
    427 
    428 (defbuild "GtkSeparatorMenuItem" ()
    429   (return (wmake pw "xul:menuseparator" (create :id self.id))))
    430 
    431 (defun find-access-key (str)
    432   ;; use (position item seq)?
    433   (let ((pos (str.index-of #\_)))
    434     (unless (minusp pos)
    435       ;; use (subseq str (1+ pos) (+ 2 pos)))))?
    436       (return (aref str (1+ pos))))))
    437 
    438 ;;(alert (find-access-key "hell_o"))
    439 ;;(alert (find-access-key "hi"))
    440 
    441 (defun remove-access-key (str)
    442   (return (str.replace #\_ "")))
    443 
    444 (defbuild "GtkMenuItem" ()
    445   ;; used to be (= "menubar" pw.tag-name)
    446   (with-properties (label)
    447     (with-signals (activate)
    448       (if (or (= "GtkMenuBar" (wclass self.parent))
    449               (plusp (length self.children)))
    450           (let ((w (wmake pw "xul:menu"
    451                           (create :id self.id
    452                                   :label (remove-access-key label)
    453                                   ;; TODO :acceltext (find-access-key label)
    454                                   :accesskey (find-access-key label))
    455                           (create :command activate))))
    456             (if (plusp (length self.children))
    457                 (wbuild w self.children)
    458                 (wmake w "xul:menupopup"))
    459             (return w))
    460           (return (wmake pw "xul:menuitem"
    461                          (create :id self.id
    462                                  :label (remove-access-key label)
    463                                  ;; TODO :acceltext (find-access-key label)
    464                                  :accesskey (find-access-key label))
    465                          (create :command activate)))))))
    466 
    467 (defbuild "GtkHBox" ()
    468   (let ((w (wmake pw "xul:hbox"
    469                   (create :id self.id
    470                           ;; TODO align for label & combo
    471                           ;;:equalsize (when (= "True" (get-property self "homogeneous")) "always")
    472                           ;;:pack "center"
    473                           :align "center"))))
    474     (wbuild w self.children)
    475     (return w)))
    476 
    477 (defpack "GtkHBox" ()
    478   (with-packing (padding expand fill pack_type)
    479     (when (= "True" expand)
    480       (wsetp w "flex" 1))
    481     ;;(wsetp w "pack" "end")
    482     #+nil(when (= "GTK_PACK_END" pack_type)
    483            (wsetp w "pack" "end"))))
    484 
    485 (defbuild "GtkVBox" ()
    486   (let ((w (wmake pw "xul:vbox" (create :id self.id))))
    487     (wbuild w self.children)
    488     (return w)))
    489 
    490 (defpack "GtkVBox" ()
    491   (with-packing (padding expand fill pack_type)
    492     (when (= "True" expand)
    493       (wsetp w "flex" 1))
    494     ;;(wsetp w "pack" "end")
    495     #+nil(when (= "GTK_PACK_END" pack_type)
    496            (wsetp w "pack" "end"))))
    497 
    498 (defbuild "GtkHButtonBox" ()
    499   (let ((w (wmake pw "xul:hbox" (create :id self.id))))
    500     (wbuild w self.children)
    501     (return w)))
    502 
    503 (defbuild "GtkVButtonBox" ()
    504   (let ((w (wmake pw "xul:vbox" (create :id self.id))))
    505     (wbuild w self.children)
    506     (return w)))
    507 
    508 (defbuild "GtkHSeparator" ()
    509   (return (wmake pw "xul:separator"
    510                  (create :id self.id
    511                          :orient "horizontal"
    512                          :class "groove"))))
    513 
    514 (defbuild "GtkVSeparator" ()
    515   (return (wmake pw "xul:separator"
    516                  (create :id self.id
    517                          :orient "vertical"
    518                          :class "groove"))))
    519 
    520 (defbuild "GtkHPaned" ()
    521   (let ((w (wmake pw "xul:hbox" (create :id self.id)))
    522         (left self.children[0])
    523         (right self.children[1]))
    524     (wbuild w (list left))
    525     ;; splitters are poor in xul, we do not worry about collapse & resize
    526     (let ((w2 (wmake w "xul:splitter" (create :id (+ self.id "-splitter")))))
    527       (wmake w2 "xul:grippy" (create :id (+ self.id "-grippy"))))
    528     (wbuild w (list right))
    529     (return w)))
    530 
    531 (defpack "GtkHPaned" ()
    532   (with-packing (shrink resize)
    533     ;; is this the right test?
    534     (when (= "True" resize)
    535       (wsetp w "flex" 1))))
    536 
    537 (defbuild "GtkVPaned" ()
    538   (let ((w (wmake pw "xul:vbox" (create :id self.id)))
    539         (top self.children[0])
    540         (bottom self.children[1]))
    541     (wbuild w (list top))
    542     (let ((w2 (wmake w "xul:splitter" (create :resizeafter "grow"))))
    543       (wmake w2 "xul:grippy"))
    544     (wbuild w (list bottom))
    545     (return w)))
    546 
    547 (defpack "GtkVPaned" ()
    548   (with-packing (shrink resize)
    549     ;; TODO
    550     #+nil(when (= "True" resize)
    551            (wsetp w "flex" 1))))
    552 
    553 (defbuild "GtkNotebook" ()
    554   (let ((tabs (list))
    555         (bodies (list)))
    556     (dolist (child self.children)
    557       (if (== "tab" (get-packing child "type"))
    558           (push child tabs) ;; appends!
    559           (push child bodies)))
    560     (let ((w (wmake nil "xul:tabbox" (create :id self.id)))
    561           (w2 (wmake w "xul:tabs"))
    562           (w3 (wmake w "xul:tabpanels" (create :flex 1))))
    563       (dolist (tab tabs)
    564         (let ((w4 (wmake w2 "xul:tab")))
    565           ;; TODO image?
    566           (if (= "GtkLabel" tab.class)
    567               (wsetp w4 "label" tab.property.label.value)
    568               (wbuild w4 (list tab)))))
    569       (dolist (body bodies)
    570         (let ((w5 (wmake w3 "xul:tabpanel")))
    571           (wbuild w5 (list body))))
    572       (waddz pw w)
    573       (return w))))
    574 
    575 (defpack "GtkNotebook" ()
    576   (with-packing (type)
    577     (let ((expand (= "True" (get-packing self "tab_expand")))
    578           (fill (= "True" (get-packing self "tab_fill"))))
    579       (unless (= "tab" type)
    580         (wsetp w "flex" 1)))))
    581 
    582 (defbuild "GtkToolbar" ()
    583   (let ((w (wmake pw "xul:toolbar"
    584                   (create :id self.id
    585                           :align "center")))) ;; like hbox
    586     (wbuild w self.children)
    587     (return w)))
    588 
    589 (defpack "GtkToolbar" ()
    590   (with-packing (expand homogeneous)
    591     ;; TODO homogeneous
    592     (when (= "True" expand)
    593       (wsetp w "flex" 1))))
    594 
    595 (defbuild "GtkToolItem" ()
    596   (let ((w (wmake pw "xul:toolbaritem" (create :id self.id))))
    597     (wbuild w self.children)
    598     (return w)))
    599 
    600 (defbuild "GtkSeparatorToolItem" ()
    601   ;; why not draw line?
    602   (let ((w (wmake pw "xul:toolbarseparator" (create :id self.id))))
    603     ;; TODO why need children?
    604     (wbuild w self.children)
    605     (return w)))
    606 
    607 (defun image-url (url)
    608   (when url
    609     ;; TODO and more like http etc?
    610     (return (+ "images/" url))))
    611 
    612 (defbuild "GtkToolButton" ()
    613   ;; TODO show label/image
    614   (with-properties (label icon tooltip)
    615     (with-signals (clicked)
    616       (let ((show-label t)
    617             (show-image t))
    618         (return (wmake pw "xul:toolbarbutton"
    619                        (create :id self.id
    620                                :label (when show-label label)
    621                                :image (when show-image (image-url icon))
    622                                :tooltiptext tooltip)
    623                        (create :command clicked)))))))
    624 
    625 (defbuild "GtkImage" ()
    626   (with-properties (pixbuf)
    627     (return (wmake pw "xul:image"
    628                    (create :id self.id :src (image-url pixbuf))))))
    629 
    630 (defbuild "GtkProgressBar" ()
    631   (return (wmake pw "xul:progressmeter"
    632                  (create :id self.id :mode "determined"))))
    633 
    634 (defbuild "GtkStatusbar" ()
    635   (return (wmake pw "xul:statusbarpanel" (create :id self.id :flex 1)))
    636   ;; TODO need packhook to set flex of the panel
    637   #+nil(let ((w (wmake pw "xul:statusbar" (create :id self.id))))
    638     (wmake w "xul:statusbarpanel" (create :flex 1))
    639     (return w)))
    640 
    641 (defun scan (regexp string)
    642   (when string
    643     (return (.match string regexp))))
    644 
    645 ;;(alert (scan "expand" "asfdexpandasfd"))
    646 
    647 ;; TODO empty table???
    648 (defbuild "GtkTable" ()
    649   (let ((n-rows (get-property self "n_rows"))
    650         (n-columns (get-property self "n_columns"))
    651         (row-spacing (get-property self "row_spacing"))
    652         (column-spacing (get-property self "column_spacing"))
    653         (homogeneous (= "True" (get-property self "homogeneous"))))
    654     (flet ((earlier (x y)
    655              (let ((xleft (get-packing x "left_attach"))
    656                    (xright (get-packing x "right_attach"))
    657                    (xtop (get-packing x "top_attach"))
    658                    (xbottom (get-packing x "bottom_attach"))
    659                    (yleft (get-packing y "left_attach"))
    660                    (yright (get-packing y "right_attach"))
    661                    (ytop (get-packing y "top_attach"))
    662                    (ybottom (get-packing y "bottom_attach"))
    663                    ;;(c1 (< xtop ytop))
    664                    ;;(c2 (and (= xtop ytop)
    665                    ;;(< xleft yleft)))
    666                    #+nil(c (or c1 c2)))
    667                #+nil(return (if c -1 1))
    668                (return (or (< xtop ytop)
    669                            (and (= xtop ytop)
    670                                 (< xleft yleft))))))
    671            (getfcols (sorted)
    672              (unless homogeneous
    673                (let ((fcols (list))
    674                      (sorted (copy-seq sorted))
    675                      (child (pop sorted)))
    676                  (dotimes (row n-rows)
    677                    (dotimes (col n-columns)
    678                      (let ((left (get-packing child "left_attach"))
    679                            (right (get-packing child "right_attach"))
    680                            (top (get-packing child "top_attach"))
    681                            (bottom (get-packing child "bottom_attach"))
    682                            (x-options (get-packing child "x_options")))
    683                        (when (and (= top row) (= left col))
    684                          ;; missing x_options = expand|fill
    685                          (when (or (null child.packing.x_options)
    686                                    (scan "expand" x-options))
    687                            (dotimes (i (1+ (- right left))) ;; 1+?
    688                              (pushnew col fcols)) ;; appends!
    689                            #+nil(loop for c from left to right
    690                                    do (pushnew col fcols)))
    691                          (setf child (pop sorted))))))
    692                  (return fcols)))))
    693       (let ((n-rows (get-property self "n_rows"))
    694             (n-columns (get-property self "n_columns"))
    695             (row-spacing (get-property self "row_spacing"))
    696             (column-spacing (get-property self "column_spacing"))
    697             (homogeneous (= "True" (get-property self "homogeneous")))
    698             (sorted (reverse (sort (copy-seq self.children) earlier))) ;; pop works reverse
    699             (fcols (getfcols sorted))
    700             (w (wmake pw "xul:grid" (create :id self.id)))
    701             (w2 (wmake w "xul:columns"))
    702             (w3 (wmake w "xul:rows")))
    703         (dotimes (col n-columns)
    704           ;; TODO homogeneous
    705           (wmake w2 "xul:column"
    706                  (create :flex (when (or homogeneous
    707                                          (member col fcols))
    708                                  1))))
    709         (let ((child (pop sorted)))
    710           (dotimes (row n-rows)
    711             ;; TODO homogeneous/height
    712             (let ((w4 (wmake w3 "xul:row" (create :align "center"))))
    713               (dotimes (col n-columns)
    714                 (let ((left (get-packing child "left_attach"))
    715                       (right (get-packing child "right_attach"))
    716                       (top (get-packing child "top_attach"))
    717                       (bottom (get-packing child "bottom_attach")))
    718                   (when (and (= top row) (= left col))
    719                     (wbuild w4 (list child))
    720                     (setf child (pop sorted))))))))
    721         (return w)))))
    722 
    723 (defbuild "GtkScrolledWindow" ()
    724   ;; have one child only; packing applies to the child
    725   (let ((child self.children[0])
    726         (w (wbuild pw (list child))))
    727     ;; TODO
    728     ;;(wsets w "overflow" "auto")
    729     (return w)))
    730 
    731 (defbuild "GtkTreeView" ()
    732   (return (wmake pw "xul:tree" (create :id self.id)))
    733   #+nil(labels ((draw-rows (rows)
    734              (html (:treechildren
    735                     (dolist (row rows)
    736                       (let ((cells (cells row))
    737                             (children (children row)))
    738                         (html ((:treeitem
    739                                 :when (:container self.children :true)
    740                                 ;;:when (:open self.children :true)
    741                                 )
    742                                (:treerow (dolist (cell cells)
    743                                            (html ((:treecell :label cell)))))
    744                                (when self.children
    745                                  (draw-rows self.children))))))))))
    746     (with-properties (model) self
    747       ;;(format t "~s~%" model)
    748       (let ((flex (eq (type-of (parent self)) 'GtkScrolledWindow))
    749             (cols (columns model))
    750             (scrollbar (eq (type-of (parent self)) 'GtkScrolledWindow))
    751             (hide-header-row (hide-header-row model)))
    752         (html ((:tree :id (wid self) :class "GtkTreeView"
    753                       ;;:when (:style scrollbar "overflow:auto")
    754                       :when (:hidecolumnpicker (or hide-header-row
    755                                                    (< (length cols) 2)) :true)
    756                       :when (:flex flex 1)
    757                       ;;:enableColumnDrag :true
    758                       ;;:seltype :single :multiple
    759                       ;;:disabled :true
    760                       ;;:when (:align scrollbar :stretch)
    761                       )
    762                (when model
    763                  (html
    764                   (:treecols
    765                    (loop for col in cols
    766                          for n from 0
    767                          do (destructuring-bind
    768                                   (cname ctitle cflex chidden cprimary)
    769                                 col
    770                               (html
    771                                ((:treecol
    772                                  :id cname
    773                                  :when (:primary cprimary :true)
    774                                  :when (:hideheader hide-header-row :true)
    775                                  ;;:ignoreincolumnpicker :true
    776                                  :when (:label (and (not hide-header-row)
    777                                                     ctitle)
    778                                                ctitle)
    779                                  :when (:flex cflex cflex)
    780                                  :when (:hidden chidden :true))))))))
    781                  (draw-rows (children model)))))))))
    782 
    783 (defbuild "xul:iframe" ()
    784   (with-properties (string1 string2)
    785     (return (wmake pw "xul:iframe"
    786                    (create :id self.id :src string1)
    787                    (create :load string2)))))
    788 
    789 (defun close-window ()
    790   ;; http://www.interwebby.com/blog/2006/02/04/3/
    791   (window.open "javascript:window.close();" "_self" ""))
    792 
    793 (defun gtk-main-quit ()
    794   (close-window))