Frequently Asked Questions

Q: What is Cells-GTK?

A: Cells-GTK is a lisp binding of GTK written by Vasilis Margioulas. It uses the Cells system, a sort of constraint propagation system written by kenny tilton. (see "What is Cells?"). As of this writing Cells-GTK runs under Allegro, Lispworks, CMUCL, SBCL and CLISP.

Q: What is Cells?

A: From the Cells website: "Cells is a mature, stable extension to CLOS that allows you to create classes, the instances of which have slots whose values are determined by a formula."

From Bill Clementson's Blog: "...Cells allows you to define classes whose slots can be dynamically (and automatically) updated and for which standard 'observers' can be defined that react to changes in those slots."

Here is an explanation from the viewpoint of a Cells-GTK programmer: Cells provides Cells-GTK with two essential features: (1) a part-subpart relationship, as is typically found in GUIs -- it allows the user to express that, e.g., this window contains these buttons and subwindows etc.; (2) the expression of a program's state machine, as viewed from the context of the user's interaction through the GUI. This last point is especially noteworthy, and needs more explanation. If you are at all familiar with developing moderately complex software that is operated through a GUI, then you have probably learned this lesson: Keeping what is presented through the GUI in-sync with what the user is allowed to do, and in-sync with the computational state of the program is often tedious, complicated work. Specifically, there are the issues of what menu items ought to be made sensitive when, what items should be presented in list, what text should appear in entries, etc. Cells-GTK helps with these tasks by providing an abstraction over the details; each of the tasks just listed can be controlled by (a) formula that specify the value of attributes of graphic features in the part-subpart declaration (that declaration is called 'defpart' in cells-gtk); and, (b) formula that specify the value of CLOS slots. [Footnote, In the details of the implementation, (a) is just a usage of (b). But it helps to think of the ability to set a GUI feature with a formula as something different than setting the value of slot with a formula.]. (c) 'observers' (to use Bill Clementson's term) that watch over GUI features and react to changes.

An example of (a) is setting the :sensitive feature of a GTK menu item. For this, I might use the formula (c? (user-file *my-gui*)). Here user-file is the accessor of some object *my-gui*. :sensitive is a boolean, so whenever there is a non-nil value in the slot user-file, the menu item is sensitive. That doesn't look a whole lot different than giving :sensitive whatever value (user-file *my-gui*) evaluates to. The difference is that in Cells-GTK, the Cells codes watches over 'instrumented' slots such as user-file and automatically updates those attributes of a Cell-GTK object (such as a menu item's :sensitive feature) that are governed by a Cells formula. Thus, I don't have to explicitly update the value of :sensitive -- it happens automatically.

So what is this talk about 'expression of a program's state machine?' Well, the idea is that in a GUI operated by Cells-GTK, transition into a state is governed by cells-formulae on GUI features and cells-instrumented slots. E.g. when the user loads a file, (c? (user-file *my-gui*)) becomes true and the program enters the state where some menu item that wasn't sensitive now is -- the cells formulae are formulae on the arcs out of states. Is that stretching it too much? I don't know, it makes sense to me.

See also:

Q: What is required to deliver executables?

There is only one data point that I know of with respect to this; I created an Win32 .exe using Lispworks. The code is available to anyone who might what to take a look (email me, it's not on a server anywhere). The .exe is available too.

Key aspects of the design:

Q: Did anyone really ask these questions?

A: Naw, I (Peter Denno) am just talking to myself.

Q: What GTK Widgets are implemented in cells-gtk?

A: [hmm, Better to ask what isn't. Most of the useful widgets are implemented, but among those, many non-essential methods are not.] A quick look at the widget gallery.... These are not implemented:

There are quite a few other widgets not listed in the Gallery that aren't implmented. But BTW, it isn't difficult to implement most of these. If you need one, implement it and drop us a note! (If you need help, just ask.)

Q: Do I have to use cells?

A: Probably not. But the demo uses them, so it might still be (despite the lack of documentation of the cells API) the path of least resistance. (This answer is mostly to shame kenny tilton into writing some documentation ;^). There is at least a few applications (mine) that uses them productively. See also:

Q: What is about?

A: Sometimes there is a need to access something that isn't nominally part of the GTK API but really is part of the GTK API as far as C programmers are concerned. An example of this is found in the specification of dialog vboxes where the spec says to just use dialog->vbox because:

   typedef struct {
      GtkWidget *vbox;
      GtkWidget *action_area;
   } GtkDialog;
But the truth is that this isn't what that C struct really looks like. It looks like this:
   typedef struct _GtkDialog  GtkDialog;
   struct _GtkDialog
      GtkWindow window;
      /*< public >*/
      GtkWidget *vbox;
      GtkWidget *action_area;
      /*< private >*/
     GtkWidget *separator;
... and window is a big struct that might change from gtk version to gtk version. Therefore there is no easy way for us (lisp ffi) to know how to address into the structure to get the vbox, so we wrote a 3 line c program to get it. (And other 3 line programs to do similar things).

You don't need to run the demo, but you will to:

(As of this writing, those are the only situations). To use you need to compile it. And that requires having access to the C header files corresponding the you are using.

Q: What is in

A:For details look at the source gtk-ffi/gtk-adds.c, but generally:

Q: What is the difference between using c-input (AKA c-in) and c-formula (AKA c?) in a slot's :initform ?

A:The two define different kinds of cells:
A c-input cell is a cell whose value may be set through explicit procedural code, using setf on the slot. Note: When the cell is referenced by other cells in a cells constraint network, setf-ing the cell causes the values of other cells to be recomputed.
A c-formula cell is a cell whose value is obtained through evaluation of a formula.
Note that the usual semantics of :initform do not apply when :initform is given by c-formula. Instead of just setting the value at initialization, the c-formula (generated from the supplied lisp form) specifies the dynamic relationship between the slot's value and other aspects of the program state.

Q: Why do I need :owner here? It looks like c? would bind self to the mk-whatever enclosing the c?

:popup ; of a treebox
   :owner self
      (mk-menu-item :label "Describe"
                    (callback (w e d)
                      (md-value (owner (upper self menu)))
                      (show-message (m:describe-item (md-value (owner (upper self menu)))))))))))

A: :popup isn't :kids. So you need another way to get into the kids hierarchy of the parent. (upper self menu) gets us to the menu and (owner (upper self menu)) jumps into the kids hierarchy, since c? always binds self to the containing mk-whatever object. (the outer c? does that here).

;;-- Another example, 

:populate-popup ; of a text-view
(c? (mk-menu-item :label "Expression Templates"
                  :owner self
                  (callback (w e d)
                    (show-message (format nil "XPath Nonsense self = ~A" (owner self))))))

A: If you macroexpand callback, you will see that it binds self again, this time to the menu-item.
(owner self) gets you into the :kids hierarchy.

Q: How do I do xyz?

A: If xyz concerns Cells-GTK or the use of Cells in Cells-GTK, ask your question on the project mailing list. The GTK documentation is also very helpful... And if it comes to it, adding the foreign function binding from the GTK API isn't hard either.

Q: What does this error mean? : "cellular slot FOO of #<BAR 21B555A4> cannot be setf unless initialized as inputp."

A:This error is signalled because you tried to setf a c-formula slot. 'inputp' refers to c-input (declaration of an input cell) which should have been used to initialize the slot. Read the cells-gtk primer for more information.

Q: Changing a cell value causes dependencies to fire, but what is considered a change?

A: As you are suggesting, setting a slot to an "equivalent" object should not cause its dependencies to fire again (in order to save substantial useless recalculations). So how is "equivalence" defined? EQL is used unless overridden by providing a function of two arguments to :unchanged-if in the slot definition of the defmodel.

Q: Can I keep a window around for redisplay after using the window border 'delete' button to delete it?

A:Yes. Do something like this:

(defmodel pseudo-dialog (window)
   :on-delete-event (callback (w e d) (gtk-widget-hide-on-delete w))))

... and, of course, store the widget somewhere so you can later display it with (gtk-widget-show-all (widget-id my-pseudo-dialog)).

Q: Can I use streams with a TextView widget?

A:Sure. Do something like this:

(defmethod initialize-instance :after ((self your-gtk-app) &key)
  (let ((message-textview [wherever it is]))
    (setf *message-stream* 
	(make-instance 'pane-stream 
		       :buffer (buffer message-textview)
		       :view message-textview))))

(defclass pane-stream (stream:fundamental-character-output-stream)
  ((view :initarg :view)
   (buffer :initarg :buffer)
   (mark :initarg :mark)
   (offset :initform 0 :accessor offset)))

(defmethod initialize-instance :after ((self pane-stream) &key)
  (with-slots (buffer mark view) self
     (let ((b (widget-id buffer))
	   (v (widget-id view)))
       (with-text-iters (iter) 
          (cgtk:gtk-text-buffer-get-iter-at-offset b iter 0)
	  (setf mark (cgtk:gtk-text-buffer-create-mark b "visibility" iter t)))
       (cgtk:gtk-text-view-set-wrap-mode v 1)  ; optional, of course. 
       (cgtk:gtk-text-view-set-editable v nil)))) ; also optional. 

;;; Some lisps might not need this one. Some might need other methods.
(defmethod stream:stream-line-column ((s pane-stream)) nil)

;;; The 'scroll-mark' stuff has not been very well tested, so you might
;;; wish to forego that feature. 
(defmethod stream:stream-write-char ((s pane-stream) (c character))
  (with-slots (view buffer offset mark) s
     (cgtk:text-buffer-append-text buffer (string c))
     (incf offset)
     (let ((buf (widget-id buffer)))
       (with-text-iters (iter) 
         (cgtk:gtk-text-buffer-get-iter-at-offset buf iter offset)
	 (cgtk:gtk-text-buffer-move-mark buf mark iter)
	 (cgtk:gtk-text-view-scroll-mark-onscreen (cgtk::id view) mark )))))

(defmethod stream:stream-write-string ((s pane-stream) string &optional start end)
  (with-slots (view buffer offset mark) s
     (cgtk:text-buffer-append-text buffer string)
     (incf offset (length string))
     (let ((buf (widget-id buffer)))
       (with-text-iters (iter) 
         (cgtk:gtk-text-buffer-get-iter-at-offset buf iter offset)
	 (cgtk:gtk-text-buffer-move-mark buf mark iter)
	 (cgtk:gtk-text-view-scroll-mark-onscreen (widget-id view) mark)))))
Q: Is there anything additional I should know about running Cells-gtk on Mac OS?

A: Check out the instructions here.

Q: While my application is running, the lisp listener is unresponsive. What can I do?

A:If you are running Slime, before you start your application, run (swank:create-server) then enter the emacs command M-x slime-connect. Accept the default host and port. (The port it suggests will be the one opened and reported by (swank:create-server)). But say 'no' to the question about closing the existing connection. At that point, you will have a second listener into your lisp image, and this one won't be hung while your application is running in the other one.

Peter Denno
Last modified: 2007-02-19 Valid XHTML 1.0!