;;;

CLIM Walkthrough

;;; ;;;

These are the notes of my exploration of CLIM. I'm using McCLIM and LispWorks to try it ;;; out. Mail rudi at constantly.at with comments, if you want. The ;;; source file is available as well (right ;;; click and save; at least my browser displays it a bit funky).

;;; ;;;

Fire up your editor, load McCLIM (see its INSTALL files), then ;;; evaluate forms one after another, redisplay the frame and watch ;;; the effects.

;;; ;;;

First, let's create a package.

(defpackage :tags-ui (:use :clim :clim-lisp)) (in-package :tags-ui) ;;;

This form and the next one are commented out. Whenever you ;;; want to see the frame we work with in this file, evaluate it or ;;; paste it into your listener. (The symbols here have package ;;; prefixes so you don't have to change the package at your ;;; REPL.)

#+nil (clim:run-frame-top-level (clim:make-application-frame 'tags-ui::tags-frame)) ;;;

When McCLIM doesn't want to let go of your windows, evaluate ;;; this form. It shouldn't be a problem once we created our example ;;; command below, though.

#+nil (progn (loop for port in climi::*all-ports* do (clim:destroy-port port)) (setq climi::*all-ports* nil)) ;;;

The first window!

;;; ;;;

The simplest possible frame: just an interactor pane. I added ;;; :width and :height, or the window becomes quite small with McCLIM. ;;; Evaluate the "run-frame-top-level ..." form to see it.

(define-application-frame tags-frame () () (:pane (make-pane 'interactor-pane :width 400 :height 300))) ;;;

Please make it go away! or, the first command

;;; ;;;

A quick command to exit the frame. define-tags-frame-comand ;;; was defined automagically by define-application-frame. ;;; *application-frame* is bound to the current frame. The command ;;; line name is "Quit Frame" (com-quit, hyphens replaced by spaces, ;;; "com-" prefix removed). Because of tab completion, you'll only ;;; have to type q <tab>, though.

;;; ;;;

Just for laughs, we display it in a menu with another name (the ;;; menu bar displays in McCLIM automatically, but not in ;;; Lispworks).

(define-tags-frame-command (com-quit-frame :name t :menu "Quit") () (frame-exit *application-frame*)) ;;;

Panes within panes

;;; ;;;

The same pane, with scroll bars around it. This showed a window ;;; of reasonable size both in McCLIM and LispWorks, so I omitted ;;; :width and :height arguments.

(define-application-frame tags-frame () () (:pane (scrolling () (make-pane 'interactor-pane)))) ;;;

This time, display only vertical scrollbar.

(define-application-frame tags-frame () () (:pane (scrolling (:scroll-bar :vertical) (make-pane 'interactor-pane)))) ;;;

MORE LAYOUT

;;; ;;;

define-application-frame takes either a :pane argument or ;;; arguments :panes and :layouts. We define some panes and a ;;; layout. vertically added scroll bars automatically.

(define-application-frame tags-frame () () (:panes (some-pane :application :background +blue+) (my-interactor :interactor)) (:layouts (default (vertically (:height 500 :width 400) some-pane my-interactor)))) ;;;

Tell the horizontal layout how to divide its space, and wrap ;;; the panes in other panes..

(define-application-frame tags-frame () () (:panes (some-pane :application :background +blue+) (my-interactor :interactor)) (:layouts (default (horizontally (:height 400 :width 800) (4/5 (spacing (:thickness 20) some-pane)) (+fill+ (outlining (:thickness 10) my-interactor)))))) ;;;

Now, can we see something?

;;; ;;;

Let's display something. Note the use of 'display-some-pane ;;; instead of #'display-some-pane — that way, a redefinition of ;;; display-some-pane will be picked up. Also note that the ;;; application frame has grown an attribute. This is nothing ;;; unusual; frames are CLOS objects, after all.

;;; ;;;

As you can see, panes can be used both as streams and as ;;; canvases. Panes can be accessed via get-frame-pane from their ;;; frame.

(defmethod display-some-pane ((frame tags-frame) stream) (format stream (tags-message frame))) (defmethod display-another-pane ((frame tags-frame) stream) (declare (ignore stream)) (let ((pane (get-frame-pane *application-frame* 'another-pane))) (window-clear pane) (draw-rectangle* pane 10 10 200 150 :filled nil :line-thickness 2) (draw-ellipse* pane 150 100 10 0 0 30))) (define-application-frame tags-frame () ((message :initform "Message" :accessor tags-message)) (:panes (some-pane :application :display-function 'display-some-pane) (another-pane :application :display-function 'display-another-pane) (my-interactor :interactor)) (:layouts (default (vertically (:height 600 :width 800) (2/3 (horizontally () some-pane another-pane)) (:fill my-interactor))))) ;;;

Pull-down Menus

;;; ;;;

Each item in a pull-down menu represents a command. Commands ;;; are organized in command tables (one of these is generated by ;;; default with define-application-frame). Submenus are collections ;;; of commands, so it makes sense to organize them as command ;;; tables.

;;;

First, let's define two commands that change the application ;;; frame's state. We don't use define-tags-frame-command, because we ;;; want the two commands callable only from the submenu, not from the ;;; listener. So, they don't belong in the default command table of ;;; tags-frame.

(define-command com-hello () (setf (tags-message *application-frame*) "Hello there!")) (define-command com-hi () (setf (tags-message *application-frame*) "Hi there!")) ;;;

Now we make a command table for the pull-down menu and one for ;;; the menu bar.

(define-command-table menu-command-table :menu (("Say Hello" :command com-hello) ("Say Hi" :command com-hi))) (define-command-table menubar-command-table :menu (("Menu" :menu menu-command-table) ("Quit" :command com-quit-frame))) ;;;

Finally, we make the frame use the menu bar. Clicking the ;;; commands changes the message slot of the frame; the display ;;; changes accordingly.

(define-application-frame tags-frame () ((message :initform "Message" :accessor tags-message)) (:menu-bar menubar-command-table) (:panes (some-pane :application :display-function 'display-some-pane) (my-interactor :interactor) (mouse :pointer-documentation)) (:layouts (default (vertically (:height 500 :width 400) (1/3 some-pane) (:fill my-interactor) mouse))))