DUIM For Common Lisp

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).

Monday 10 May 2010

It must have been a long day...


I've managed to fix a few of the problems with the graph layouts that were all caused by "d'oh, I'm so dumb" errors:
  • the backwards graph-edge layout in the tree-graph-pane (the middle one) were caused by a simple '- rather than +' error in the edge layout code.
  • the pixmaps for the control buttons were incorrectly positioned because the medium transform was applied twice (once by draw-image and once by do-copy-pixmap).
Now those are sorted, the graphs look much better.

There's still (obviously) an issue with the control buttons, which are supposed to look like a box with a + in and a box with a - in depending on whether the button is to expand or contract the adjacent node.

Initially I thought this was to do with incorrect native drawables being associated to mediums but after looking at the code again I'm not so sure. I did manage to come up with what I think is a more elegant solution to the general problem of getting the correct drawable though; instead of squirreling away the sheet that was initially attached to the medium during attach-medium, I instead just find the mirror for the sheet (which the previous attempt did anyway on its way to finding a widget object) and take the drawable of that mirror. It does basically the same thing as yesterday's solution but doesn't require a slot. I can't at the moment imagine a scenario where this won't be acceptable and I'm keeping my fingers crossed that I don't come up with one any time soon.

That leaves a bit of a puzzle with respect to the control buttons though. Using the buttons in the "simple graphics" ui test (for that test's test image) works; the icon is displayed. Using the buttons in the graph layouts doesn't work; GTK "renders" a bunch of random memory onto the screen. This is true even if the simple graphics pane is wrapped in a scroller, so the problem isn't anything to do with scrollers as far as I can tell. I'll just have to keep digging at this and put off some potentially more interesting work to another day.

Sunday 9 May 2010

Trucking along...

Removing all the references (and comments referring to) DEFINE-DYLAN-CLASS has now been completed. There were more of them than I remembered (even with a revision control system I tend to prefer to comment things out rather than remove them altogether), but there are a lot of classes defined by DUIM. At least they're gone now.

Defining :default-initargs properly resolved a few issues with the scrolling; in the hopes that the lack of scrolling was preventing tree + graph controls from being displayed (they're both displayed in scrollers) I added a <gtk-viewport> type built on GtkLayout to contain the scrolled sheet. Now this is in place, scrolling "seems" to behave itself, more or less. is a little like a cross between a <gtk-fixed> in that in can contain child widgets, and <gtk-drawing-area> in that it provides a drawable surface. It offers a couple of other facilities too (like being able to attach scroll bars to it) but for now I'm going to punt and let DUIM deal with all of that.

Unfortunately fixing scrolling didn't help with tree + graph controls, which still weren't being displayed. After much fun and games, it turns out that these controls end up drawing to the "wrong" drawable. The issue here is that when mediums are attached to sheets, those sheets are not yet mapped and there are no (x) drawables so the code ends up associated a null drawable to the medium. Whenever an attempt is made to draw to the medium the code checks that the drawable is non-null and if it is null, again attempts to set the drawable for the medium.
The problem in all this is two-fold:
  • The sheet being drawn to is not necessarily the one that the medium was initially attached to (child sheets can use the medium of a parent, and DUIM rebinds the medium-sheet to be the sheet where drawing "should go" so that transforms etc. don't lose)
  • The drawable for everything comes from the GTK widget->window pointer, except for GtkLayout where it comes from layout->bin_window instead.
In most cases (because the same X window is referred to by widget->window) it doesn't matter which sheet's drawable gets hooked up to the medium. In the case of GtkLayout though, it does matter because the widget->window value of the sheet that is contained in the layout does not match layout->bin_window and the code ends up attempting to draw onto a drawable which is obscured by the layout->bin_window drawable.
At the point we first have a non-null native drawable, we've very possibly lost the reference to the sheet to which the medium was attached (i.e. we are drawing to a child sheet of the sheet the medium was attached to). In the case of viewports the drawable needs to come from the original sheet.
I've fixed this by keeping track of the sheet the medium is attached to in a slot in <gtk-medium>, but I don't really like the solution. I'll try to come up with a better one.

With all that done, tree and graph controls appear on screen, at last:


Hrm. There's still a few issues with tree + graph drawing, apparently... I'm pretty sure what the problem is with the control-buttons, and that is the pixmaps that they are drawn into are created on the "wrong" drawable which at least should now be relatively easy to fix (that is, it's the same problem I've already solved for viewport children).

At least I have some clear problems to overcome now with these controls, and a couple more issues can be knocked off the "major stuff to do" list and be moved onto the "stuff that really needs cleaning up" list.

Wednesday 5 May 2010

Default initargs and object instantiation

Whilst running through the gui tests for DUIM I was puzzled by the behaviour of dialog boxes. As far as I could tell, most of them should have been running modally, but they were all running modelessly (well, all apart from the "standard dialogs" which use a completely different create/map/event loop process to everything else).

What to do, apart from to examine the code?

It turns out that the <dialog-frame> classes (which inherit from <basic-frame>) all contain Dylan "keyword" slots. I had assumed (even after reading the relevant sections of the DRM) that this was a straightforward shorthand for specifying a slot with a keyword initarg, but after looking in detail at the frame creation code on <basic-frame> and re-reading the DRM it becomes apparent that these are not slots at all, but rather Dylan's way of specifying CL's :default-initargs class option.

After changing these "slots" to be :default-initargs, things run much better (i.e. more like I expected them to run). The initialize-instance method on <basic-frame> is now passed the initargs it needs to construct frames properly (barring further errors on my part). This also resolves a couple of the issues with scrolling that I've been having.

Unfortunately the DEFINE-DYLAN-CLASS macro I wrote (and that is still used by other macros, such as DEFINE-FRAME) doesn't deal with "keyword" slots properly. I spent some time a while ago making most classes just use DEFCLASS instead of my Dylan "compatability" macro -- it would seem the time has come to port the remaining uses over to DEFCLASS and to rewrite some of those macros. At least I know what I'll be doing this evening given the opportunity.

Monday 3 May 2010

Another year...

More than a year since the last post, where does the time go? I put DUIM down for a while whilst a few other things took my interest, but after a bit of a break I'm back to hacking it again.

Things appear to be coming together a little more:-


Finally colours are working, as is (the easy bit) of text rendering. Quite a few of the gadgets that didn't used to work properly have been fixed, and I've added code for image drawing / double buffering / bitblting etc.

I've decided not to write native (= GTK+) toolkit wrappers for the remaining pane types. There are still several things that aren't working properly (most notably scrollers and graph panes) and I suspect the easiest way to get them going will be to just wrap as many native panes as possible -- but if I can't get user-defined panes working on top of DUIM, what hope does any other unfortunate who uses it got? This is the main reason I went with the DUIM-supplied spin-box pane (visible as the "thickness" control on the random rectangles test in the screenshot above) is the DUIM-supplied pane rather than a GtkSpinButton.

Once everything works "as it should" I intend to migrate the few remaining non-native widgets over to wrapped GTK+ widgets.

Fixes since last year include:

  • A bunch of gadgets have been "GTK-ified": list controls, option + combo boxes, sliders.
  • Use bordeaux-threads for threading primitives rather than hand-rolling a generic interface + implementations for different CL implementations.
  • Fixes for a couple of layout issues for top-level sheets, menu-bars, text-editor gadgets, others.
  • Drawing implementations for basic text drawing, drawing in colour, images + pixmaps, clipping regions.
  • Support for key events -- basic ascii generates sensible keysyms (although the keysyms for Unicode chars etc. is likely to be ugly for the time being).
  • Support added for setting and changing the input focus.
  • Fixed radio buttons so that setting their state computationally doesn't lose. A bunch of work on (DUIM-provided) table controls.
  • Command tables now seem to work after fixing a couple of bugs in my macros.
The todo list still contains (major issues, there are many minor issues too):-
  • Resizing one of the DUIM frames doesn't lead to relayout; minimize + restore after resizing doesn't lead to redrawing.
  • When the last active DUIM frame is closed, the event loop is not exited (to be honest I'm not sure it should be, or if the user should just be prevented from closing the last frame).
  • No support for path drawing (this is likely to remain unsupported until some Cairo support is added to the back end).
  • Popup menu + dialog modality needs investigating.
  • Mouse motion event support.
  • Scrolling.
  • Mnemonics + accelerator support.
  • Clipboard handling.
I'll add the usual promise to update this blog more regularly (which I'm bound to break, so don't hold your breath waiting for it to actually happen ;)

Monday 20 April 2009

Obligatory screenshot


Now that duim + the GTK back end is at least starting with no panics I figured it was time to upload a screenshot.

There are still a bunch of issues outstanding; some of the tests blow out because they're being passed vectors when they expect lists (this needs a thorough review through all the code, still...); the layout doesn't behave well (since layout is still outstanding on the opendylan duim todo list I believe this is fundamental to the GTK back end as it currently stands, and nothing to do with the port); there are no colours (also an opendylan todo) or fonts; plus lots of other things. At least something that looks right is appearing on screen now.

The reason for the display not showing the 'simple gadgets' previously was that the event handlers that were created were not returning the correct value to cause the event to be propagated to other 'interested parties' (namely the GTK widgets themselves). I also set the GtkFixed that's used as the top-level-layout so that it creates its own X window (gtk_fixed_set_has_window ()), although I need to test the behaviour without doing that and remove that step if it doesn't actually achieve anything.

Next steps: continue working so that all the tests at least run without throwing to the debugger (mainly sorting out list/sequence/vector issues, finally). Look @ colours (black and 'white' is so drab). Fix basic gadget functionality (for example, a button-box of radio buttons doesn't cause all the buttons to be linked; they still all operate independently). + whatever else I think of whilst hacking.

Followers