I’m not fond of MVC and yet I know of nothing better. What would I want in an alternative?
Most UI toolkits claim to follow the model-view-controller paradigm if they make any such claim at all. In my experience though, they either mush together the controller and view, or the model and view, or all three. As such, there isn’t an opportunity to have the same information represented in multiple places within the interface. Instead, you have repeated information that is synchronized through a complex scaffolding of change listeners. Yuck.
First, I should mention why I want such a thing. I am planning to participate in a study group centered around this MIT OpenCourseWare course in Numeric Photography. I’ve done plenty of digital imaging in my day. The assignments for this course though seem to center on novel user interfaces to digital imaging and artistic value of the end results. The original course was all done as Java applets. I considered using Armed Bear Common Lisp to try to write Lisp code that interacted with the user through a Java applet. My initial tests on this went poorly since the ABCL code wants to read and write from your .abclrc and such. I toyed with code signing and permission granting on it, but wasn’t too satisfied with how easily someone else would be able to use it. Plus, I wasn’t too keen on doing so much Java.
So, I want to start with as few dependencies as possible for standalone Lisp applications. If I could keep the dependencies down to a GUI library of my own crafted atop cl-opengl and cl-png, that would be great. I suspect that I will have to throw in zpb-ttf at some point to get text into the UI, too.
I am going to use the example of a simple checkbox. At its heart, a checkbox is simply a boolean value. The meat or logic of the code doesn’t have to care whether the checkbox is visible or enabled or being clicked or has recently changed. The meat of the code only has to know if it is checked or not. It would be nice if I could get away with just having a boolean anywhere in my code. However, some things will need to be triggered when the boolean changes. As such, I am going to need some setter/getter artifice around the boolean. So, let’s make a structure with a single boolean slot.
((value :initarg :value :type (member nil t)))
(:default-initargs :value nil))
Let’s call this the
This is all that most of my application will need to know. Somehow there is a checkbox. It is either checked or not.
How is the checkbox state presented in the interface? It could be drawn on the screen as a little box that either contains a check or not. It could be drawn as a menu item that either has a check beside it or not. It could be reflected in the state of some LED somewhere. It could be presented in hundreds of web browsers across the company as either a green box or a red box. Let’s call each of these a
Somehow, each of these Checkbox Representations has to be notified when the value changes. If I am willing to also include a dependency on Cells (which hasn’t been touched since May of 2005), then I can get this kind of notification for free.
((value :initform (c-in nil) :accessor checkbox-value)))
(defclass checkbox-as-colored-box ()
((checkbox :initarg :checkbox :type checkbox :reader checkbox)
(color :cell t :initform (c? (if (checkbox-value (checkbox self))
(def-c-output color ((self checkbox-as-colored-box))
Any time the value of the checkbox changes, the color of the checkbox-as-colored-box will change and the trigger to redraw-checkbox-as-colored-box will be invoked with the new color value.
If I don’t use Cells, then I have to create a layer around the checkbox that tracks who needs to be notified when the value changes. Let’s call this a
Checkbox Monitor. It is very tempting to fold this right into the checkbox. After all, when the checkbox changes, the monitor has to know. For a slightly better division of functionality, one might make monitored-checkbox a subclass of checkbox:
((listeners :initform nil)))
In this way, UI components can register with the monitored-checkbox, but the main application never has to notice that the checkbox has the weird methods for adding listeners and such. With an appropriate :around or :after method, the checkbox code doesn’t even have to know if it is part of a monitored-checkbox. It would be nice if the code didn’t have to know this at instantiation time, but I’m not seeing an obvious way around that without Cells.
With Cells, each representation could monitor the Checkbox Base. Without the Cells-type approach, there can really only be one Monitor per Base. (Technically, there could be more, but they could only be through a direct inheritance chain… you could wrap the Monitor with a Monitor2 and that with a Monitor3, etc. But, you wouldn’t want to try to instantiate a Monitor123 or anything like that.) For now, let’s explore the non-Cells case.
The idea so far is that you can create a Checkbox Monitor which is a subclass of Checkbox Base. Then, you can create some Checkbox Representations and register them with the monitor. The meat of your code can think of it as a Checkbox Base.
How is it toggled? There are a variety of ways that could happen. Someone could hit a global hot-key that toggles the checkbox. Someone could select a menu item that toggles the checkbox. Someone could mouse click on a representation of that checkbox. My application could receive an event from a remote source like a secondary display, a database, or a filesystem that toggles the checkbox. Someone could hit the space bar or the enter key while a representation of that checkbox has keyboard focus.
I am not sure we need to create a class hierarchy around changing the checkbox. We can simply call the setter on the checkbox. The monitor will catch it and notify everyone who needs to know. Further, the checkbox itself will be set for anyone who needs to passively query (rather than needing to track its every change).
The big question then is can we enable or disable the checkbox? This would have to either be a property of the Checkbox Base (which doesn’t seem right to me) or a joint effort spanning all places that wish to toggle the checkbox (which also doesn’t seem right). Fortunately, I think we can use multiple-inheritance and more :around methods to take care of this.
((value :initform nil :type (member nil t) :accessor checkbox-value)))
(defclass monitor ()
((listeners :initform nil)))
(defclass enabler ()
((enabled :initform t :type (member nil t) :accessor control-enabled)))
(defclass enabled-checkbox-monitor (enabler monitor checkbox)
(defmethod (setf checkbox-value) :around (new-value checkbox)
(when (control-enabled checkbox)
Is this any better than the status quo? I’m not sure. I will have to write more code to find out.