Monday 29 November 2010

DUIM (and CLIM) links -- for those that are interested

Since I've been asked to provide a few links to explain what on earth DUIM is, our usual programming is suspended whilst I dig few some of my references.

There's not a huge amount of information out there on DUIM or its predecessor, CLIM. I guess they were niche even at the height of their popularity and like many things CL (and Dylan) related the ratings in the popularity stakes are getting worse.

That said, there is some documentation. For DUIM the repository of all knowledge is at the OpenDylan site.

In particular, the DUIM user guide and reference guides are useful. There's also some information on the Dylan Wiki.

DUIM was originally created by a now defunct company called Functional Developer. The DUIM repository at OpenDylan.org is based on their code.

The code I've been working on is as direct a translation from the Dylan that I have been able to manage as it was about 2-3 years ago (the OpenDylan devs have also been hacking on DUIM in that time). I'd say in general the CL implementation mirrors the Dylan implementation (as was) very well. The syntax is probably 95% what you'd expect from reading the Dylan docs, assuming a cursory understanding of Dylan syntax and a similar understanding of CL syntax. A few of the macros are slightly different (for example, many of the CL macros use keywords for "literals" in macros (such as :PANE in frame-defining forms - the Dylan uses just PANE), and CLIM's MAKE-PANE has returned since CL doesn't implement dispatching the same way Dylan does (Dylan's method is superior in my humble opinion). Also the Dylan "xxx-setter" methods are replaced with CL "(setf xxx)" methods).

For those interested in CLIM, there's a collection of links about it on cliki.

Thursday 25 November 2010

Multiple Layout Frame issue

Whilst the blog has been quiet of late, work is progressing on the DUIM port. Ongoing work currently includes focus and keyboard handling, mnemonics and accelerators, and making use of GTK stock items where possible.

As usual, I've been attempting to fix failing tests too. The most recent one to be resolved is the "multiple layout frame". In DUIM (and CLIM) it's possible to define multiple layouts and to switch between them. This test verifies that the layout-switching functionality works and needless to say it didn't work initially. The frame was mapped correctly and the first time the layout was switched worked too, but the next time the layout was switched DUIM would blow out with a "no mirrored ancestor" error for one of the drawing areas.

After much head-scratching the clouds of mystery evaporated; it is probably useful to see parts of the macroexpansion of the <MULTIPLE-LAYOUT-FRAME> definition form:


(DEFINE-FRAME-CLASS <MULTIPLE-LAYOUT-FRAME> (<SIMPLE-FRAME>)
((CURRENT-LAYOUT :INITFORM :DEBUGGING :ACCESSOR CURRENT-LAYOUT)
(:PANE FILE-MENU (FRAME) ...
(:PANE SWITCH-BUTTON (FRAME)
(MAKE-PANE '<MENU-BUTTON>
:LABEL "Switch layouts"
:ACTIVATE-CALLBACK
#'(LAMBDA (BUTTON)
(ECASE (CURRENT-LAYOUT FRAME)
(:DEBUGGING
(SETF (CURRENT-LAYOUT FRAME) :INTERACTING)
(SETF (FRAME-LAYOUT FRAME)
(INTERACTING-LAYOUT FRAME)))
(:INTERACTING
(SETF (CURRENT-LAYOUT FRAME) :DEBUGGING)
(SETF (FRAME-LAYOUT FRAME)
(DEBUGGING-LAYOUT FRAME)))))))
(:PANE EXIT-BUTTON (FRAME) ...
(:PANE CONTEXT-PANE (FRAME) ...
(:PANE STACK-PANE (FRAME) ...
(:PANE SOURCE-PANE (FRAME) ...
(:PANE INTERACTOR-PANE (FRAME) ...
(:PANE MESSAGE-PANE (FRAME) ...
(:PANE DEBUGGING-LAYOUT (FRAME)
(VERTICALLY NIL
(WITH-BORDER (:TYPE :SUNKEN)
(CONTEXT-PANE FRAME))
(HORIZONTALLY NIL
(WITH-BORDER (:TYPE :SUNKEN)
(STACK-PANE FRAME))
(WITH-BORDER (:TYPE :SUNKEN)
(SOURCE-PANE FRAME)))
(WITH-BORDER (:TYPE :SUNKEN)
(INTERACTOR-PANE FRAME))
(WITH-BORDER (:TYPE :SUNKEN)
(MESSAGE-PANE FRAME))))
(:PANE INTERACTING-LAYOUT (FRAME)
(VERTICALLY NIL
(WITH-BORDER (:TYPE :SUNKEN)
(INTERACTOR-PANE FRAME))
(WITH-BORDER (:TYPE :SUNKEN)
(MESSAGE-PANE FRAME))))
(:LAYOUT (FRAME) (DEBUGGING-LAYOUT FRAME))
(:MENU-BAR (FRAME) ...
(:DEFAULT-INITARGS :WIDTH 300 :HEIGHT 400))

(DEFMETHOD FILE-MENU ((FRAME <MULTIPLE-LAYOUT-FRAME>)) ...
(DEFMETHOD SWITCH-BUTTON ((FRAME <MULTIPLE-LAYOUT-FRAME>)) ...
(DEFMETHOD EXIT-BUTTON ((FRAME <MULTIPLE-LAYOUT-FRAME>)) ...
(DEFMETHOD CONTEXT-PANE ((FRAME <MULTIPLE-LAYOUT-FRAME>)) ...
(DEFMETHOD STACK-PANE ((FRAME <MULTIPLE-LAYOUT-FRAME>)) ...
(DEFMETHOD SOURCE-PANE ((FRAME <MULTIPLE-LAYOUT-FRAME>)) ...
(DEFMETHOD INTERACTOR-PANE ((FRAME <MULTIPLE-LAYOUT-FRAME>)) ...
(DEFMETHOD MESSAGE-PANE ((FRAME <MULTIPLE-LAYOUT-FRAME>)) ...

(DEFMETHOD DEBUGGING-LAYOUT ((FRAME <MULTIPLE-LAYOUT-FRAME>))
(LET ((DUIM-FRAMES-INTERNALS::_FRAMEM (FRAME-MANAGER FRAME)))
(OR (DEBUGGING-LAYOUT-PANE FRAME)
(SETF (DEBUGGING-LAYOUT-PANE FRAME)
(WITH-FRAME-MANAGER (DUIM-FRAMES-INTERNALS::_FRAMEM)
(VERTICALLY NIL
(WITH-BORDER (:TYPE :SUNKEN)
(CONTEXT-PANE FRAME))
(HORIZONTALLY NIL
(WITH-BORDER (:TYPE :SUNKEN)
(STACK-PANE FRAME))
(WITH-BORDER (:TYPE :SUNKEN)
(SOURCE-PANE FRAME)))
(WITH-BORDER (:TYPE :SUNKEN)
(INTERACTOR-PANE FRAME))
(WITH-BORDER (:TYPE :SUNKEN)
(MESSAGE-PANE FRAME))))))))

(DEFMETHOD INTERACTING-LAYOUT ((FRAME <MULTIPLE-LAYOUT-FRAME>))
(LET ((DUIM-FRAMES-INTERNALS::_FRAMEM (FRAME-MANAGER FRAME)))
(OR (INTERACTING-LAYOUT-PANE FRAME)
(SETF (INTERACTING-LAYOUT-PANE FRAME)
(WITH-FRAME-MANAGER (DUIM-FRAMES-INTERNALS::_FRAMEM)
(VERTICALLY NIL
(WITH-BORDER (:TYPE :SUNKEN)
(INTERACTOR-PANE FRAME))
(WITH-BORDER (:TYPE :SUNKEN)
(MESSAGE-PANE FRAME))))))))

(DEFMETHOD FRAME-LAYOUT ((FRAME <MULTIPLE-LAYOUT-FRAME>)) ...
(DEFMETHOD FRAME-MENU-BAR ((FRAME <MULTIPLE-LAYOUT-FRAME>)) ...



The defining form starts with the frame class itself. There's nothing particularly interesting in this part of the code so much of the code is elided. The parts of interest have been left in. Initially the frame layout is the "debugging layout" which shows a bunch of panes:



This layout is generated when the DEBUGGING-LAYOUT method is invoked, which returns either the DEBUGGING-LAYOUT-PANE if the method has been invoked before (i.e. the layout has already been shown), or makes all the panes in that layout and then returns them (when the layout has not been shown).
The INTERACTING-LAYOUT method behaves similarly, and creates a layout that looks like this:



The "switch layouts" button unmaps the sheets included in the frame's current layout, and maps the sheets included in the layout being switched to.

The thing to note is that both these layouts refer to the some of the same child panes (i.e. the "interactor pane" and the "message pane").

So what happens is:-

1. The frame is initially shown with the debugging layout. The DEBUGGING-LAYOUT method is invoked to return the layout. Since the panes haven't been constructed yet, the layout is constructed and stored in the DEBUGGING-LAYOUT-PANE slot.

After this step, the pane hierarchy is similar to the following:


DEBUGGING-LAYOUT
VBOX
... more panes ...
BORDER-PANE D1
INTERACTOR-PANE P1
BORDER-PANE D2
MESSAGE-PANE P2


At this point all parent - child relationships are correct. In particular, the INTERACTOR-PANE P1 and MESSAGE-PANE P2 are added as children of their respective border panes and the parents of P1 and P2 are the border panes D1 and D2 respectively.


2. Switch layouts; the INTERACTING-LAYOUT method is invoked to return the layout. Since the layout hasn't been constructed yet, it is constructed and stored in the INTERACTING-LAYOUT-PANE slot.

After this step, the pane hierarchy is similar to the following:


INTERACTING-LAYOUT
VBOX
BORDER-PANE I1
INTERACTOR-PANE P1
BORDER-PANE I2
MESSAGE-PANE P2


At this point all parent - child relationships in this layout are correct. Note that whilst the VBOX and BORDER-PANEs are all new sheets and play no part in the DEBUGGING-LAYOUT, the panes P1 and P2 are the same instances that are also in the DEBUGGING-LAYOUT.

Since sheets are constructed from the top-down, the parent of P1 is now I1 and the parent of P2 is now I2.

Note that when children are added to a sheet, both the children and the new parent are modified, but any old parent of the child sheets is not informed of any changes (which is fortunate, or this issue wouldn't be as easily fixed).


3. Switch layouts a second time; the DEBUGGING-LAYOUT method is invoked to return the layout. Since the layout has already been constructed, the layout stored in the DEBUGGING-LAYOUT-PANE slot in step 1 is returned:


DEBUGGING-LAYOUT
VBOX
... more panes ...
BORDER-PANE D1
INTERACTOR-PANE P1
BORDER-PANE D2
MESSAGE-PANE P2


Unfortunately, whilst the border panes D1 and D2 have panes P1 and P2 as children, the parents of panes P1 and P2 were changed in step 2 to be the border panes I1 and I2, so when Silica tries to map P1 or P2 it complains that they don't have a mirrored parent.


To fix this the code now checks, when the frame layout is changed (the methods involved in the backend are actually UPDATE-FRAME-WRAPPER, and TOP-LEVEL-LAYOUT-CHILD), that all the sheets involved in the layout have the correct parents set. The parent has to be changed where they're wrong using the "internal" slot writer to prevent the border pane (which already has the child to be updated as a child) from complaining that it already has a child.

I'm not sure I like the fix (using a slot-writer that isn't exported feels wrong to me, and I'm not 100% that the fix won't introduce really subtle bugs in the future if the right circumstances should be met) so I'll be keeping this on my todo list (and reminding myself of it with this blog posting).

Followers