(Mc)CLIM and Becoming Enlightened

This is a cross post from a Gemini site I recently set up.

About four years ago (based on Github’s estimate), I took an interest in the Common Lisp Interface Manager (CLIM), and more particularly in the McCLIM implementation of CLIM. I made a couple of YouTube videos of myself poking around and learning how to use it, but I did not get far at the time: I had quit my programming job some years earlier and was in law school, and mostly quit programming even as a hobby shortly thereafter. My favorite of the videos was a snake game, by the way:

Drawing in McCLIM (And a Snake Game)

When I decided to jump back into Common Lisp a couple of weeks ago, one of the first things I did was go back to that game, clean up the code a bit, and remove a line using a feature that is not present in current versions of McCLIM (and thus prevented the game from running). I had a lot of fun trying to learn McCLIM, but I constantly felt somewhat overwhelmed. The documentation seemed to hint at great power, but I could not see it. It seemed too complicated for a GUI library with a very dated appearance.

I finally obtained the first spark of enlightenment while writing my Gemini client. The client suffers from the same problem of treating McCLIM like a GUI library, but a couple of tasks changed my perspective. The first was links: I needed a way to get some “clickable” text onto a page, and perform an action when it was clicked. The second was similar: Bookmarks. In Observatory’s bookmark interface, bookmarks act like links that you can click on to visit a page, but have the additional option of deleting the bookmark when you bring up a context menu by right-clicking. The bookmark issue was fairly easy after I got links working (and, admittedly, gave up on a more complicated bookmark interface).

I spent several hours reading through the CLIM specifications and McCLIM’s user guide in order to figure out what to do about links. I had thought about just using push buttons (I think there may be a Gemini client out there that did just that), but I really wanted to use normal, albeit perhaps colored, text that was clickable. I had the idea that this thing called “presentations” might do the trick. I read through the portions of the documentation that describe presentations a couple of times before deciding that they wouldn’t work: They seemed to be about presenting an object to the user, and perhaps requesting user input to manipulate the object. I mean, you write translators to change objects into other objects, and these are somehow associated with presentations. What was that all about, and what could it possibly have to do with just making text clickable? Too much for links, and not quite relevant. I looked into putting gadgets (CLIM’s term for widgets) into the stream (the main pane of the output window is set up as a stream in Observatory, so the document can be displayed by pointing standard output to it), but again, I decided I wouldn’t be satisfied with buttons for links. Finally I read about presentations again, and this time I carefully reviewed the with-output-as-presentation and define-presentation-to-command-translator macros. This was the beginning of enlightenment.

Here is the code for writing a link to the application window in Observatory:

(defmethod write-doc-part ((line link-line))
  (let ((res (link-line-uri line)))
    (if res
    (if (string= (resource-protocol res) "gemini")
        (with-drawing-options (t :ink *link-color*)
          (with-output-as-presentation (t line 'link-line)
        (format t "~a" (resource-get-uri res))))
        (format t "~a" (resource-get-uri res)))))
  (format t " -- ~a~%" (link-line-description line)))

It works a little different from links in other Gemini clients: Instead of showing the link description and making it clickable, it displays the URI and makes it clickable, with the link description beside it. It would be trivial to make it work like other clients, but I found this to be more informative to the user about where he or she is being taken. But that is not relevant here. The point is, the clickable portion of the link is within with-drawing-options (which changes the color to blue) and with-output-as-presentation. The later is the important part: It associates the text that gets printed out by the call to format with ‘line’, an instance of the class ‘link-line’. We make it clickable by creating the aforementioned presentation to command translator:

(define-presentation-to-command-translator follow-link
  (link-line com-follow-link observatory-app
         :gesture :select
         :menu t)
  (object) (list object))

The above code associates the ‘link-line’ class with the command (defined elsewhere) ‘com-follow-link’. This command is run whenever the ‘select’ gesture is performed on a presentation of a link-line, resulting in the link being followed. The ‘select’ gesture is associated with a left-click by default in CLIM. (As an aside, “:menu t” makes the command show up in a context menu when you right click, so there are actually two ways to follow a link.)

Bookmarks have another option, since they can be deleted. So they have their own class with another presentation to command translator. This one also shows up in the right-click context menu:

(define-presentation-to-command-translator delete-bookmark
    (bookmark-holder com-delete-bookmark observatory-app
             :gesture :delete
             :menu t)
    (object) (list object))

The delete gesture is associated with SHIFT+middle-click, if I understand the documentation correctly, but I just assume users will use the right-click menu to perform the action. I expect ‘bookmark-holder’ will end up as a subclass of ‘link-line’ eventually, as I clean up some things in Observatory, assuming I continue to work on it.

Where is the enlightenment, you ask? Well, I learned from this experience that I should not think of CLIM as a GUI library. It is first and foremost an abstraction for presenting objects to the user, and providing him or her with operations, such as commands, that act on or using those objects. The interface is secondary, and should arise naturally from the commands made available to the user. This is a different paradigm from traditional event-driven GUI libraries, in which one crafts out the interface and then associates widgets with callback functions. It has its event loop, sure, but the programmer is expected to think more in terms of asking the user for input and acting on it, like a command line program, rather than waiting for events. Or really, a strange hybrid of the two programming styles, with some new concepts about how users interact with objects in the program. If my memory retains a correct history of those videos I made a few years ago, I remember being impressed by McCLIM’s interactor pane (which does not make an appearance in Observatory, by the way). I thought it could be something neat and useful, but I never really figured it out. I think I understand now: It is another way to get commands and references to objects from the user.

The next time I write a CLIM program, I will start out with a different type of thinking and different expectations. The interface will grow out of the needs of the program, rather than the program being made to fit into a predetermined type of interface. I will think about what type of things I want to do, and what data I want to operate on, and start out by providing commands for the user to work on that data. When a need arises for the user to provide input to a command, such as a reference to an object, that is where a bit of the interface will arise, naturally to fulfill a purpose.