Copyright © 2004,2005,2006,2007,2008,2017,2019 the McCLIM hackers.
CLIM is a large layered software system that allows the user to customize it at each level. The most simple ways of using CLIM is to directly use its top layer, which contains application frames, panes, and gadgets, very similar to those of traditional windowing system toolkits such as GTK, Tk, and Motif.
But there is much more to using CLIM. In CLIM, the upper layer with panes and gadgets is written on top of a basic layer containing more basic functionality in the form of sheets. Objects in the upper layer are typically instances of classes derived from those of the lower layer. Thus, nothing prevents a user from adding new gadgets and panes by writing code that uses the sheet layer.
Finally, since CLIM is written in Common Lisp, essentially all parts of it can be modified, replaced, or extended.
For that reason, a user’s manual for CLIM must contain not only a description of the protocols of the upper layer, but also of all protocols, classes, functions, macros, etc. that are part of the specification.
This manual documents McCLIM 0.9.8 which is a mostly complete implementation of the CLIM 2.0 specification and its revision 2.2. To our knowledge version 2.2 of the CLIM specification is only documented in the “CLIM 2 User’s Guide” by Franz. While that document is not a formal specification, it does contain many cleanups and is often clearer than the official specification; on the other hand, the original specification is a useful reference. This manual will note where McCLIM has followed the 2.2 API.
Also, some protocols mentioned in the 2.0 specification, such as parts of the incremental redisplay protocol, are clearly internal to CLIM and not well described. It will be noted here when they are partially implemented in McCLIM or not implemented at all.
Many new users of CLIM have a hard time trying to understand how it works and how to use it. A large part of the problem is that many such users are used to more traditional GUI toolkits, and they try to fit CLIM into their mental model of how GUI toolkits should work.
But CLIM is much more than just a GUI toolkit, as suggested by its name, it is an interface manager, i.e. it is a complete mediator between application “business logic” and the way the user interacts with objects of the application. In fact, CLIM doesn’t have to be used with graphics output at all, as it contains a large collection of functionality to manage text.
Traditional GUI toolkits have an event loop. Events are delivered to GUI elements called gadgets (or widgets), and the programmer attaches event handlers to those gadgets in order to invoke the functionality of the application logic. While this way of structuring code is sometimes presented as a virtue (“Event-driven programming”), it has an unfortunate side effect, namely that event handlers are executed in a null context, so that it becomes hard to even remember two consecutive events. The effect of event-driven programming is that applications written that way have very rudimentary interaction policies.
At the lowest level, CLIM also has an event loop, but most application programmers never have any reason to program at that level with CLIM. Instead, CLIM has a command loop at a much higher level than the event loop. At each iteration of the command loop:
Instead of attaching event handlers to gadgets, writing a CLIM application therefore consists of:
By using CLIM as a mediator of command invocation and argument acquisition, you can obtain some very modular code. Application logic is completely separate from interaction policies, and the two can evolve separately and independently.
Mike McDonald started developing McCLIM in 1998. His initial objective was to be able to run the famous “address book” demo, and to distribute the first version when this demo ran. With this in mind, he worked “horizontally”, i.e., writing enough of the code for many of the chapters of the specification to be able to run the address book example. In particular, Mike wrote the code for chapters 15 (Extended Stream Output), 16 (Output Recording), and 28 (Application Frames), as well as the code for interactor panes. At the end of 1999, Mike got too busy with other projects, and nothing really moved.
Also in 1998, Gilbert Baumann started working “vertically”, writing a mostly-complete implementation of the chapters 3 (Regions) and 5 (Affine Transformations). At the end of 1999, he realized that he was not going to be able to finish the project by himself. He therefore posted his code to the free-CLIM mailing list. Gilbert’s code was distributed according to the GNU Lesser General Public License (LGPL).
Robert Strandh picked up the project in 2000, starting from Gilbert’s code and writing large parts of chapters 7 (Properties of Sheets) and 8 (Sheet Protocols) as well as parts of chapters 9 (Ports, Grafts, and Mirrored Sheets), 10 (Drawing Options), 11 (Text Styles), 12 (Graphics), and 13 (Drawing in Color).
In early 2000, Robert got in touch with Mike and eventually convinced him to distribute his code, also according to the LGPL. This was a major turning point for the project, as the code base was now sufficiently large that a number of small demos were actually running. Robert then spent a few months merging his code into that produced by Mike.
Following the early development of McCLIM, the project entered a period of consolidation, unifying the code base and expanding its functionality. In 2000, Arthur Lemmens developed the first version of the gadget library, while Bordeaux students Iban Hatchondo and Julien Boninfante, hired by Robert, implemented pane protocols and additional gadgets such as push buttons, demonstrated through a simple calculator demo.
A major milestone came at the LSM-2000 meeting in Bordeaux in July of 2000, where Gilbert Baumann merged his work on regions and transformations with the main code base written by Mike, Robert, Iban, and Julien. This created a unified system with near-complete support for regions, transformations, sheet protocols, ports, graphics, mediums, panes, and gadgets.
Meanwhile, Mike was again able to work on the project, and during 2000 added much of the missing code for handling text interaction and scrolling. In particular, output recording could now be used to redisplay the contents of an interactor pane. Mike and Robert also worked together to make sure the manipulation of sheet transformations and sheet regions as part of scrolling and space allocation respected the specification.
One of the first complex applications built on McCLIM during this period was Gsharp, an interactive score editor. Gsharp served as a “killer app”, exercising many toolkit features. As part of the Gsharp project, Robert wrote the code for menu bars and for a large part of chapter 27 (Command Processing). Julien Boninfante also extended the system to allow images as button labels.
Additional contributions came from Bordeaux students Gregory Bossard, Michel Cabot, Cyrille Dindart, Lionel Vergé, Loïc Lacomme, Nicolas Louis, Arnaud Rouanet, and Lionel Salabartan. They worked on efficient 2D image rendering, file-selector gadget, and various minor protocols. Arnaud Rouanet and Lionel Vergé were later hired in the summer of 2001 and worked on output recording, various sheet protocols, and the first version of the PostScript backend. They also developed CLIM-fig, a demonstration of output recording. Arnaud Rouanet became increasingly involved with McCLIM, contributing fixes and new code for regions, graphics, and CLX mediums.
Timothy Moore contributed essential functionality to the presentation system, including presentation type definitions, method dispatch, and the core present and accept methods. He also began developing input editing streams, which formed the basis for the Goatee editor written in 2002.
Alexey Dejneka implemented table formatting, bordered output, and extended the PostScript output facility. Meanwhile, Brian Spilsbury worked on internationalization support, including enabling Unicode in SBCL and CMUCL, and also contributed to a GTK-like gadget set and early OpenGL backend work.
By 2002, McCLIM had evolved into a robust, unified system capable of supporting complex applications like Gsharp and providing a solid foundation for the rapid expansion that followed.
From 2003 onward, McCLIM entered a period of rapid functional growth, evolving from an experimental system into a fully usable and extensible graphical toolkit. Core infrastructure saw significant improvements, including enhancements to the presentation system, gadget support, formatting, and backend integration. Mike McDonald (2003-2004) continued to refine compatibility across implementations and integrated external patches, ensuring a solid foundation for further development. Alexey Dejneka (until 2003) contributed to output recording, formatting, presentations, and drawing primitives, helping stabilize the core functionality. In 2004 McCLIM hosting has moved to common-lisp.net.
During this period several contributors strengthened McCLIM’s core
infrastructure. Andy Hefner (2003-2010) worked on the presentation
system, formatting, gadgets, and backends, and implemented a native
Lisp TrueType renderer and the clim-listener. Gilbert Baumann
(2003-2005, 2010) worked on core functionality including the space
allocation protocol and gadgets, authored most of the X11
backend with antialiased fonts, and briefly returned in 2010 to
improve the windowing subsystem. Timothy Moore (2003-2006) continued
improving presentation system and added presentation histories. He
worked on core infrastructure across whole system and designed the
backend architecture.
During this period, input editing and editor functionality saw major advancement. Troels Henriksen (2006-2008) integrated and extended Drei, a port of Climacs’ input editing substrate, which replaced Goatee as the core input system, bringing incremental redisplay, syntax highlighting, and buffer management. Christophe Rhodes (2003-2010) contributed to command processing, layout optimization, and Drei integration, alongside portability and core system improvements. David Lichteblau (2006-2008) focused on backend rendering, substantially improving performance and stability.
System-wide infrastructure also advanced through improved Unicode and buld system support (Andreas Fuchs, 2005-2008), as well as portability, core protocol correctness, and documentation improvements (Robert Strandh, 2003-2010). Duncan Rose (2004-2006) worked on porting McCLIM to Cocoa, which yielded enhancements benefiting all backends. Dan Barlow (2003-2005) improved SBCL compatibility and documentation, while Peter Scott (2005-2008) contributed numerous smaller, targeted improvements.
This period also saw McCLIM supporting notable applications like Closure, Gsharp, and Climacs, with contributors often working across the system and these projects. Together, these efforts transformed McCLIM into a functional, extensible graphical toolkit suitable for real-world applications.
Starting from 2009 project development went on hiatus with occasional commits from Tim Moore and Robert Strandh, who maintained McCLIM until other developers joined in. During that time development moved to GitHub repository under Robert Strandh’s profile.
From 2016 onward, McCLIM entered a period of sustained revival and modernization. Daniel Kochmański (2016-present) began by improving portability, replacing platform-specific multiprocessing code with a portable bordeaux-threads layer. Later that year, Robert Strandh (2016-present) brought him on as a maintainer, and together they organized a fundraiser with bounties to encourage wider community involvement, providing crucial support for ongoing development.
Between 2016 and 2018, the focus was on cleanup and building the
community. McCLIM repository moved to a separate organization
(still on GitHub). Alessandro Serra (2016-2017) fixed numerous gadget
issues and implemented a software renderer based on cl-vectors,
while Elias Mårtenson (2016-2018) improved the CLX backend and rewrote
the FFI freetype renderer to ensure proper text shaping. Nisar Ahmad
(2017-2022) conducted extensive testing across the system, reporting
bugs and suggesting improvements that significantly enhanced overall
stability.
From 2019 to 2021, development emphasized refactoring and performance. Jan Moringen (2017-2022) and Daniel Kochmański (2016-present) co-maintained the project, performing extensive peer reviews, improving documentation and introducing comprehensive test suites to strengthen reliability and specification compliance. The geometry module was overhauled, the CLX backend was rewritten for faster rendering, transformations were accelerated, and transparency support was added. During this period, Cyrus Harmon (2017-2019) contributed fixes to printing and geometry and developed a PDF backend, further extending McCLIM’s capabilities.
From 2022 onward, the focus shifted to new functionality and modernization of core subsystems. from Daniel Kochmański (2016-present) refactored the native TrueType renderer, introduced kerning and transformed text support, rewrote input editing, and implemented abstractions for completion and accept-values, while making drawing to CLIM streams thread-safe. Andrea De Michele (2018-present) contributed extensively through testing, patches, and new demonstration examples, while Charlie McMackin (2024-present) began work on the Wayland backend, improving gadgets and implementing missing slider options to better align with the specification. In 2023 McCLIM repository has been moved from GitHub to Codeberg.
These combined efforts of cleanup, refactoring, and feature development transformed McCLIM into a robust, maintainable, and extensible graphical toolkit ready for contemporary applications.
The McCLIM source code comes with a number of examples and applications. They are intended to showcase specific CLIM features, demonstrate programming techniques or provide useful tools. You may find them in directories Examples and Apps.
To run a minimal McCLIM application frame you need to load the system
mcclim, define the frame and call find-application-frame on it:
(eval-when (:compile-toplevel :load-toplevel :execute)
(unless (member :mcclim *features*)
(ql:quickload "mcclim")))
(in-package "CLIM-USER")
(define-application-frame smoke-test ()
())
(find-application-frame 'smoke-test)
The opened application will greet the user with an interactor and the prompt “Command:”. To see global commands type “Help Commands”.
Figure 1.1 shows what ought to be visible on the screen.
Figure 1.1: Screenshot of a minimal application.
See Getting started for a more complete example.
To start the examples, load the system clim-examples and call the
function (clim-demo:demodemo). The easiest way to try this is
to use Quicklisp:
(ql:quickload "clim-examples") (clim-demo:demodemo)
This will open a window presenting a set of available examples.
Additionally McCLIM has a few bundled applications:
CLIM-enabled Lisp listener.
System name is clim-listener. See Listener for more
information.
(ql:quickload "clim-listener") (clim-listener:run-listener)
This will open a graphical Lisp listener in a separate window.
CLIM-enabled Lisp inspector.
System name is clouseau. See Inspector for more information.
(ql:quickload "clouseau") (clouseau:inspect clim:+indian-red+)
This will open a graphical Lisp inspector in a separate window.
CLIM-enabled Lisp debugger.
System name is clim-debugger. See Debugger for more
information.
(ql:quickload "clim-debugger") (clim-debugger:with-debugger () (break "simple-break"))
This will open a graphical Lisp debugger in a separate window.
An application frame (simply a frame) pieces together the
application state, its visual appearance and user interactions. A frame
is an instance of the class defined with define-application-frame.
A command table is an object that mediates between the frame and the user. They store commands, menus and presentation translators.
A command represents a user interaction that modifies the frame.
An action represents a user interaction that causes side-effects without progressing the command loop.
A presentation links screen output to objects.
A pane is an interactive object that defines a visual appearance of the application. Available panes come in three types:
Spatially organizes other panes.
Reusable components for rudimentary interactions. Frequently used
CLIM gadgets are push-buttons, sliders, etc.
Extensible components for presenting application specific information,
like application-pane and interactor-pane.
An output history is a display list that captures the output displayed to CLIM stream pane.
An input context is a dynamic state denoting the set of presentation types that are currently relevant pending operations and associated handlers.
In other words it defines what kinds of objects the system is currently expecting the user to interact with.
A gesture is a named set of events recognized by CLIM. Most
notably the gesture :select means pressing the left pointer
button and the gesture :menu means pressing the right button.
Three central ideas in CLIM are that a frame holds the application state, presentations link screen output to objects and commands modify the state.
A default interaction model works in a read-execute-display-loop where
CLIM reads a command from the user, executes it to update the application
state and redisplays panes.
While reading the command the user interacts with the application to supply the command itself, or to cause side effects.
Commands participate in the command loop and trigger redisplay; actions are for immediate side effects that do not.
Here is a structural overview of the application; real definitions are provided in subsequent sections and are concluded with a full listing.
(defpackage "FIRST-APP" ;; Imports symbols from McCLIM. (:use "CLIM-LISP" "CLIM" "CLIM-EXTENSIONS") ;; The package will only export the frame name. (:export "STUDY")) (in-package "FIRST-APP") (define-application-frame study () ((shelf :initarg :shelf :accessor shelf) (book :initform nil :accessor book)) (:panes ...) (:layouts ...)) (defclass book () ((title :initarg :title :reader title) (author :initarg :author :reader author))) (define-presentation-method present ...) (define-presentation-method accept ...) (define-study-command (com-read-book :name t) ...) (define-study-command (com-stop-reading-book :name t) ...) (define-presentation-to-command-translator act-take-book ...) (define-presentation-to-command-translator act-drop-book ...) (define-presentation-action act-copy-book ...) (defun display-study (frame stream) ...)
Figure 1.1 shows what ought to be visible on the screen.
Figure 1.2: Screenshot of the first application.
(defpackage "FIRST-APP" ;; Imports symbols from McCLIM. (:use "CLIM-LISP" "CLIM" "CLIM-EXTENSIONS") ;; The package will only export the frame name. (:export "STUDY")) (in-package "FIRST-APP")
As we can see in this example, we have put our application in a separate
package, here a package named FIRST-APP. While not required, putting
the application in its own package is good practice.
The package for the application uses three packages:
CLIM-LISP, CLIM and CLIM-EXTENSIONS. The
CLIM-LISP package replaces the COMMON-LISP package
for CLIM applications. It is essentially the same as the
COMMON-LISP package as far as the user is concerned, the
CLIM package is the one that contains all the symbols needed
for using CLIM, and the package CLIM-EXTENSIONS contains all
the symbols defined for McCLIM extensions.
In our example, we export the symbol that corresponds to the frame class that may be used to start our application using functions run-frame-top-level and find-application-frame.
;;; This macro is like DEFCLASS with additional options. (define-application-frame study () ((shelf :initarg :shelf :accessor shelf) (book :initform nil :accessor book)) ;; :PANES option specifies named sub-windows, their types and initargs. (:panes (app :application :display-function 'display-study :width 1280) (int :interactor)) ;; :LAYOUTS option arranges panes in a hierarchy. (:layouts (default (vertically () app int)) (onlyapp app)))
Each CLIM application is represented by an application frame. As a CLIM user, you typically define a class that contains slots with the application specific data. It is considered good style to not store the state outside of the frame (i.e in global variables).
The macro define-application-frame defines a new frame class. It works similar to defclass, but also allows you to specify additional options, like panes and layouts.
In our example, we have defined an application frame called study,
which becomes a CLOS class that automatically inherits from standard
CLIM application frame class. It contains slots storing the shelf and
the current book (if any).
The :panes option allows us to define named panes. Each pane is
defined by a list where the first element is the pane’s name and
remaining elements denote its type and initialization arguments.
In our case we define two named panes:
appThe application pane that displays the current application state by
calling a function display-study.
intThe interactor pane that reads commands from the user. It works like a command line in the terminal.
The :layouts option allows us to specify how panes are arranged in
the frame. Each layout is defined by a list where the first element is
the layout’s name and the second form evaluates to a pane. Macros like
vertically or tabling create a pane that composes other panes.
In our case we define two layouts:
defaultThis layout arranges panes one below another – the application pane at the top, and the interactor pane at the bottom. It is useful for power users who want to have access to the command line.
onlyappThis layout uses only the application pane. It presents an uncluttered interface without the command line. It makes a useful default for non-technical users.
;;; Standard classes are implicit presentation types, that is there is
;;; no need to separately call DEFINE-PRESENTATION-TYPE for them.
;;; (defclass book () ((title :initarg :title :reader title) (author
;;; :initarg :author :reader author)))
;;; The presentation method PRESENT defines how we should display the
object. (define-presentation-method present ((book book) (type book)
stream view &key acceptably for-context-type) (declare (ignore
acceptably for-context-type)) (with-text-face (stream :bold) (princ
(title book) stream)) (princ " by " stream) (with-text-face (stream
:italic) (princ (author book) stream)))
(define-presentation-method accept ((type book) stream view &key default
default-type) (declare (ignore default default-type))
(with-application-frame (frame) (completing-from-suggestions
(stream) (dolist (book (shelf frame)) (suggest (format nil "~a by
~a" (title book) (author book)) book)))))
Presentation types may be abstract, or they may be associated with the
standard-class. standard-class instances are implicitly treated
as unparametrized presentation types associated with that class.
We define only one presentation type book. It stores the information
about the book title and its author.
The presentation method present draws the object on the screen. Objects presented with the function present are recognized by CLIM when their presentation type matches the input context.
The presentation method accept is responsible for parsing input into the object of the requested presentation type. Standard classes don’t have default readable representation, so we need to specialize the parser to allow typing a book from keyboard (and to enable completions).
CLIM operates on CLIM stream panes. That means in particular,
that we can mix standard Common Lisp and CLIM-specific operators. In
this case we add the additional style to the book’s title and author in
present and add the autocompleter for reading books in accept.
;;; DEFINE-STUDY-COMMAND is a macro defined by
;;; DEFINE-APPLICATION-FRAME. It adds a new command to the command table
;;; associated with the frame. (define-study-command (com-read-book
;;; :name t) ((book 'book)) (with-application-frame (frame) (when (book
;;; frame) (com-stop-reading-book)) (setf (shelf frame) (remove book
;;; (shelf frame))) (setf (book frame) book)))
(define-study-command (com-stop-reading-book :name t) ()
(with-application-frame (frame) (let ((book (book frame))) (when
book (push (book frame) (shelf frame)) (setf (book frame) nil)))))
Code that modifies the application state should be put in commands. This ensures that CLIM redisplays the frame in a coordinated manner.
When we’ve defined the application frame, we’ve also created a
corresponding command table with the same name, and a macro
define-study-command. This macro defines a new command and adds
it to the command table study.
In our application we define two commands: one to pick up the book, and
another to return it on the shelf. To access the frame we use the macro
with-application-frame that binds a variable to the current frame.
Notice that commands, in order to be available from the interactor, must
have an option of :name t. The reason is that some commands will be
available only from menus or by some other mechanism. To put the command
in the menu, you may add another option :menu t.
Command arguments are specified as the command name, its presentation type (evaluated) and some other options that we don’t use here. When CLIM reads the command argument, its presentation type becomes part of the input context, so it is possible to either type it with keyboard (when the interactor is present), or click objects presented on the screen with the pointer.
Command: Read Book flatland Command: Stop Reading Book
Notice that after we type in the interactor Read Book , we can
use tab to autocomplete words from the suggestions provided by our
presentation method accept.
(define-presentation-to-command-translator act-take-book (book
com-read-book study :gesture :select :tester ((object frame) (and
(null (book frame)) (member object (shelf frame))))) (object)
`(,object))
(define-presentation-to-command-translator act-drop-book (book
com-stop-reading-book study :gesture :select :tester ((object frame)
(eql object (book frame)))) (object) `())
(define-presentation-action act-copy-book (book nil study :gesture nil)
(object window) (publish-selection window :clipboard
(present-to-string object 'book) 'string))
Besides using the application by typing commands in the interactor, it is possible to define interactions directly using objects presented on the screen, by using menu and by using keyboard accelerators.
Presentation translators can be used to implement interactions that work on objects presented on the screen.
When the user moves the pointer above a presentation, CLIM looks for applicable translators from its presentation type to presentation types in the current input context. If such translators exist, it highlights the presentation type.
Presentation translator may be activated by either making a gesture that is specified for it, or by selecting it from a presentation menu opened by clicking on the presentation with the right pointer button.
Presentation to command translators are defined with the operator
define-presentation-to-command-translator. As the name suggests,
they are used to translate presentations to commands.
The name is used to identify the translator. The required arguments in the first lambda list denote the presentation type to match with the presentation, the command name of the command that will be returned by the translator, and the command table that the translator belongs to. Then we have the activation gesture and the tester. The tester may be used to further narrow the translator applicability based on the object.
Presentation to action translators are defined with the operator
define-presentation-action. Actions, unlike commands, do not
progress the command loop and do not implicitly cause the redisplay of
the frame. They are defined for side effects that may happen while we
wait for a command, i.e to send notifications.
The name is used to identify the translator. The required arguments in the first lambda list denote the presentation type to match with the presentation, the presentation type to matching with the input context, and the command table that the translator belongs to. Then we have the activation gesture and the tester. The tester may be used to further narrow the translator applicability based on the object.
We define two translators to commands that are used to implement the style where it is enough to click on a book on the shelf to read it (unless the desk is already occupied), and by clicking on a book on the desk that book is returned to the shelf.
The presentation action is used to copy the book title and author to the
clipboard. It is available from the menu that opens when we right-click
on the presentation. Note that the target presentation is nil,
this is because nil is an universal subtype, so the action will
be applicable disregarding of the current input context.
:gesture nil means, that the action is only available from the
presentation menu, not via a direct pointer gesturs.
;;; The display function for the "app" pane. (defun display-study (frame stream) (format stream "Books on the shelf:~%") (indenting-output (stream 15) (dolist (book (shelf frame)) (present book 'book :stream stream :single-box t) (terpri stream))) (stream-increment-cursor-position stream 0 20) (let ((book (book frame))) (if (null book) (format stream "Not reading anything.") (progn (format stream "Currently reading ") (present (book frame) 'book :stream stream :single-box t) (format stream ".")))))
application panes are streams that are typically used to display
application specific data. These streams can be used in ordinary input
and output operators, such as format and read, and in CLIM
specific operators, such as present, accept and draw-line.
A display function is called with the frame and the pane as arguments.
In our example we first present all available books, and then display
what is currently being read. Note that books are presented with the
function present to create a link between graphical output and the
underlying object.
We use an option :single-box to draw a rectangle when the
presentation is highlighted – otherwise borders would be drawn around
each individual object that is part of the presentation.
(defpackage "FIRST-APP"
;; Imports symbols from McCLIM.
(:use "CLIM-LISP" "CLIM" "CLIM-EXTENSIONS")
;; The package will only export the frame name.
(:export "STUDY"))
(in-package "FIRST-APP")
;;; This macro is like DEFCLASS with additional options.
(define-application-frame study ()
((shelf :initarg :shelf :accessor shelf)
(book :initform nil :accessor book))
;; :PANES option specifies named sub-windows, their types and initargs.
(:panes
(app :application :display-function 'display-study :width 1280)
(int :interactor))
;; :LAYOUTS option arranges panes in a hierarchy.
(:layouts
(default (vertically () app int))
(onlyapp app)))
;;; Standard classes are implicit presentation types, that is there is no need
;;; to separately call DEFINE-PRESENTATION-TYPE for them.
(defclass book ()
((title :initarg :title :reader title)
(author :initarg :author :reader author)))
;;; The presentation method PRESENT defines how we should display the object.
(define-presentation-method present
((book book) (type book) stream view &key acceptably for-context-type)
(declare (ignore acceptably for-context-type))
(with-text-face (stream :bold)
(princ (title book) stream))
(princ " by " stream)
(with-text-face (stream :italic)
(princ (author book) stream)))
(define-presentation-method accept
((type book) stream view &key default default-type)
(declare (ignore default default-type))
(with-application-frame (frame)
(completing-from-suggestions (stream)
(dolist (book (shelf frame))
(suggest (format nil "~a by ~a" (title book) (author book)) book)))))
;;; DEFINE-STUDY-COMMAND is a macro defined by DEFINE-APPLICATION-FRAME. It adds
;;; a new command to the command table associated with the frame.
(define-study-command (com-read-book :name t)
((book 'book))
(with-application-frame (frame)
(when (book frame)
(com-stop-reading-book))
(setf (shelf frame) (remove book (shelf frame)))
(setf (book frame) book)))
(define-study-command (com-stop-reading-book :name t)
()
(with-application-frame (frame)
(let ((book (book frame)))
(when book
(push (book frame) (shelf frame))
(setf (book frame) nil)))))
(define-presentation-to-command-translator act-take-book
(book com-read-book study
:gesture :select
:tester ((object frame)
(and (null (book frame))
(member object (shelf frame)))))
(object)
`(,object))
(define-presentation-to-command-translator act-drop-book
(book com-stop-reading-book study
:gesture :select
:tester ((object frame)
(eql object (book frame))))
(object)
`())
(define-presentation-action act-copy-book
(book nil study :gesture nil)
(object window)
(publish-selection window :clipboard (present-to-string object 'book) 'string))
;;; The display function for the "app" pane.
(defun display-study (frame stream)
(format stream "Books on the shelf:~%")
(indenting-output (stream 15)
(dolist (book (shelf frame))
(present book 'book :stream stream :single-box t)
(terpri stream)))
(stream-increment-cursor-position stream 0 20)
(let ((book (book frame)))
(if (null book)
(format stream "Not reading anything.")
(progn
(format stream "Currently reading ")
(present (book frame) 'book :stream stream :single-box t)
(format stream ".")))))
Above is a complete program listing. In mere 100 lines of code we’ve implemented an application that defines various user interactions and displays formatted output.
(defun make-test-shelf ()
(flet ((make-book (title author)
(make-instance 'book :title title :author author)))
(list (make-book "Flatland: A Romance of Many Dimensions" "Edwin Abbott Abbott")
(make-book "The Lord of the Rings" "J. R. R. Tolkien")
(make-book "Moomins" "Tove Jansson")
(make-book "The Little Prince" "Antoine de Saint-Exupery"))))
;;; FIND-APPLICATION-FRAME ensures that a frame of the specified type exists,
;;; and if it doesn't it creates a new one.
(find-application-frame 'study :shelf (make-test-shelf) :current-layout 'onlyapp)
;;; RUN-FRAME-TOP-LEVEL is used to start an application that has been already
;;; created. The frame state persists after closing so it can be opened again.
(defvar *app*
(make-application-frame 'study :shelf (make-test-shelf) :current-layout 'onlyapp))
(run-frame-top-level *app*)
To run an application we can use find-application-frame. It ensures
that a frame instance of the specified class is running, and if not it
starts a new one (when possible - in a separate process). Additional
arguments are passed as initargs to the frame.
Alternatively use run-frame-top-level to start a frame that already
exists. This is useful when we care about the frame state. The function
runs synchronously, so if we want to run it in a separate thread then we
need to create it manually.
The initarg :current-layout allows the user to specify the
initial layout. It is possible to change it at runtime by calling the
function (setf frame-current-layout).
We’ve mentioned that CLIM operates inside the command loop and that while a command is being read, auxilliary interactions are possible. This is possible, because a command loop runs inside an event loop that is part of the underlying implementation.
Presentation translators are the primary example where CLIM runs user-defined code in response to an event. So what happens when you click a presentation?
The frame button press handler visits presentations below the pointer and tries to find the innermost applicable presentation. Applicability is decided by testing translators available in the command table:
If more than one presentation translator matches the same innermost presentation, then ties are resolved using the translator with the highest priority which may be specified when defining a translator. When there is no winner, then one of translators is chosen.
When the presentation translator is chosen, it is called to translate
the presentation to the input context type, and the program control is
transferred to the handler. Internally the input context will be
established with a macro with-input-context. For example:
(with-input-context (`(command :command-table ,command-table))
(object)
(simple-event-loop frame)
(t (throw :command object)))
the command processor will then execute the command and redisplay the frame and start the next command loop iteration.
It may happen, that the presentation translator will return nil
instead of the new presentation, or signal a serious-condition.
In that case, despite of evaluating the translator body, then input
handler is not invoked. That’s how presentation actions are implemented.
Add a new slots to the book called “rate” and define a command
com-rate-book that accepts one argument of type integer.
Define a command com-sort-shelf that accepts an argument of type
'(completion (:author :title :rating :unsorted)).
After taking the book from the shelf keep the reference to it on the
shelf, that is present the book but make it unavailable. The function
present accepts a keyword argument :sensitive, use it to
disable translators for the object.
To show that the book is not available, wrap a call to present in
surrounding-output-with-border (stream :shape :crossout).
com-switch-layout
This command should alternate between default and onlyapp
layouts and should be available in the menu. The command should not be
available in the command line.
Although it is easy to imagine panes in terms of their appearance on screen, they are much richer: they are actually the series of operations that produces that appearance. They are not only the end product visible on a screen, but they contain all the step-by-step information that led to that representation.
More precisely, CLIM panes record the series of operations that generates an output. This means that such a pane maintains a display list, consisting of a sequence of output records, ordered chronologically, from the first output record to be drawn to the last.
This display list is used to fill in damaged areas of the pane, for instance as a result of the pane being partially or totally covered by other panes, and then having some or all of its area again becoming visible. The output records of the display list that have some parts in common with the exposed area are partially or totally replayed (in chronological order) to redraw the contents of the area.
An application can have a pane establish this display list in several fundamentally different ways, each more sophisticated:
Very simple applications have no internal data structure to keep track of application objects, and simply produce output to the pane from time to time as a result of running commands, occasionally perhaps erasing the pane and starting over. Such applications typically use text or graphics output as a result of running commands. CLIM maintains the display list for the pane, and adds to the end of it, each time also producing the pixels that result from drawing the new output record. If the pane uses scrolling (which it typically does), then CLIM must determine the extent of the pane so as to update the scroll bar after each new output.
More complicated applications use a display function. Before the display function is run, the existing display list is typically deleted, so that the purpose of the display function becomes to establish an entirely new display list. The display function might for instance produce some kind of form to be filled in, and application commands can use text or graphics operations to fill in the form. A game of tic-tac-toe could work this way, where the display function draws the board and commands draw shapes into the squares.
Even more complicated applications might have some internal data structure that has a direct mapping to output, and commands simply modify this internal data structure. In this case, the display function is run after each time around the command loop, because a command can have modified the internal data structure in some arbitrary ways. Some such applications might simply want to delete the existing display list and produce a new one each time (to minimize flicker, double buffering could be used). This is a very simple way of structuring an application, and entirely acceptable in many cases. Consider, for instance, a board game where pieces can be moved (as opposed to just added). A very simple way of structuring such an application is to have an internal representation of the board, and to make the display function traverse this data structure and produce the complete output each time in the command loop.
Some applications have very large internal data structures to be
displayed, and it would cause a serious performance problem if the
display list had to be computed from scratch each time around the
command loop. To solve this problem, CLIM contains a feature called
incremental redisplay. It allows many of the output records to be kept
from one iteration of the command loop to the next. This can be done in
two different ways. The simplest way is for the application to keep the
simple structure which consists of traversing the entire data structure
each time, but at various points indicate to CLIM that the output has
not changed since last time, so as to avoid actually invoking the
application code for computing it. This is accomplished by the use of
updating-output. The advantage of updating-output is that the
application logic remains straightforward, and it is up to CLIM to do
the hard work of recycling output records. The disadvantage is that for
some very demanding applications, this method might not be fast enough.
The other way is more complicated and requires the programmer to structure the application differently. Essentially, the application has to keep track of the output records in the display list, and inform CLIM about modifications to it. The main disadvantage of this method is that the programmer must now write the application to keep track of the output records itself, as opposed to leaving it to CLIM.
While the example in the previous section is a very simple way of structuring an application (let commands arbitrarily modify the data structure, and simply erase the pane and redisplay the structure after each iteration of the command loop), the visual result is not so great when many objects are to be displayed. There is most often a noticeable flicker between the moment when the pane is cleared and the objects are drawn. Sometimes this is inevitable (as when nearly all objects change), but most of the time, only an incremental modification has been made, and most of the objects are still in the same place as before.
In simple toolkits, the application programmer would have to figure out what has changed since the previous display, and only display the differences. CLIM offers a mechanism called incremental redisplay that automates a large part of this task. As we mentioned earlier, CLIM captures output in the form of output records. The same mechanism is used to obtain incremental redisplay.
To use incremental redisplay, client code remains structured in the simple way that was mention above: after each iteration of the command loop, the display function output the entire data structure as usual, except that it helps the incremental redisplay mechanism by telling CLIM which piece of output corresponds to which piece of output during the previous iteration of the command loop. It does this by giving some kind of unique identity to some piece of output, and some means of indicating whether the contents of this output is the same as it was last time. With this information, the CLIM incremental redisplay mechanism can figure out whether some output is new, has disappeared, or has been moved, compared to the previous iteration of the command loop. As with re-exposure, CLIM guarantees that the result is identical to that which would have been obtained, had all the output records been output in order to a blank pane.
The next example illustrates this idea. It is a simple application that displays a fixed number (here 20) of lines, each line being a number. Here is the code:
(in-package :common-lisp-user)
(defpackage "APP"
(:use :clim :clim-lisp)
(:export "APP-MAIN"))
(in-package :app)
(define-application-frame superapp ()
((numbers :initform (loop repeat 20 collect (list (random 100000000)))
:accessor numbers)
(cursor :initform 0 :accessor cursor))
(:pointer-documentation t)
(:panes
(app :application
:height 400 :width 600
:incremental-redisplay t
:display-function 'display-app)
(int :interactor :height 200 :width 600))
(:layouts
(default (vertically () app int))))
;; As usual, the displaying code relates to a pane, not the application frame.
(defun display-app (frame pane)
(loop
;; Taking items one-by-one from the frame slot 'numbers'...
for current-element in (numbers frame)
;; ...and increasing line-by-line...
for line from 0
;; ...print a star if the cursor is on that line...
;; (Note that here, there is no incremental redisplay. The output
;; record of the star will be printed at each call of the display
;; function, i.e. at each iteration of the command loop.)
do (princ (if (= (cursor frame) line) "*" " ") pane)
;; ...and incrementally update the rendering instructions of the
;; number on that line.
;; (Note that 'numbers' was defined as a list of lists, each
;; sublist holding an individual number. The reason for that is
;; explained below, but this is why (car current-element) is
;; needed.)
do (updating-output (pane :unique-id current-element
:id-test #'eq
:cache-value (car current-element)
:cache-test #'eql)
(format pane "~a~%" (car current-element)))))
;;
;; Command definitions
;;
;; Increase the value of the number on the current line.
(define-superapp-command (com-add :name t) ((number 'integer))
(incf (car (elt (numbers *application-frame*)
(cursor *application-frame*)))
number))
;; Move the cursor one line down (increasing the cursor position),
;; looping back to the beginning if going too far.
(define-superapp-command (com-next :name t) ()
(incf (cursor *application-frame*))
(when (= (cursor *application-frame*)
(length (numbers *application-frame*)))
(setf (cursor *application-frame*) 0)))
;; Move the cursor one line up
(define-superapp-command (com-previous :name t) ()
(decf (cursor *application-frame*))
(when (minusp (cursor *application-frame*))
(setf (cursor *application-frame*)
(1- (length (numbers *application-frame*))))))
;; Command to quit the app
(define-superapp-command (com-quit :name t) ()
(frame-exit *application-frame*))
;; Exported function to launch an instance of the application frame
(defun app-main ()
(run-frame-top-level (make-application-frame 'superapp)))
We store the numbers in a slot called numbers of the application
frame. However, we store each number in its own list. This is a simple
way to provide a unique identity for each number. We could not use the
number itself, because two numbers could be the same and the identities
would not be unique. Instead, we use the cons cell that store the
number as the unique identity. By using :id-test #'eq we inform
CLIM that it can figure out whether an output record is the same as one
that was issued previous time by using the function eq to compare
them. But there is a second test that has to be verified, namely
whether an output record that was issued last time has to be redisplayed
or not. That is the purpose of the cache-value. Here we use the number
itself as the cache value and eql as the test to determine whether
the output is going to be the same as last time.
For convenience, we display a * at the beginning of the current
line, and we provide two commands next and previous
to navigate between the lines.
Notice that in the declaration of the pane in the application frame, we
have given the option :incremental-redisplay t. This informs CLIM
not to clear the pane after each command-loop iteration, but to keep the
output records around and compare them to the new ones that are produced
during the new iteration.
The concept of presentation types is central to CLIM. Client code can choose to output graphical or textual representations of application objects either as just graphics or text, or to associate such output with an arbitrary Common Lisp object and a presentation type. The presentation type is not necessarily related to the idea Common Lisp might have of the underlying object.
When a CLIM command or some other client code requests an object (say as an argument) of a certain presentation type, the user of the application can satisfy the request by clicking on any visible output labeled with a compatible presentation type. The command then receives the underlying Common Lisp object as a response to the request.
CLIM presentation types are usually distinct from Common Lisp types. The reason is that the Common Lisp type system, although very powerful, is not quite powerful enough to represent the kind of relationships between types that are required by CLIM. However, every Common Lisp class (except the built-in classes) is automatically a presentation type.
A presentation type has a name, but can also have one or more
parameters. Parameters of presentation types are typically used
to restrict the type. For instance, the presentation type integer
takes as parameters the low and the high values of an interval. Such
parameters allow the application to restrict objects that become
clickable in certain contexts, for instance if a date in the month of
March is requested, only integers between 1 and 31 should be clickable.
Consider the following example:
(in-package :common-lisp-user)
(defpackage :app
(:use :clim :clim-lisp)
(:export #:app-main))
(in-package :app)
(define-application-frame superapp ()
()
(:pointer-documentation t)
(:panes
(app :application :display-time t :height 300 :width 600)
(int :interactor :height 200 :width 600))
(:layouts
(default (vertically () app int))))
(defun app-main ()
(run-frame-top-level (make-application-frame 'superapp)))
(define-superapp-command (com-quit :name t) ()
(frame-exit *application-frame*))
(define-presentation-type name-of-month ()
:inherit-from 'string)
(define-presentation-type day-of-month ()
:inherit-from 'integer)
(define-superapp-command (com-out :name t) ()
(with-output-as-presentation (t "The third month" 'name-of-month)
(format t "March~%"))
(with-output-as-presentation (t 15 'day-of-month)
(format t "fifteen~%")))
(define-superapp-command (com-get-date :name t)
((name 'name-of-month) (date 'day-of-month))
(format (frame-standard-input *application-frame*)
"the ~a of ~a~%" date name))
In this application, we have two main panes, an application pane and an
interactor pane. The application pane is given the option
:display-time t which means that it will not be erased before every
iteration of the command loop.
We have also defined two presentation types: name-of-month and
day-of-month. The out command uses
with-output-as-presentation in order to associate some output, a
presentation type, and an underlying object. In this case, it will show
the string “March” which is considered to be of presentation type
name-of-month with the underlying object being the character string
"The third month". It will also show the string “fifteen” which
is considered to be of presentation type day-of-month with the
underlying object being the number 15. The argument t to
with-output-as-presentation indicates that the stream to present on
is *standard-output*.
Thus, if the out command has been executed, and then the user types
‘Get Date’ in the interactor pane, the get-date command will
try to acquire its arguments, the first of presentation type
name-of-month and the second of type day-of-month. At the first
prompt, the user can click on the string ‘March’ but not on the
string ‘fifteen’ in the application pane. At the second prompt it
is the string ‘fifteen’ that is clickable, whereas ‘March’ is
not.
The get-date command will acquire the underlying objects. What is
finally displayed (in the interactor pane, which is the standard input
of the frame), is ‘the 15 of The third month’.
The CLIM specification mentions a concept called a view, and also lists a number of predefined views to be used in various different contexts.
In this chapter we show how the view concept can be used in some
concrete programming examples. In particular, we show how to use a
single pane to show different views of the application data structure at
different times. To switch between the different views, we supply a set
of commands that alter the stream-default-view feature of all CLIM
extended output streams.
The example shown here has been stripped to a bare minimum in order to illustrate the important concepts. A more complete version can be found in Examples/views.lisp in the McCLIM source tree.
Here is the example:
(cl:in-package #:clim-user)
;;; part of application "business logic"
(defclass person ()
((%last-name :initarg :last-name :accessor last-name)
(%first-name :initarg :first-name :accessor first-name)
(%address :initarg :address :accessor address)
(%membership-number :initarg :membership-number :reader membership-number)))
;;; constructor for the PERSON class. Not strictly necessary.
(defun make-person (last-name first-name address membership-number)
(make-instance 'person
:last-name last-name
:first-name first-name
:address address
:membership-number membership-number))
;;; initial list of members of the organization we imagine for this example
(defparameter *members*
(list (make-person "Doe" "Jane" "123, Glencoe Terrace" 12345)
(make-person "Dupont" "Jean" "111, Rue de la Republique" 54321)
(make-person "Smith" "Eliza" "22, Trafalgar Square" 121212)
(make-person "Nilsson" "Sven" "Uppsalagatan 33" 98765)))
;;; the CLIM view class that corresponds to a list of members, one member
;;; per line of text in a CLIM application pane.
(defclass members-view (view) ())
;;; since this view does not take any parameters in our simple example,
;;; we need only a single instance of it.
(defparameter *members-view* (make-instance 'members-view))
;;; the application frame. It contains instance-specific data
;;; such as the members of our organization.
(define-application-frame views ()
((%members :initform *members* :accessor members))
(:panes
(main-pane :application :height 500 :width 500
:display-function 'display-main-pane
;; notice the initialization of the default view of
;; the application pane.
:default-view *members-view*)
(interactor :interactor :height 100 :width 500))
(:layouts
(default (vertically ()
main-pane
interactor))))
;;; the trick here is to define a generic display function
;;; that is called on the frame, the pane AND the view,
;;; whereas the standard CLIM display functions are called
;;; only on the frame and the pane.
(defgeneric display-pane-with-view (frame pane view))
;;; this is the display function that is called in each iteration
;;; of the CLIM command loop. We simply call our own, more elaborate
;;; display function with the default view of the pane.
(defun display-main-pane (frame pane)
(display-pane-with-view frame pane (stream-default-view pane)))
;;; now we can start writing methods on our own display function
;;; for different views. This one displays the data each member
;;; on a line of its own.
(defmethod display-pane-with-view (frame pane (view members-view))
(loop for member in (members frame)
do (with-output-as-presentation
(pane member 'person)
(format pane "~a, ~a, ~a, ~a~%"
(membership-number member)
(last-name member)
(first-name member)
(address member)))))
;;; this CLIM view is used to display the information about
;;; a single person. It has a slot that indicates what person
;;; we want to view.
(defclass person-view (view)
((%person :initarg :person :reader person)))
;;; this method on our own display function shows the detailed
;;; information of a single member.
(defmethod display-pane-with-view (frame pane (view person-view))
(let ((person (person view)))
(format pane "Last name: ~a~%First Name: ~a~%Address: ~a~%Membership Number: ~a~%"
(last-name person)
(first-name person)
(address person)
(membership-number person))))
;;; entry point to start our applciation
(defun views-example ()
(run-frame-top-level (make-application-frame 'views)))
;;; command to quit the application
(define-views-command (com-quit :name t) ()
(frame-exit *application-frame*))
;;; command to switch the default view of the application pane
;;; (which is the value of *standard-output*) to the one that
;;; shows a member per line.
(define-views-command (com-show-all :name t) ()
(setf (stream-default-view *standard-output*) *members-view*))
;;; command to switch to a view that displays a single member.
;;; this command takes as an argument the person to display.
;;; In this application, the only way to satisfy the demand for
;;; the argument is to click on a line of the members view. In
;;; more elaborate application, you might be able to type a
;;; textual representation (using completion) of the person.
(define-views-command (com-show-person :name t) ((person 'person))
(setf (stream-default-view *standard-output*)
(make-instance 'person-view :person person)))
The example shows a stripped-down example of a simple database of members of some organization.
The main trick used in this example is the display-main-pane
function that is declared to be the display function of the main pane in
the application frame. The display-main-pane function trampolines
to a generic function called display-pane-with-view, and which takes
an additional argument compared to the display functions of CLIM panes.
This additional argument is of type view which allows us to dispatch
not only on the type of frame and the type of pane, but also on the type
of the current default view. In this example the view argument is
simply taken from the default view of the pane.
A possibility that is not obvious from reading the CLIM specification
is to have views that contain additional slots. Our example defines two
subclasses of the CLIM view class, namely
members-view and person-view.
The first one of these does not contain any additional slots, and is
used when a global view of the members of our organization is wanted.
Since no instance-specific data is required in this view, we follow the
idea of the examples of the CLIM specification to instantiate a
singleton of this class and store that singleton in the
stream-default-view of our main pane whenever a global view of our
organization is required.
The person-view class, on the other hand, is used when we want a
closer view of a single member of the organization. This class
therefore contains an additional slot which holds the particular person
instance we are interested in. The method on display-pane-with-view
that specializes on person-view displays the data of the particular
person that is contained in the view.
To switch between the views, we provide two commands. The command
com-show-all simply changes the default view of the main pane to be
the singleton instance of the members-view class. The command
com-show-person is more complicated. It takes an argument of type
person, creates an instance of the person-view class initialized
with the person that was passed as an argument, and stores the instance
as the default view of the main pane.
A command table is an object that is used to determine what commands are available in a particular context and the ways in which commands can be executed.
Simple applications do not manage command tables explicitly. A default
command table is created as a result of a call to the macro
define-application-frame and that command table has the same name as
the application frame.
Each command table has a name and that CLIM manages a global namespace for command tables.
This function returns the command table with the name name. If
there is no command table with that name, then what happens depends on
the value of errorp. If errorp is true, then an
error of type command-table-not-found is signaled. If errorp
is false, otherwise nil is returned.
An active gadget is available for input. For most gadgets, this means processing events when active and ignoring events when not active.
The exact definition of arming and disarming varies between kinds of gadgets, but typically a gadget becomes armed when the pointer is moved into its region.
TODO
A mirror of a sheet which is not shared with any of the
ancestors of the sheet. All grafted McCLIM sheets have mirrors, but not
all have direct mirrors. A McCLIM sheet that does not have a direct
mirror uses the direct mirror of its first ancestor having a direct
mirror for graphics output. Asking for the direct mirror of a sheet
that does not have a direct mirror returns nil.
Whether a McCLIM sheet has a direct mirror or not, is decided by the
frame manager. Some frame managers may only allow for the graft to be a
mirrored sheet. Even frame managers that allow hierarchical
mirrors may decide not to allocate a direct mirror for a particular
sheet. Although sheets with a direct mirror must be instances of the
class mirrored-sheet-mixin, whether a McCLIM sheet has a
direct mirror or not is not determined statically by the class of a
sheet, but dynamically by the frame manager.
An object that contains the state information required for producing output on a particular sheet.
A transformation that converts the coordinates presented to the drawing functions to the medium’s coordinate system. The identity transformation by default.
A device window such as an X11 window that parallels a sheet in the CLIM sheet hierarchy. A sheet having such a direct mirror is called a mirrored sheet. When drawing functions are called on a mirrored sheet, they are forwarded to the host windowing system as drawing commands on the mirror.
CLIM sheets that are not mirrored must be descendents (direct or indirect) of a mirrored sheet, which will then be the sheet that receives the drawing commands.
A sheet in the CLIM sheet hiearchy that has a direct
parallel (called the direct mirror) in the host
windowing system. A mirrored sheet is always an instance of the class
mirrored-sheet-mixin, but instances of that class are not
necessarily mirrored sheets. The sheet is called a mirrored sheet only
if it currently has a direct mirror. There may be several reasons for
an instance of that class not to currently have a direct mirror. One is
that the sheet is not grafted. Only grafted sheets can have
mirrors. Another one is that the frame manager responsible for
the look and feel of the sheet hierarchy may decide that it is
inappropriate for the sheet to have a direct mirror, for instance if the
underlying windowing system does not allow nested windows inside an
application, or that it would simply be a better use of resources not to
create a direct mirror for the sheet. An example of the last example
would be a stream pane inside a the viewport of a
scroller pane. The graphics objects (usually text) that appear
in a stream pane can have very large coordinate values, simply because
there are many lines of text. Should the stream pane be mirrored, the
coordinate values used on the mirror may easily go beyond what the
underlying windowing system accepts. X11, for instance, can not handle
coordinates greater than 64k (16 bit unsigned integer). By not having a
direct mirror for the stream pane, the coordinates will be translated to
those of the (not necessarily direct) mirror of the viewport
before being submitted to the windowing system, which gives more
reasonable coordinate values.
It is important to realize the implications of this terminology. A
mirrored sheet is therefore not a sheet that has a mirror. All grafted
sheets have mirrors. For the sheet to be a mirrored sheet it has to
have a direct mirror. Also, a call to sheet-mirror
returns a mirror for all grafted sheets, whether the sheet is a mirrored
sheet or not. A call to sheet-direct-mirror, on the other
hand, returns nil if the sheet is not a mirrored sheet.
The transformation that transforms coordinates in the coordinate system of a mirror (i.e. the native coordinates of the mirror) to native coordinates of its parent in the underlying windowing system. On most systems, including X, this transformation will be a simple translation.
Each mirror has a coordinate system called the native coordinate system. Usually, the native coordinate system of a mirror has its origin in the upper-left corner of the mirror, the x-axis grows to the right and the y-axis downwards. The unit is usually pixels, but the frame manager can impose a native coordinate system with other units, such as millimeters.
The native coordinate system of a sheet is the native coordinate system of its mirror (direct or not). Thus, a sheet without a direct mirror has the same native coordinate system as its parent. To obtain native coordinates of the parent of a mirror, use the mirror transformation.
The native region of a sheet is the intersection of its region and the sheet region of all of its parents, expressed in the native coordinates of the sheet.
TODO
A bounded area of an otherwise infinte drawing plane that is visible unless it is covered by other visible areas.
The coordinate system of coordinates obtained by application of the user transformation.
The region of a sheet determines the visible part of the drawing plane. The dimensions of the sheet region are given in sheet coordinates. The location of the visible part of a sheet within its parent sheet is determined by a combination of the sheet transformation and the position of the sheet region.
For instance, assuming that the sheet region is a rectangle with its upper-left corner at (2, 1) and that the sheet transformation is a simple translation (3, 2). Then the origin of the sheet coordinate system is at the point (3, 2) within the sheet coordinate system of its parent sheet. The origin of its coordinate system is not visible, however, because the visible region has its upper-left corner at (2, 1) in the sheet coordinate system. Thus, the visible part will be a rectangle whose upper-left corner is at (5, 3) in the sheet coordinate system of the parent sheet.
Panes and gadgets alter the region and sheet transformation of the underlying sheets (panes and gadgets are special kinds of sheets) to obtain effects such as scrolling, zooming, coordinate system transformations, etc.
The transformation used to transform sheet coordinates of a sheet to sheet coordinates of its parent sheet. The sheet transformation determines the position, shape, etc. of a sheet within the coordinate system of its parent.
Panes and gadgets alter the transformation and sheet region of the underlying sheets (panes and gadgets are special kinds of sheets) to obtain effects such as scrolling, zooming, coordinate system transformations, etc.
TODO
A clipping region used to limit the effect of
drawing functions. The user clipping region is stored
in the medium. It can be altered either by updating the
medium, or by passing a value for the :clipping-region
drawing option to a drawing function.
The coordinate system of coordinates passed to the drawing functions.
A transformation used to transform user coordinates into sheet coordinates. The user transformation is stored in the medium. It can be altered either by updating the medium, or by passing a value for the :transformation drawing option to a drawing function.
TODO
CLIM uses a number of different coordinate systems and transformations to transform coordinates between them.
The coordinate system used for the arguments of drawing functions is called the user coordinate system, and coordinate values expressed in the user coordinate system are known as user coordinates.
Each sheet has its own coordinate system called the sheet coordinate system, and positions expressed in this coordinate system are said to be expressed in sheet coordinates. User coordinates are translated to sheet coordinates by means of the user transformation also called the medium transformation. This transformation is stored in the medium used for drawing. The medium transformation can be composed temporarily with a transformation given as an explicit argument to a drawing function. In that case, the user transformation is temporarily modified for the duration of the drawing.
Before drawing can occur, coordinates in the sheet coordinate system must be transformed to native coordinates, which are coordinates of the coordinate system of the native windowing system. The transformation responsible for computing native coordinates from sheet coordinates is called the native transformation. Notice that each sheet potentially has its own native coordinate system, so that the native transformation is specific for each sheet. Another way of putting it is that each sheet has a mirror, which is a window in the underlying windowing system. If the sheet has its own mirror, it is the direct mirror of the sheet. Otherwise its mirror is the direct mirror of one of its ancestors. In any case, the native transformation of the sheet determines how sheet coordinates are to be translated to the coordinates of that mirror, and the native coordinate system of the sheet is that of its mirror.
The composition of the user transformation and the native transformation is called the device transformation. It allows drawing functions to transform coordinates only once before obtaining native coordinates.
Sometimes, it is useful to express coordinates of a sheet in the coordinate of its parent. The transformation responsible for that is called the sheet transformation.
Drawing functions are typically called with a sheet as an argument.
A sheet often, but not always, corresponds to a window in the underlying windowing system.
CLIM sheets are organized into a hierarchy. Each sheet has a sheet transformation and a sheet region. The sheet tranformation determines how coordinates in the sheet’s own coordinate system get translated into coordinates in the coordinate system of its parent. The sheet region determines the potentially visible area of the otherwise infinite drawing plane of the sheet. The sheet region is given in the coordinate system of the sheet.
In McCLIM, every grafted sheet has a native transformation. The native transformation is used by drawing functions to translate sheet coordinates to native coordinates, so that drawing can occur on the (not necessarily immediate) mirror of the sheet. It would therefore be enough for sheets that support the output protocol to have a native transformation. However, it is easier to generalize it to all sheets, in order to simplify the programming of the computation of the native transformation. Thus, in McCLIM, even sheets that are mute for output have a native transformation.
In McCLIM, every grafted sheet also has a native region. The native region is intersection the sheet region and the region of all of its ancestors, except that the native region is given in native coordinates, i.e. the coordinates obtained after the application of the native transformation of the sheet.
A typical windowing system provides a hierarchy of rectangular areas called windows. When a drawing functions is called to draw an object (such as a line or a circle) in a window of such a hierarchy, the arguments to the drawing function will include at least the window and a number of coordinates relative to (usually) the upper left corner of the window.
To translate such a request to the actual altering of pixel values in the video memory, the windowing system must translate the coordinates given as argument to the drawing functions into coordinates relative to the upper left corner of the entire screen. This is done by a composition of translation transformations applied to the initial coordinates. These transformations correspond to the position of each window in the coordinate system of its parent.
Thus a window in such a system is really just some values indicating its height, its width, and its position in the coordinate system of its parent, and of course information about background and foreground colors and such.
CLIM generalizes the concept of a hierarchy of window in a windowing system in several different ways. A window in a windowing system generalizes to a sheet in CLIM. More precisely, a window in a windowing system generalizes to the sheet region of a sheet. A CLIM sheet is an abstract concept with an infinite drawing plane and the region of the sheet is the potentially visible part of that drawing plane.
CLIM sheet regions don’t have to be rectangular the way windows in most windowing systems have to be. Thus, the width and the height of a window in a windowing system generalizes to an arbitrary region in CLIM. A CLIM region is simply a set of mathematical points in a plane. CLIM allows this set to be described as a combination (union, intersection, difference) of elementary regions made up of rectangles, polygons and ellipses.
Even rectangular regions in CLIM are generalizations of the width+height concept of windows in most windowing systems. While the upper left corner of a window in a typical windowing system has coordinates (0,0), that is not necessarily the case of a CLIM region. CLIM uses that generalization to implement various ways of scrolling the contents of a sheet. To see that, imagine just a slight generalization of the width+height concept of a windowing system into a rectangular region with x+y+width+height. Don’t confuse the x and y here with the position of a window within its parent, they are different. Instead, imagine that the rectangular region is a hole into the (infinite) drawing plane defined by all possible coordinates that can be given to drawing functions. If graphical objects appear in the window with respect to the origin of some coordinate system, and the upper-left corner of the window has coordinates (x,y) in that coordinate system, then changing x and y will have the effect of scrolling.
CLIM sheets also generalize windows in that a window typically has pixels with integer-value coordinates. CLIM sheets, on the other hand, have infinte resolution. Drawing functions accept non-integer coordinate values which are only translated into integers just before the physical rendering on the screen.
The x and y positions of a window in the coordinate system of its parent window in a typical windowing system is a translation transformation that takes coordinates in a window and transform them into coordinates in the parent window. CLIM generalizes this concepts to arbitrary affine transformations (combinations of translations, rotations, and scalings). This generalization makes it possible for points in a sheet to be not only translated compared to the parent sheet, but also rotated and scaled (including negative scaling, giving mirror images). A typical use for scaling would be for a sheet to be a zoomed version of its parent, or for a sheet to have its y-coordinate go the opposite direction from that of its parent.
When the shapes of, and relationship between sheets are as simple as those of a typical windowing system, each sheet typically has an associated window in the underlying windowing system. In that case, drawing on a sheet translates in a relativly straightforward way into drawing on the corresponding window. CLIM sheets that have associated windows in the underlying windowing system are called mirrored sheets and the system-dependent window object is called the mirror. When shapes and relationships are more complicated, CLIM uses its own transformations to transform coordinates from a sheet to its parent and to its grandparent, etc., until a mirrored sheet is found. To the user of CLIM, the net effect is to have a windowing system with more general shapes of, and relationships between windows.
Panes are subclasses of sheets. Some panes are layout panes that determine the size and position of its children according to rules specific to each particular type of layout pane. Examples of layout panes are vertical and horizontal boxes, tables etc.
According to the CLIM specification, all CLIM panes are rectangular objects. For McCLIM, we interpret that phrase to mean that:
Of course, the specification is unclear here. Panes are subclasses of sheets, and sheets don’t have a shape per-se. Their regions may have a shape, but the sheet itself certainly does not.
The phrase in the specification could mean that the sheet-region of a pane is a subclass of the region class rectangle. But that would not exclude the possibility that the region of a pane would be some non-rectangular shape in the native coordinate system. For that to happen, it would be enough that the sheet-transformation of some ancestor of the pane contain a rotation component. In that case, the layout protocol would be insufficient in its current version.
McCLIM panes have the following additional restrictions:
Thus, the panes form a prefix in the hierarchy of sheets. It is an error for a non-pane to adopt a pane.
Notice that the native transformation of a pane need not be the identity transformation. If the pane is not mirrored, then its native transformation is probably a translation of that of its parent.
Notice also that the native transformation of a pane need not be the composition of the identity transformation and a translation. That would be the case only of the native transformation of the top level sheet is the identity transformation, but that need not be the case. It is possible for the frame manager to impose a coordinate system in (say) millimeters as opposed to pixels. The native transformation of the top level sheet of such a frame manager is a scaling with coefficients other than 1.
There is some confusion about the options that are allowed when a pane
is created with make-pane. Some parts of the specification suggest
that stream panes such as application panes and interactor panes can be
created using make-pane and an option :scroll-bars. Since
these application panes do not in themselves contain any scroll bars,
using that option results in a pane hierarchy being created with the
topmost pane being a pane of type scroller-pane.
As far as McCLIM is concerned, this option to make-pane is
obsolete. 1
This does not apply for using this option together with the equivalent
keyword, i.e., :application or :interactor, in the
:panes section of define-application-frame, because they
are created by the function make-clim-stream-pane which does
specify this argument.
Instead, we recommend following the examples of the specification, where
scroll bars are added in the layouts section of
define-application-frame.
When specification talks about panes in a fashion implying some order
(i.e “first application-pane”) McCLIM assumes order of definition,
not order of appearing in layout. Particularly that means, that if one
pane is put before another in :panes option, then it precedes
it. It is relevant to frame-standard-output (therefore binding of
*standard-output*) and other similar functions.
Every pane class accepts the initialization argument :name the
value of which is typically a symbol in the package defined by the
application. The generic function pane-name returns the value of
this initialization argument. There is no standard way of changing the
name of an existing pane. Using the function
reinitialize-instance may not have the desired effect, since the
application frame may create a dictionary mapping names to panes, and
there is no way to invalidate the contents of such a potential
dictionary.
The function find-pane-named searches the pane hierarchy of the
application frame, consulting the names of each pane until a matching
name is found. The CLIM specification does not say what happens if a
name is given that does not correspond to any pane. McCLIM returns
nil in that case. If pane names are not unique, it is
unspecified which of several panes is returned by a call to this
function.
If the advice of Creating panes is followed, then the name given
in the :panes option of the macro define-application-frame
will always be the name of the top-level pane returned by the
body following the pane name.
If that advice is not followed, then the name given to a pane in the
:panes option of the macro define-application-frame may or
may not become the name of the pane that is constructed by the
body that follows the name. Recall that the syntax of the
expression that defines a pane in the :panes option is
(name . body). Currently, McCLIM does the following:
body creates a pane by using a keyword, or by using an
explicitly mentioned call to make-pane, then the name is given to
the pane of the type explicitly mentioned, even when the option
:scroll-bars is given.
body creates a pane by calling some arbitrary form other
than a call to make-pane, then the name is given to the topmost
pane returned by the evaluation of that form.
We reserve the right to modify this behavior in the future. Application code should respect the advice given in Creating panes.
Recall that redisplay refers to the creation of the output history of a pane. There are two typical ways of creating this output history:
application-pane is typically used, and the default value of the
:display-time option is used, which means that some kind of
application-supplied display function is executed at the end of
each iteration of the command loop. In this situation, the output
history is either recomputed from scratch in each iteration, or the
programmer can use the incremental redisplay facility to reuse
some of the existing output records in the history.
:display-time option is either going to be t or nil. With
both of these options, the output history is maintained intact after
each iteration of the command loop. Instead, when user actions are
issued, more output records are simply added to the existing output
history.
For the second possibility, the pane is never redisplayed. Instead, the action of updating the pane contents is referred to as replaying the output history. The remainder of this section is entirely dedicated to the redisplay action.
It is occasionally necessary for the application to redisplay a pane explicitly, as opposed to letting the command loop handle it. For example, if the application data structure is updated in some way, but this update is not the result of a command, then after such an update, the redisplay function needs to be executed explicitly. Such an update could be the result of a timer event, or of communication with an external process.
Calling this generic function causes an immediate redisplay of pane. When force-p is false and the incremental redisplay facility is in use for pane, then output records are reused as appropriate. Supplying a true value for force-p causes the entire output history to be recomputed from scratch.
Notice that this function does not check whether the pane has been
marked to need redisplay, as indicated by a call to the generic
function pane-needs-redisplay. It results in an unconditional
redisplay of pane.
Calling this generic function causes an immediate redisplay of all the
panes of frame that are visible in the current layout. This
function simply calls redisplay-frame-pane for each visible pane
of frame.
Again, notice that no check is being made as to whether the visible
panes have been marked as needing redisplay. This function calls
redisplay-frame-pane unconditionally for each visible pane, and
since redisplay-frame-pane redisplays the pane unconditionally,
it follows that all visible panes are unconditionally redisplayed.
Also notice that the implication of this unconditional behavior on the
part of redisplay-frame-panes means that this is not the
function called by the standard command loop. The standard command
loop only redisplays panes that have been marked as needing redisplay,
though when the value of the :display-time option is
:command-loop for some pane, then it is always marked as needing
redisplay in each iteration of the command loop.
There is a set of fundamental rules of CLIM dividing responsibility between a parent pane and a child pane, with respect to the size and position of the region of the child and the sheet transformation of the child. This set of rules is called the layout protocol.
The layout protocol is executed in two phases. The first phase is called the space compostion phase, and the second phase is called the space allocation phase.
The space composition is accomplished by the generic function
compose-space. When applied to a pane, compose-space
returns an object of type space-requirement indicating the needs
of the pane in terms of preferred size, minimum size and maximum size.
The phase starts when compose-space is applied to the top-level pane of
the application frame. That pane in turn may ask its children for their
space requirements, and so on until the leaves are reached. When the
top-level pane has computed its space requirments, it asks the system
for that much space. A conforming window manager should respect the
request (space wanted, min space, max space) and allocate a top-level
window of an acceptable size. The space given by the system must then
be distributed among the panes in the hierarchy
space-allocation.
Each type of pane is responsible for a different method on compose-space. Leaf panes such as labelled gadgets may compute space requirements based on the size and the text-style of the label. Other panes such as the vbox layout pane compute the space as a combination of the space requirements of their children. The result of such a query (in the form of a space-requirement object) is stored in the pane for later use, and is only changed as a result of a call to note-space-requirement-changed.
Most composite panes can be given explicit values for the values
of :width, :min-width, :max-width,
:height, :min-height, and :max-height
options. If such arguments are not given (effectively making these
values nil), a general method is used, such as computing from children
or, for leaf panes with no such reasonable default rule, a fixed value
is given. If such arguments are given, their values are used instead.
Notice that one of :height and :width might be
given, applying the rule only in one of the dimensions.
Subsequent calls to compose-space with the same arguments are assumed to return the same space-requirement object, unless a call to note-space-requirement-changed has been called in between.
When allocate-space is called on a pane P, it must compare the space-requirement of the children of P to the available space, in order to distribute it in the most preferable way. In order to avoid a second recursive invokation of compose-space at this point, we store the result of the previous call to compose-space in each pane.
To handle this situtation and also explicitly given size options, we use
an :around method on compose-space. The
:around method will call the primary method only if necessary
(i.e., (eq (slot-value pane 'space-requirement) nil)), and store
the result of the call to the primary method in the
space-requirement slot.
We then compute the space requirement of the pane as follows:
(setf (space-requirement-width ...) (or explicit-width
(space-requirement-width request)) ...
(space-requirement-max-width ...) (or explicit-max-width
explicit-width (space-requirement-max-width request)) ...)
When the call to the primary method is not necessary we simply return the stored value.
The spacer-pane is an exception to the rule indicated above. The
explicit size you can give for this pane should represent the margin
size. So its primary method should only call compose on the child. And
the around method will compute the explicit sizes for it from the space
requirement of the child and for the values given for the surrounding
space.
The purpose of the change-space notification protocol is to force a recalculation of the space occupied by potentially each pane in the pane hierarchy. The protocol is triggerred by a call to note-space-requirement-changed on a pane P. In McCLIM, we must therefore invalidate the stored space-requirement value and re-invoke compose-space on P. Finally, the parent of P must be notified recursively.
This process would be repeated for all the panes on a path from P to the top-level pane, if it weren’t for the fact that some panes compute their space requirements independently of those of their children. Thus, we stop calling note-space-requirement-changed in the following cases:
restraining-pane,
top-level-sheet-pane, or
:width and
:height
In either of those cases, allocate-space is called.
Issue: This description is wrong. note-space-requirement-change is called by CLIM after the space requirements has changed. Application programmer should call change-space-requirements to indicate that compose-space may now return something different from the previous invocation and/or to update user space requirements options. This may (but doesn’t have to) trigger layout-protocol. Macro changing-space-requirements should be described too. — JD
The CLIM specification says that the output record should capture its clipping region. However this is not a feasible solution because when the output record is moved, it is impossible to tell whether the clip should be moved with it or not.
For example, we may want to clip all output to happen in a heart-shaped region - this clip should never move. Then we format a list of items and each item is clipped with a circle - these clips should be moved with their corresponding records.
The only solution that embraces both cases is to treat the clipping region as a compound output record. Then whether it is moved or not depends on whether it is a child or a parent of the moved output record.
If line-style-joint-shape is :miter and the angle between
two consequent lines is less than the values return by
medium-miter-limit, :bevel is used instead.
Returns the thickness in device units of a line, rendered on medium with the style line-style.
Return a dash length or a sequence of dash lengths device units for a dashed line, rendered on medium with the style line-style.
Additional protocol generic function. parent may be an output
record or nil.
Displays the output captured by record on the stream, exactly as it was originally captured. The current user transformation, line style, text style, ink and clipping region of stream are all ignored. Instead, these are gotten from the output record.
Only those records that overlap region are displayed.
Maps over all of the children of record, calling function on
each one. It is a function of one or more arguments and called with all
of function-args as apply arguments.
Maps over all of the children of record that contain the point at
(x,y), calling function on each one. function is
a function of one or more arguments, the first argument being the record
containing the point. function is also called with all of
function-args as apply arguments.
If there are multiple records that contain the point,
map-over-output-records-containing-position hits the most
recently inserted record first and the least recently inserted record
last. Otherwise, the order in which the records are traversed is
unspecified.
Maps over all of the children of record that overlap the
region, calling function on each one. function is a
function of one or more arguments, the first argument being the record
overlapping the region. function is also called with all of
function-args as apply arguments.
If there are multiple records that overlap the region and that overlap
each other, map-over-output-records-overlapping-region hits the least
recently inserted record first and the most recently inserted record
last. Otherwise, the order in which the records are traversed is
unspecified.
Class precedence list: standard-output-recording-stream, output-recording-stream, standard-object, slot-object, t
Slots:
local-record-p
This flag is used for dealing with streams outputting strings char-by-char.
This class is mixed into some other stream class to add output recording facilities. It is not instantiable.
Sets record to be the parent of child.
If child is a child of record, sets the parent of
child to nil.
Sets the parent of all children of record to nil.
Same as in CLIM 2.2 (missing constructor added).
Creates a new output record of type record-type and then captures
the output of body into the new output record, and inserts the new
record into the current "open" output record associated with
stream. If record is supplied, it is the name of a variable
that will be lexically bound to the new output record inside the
body. initargs are CLOS initargs that are passed to
make-instance when the new output record is created. It returns
the created output record. The stream argument is a symbol that
is bound to an output recording stream. If it is t,
*standard-output* is used.
Creates a new output record of type record-type and then captures
the output of body into the new output record. The cursor
position of stream is initially bound to (0,0). If record
is supplied, it is the name of a variable that will be lexically bound
to the new output record inside the body. initargs are CLOS
initargs that are passed to make-instance when the new output
record is created. It returns the created output record. The
stream argument is a symbol that is bound to an output recording
stream. If it is t, *standard-output* is used.
By default command tables inherit from global-command-table. A
command table inherits from no command table if nil is passed as
an explicit argument to inherit-from.
CLIM applications are most often structured around the command loop. The various steps that such an application follow are:
This way of structuring an application is very simple. The resulting
code is very easy to understand, and the relationship between the code
of a redisplay function and the output it produces is usually obvious.
The concept of output records storing the output in the application
pane is completely hidden, and instead output is produced using
textual or graphic drawing functions, or more often produced
indirectly through the use of present or
with-output-as-presentation.
However, if the model contains a large number of objects, then this simple way of structuring an application may penalize performance. In most libraries for creating graphic user interfaces, the application programmer must then rewrite the code for manipulating the model, and especially for incrementally altering the output according to the modification of the model resulting from the execution of a command.
In CLIM, a different mechanism is provided called incremental redisplay. This mechanism allows the user to preserve the simple logic of the display function with only minor modifications while still being able to benefit in terms of performance.
In the McCLIM codebase, we try to follow certain coding conventions for consistency and better maintainability. We default to conventions mentioned in the specification and to the usual conventions. The conventions described in the following sections cover aspects not discussed in these documents.
In addition to the packages mentioned in the specification, McCLIM
defines the packages clim-extensions and clim-backend.
Both are used by the clim-internals package to avoid conflicts.
clim-extensions (which has the nickname clime) exports all
extensions provided by McCLIM. While each extension may define its own
package for implementation purposes, application programmers should
access symbols only via the clim-extensions package.
clim-backend (which has the nickname climb) exports
symbols which are intended for use by backend writers. It uses the
packages clim and clim-extensions.
Examples and demos which are distributed with McCLIM are loaded from
a separate system named clim-examples. Each example should be
put either directly in the package clim-demo or live in its own
package the name of which should be start with clim-demo., for
instance clim-demo.foobar-example.
CLIM specifies the protocol to set the pointer cursor but does not specify what are its valid values.
McCLIM specifies an obligaory set of system cursors designated by a symbol. They are listed in the table below.
Backends may implement other valid cursor values. Backends are also
encouraged to support designs of the class image-pattern.
:defaultNormal cursor (i.e an arrow).
:promptThe element underneath the cursor is editable (i.e an i-beam).
:buttonThe element underneath the cursor is actionable (i.e a hand).
:busyCursor signaling that the application is busy (i.e a hourglass).
:not-allowedCursor signaling that the action is not allowed.
:positionPrecise cursor for selecting a point (i.e a crosshair).
:moveThe element underneath the cursor is being moved (i.e four arrows).
:arrow-weThe element underneath the cursor may be dragged horizontally.
:arrow-nsThe element underneath the cursor may be dragged vertically.
:grabThe element underneath the cursor is being grabbed (i.e a clenched hand).
:helpThe element underneath the cursor is inspectable (i.e a question mark).
Backend provides platform specific API for low level drawing operations, getting events, managing window geometry properties and providing native look-and-feel to the application.
There are three types of backends:
This type doesn’t implement any kind of events and allows only drawing on it. A good example of it is the See PostScript backend which is part of CLIM II specification.
OpenGL, X, or HTML 5 canvas are resources which provide only drawing and event handling primitives. In this case we need to wrap their APIs for McCLIM to use. McCLIM will then use these drawing and windowing primitives to implement portable widgets.
Native backend is based on already complete GUI library which provides a rich set of widgets (for example Cocoa or Win32 API). Additionally to the things needed to be implement in the first two cases, we can also map these native look and feel widgets in McCLIM.
The clim-null backend can be used as a template to start with a
new backend. If the underlying library you write backend for manages
window hierarchy, positioning and events, it is possible to base new
pane types on mirrored-sheet-mixin class which provides native
handles into native windowing system. Mirrored and “native lisp”
sheets may be freely mixed in the pane hierarchy.
NEW CLASS FOR BACKEND `FOO' --------------------------- foo-frame-manager foo-native-frame-manager (optional) foo-graft foo-port foo-medium foo-pointer
EVENT HANDLING (in port.lisp) ----------------------------- ;;; Originally in CLIM-INTERNALS synthesize-pointer-motion-event
GRAFT (in grafts.lisp) ----------------------------------- ;;; Originally in CLIM graft ; root window/screen graft-height ; screen height graft-width ; screen width
MEDIUM DRAWING (in medium.lisp) ------------------------------- ;;; Originally in CLIM medium-draw-ellipse* medium-draw-line* medium-draw-lines* medium-draw-point* medium-draw-points* medium-draw-polygon* medium-draw-rectangle* medium-draw-rectangles* medium-draw-text*
MEDIUM OPERATIONS (in medium.lisp) ---------------------------------- ;;; Originally in CLIM make-medium ; make medium for a given sheet medium-beep medium-buffering-output-p medium-clear-area medium-copy-area medium-finish-output medium-force-output medium-line-style medium-text-style
PORT (BRIDGE) TO GUI (A SERVER LIKE) ------------------------------------ ;;; Originally in CLIM destroy-port ;;; Originally in CLIM-INTERNALS enable-mirror disable-mirror set-mirror-name set-mirror-icon set-mirror-geometry port-force-output set-sheet-pointer-cursor
FRAME MANAGER, PANES AND GADGETS -------------------------------- ;;; Originally in CLIM ;; in frame-manager.lisp make-pane-1 note-space-requirements-changed adopt-frame ;; in port.lisp or pane.lisp/gadget.lisp allocate-space destroy-mirror handle-repaint realize-mirror
POINTER (port.lisp or pointer.lisp) ----------------------------------- ;;; Originally in CLIM pointer-button-state pointer-position
TEXT SIZE (medium.lisp) ----------------------- ;;; Originally in CLIM-INTERNALS text-style-character-width ;;; Originally in CLIM text-size text-style-ascent text-style-descent text-style-height text-style-mapping text-style-width
A backend implementation may register additional output destination
types for the :output-destination keyword parameter accepted by
some commands. Doing so allows the command to be invoked with
*standard-output* bound to a stream provided by the backend that
redirects the command’s output to a non-default destination such as a
vector graphics or raster image file.
To support this protocol in a backend, three things are required:
clim-backend:output-destination or one of its subclasses).
Call continuation (with no arguments) with *standard-output*
rebound according to destination.
Register class-name as an additional output destination type under
the name name. class-name must name a subclass of
clim-backend:output-destination. A method on
clim-backend:invoke-with-standard-output must be applicable
to an instance of class-name.
MISC ---- ;;; Originally in CLIM-EXTENSIONS medium-miter-limit ; determine a draw for miter < sina/2
NO LONGER NEEDED IN BACKEND --------------------------- queue-callback ; moved to clim-core medium-clipping- ; moved to clim-basic
By default when the application frame class is redefined with the
macro clim:define-application-frame panes and layouts of the
existing instances are not changed. To update the frame programmer
must call the function reinitialize-instance. The function may
be called on a running frame ouside of its event loop.
McCLIM extends the macro clim:define-application-frame to
update existing frames automatically when the new option
:reinitialize-frames is not nil. Instances are updated
by defining a method update-instance-for-redefined-class :after
and making all instances obsolete. Then a slot of each adopted frame
is read to trigger the update without delay.
nil (a default) then nothing happens.
t then all instances are updated by calling
on them the function reinitialize-instance (running frames are
updated immedietely and the reset whenever the implementation chose to
call the function update-instance-for-redefined-class).
reinitialize-instance.
The following initargs when present in the class definition are
applied to reinitialize-instance: :current-layout,
:pretty-name, :icon, :command-table,
:menu-bar and :pointer-documentation. For example:
(define-application-frame example-frame ()
((xxx :initarg :xxx :reader xxx))
(:panes (i :interactor)
(y :push-button :label "BAMasdf DIM DOM"))
(:layouts (l1 (vertically () i y))
(l2 (vertically () y)))
(:pointer-documentation t)
(:current-layout l2)
(:pretty-name "hellobo")
(:reinitialize-frames :pretty-name "foo" :xxx 15))
After compiling the above form all instances of the class example-frame will be reinitialized with:
(reinitialize-instance instance :pretty-name "foo"
:xxx 15
:current-layout 'l2
:pretty-name "hellobo"
:pointer-documentation T)
According to the CLIM specification, an icon can be defined for an
application frame class using the :icon initarg of the
define-application-frame macro. McCLIM extends this design to
top-level sheets and adds
Return the icon or icons of sheet.
These icons are typically used by window managers to represent windows that are not currently visible or added to other representations of windows to make them more easily recognizable.
Set icon or icons of sheet to new-value.
new-value must be a clim-extensions:image-pattern or a
sequence of those. If a sequence is supplied, the window manager is
instructed to prefer the first element, if possible. Some window
managers select different icons for different purposes based on the icon
sizes.
Return the icon or icons of frame.
The return value is either a clim-extensions:image-pattern or
sequence of those. These icons are typically used – via the top-level
sheet of frame – by window managers to represent windows that are
not currently visible or added to other representations of windows to
make them more easily recognizable.
Set icon or icons of frame to new-value.
new-value must be a clim-extensions:image-pattern or a
sequence of those. If a sequence is supplied, the window manager is
instructed to prefer the first element, if possible. Some window
managers select different icons for different purposes based on the icon
sizes. This function also sets new-value as the icon(s) of the
top-level sheet of frame.
Notify client that the pretty name of frame, managed by frame-manager, changed to new-icon.
frame-manager can be nil if frame is not owned by a
frame manager at the time of the change.
According to the CLIM specification, a frame has a name and a pretty name the latter of which can be changed. McCLIM extends this design to top-level sheets in form of the following protocol:
Return the name of sheet. The returned name is a symbol and does not change. For sheets which are also panes, the returned name is identical to the pane name.
Return the pretty name of sheet. The returned name is a string and may change over time. The pretty name usually corresponds to the title of the associated window.
Set sheet’s pretty name to new-value. new-value must be a string. Changing the pretty name of sheet usually changes the title of the window associated with it.
The class clim-extensions:top-level-sheet-mixin can be added as a
superclass to sheet classes that implement the above protocol. Otherwise
default methods on clim-extensions:sheet-name and
clim-extensions:sheet-pretty-name return nil and "(Unnamed
sheet)" respectively.
Furthermore McCLIM provides a way for clients to be notified when the pretty name of a frame changes:
Notify client that the pretty name of frame, managed by frame-manager,
changed to NEW-NAME.
frame-manager can be NIL if frame is not owned by
a frame manager at the time of the change.
According to the CLIM specification, a frame may be shrunk the function shrink-frame. This operation disables a top-level sheet by minimizing it.
McCLIM extends this design to top-level sheets and ports in form of the following protocol:
Calling this method on a top-level sheet should disable the sheet and minimize the window associated with the sheet.
Calling this method on a port and on a mirrored sheet should minimize the window associated with the sheet.
When the command table is changed this may result in necessity to change the menu. Menu is managed by the frame manager so McCLIM adds the appropriate notification protocol.
McCLIM extension: Notify client that the command-table of FRAME, managed by FRAME-MANAGER, changed to NEW-COMMAND-TABLE.
standard-extended-output-stream has been extended by protocols
complementary to already defined ones. Some were clearly missing given
how it is specified - like margins and word wrap. Some are useful in
contemporary text displaying applications like text direction and
alignment.
Both functions return two values, x and y coordinates of the respective position. Initial position is where the cursor is placed on a fresh page, and the final position is where the cursor is placed right before the page ends. Coordinates depend on current margins and text alignment.
This function returns a region which corresponds to the stream page format. This region corresponds the stream margins.
Execute body in a dynamic environment where stream’s margins are augmented with left, right, top and bottom. Not all margins have to be specified. If they are not current margin values are taken as defaults.
Each margin must be in one of following formats: (:relative
space) or (:absolute space). space may be
specified as for :x-spacing and :y-spacing for horizontal
and vertical margins accordingly. If a margin is “absolute” then it
corresponds to its exact placement in stream coordinates. “relative”
margins are relative to the stream viewport region.
If the Boolean move-cursor is T then the cursor is left
where it was placed after the last operation. Otherwise upon completion
of body, the cursor position is restored to its previous value.
Programmers using clime:with-temporary-margins should begin
body with a call to the function
clim:stream-set-cursor-position which will set the cursor to
clime:page-initial-position.
The macro clim:filling-output behaves the same as before with a
few additions:
:after-line-break-subsequent is complementary to
:after-line-break-initially, it decides whether
:after-line-break is printed for lines after the first
break. It defaults to T.
:after-line-break-composed decides whether
after-line-break from the external filling-output should
be called as well (defaults to T).
:after-line-break may be a string or a function accepting two
arguments: a stream and a flag indicating whether it is a soft newline
or not. The function will be executed conditionally depending on values
of :after-line-break-initially and
:after-line-break-subsequent flags.
The macro preserves a text-style, ink and indentation from
state in which it was invoked. That means in particular that
indenting-output may be called from inside filling-output
and after-line-break will be printed without this indent.
CLIM specifies clim:blank-area as a special presentation type
that represents places at which no other presentation is applicable
given the current input context. The value of
clim:*null-presentation* is specified to a presentation instance
with presentation type clim:blank-area. The specification
implies that there is only one such instance. McCLIM slightly
deviates from this by
clim:blank-area.
clim:blank-area presentation
type: &key sheet region.
When the system constructs a presentation instance with presentation
type clim:blank-area for a pointer motion or click at position
(x,y) on sheet sheet:
sheet presentation type parameter is
sheet.
region presentation type parameter is a
clim:point instance with position (x,y).
The subtype relation for clim:blank-area presentation types with
supplied parameters is based on the sheet matching if supplied and the
region of the subtype being contained in the region of the supertype if
supplied.
The tab layout is a composite pane arranging its children so that exactly one child is visible at any time, with a row of buttons allowing the user to choose between them.
See also the tabdemo.lisp example code located under
Examples in the McCLIM distribution. It can be started using
(clim-demo:demodemo).
Class precedence list: tab-layout, sheet-multiple-child-mixin, basic-pane, sheet-parent-mixin, pane, standard-repainting-mixin, standard-sheet-input-mixin, sheet-transformation-mixin, basic-sheet, sheet, bounding-rectangle, standard-object, slot-object, t
The abstract tab layout pane is a composite pane arranging
its children so that exactly one child is visible at any time, with a row of
buttons allowing the user to choose between them. Use with-tab-layout to
define a tab layout and its children, or use the :pages argument
to specify its contents when creating it dynamically using make-pane.
Class precedence list: tab-layout-pane, tab-layout, sheet-multiple-child-mixin, basic-pane, sheet-parent-mixin, pane, standard-repainting-mixin, standard-sheet-input-mixin, sheet-transformation-mixin, basic-sheet, sheet, bounding-rectangle, standard-object, slot-object, t
A pure-lisp implementation of the tab-layout, this is
the generic implementation chosen by the CLX frame manager automatically.
Users should create panes for type tab-layout, not tab-layout-pane, so
that the frame manager can customize the implementation.
Class precedence list: tab-page, standard-object, slot-object, t
Instances of tab-page represent the pages in a tab-layout.
For each child pane, there is a tab-page providing the page’s
title and additional information about the child. Valid initialization
arguments are :title, :pane (required),
:presentation-type and :drawing-options (optional).
Return a tab-layout. Any keyword arguments, including its name, will be
passed to make-pane. Child pages of the tab-layout can be specified using
body, using lists of the form (title pane &key presentation-type
drawing-options enabled-callback). default-presentation-type will be passed
as :presentation-type to pane creation forms that specify no type themselves.
Return all tab-pages in tab-layout, in order
from left to right. Do not modify the resulting list destructively.
Use the setf function of the same name to assign a new list of pages.
The setf function will automatically add tabs for new page objects, remove
old pages, and reorder the pages to conform to the new list.
Return the tab-layout tab-page belongs to.
Return the title displayed in the tab for tab-page.
Use the setf function of the same name to set the title dynamically.
Return the CLIM pane page displays. See also
sheet-to-page, the reverse operation.
Return the type of the presentation used when tab-page’s header
gets clicked. Use the setf function of the same name to set the
presentation type dynamically. The default is tab-page.
Return the drawing options of tab-page’s header. Use the
setf function of the same name to set the drawing options
dynamically.
|
Note: Not all implementations of the tab layout will understand all
drawing options. In particular, the Gtkairo backends understands only
the |
Add page at the left side of tab-layout. When enable is true, move focus
to the new page. This function is a convenience wrapper; you can also
push page objects directly into tab-layout-pages and enable them using
(setf tab-layout-enabled-page).
Remove page from its tab layout. This is a convenience wrapper
around sheet-disown-child, which can also be used directly to
remove the page’s pane with the same effect.
The currently visible tab page of tab-layout, or nil if the tab
layout does not have any pages currently. Use the setf function of the name
to change focus to another tab page.
For sheet that is a child of a tab layout, return the page corresponding
to sheet. See also tab-page-pane, the reverse operation.
Find the tab page with the specified name in tab-layout. Note that uniqueness of names is not enforced; the first page found will be returned.
Move the focus in page’s tab layout to page. This function
is a one-argument convenience version of (setf tab-layout-enabled-page), which
can also be called directly.
Remove the tab page with the specified title from tab-layout.
Note that uniqueness of titles is not enforced; the first page found will
be removed. This is a convenience wrapper, you can also use
find-tab-page-named to find and the remove a page yourself.
This internal function is called by the setf methods for
tab-page-title and tab-page-drawing-options to inform
page’s tab-layout about the changes, allowing it to update its
display. Only called by the tab-layout implementation and
specialized by its subclasses.
McCLIM extends the legal values for the family and face
arguments to make-text-style to include strings (in additional to
the portable keyword symbols), as permitted by the CLIM spec, section
11.1.
Each backend defines its own specific syntax for these family and face names.
The CLX backend maps the text style family to the X font’s foundry and family values, separated by a dash. The face is mapped to weight and slant in the same way. For example, the following form creates a text style for -misc-fixed-bold-r-*-*-18-*-*-*-*-*-*-*:
(make-text-style "misc-fixed" "bold-r" 18)
In the GTK backend, the text style family and face are used directly as the Pango font family and face name. Please refer to Pango documentation for details on the syntax of face names. Example:
(make-text-style "Bitstream Vera Sans" "Bold Oblique" 54)
McCLIM’s font listing functions allow applications to list all
available fonts available on a port and create text style
instances for them.
Example:
* (find "Bitstream Vera Sans Mono"
(clim-extensions:port-all-font-families (clim:find-port))
:key #'clim-extensions:font-family-name
:test #'equal)
#<CLIM-GTKAIRO::PANGO-FONT-FAMILY Bitstream Vera Sans Mono>
* (clim-extensions:font-family-all-faces *)
(#<CLIM-GTKAIRO::PANGO-FONT-FACE Bitstream Vera Sans Mono, Bold>
#<CLIM-GTKAIRO::PANGO-FONT-FACE Bitstream Vera Sans Mono, Bold Oblique>
#<CLIM-GTKAIRO::PANGO-FONT-FACE Bitstream Vera Sans Mono, Oblique>
#<CLIM-GTKAIRO::PANGO-FONT-FACE Bitstream Vera Sans Mono, Roman>)
* (clim-extensions:font-face-scalable-p (car *))
T
* (clim-extensions:font-face-text-style (car **) 50)
#<CLIM:STANDARD-TEXT-STYLE "Bitstream Vera Sans Mono" "Bold" 50>
Class precedence list: font-family, standard-object, slot-object, t
The protocol class for font families. Each backend defines a subclass of font-family and implements its accessors. Font family instances are never created by user code. Use port-all-font-families to list all instances available on a port.
Class precedence list: font-face, standard-object, slot-object, t
The protocol class for font faces Each backend defines a subclass of font-face and implements its accessors. Font face instances are never created by user code. Use font-family-all-faces to list all faces of a font family.
Returns the list of all font-family instances known by PORT.
With INVALIDATE-CACHE, cached font family information is discarded, if any.
Return the font family’s name. This name is meant for user display, and does not, at the time of this writing, necessarily the same string used as the text style family for this port.
Return the port this font family belongs to.
Return the list of all font-face instances for this family.
Return the font face’s name. This name is meant for user display, and does not, at the time of this writing, necessarily the same string used as the text style face for this port.
Return the font family this face belongs to.
Return the list of all font sizes known to be valid for this font, if the font is restricted to particular sizes. For scalable fonts, arbitrary sizes will work, and this list represents only a subset of the valid sizes. See font-face-scalable-p.
Return an extended text style describing this font face in the specified size. If size is nil, the resulting text style does not specify a size.
This extension has the goal to provide a fast and flexible way to display images in the screen. An image is a rectangular object and is represented as a rectangular pattern and follows pattern protocol. Pixel values are represented as 32-bit RGBA numbers.
collapse-pattern make-pattern-from-bitmap-file
define-bitmap-file-reader define-bitmap-file-writer bitmap-format-supported-p bitmap-output-supported-p read-bitmap-file write-bitmap-file
Images are read with read-bitmap-file.
Images are wrote with write-bitmap-file.
Images are created with make-image.
Images are cloned with clone-image.
Images are copied with copy-image.
Images are blended with blend-image.
Images are filled with fill-image.
Operations having source and destination image as arguments may use the same image without copying it.
To draw an image use draw-pattern* or draw-design. Image
may be also used as an ink in other drawing functions.
CLIM specification defines a macro
with-output-to-postscript-stream that is used to create a
backend including a medium that implements:
McCLIM generalizes this operator by defining a macro and a specializable generic function:
Within body, stream-var is bound to a stream that implements the
output protocols so it is suitable as a stream or medium argument to any CLIM
output utility, such as draw-line* or write-string.
The value of backend must be a server path that is a suitable argument to
the function find-port, for example :ps or niL.
The value of options depends on the actual backend and allows to specify
the backend-specific options (for example :width and :height).
When the macro is used with an interactive backend then the default method opens
a window stream with open-window-stream. options are as for this
function except for that the :port keyword parameter is supplied by the
default method.
The only valid value of destination in this default method is nil.
This backend symbol designator is :ps.
Valid values of destination are:
This backend accepts the following options:
This backend symbol designator is :pdf.
Valid values of destination are:
This backends accepts the same set of options as the PostScript backend.
This backend symbol designator is :svg. To make this backend
available load the system "mcclim-svg".
Valid values of destination are:
This backend accepts the following options:
:units units, defaults to :compute
:units units, defaults to :compute
96
:device
:default
This backend symbol designator is :raster.
Valid values of destination are:
This backend accepts the following options:
format is a symbol that names the type of the image. Valid values are
:png, :jpg, :jpeg, tiff, tif, gif,
pbm, pgm, and ppm. Its default value is :png.
Functions draw-arrow and draw-arrow* can take a
:head-filled keyword argument, which is a generalized boolean,
and is false by default. If true, any arrow heads are filled in;
otherwise, they are drawn as lines.
McCLIM extends the set of gesture types and specs with the following:
:pointer-button
This gesture type is the same but the set of allowed buttons is extended
with: (member :wheel-up :wheel-down :wheel-left :wheel-right).
When the user scrolls the pointer then the backend will send events
matching types: :pointer-scroll, :pointer-button-press and
:ponter-button-release.
:pointer-motion
This gesture type will match all pointer-motion events. The gesture spec
accepts the same set of buttons as the :pointer-button but it may
be a list. For example:
(define-gesture-name whoosh :pointer-motion ((:left :right) :control))
:pointer-scroll
This gesture type will match the scroll events. The gesture spec is
similar to :pointer-button, but the name of a pointer button is
of type (member :wheel-up :wheel-down :wheel-left :wheel-right).
:timer
This gesture type will match the timer events. The gesture spec is a keyword that is the timer event qualifier to distinguish them.
(define-gesture-name cookie :timer :cookie) (define-gesture-name school :timer :school) ;;; cookie in 20s! (schedule-timer-event my-sheet :cookie 20) ;;; school in 2 hours! (schedule-timer-event my-sheet :school (* 2 60 60))
:indirect
This gesture type will match other gesture names. It is possible to create “group” gestures and configurable gesture sets for different programs.
(defun use-wsad () (define-gesture-name move-north :indirect wsad-up) (define-gesture-name move-south :indirect wsad-down) (define-gesture-name move-west :indirect wsad-left) (define-gesture-name move-east :indirect wsad-right)) (defun use-arrows () (define-gesture-name move-north :indirect arrow-up) (define-gesture-name move-south :indirect arrow-down) (define-gesture-name move-west :indirect arrow-left) (define-gesture-name move-east :indirect arrow-right)) (define-gesture movement-group :indirect move-north :unique t) (define-gesture movement-group :indirect move-south :unique nil) (define-gesture movement-group :indirect move-west :unique nil) (define-gesture movement-group :indirect move-east :unique nil)
The debugger is used for interactively inspecting stack frames when an
unhandled condition is encountered. Given high enough debug
settings, the debugger can inspect frame-local variables, evaluate code
in particular stack frame and invoke available restarts.
To get up and running quickly with Debugger:
(ql:quickload 'clim-debugger)
(clim-debugger:with-debugger () (error "test"))
The debugger is inspired by SLIME’s debugger and uses Swank to gain portability across implementations. The application is still under development and some details may change in the future.
Clicking frame with the mouse pointer toggles the display of its details
and selects it. Each locale value may be inspected by selection with
mouse pointer. The selected frame is distinguished from others with red
color. The Eval in frame command evaluates expression in the
selected frame.
Warning: these key accelerators may change in the future.
Mark previous frame active
Mark next frame active
Show more frames
Eval in active frame
Toggle active frame details
Invoke nth restart
Quit debugger
Starts debugger with condition. me-or-my-encapsulation should be supplied by the Lisp implementation allowing to encapsulate or supply different debugger for recursive debugger calls.
Executes the code in body invoking the CLIM debugger when an
unhandled condition is signalled. The macro binds *debugger-hook*
to #'debugger. Bindings are inherited by new threads.
options are not used at the moment.
Installs clim-debugger globally (no need to wrap body in
with-debugger).
“Clouseau” is used for interactively inspecting Common Lisp objects. It lets you look inside objects, inspect slots, disassemble and trace functions, view keys and values in hash tables, and quite a few other things as well. It can be extended to aid in debugging of specific programs, similar to the way the Lisp printer can be extended with print-object. The inspector can be used as a standalone application or embedded into CLIM applications.
The inspector should be portable, but has only been tested in SBCL, CCL and ECL so far. Some features of the inspector have to use non-standard features of implementations and are thus not available in all implementations.
To get up and running quickly with Clouseau:
clouseau system with (ql:quickload
"clouseau"). Alternatively, use (asdf:load-system "clouseau"),
potentially after manually loading
mcclim/Apps/Clouseau/clouseau.asd.
(clouseau:inspect object) where
object can be any Lisp object. If you use a multithreaded Lisp
implementation, you can also include the :new-process keyword
argument. If it is true, then Clouseau is started in a separate thread,
causing the above call to return immediately.
For example, executing the following code
(defclass foo () ((a :initarg :a) (b :initform #(1 2 3)) (c :initarg :c))) (clouseau:inspect (make-instance 'foo :c (make-instance 'foo)))
should cause a window similar to the one shown below to open:
That’s really all you need to know to get started. The best way to learn how to use Clouseau is to start inspecting your own objects.
After starting the inspector, a window similar to the one shown below should appear:
The central pane shows the tree of objects currently being inspected.
In the above example, the root object is an instance of a class named
FOO with three slots, A, B and C. Commands can be
issued by clicking on objects in the central pane or by typing command
names into the pane below it. The bottom pane shows available commands
for the object under the mouse pointer and how to invoke them.
By default, Clouseau will display a CLIM interactor pane for typing named commands and printing command output. The keyboard shortcut C-i (control key and "i" key pressed at the same time) toggles visibility of the interactor pane.
Within the currently displayed object tree, each visible object is
either collapsed, meaning it is displayed compactly and objects
contained in it are not displayed, or expanded, meaning that it
is displayed in more detail and objects immediately contained in it are
displayed (those objects are initially collapsed but may be expanded).
The collapsed representation of an object may be something like
#<STANDARD-CLASS SALAD-MIXIN>. To expand collapsed objects,
left-click on them. Left-click on them again and they will go back to a
collapsed form.
Note: When collapsing objects, make sure not to click on one of the object’s children since that expands the child instead of collapsing the object. The object that would be affected by a mouse click at the current pointer position is always indicated by surrounding it with a black rectangle that is updated as the pointer moves. In addition, the pointer-documentation pane at the bottom of the window always describes the available actions for the object currently under the pointer.
An expanded object is related to its children through “places”. For example, a standard object has a place for each of its slots and a child object for each of its bound slots. Other examples of places include:
Places are generally visually represented as some kind of bullet or arrow symbol. Examples include: •, ⁃ and →. Immutable places (such as the length of a non-adjustable vector) are displayed in orange, mutable places (such as object slots, vector elements or the length of an adjustable array) are displayed in purple.
Places provide their own commands which are to some extent independent of the respective objects they contain. The applicable commands for a given place can be seen by right-clicking it. Some common place commands are:
Copy Place ValueThis command copies the value of the place into another place. The source place must have a value (i.e. if it is a slot, it must be bound). The target place must be mutable and accept the type of object stored in the source place. This command can be invoked by clicking the source place and dragging it onto the target place.
Swap Place ValuesThis command swaps the values of two places. It is similar to
Copy Place Value except that it requires the source place to
be mutable as well. It can be invoked via drag-and-drop by holding down
the control key.
Set PlaceThis command sets the value of a place to the result of evaluating a form. Only mutable places support this. The command can be invoked by holding down the meta key and left-clicking the place. It will ask for a form to evaluate, evaluate the specified form and attempt to set the place to the value produced by evaluating the form. See Evaluating Forms.
Remove Place ValueThis command removes the value of a place. Not all mutable places support this. Here are some examples of places that do:
Set Place To TrueSome places have specialized commands. For example, if the type of a
place is known to be Boolean and the current value is false, this
command sets the value of the place to t. Similarly, Set
Place To False sets the value to nil.
Increment PlaceIf the value of a place is an integer x, this command replaces
the value of the place with the integer x + 1. Similarly,
Decrement Place replaces the value with x - 1. These
commands will be particularly handy when it will become possible to bind
them to mousewheel-up and mousewheel-down gestures.
The context menu of every object provides an Evaluate a form in
this context command. When invoked, this commands asks for a form in
the interactor pane. Any valid Common Lisp form can be entered.
Generally, the commands Set Place, Eval With
Context, Eval Inspect and Eval all read a form and
evaluate it. During the evaluation of the form, the special variable
cl:** is bound to the root object. If the command has been invoked
on an object or place, cl:* is bound to that object or the current
value of that place.
Clouseau can handle numerous object types in different ways. Here are some handy features you might miss if you don’t know to look for them:
Standard objects have their slots shown, either grouped by superclass or
as a “flat” list. The Change Class command can be used to
change the class of the inspected object.
Structures are inspected the same way as standard objects except that
the Change Class command is not available.
You can disassemble functions with the Show Disassembly
command. If the disassembly is already shown, Hide Disassembly
hides it. Named functions can also be traced and untraced with the
Trace Function and Untrace Function commands.
In addition to everything possible with standard objects and functions,
you can remove methods from generic functions with the Remove
Place Value and Remove all Methods commands.
The value, function and type “slots” of a symbol are mutable places and can thus be manipulated using the usual place commands3.
Lists and conses can be displayed in either the classic format (such as
(1 3 (4 . 6) "Hello" 42)), as a list, or a more graphical cons-cell
diagram format. The default is the classic format when collapsed and a
list when expanded, but this can be toggled with the Conses as
List and Conses as Graph commands.
The graphical cons cell diagram looks like this:
Sometimes the contents of inspected objects changes over time. Consider an object corresponding to a rigid body in a dynamics simulation: its position, velocity and acceleration usually change with each simulation step. In some cases, it is useful to have such changes automatically reflected in the inspector’s display of the object. Assuming the client has the ability to call the inspector after relevant changes have happened, the following pattern can be used:
:new-process t and hold on to
the second return value which is the application frame of the inspector
instance.
(setf (clouseau:root-object
frame :run-hook-p t) object) where object can be the
same root object as before. This call causes the inspector to redisplay
inspected objects using their state at the time of the call.
Here is a complete example:
(let* ((list (list #C(1 0))) ; mutable object
(frame (nth-value 1 (clouseau:inspect ; make inspector, keep frame
list :new-process t)))) ; runs in its own thread
;; Now change the object by replacing the car of the cons cell, then
;; notify the inspector. Repeat 30 times a second.
(loop :for i :from 0 :by 0.1
:do (setf (first list) (complex (* 1 (cos i)) (* 1 (sin i))))
(setf (clouseau:root-object frame :run-hook-p t) list)
(sleep 1/30)))
Sometimes Clouseau’s built-in inspection abilities aren’t enough, and you want to extend it to inspect one of your own classes in a special way. Clouseau supports this, and it’s fairly simple and straightforward.
Suppose that you’re writing a statistics program and you want to specialize the inspector for your application. When you’re looking at a sample of some characteristic of a population, you want to be able to inspect it and see some statistics about it, like the average. This is easy to do.
We define a class for a statistical sample. We’re keeping this very basic, so it’ll just contain a list of numbers:
(cl:in-package #:clim-user)
(defclass sample ()
((%data :initarg :data
:accessor data
:type list
:initform '()))
(:documentation "A statistical sample"))
(defgeneric sample-size (sample)
(:documentation "Return the size of a statistical sample"))
(defmethod sample-size ((sample sample))
(length (data sample)))
The print-object method we define will print samples
unreadably, just showing their sample size. For example, a sample with
nine numbers will print as #<SAMPLE n=9>. We create such a sample
and call it *my-sample*:
(defmethod print-object ((object sample) stream)
(print-unreadable-object (object stream :type t)
(format stream "n=~D" (sample-size object))))
(defparameter *my-sample*
(make-instance 'sample
:data '(12.8 3.7 14.9 15.2 13.66
8.97 9.81 7.0 23.092)))
We need some basic statistics functions. First, we’ll do sum:
(defgeneric sum (sample) (:documentation "The sum of all numbers in a statistical sample")) (defmethod sum ((sample sample)) (reduce #'+ (data sample)))
Next, we want to be able to compute the mean. This is just the standard average that everyone learns: add up all the numbers and divide by how many of them there are. It’s written \overline {x}.
(defgeneric mean (sample) (:documentation "The mean of the numbers in a statistical sample")) (defmethod mean ((sample sample)) (/ (sum sample) (sample-size sample)))
Finally, to be really fancy, we’ll throw in a function to compute the standard deviation which is a measurement of how spread out or bunched together the numbers in the sample are. It’s called s, and it’s computed like this: s = \sqrt{{1 \over N-1} \sum_{i=1}^N (x_i - \overline {x})^2}.
(defgeneric standard-deviation (sample)
(:documentation "Find the standard deviation of the numbers
in a sample. This measures how spread out they are."))
(defmethod standard-deviation ((sample sample))
(let ((mean (mean sample)))
(sqrt (/ (loop for x in (data sample)
sum (expt (- x mean) 2))
(1- (sample-size sample))))))
This is all very nice, but when we inspect *my-sample* all we see
is its class and its single slot. Since we just defined two methods for
summarizing samples, there’s a lot of potential being missed here. How
do we take advantage of it?
Let us start by taking a closer look at how the inspector presents inspected objects. Each occurrence of an inspected object on the screen 4 has an associated state which in turn stores a presentation style for the object. The state and style control how an object is presented and which commands can be applied to it. Object states and thus styles for each occurrence of an object are independent from those associated with other occurrences of the same object.
The state characterizes the object and the class of the state can depend
on the particular object. State classes are generally subclasses of
clouseau:inspected-object. Objects are presented with
clim:present using the name of the class of the state as the
presentation type. The class of the state therefore determines which
commands are applicable to a given object.
The style indicates how the object or certain parts of the object should
currently be presented. The style is stored in the state and can
be replaced with a different style via certain commands such as
Expand and Collapse. Clouseau comes with a basic
hierarchy of styles:
The figure below illustrates for a vector as an example of an
inspected object which parts of the visible output are produced by the
clouseau:inspect-object-using-state methods specialized to
the respective style keywords. The colors of the borders correspond the
colors in the previous figure.
We can define specialized inspection methods for our objects. To do
this, we define methods on
clouseau:inspect-object-using-state which expects the
object, the associated state, the current style and a target stream as
its arguments. To change how sample objects are presented in
the :collapsed and :expanded styles, we could define methods
which are specialized to those styles. However, because we defined
print-object for the sample class to be as informative
as we want the simple representation to be, we don’t need to define a
clouseau:inspect-object-using-state method for the
:collapsed style. We will, define methods for the
:expanded-header and :expanded-body styles, though:
(defmethod clouseau:inspect-object-using-state ((object sample) (state clouseau:inspected-instance) (style (eql :expanded-header)) (stream t)) (format stream "SAMPLE n=~D" (sample-size object))) (defmethod clouseau:inspect-object-using-state ((object sample) (state clouseau:inspected-instance) (style (eql :expanded-body)) (stream t)) (clouseau:formatting-place (object 'clouseau:reader-place 'mean present-place present-object) (write-string "mean" stream) ; label (present-place stream) ; place indicator for the "slot" (present-object stream)) ; the value of the "slot" is the object (fresh-line stream) (clouseau:formatting-place (object 'clouseau:reader-place 'standard-deviation present-place present-object) (write-string "std. dev." stream) ; label (present-place stream) ; place indicator for the "slot" (present-object stream))) ; the value of the "slot" is the object
With the above methods in place, our object is presented like this for
the :expanded style:
This is already pretty functional: our statistical summaries are presented as a label, an immutable place and a value. The places and values behave as expected with respect to presentation highlighting and available commands.
Presenting the place indicators using the usual style would make their
nature more obvious. What we also want is something visually more
closely adapted to our needs. It would be nice if we could just have a
table of things like \overline {x} = 12.125776 and have them
come out formatted nicely. Before we attempt mathematical symbols,
let’s focus on getting the basic layout right. For this, we can use
CLIM’s table formatting, Clouseau’s convenience function
clouseau:format-place-row and the clouseau:reader-place
place class:
(defmethod clouseau:inspect-object-using-state ((object sample) (state clouseau:inspected-instance) (style (eql :expanded-body)) (stream t)) (formatting-table (stream) (clouseau:format-place-row stream object 'clouseau:reader-place 'mean :label "mean") (clouseau:format-place-row stream object 'clouseau:reader-place 'standard-deviation :label "std. dev.")))
clouseau:format-place-row creates one instance of
clouseau:reader-place for each of our statistical functions
mean and standard-deviation. Each of these place
instances together with the values returned by the respective function
are then presented as a table row with three cells corresponding to the
label, the place and the value.
This refinement gets us most of the way towards the goal:
Finally, for our amusement and further practice, we’ll try to get some mathematical symbols — in this case we’ll just need \overline {x}. We can get this by printing an italic x and drawing a line over it:
(defun xbar (stream)
"Draw an x with a bar over it"
(with-room-for-graphics (stream)
(with-text-face (stream :italic)
(princ #\x stream)
(draw-line* stream 0 0 (text-size stream #\x) 0))))
(defmethod clouseau:inspect-object-using-state
((object sample)
(state clouseau:inspected-instance)
(style (eql :expanded-body))
(stream t))
(formatting-table (stream)
(clouseau:format-place-row
stream object 'clouseau:reader-place 'mean
:label #'xbar)
(clouseau:format-place-row
stream object 'clouseau:reader-place 'standard-deviation
:label #\S :label-style '(:text-face :italic))))
Finally, to illustrate the use of the :expanded-header style,
suppose that we want the ‘n=9’ (or whatever the sample size
n equals) part to have an italicized n:
easily:
(defmethod clouseau:inspect-object-using-state ((object sample) (state clouseau:inspected-instance) (style (eql :expanded-header)) (stream t)) (clouseau::inspect-class-as-name (class-of object) stream) (write-char #\Space stream) (with-drawing-options (stream :text-family :serif :text-face :italic) (write-char #\n stream)) (format stream "=~D" (sample-size object)))
Our final version looks like this:
For more examples of how to extend the inspector, you can look at the files in the Apps/Clouseau/src/objects/ directory.
The following symbols are exported from the clouseau package:
Inspect object in a new inspector window.
Return two values: 1) object 2) the created inspector application frame.
If new-process is false (the default), this function returns to the caller after the inspector window has been closed. If new-process is true, this function returns to the caller immediately and the inspector frame executes in a separate thread.
handle-errors controls whether errors signaled when printing and
inspecting objects should be handled. handle-errors must be a
valid type specifier (t and nil are legal
values). Signaled errors of the specified type will be handled by
printing an error messages in place of the inspected object. Other
errors will not be handled and might invoke the debugger in the usual
way.
The following functions and macro can be used to add support for custom object types to Clouseau:
Present object to stream according to state and style.
state stores information that is permanently associated with object.
style on the other hand consists of transient information such as whether object should be presented in expanded or collapsed form.
stream is the stream to which object should be presented.
Example:
(defmethod clouseau:inspect-object-using-state
((object symbol)
(state clouseau:inspected-object)
(style (eql :expanded-body))
(stream t))
(clouseau:formatting-place
(object 'clouseau:reader-place 'symbol-name
present-place present-object)
(write-string "Symbol name" stream)
(present-place stream)
(present-object stream)))
Note that presentation is a representation of object in stream.
state is the state associated with object.
The main purpose of this generic function is tracking multiple occurrences of objects so the circularity can be indicated.
Call thunk with clouseau:note-object-occurrence calls devoid of effects.
Execute body with clouseau:note-object-occurrence calls devoid of effects.
Execute body with present-place and present-object bound to print functions.
Before body is executed, an instance of place-class representing the child of container selected by place-class and cell is created and stored in the place associated with container unless such an instance already exists.
place-class must be a symbol naming a class or a class object and
is passed to make-instance to create the object which represents
the place.
cell indicates the cell within the place that is being
formatted. For example, if container is a one-dimensional array
and place-class designates (a subclass of)
array-element-place, a suitable value for cell is any array
index that is valid for container.
present-place is bound to a function that, when called with a
stream as its sole argument, outputs a presentation corresponding to the
created place to the stream. The produced presentation will be of
presentation-type place.
present-object is bound to a function that, when called with a stream as its sole argument, outputs a presentation corresponding to the child of container selected by place-class and cell.
Example:
This application of the macro
(clouseau:formatting-place
(object 'clouseau:reader-place 'symbol-name
present-place present-object)
(write-string "Symbol name" stream) ; Label
(present-place stream) ; Write place presentation
(present-object stream)) ; Write value presentation
outputs the name of the symbol object as an immutable place to
stream like this:
Symbol name → <value>
^ Label ^ Value presentation
^ Place presentation
In the above example a place of type clouseau:reader-place is
created and the cell for that place is the symbol
symbol-name. For this combination, the value of the place is
the result of evaluating (symbol-value object).
Present the child of object selected by place-class and cell to stream.
Retrieve an existing place instance for the child of object selected by place-class and cell or make a new instance of place-class.
Each of the following is written to stream within a separate clim:formatting-cell:
nil, label is written to stream. If label
is a function, it is called with stream as its sole argument.
If label-style is non-nil, it must be either a keyword naming
one of the styles known to clouseau:call-with-style or a list of
arguments suitable for clim:invoke-with-drawing-options. Either
way, the specified style is applied when outputting label.
clouseau:place.
place-style works like label-style.
object-style works like label-style.
Example:
The call
(clouseau:format-place-cells stream symbol 'clouseau:reader-place 'symbol-name :label "Symbol name")
outputs the name of symbol as an immutable place to stream
in three table cells like this:
cell 1 cell 2 cell 3
v v v
Symbol name | → | <value>
^ Label ^ Value presentation
^ Place presentation
Like clouseau:format-place-cells but surrounded by an additional clim:formatting-row.
It should normally not be necessary to directly call or define methods on the following generic functions:
Present place to stream.
By default, retrieve the value of place and inspect it using
inspect-object.
User code normally does not have to define methods on this generic functions.
Present object to stream.
By default, calls inspector-object-using-state.
stream is the stream to which object should be presented.
User code normally does not have to define methods on this generic functions.
A previous version of Clouseau provided the following functions and macros which are now deprecated:
A brief version of inspect-object. The output should be short, and should try to fit on one line.
|
Deprecated A method on clouseau:inspect-object-using-state specialized
to style |
Present object in tabular form on pane, with header
evaluated to print a label in a box at the top. body should output
the rows of the table, possibly using inspector-table-row.
|
Deprecated clim:formatting-table should be used instead. |
Output a table row with two items, produced by evaluating left and
right, on pane. This should be used only within
inspector-table.
When possible, you should try to use this and inspector-table for
consistency, and because they handle quite a bit of effort for you.
|
Deprecated clouseau:format-place-row should be used instead. |
This is just an inspector-specific version of define-command. If you want to define an inspector command for some reason, use this.
|
Deprecated clim:define-command with |
The McCLIM Listener provides an interactive toplevel with full access to the graphical capabilities of CLIM and a set of built-in commands intended to be useful for Lisp development and experimentation. Present features include:
Run command or
#! macro
To get up and running quickly with the Listener:
clim-listener system with (ql:quickload
"clim-listener"). Alternatively, use (asdf:load-system
"clim-listener"), potentially after manually loading
mcclim/Apps/Listener/clim-listener.asd.
(clim-listener:run-listener). If you use a multithreaded Lisp
implementation, you can also include the :new-process keyword
argument. If it is true, then the Listener is started in a separate
thread, causing the above call to return immediately.
After starting the Listener, a typical Lisp prompt will be displayed, with the package name preceding the prompt. You may type Lisp forms or commands to this prompt. The , (comma) character starts a command, every other input will be treated by the Listener as a form to be evaluated.
At the bottom of the window is a “wholine” which shows various things
such as the username/hostname, package, current directory
(*default-pathname-defaults*), the depth of the directory stack (if
not empty), and the current memory usage. Some of these items will be
sensitive to pointer gestures.
The command Help (with) Commands will produce a list of
available commands.
HelpClear Output HistoryClears the screen
ExitAproposDescribeRoomTraceUntraceEvalLoad FileCompile FileCompile and LoadShow Class SuperclassesShow Class SubclassesShow Class SlotsShow Class Generic FunctionsShow Applicable MethodsShow DirectoryUp DirectoryEdit FileProbably broken
Show FileAlmost certainly broken
Display Directory StackPush DirectoryPop DirectorySwap DirectoryDrop DirectoryRunRun an external program
Background RunAs above, but don’t wait for program to complete
Although there are commands for running external programs, the #! macro character tries to provide a nicer interface. It allows you to run external programs as a lisp function call, and attempts to transform the arguments in some meaningful way. Several transformations are performed:
:v becomes -v).
Longer keywords become an option preceded by two dashes (e.g.,
:verbose becomes --verbose)
directory function of various CL environments)
My apologies to anyone doing something more useful with this macro character if I have clobbered your readtable.
Calling CLIM commands from Lisp is straightforward. By convention,
the pretty names used at the interactor map to a function name which
implements the command body by upcasing the name, replacing spaces with
hyphens, and prepending COM- (e.g., Show Directory becomes
COM-SHOW-DIRECTORY).
McCLIM’s implementation of the clim:define-command macro
accepts a :provide-output-destination-keyword keyword argument
which allows redirecting the output of the command from
*standard-output* to some other destination. The Listener code
supplies this argument for many of the defined commands. As a result,
many Listener commands can be invoked with the :output-destination
keyword, for example
CLIM-USER> ,Show Class Superclasses (class) standard-class (keywords) :output-destination (output-destination) Postscript File (destination file) /tmp/output.ps
will redirect the command’s output to the file /tmp/output.ps.
When a command executed in the Listener signals an error, the default
behavior consists in invoking the McCLIM Debugger (see Debugger).
This behavior can be controlled by supplying a Boolean value for the
:debugger keyword argument when calling the
clim-listener:run-listener function.
| Jump to: | A B C D E I L M N P R S U V W |
|---|
| Jump to: | A B C D E I L M N P R S U V W |
|---|
| Jump to: | (
A C D F I L M N P R S T W |
|---|
| Jump to: | (
A C D F I L M N P R S T W |
|---|
In the specification, there is no example of the
use of this option to make-pane or to the equivalent keywords
in the :panes section of define-application-frame.
There is however one instance where the :scroll-bars option is
mention for pane creation. We consider this to be an error in the
specification.
Some authors use the term business logic instead of model. Both words refer to the representation of the intrinsic purpose of the application, as opposed to superficial characteristics such as how objects are physically presented to the user.
Unless the implementation supports package locks and the symbol’s home package is locked
There can be more than one occurrence. For example,
the class class occurs twice in the following
list: (list (find-class 'class) (find-class 'class)).