| Command Processing | Contents | Index | Panes |
28 Application Frames
28.1 Overview of Application Frames
Application frames (or simply, frames ) are the central
abstraction defined by CLIM for presenting an application's user interface.
Many of the other features and facilities provided by CLIM (for example, the
generic command loop, gadgets, look and feel independence) can be conveniently
accessed through the frame facility. Frames can be displayed as either
top-level windows or regions embedded within the space of the user interfaces of
other applications. In addition to controlling the screen real estate managed
by an application, a frame keeps track of the Lisp state variables that contain
the state of the application.
The visual aspect of an application frame is established by defining a hierarchy of panes . CLIM panes are interactive objects that are analogous to the windows, gadgets, or widgets of other toolkits. Application builders can compose their application's user interface from a library of standard panes or by defining and using their own pane types. Application frames can use a number of different types of panes including layout panes for spatially organizing panes, user panes for presenting application specific information, and gadget panes for displaying data and obtaining user input. Panes are describe in greater detail in Chapter Panes and Chapter Gadgets .
Frames are managed by special applications called frame managers . Frame managers control the realization of the look and feel of a frame. The frame manager interprets the specification of the application frame in the context of the available window system facilities, taking into account preferences expressed by the user. In addition, the frame manager takes care of attaching the pane hierarchy of an application frame to an appropriate place in a window hierarchy. The most common type of frame manager is one that allows the user to manipulate the frames of other applications. This type of application is typically called a desktop manager, or in X Windows terminology, a window manager. In many cases, the window manager will be a non-Lisp application. In these cases, the frame manager will act as a mediator between the Lisp application and the host desktop manager.
Some applications may act as frame managers that allow the frames of other applications to be displayed with their own frames. For example, a text editor might allow figures generated by a graphic editor to be edited in place by managing the graphics editor's frame within its own frame.
Application frames provide support for a standard interaction processing loop, like the Lisp ``read-eval-print'' loop, called a command loop . The application programmer has to write only the code that implements the frame-specific commands and output display functions. A key aspect of the command loop is the separation of the specification of the frame's commands from the specification of the end-user interaction style.
The standard interaction loop consists of reading an input ``sentence'' (the command and all of its operands), executing the command, and updating the displayed information as appropriate. CLIM implementations are free to run the display update part of the loop at a lower priority than command execution, for example, some implementations may choose not to update the display if there is typed-ahead input. Note that by default command execution and display will not occur simultaneously, so user-defined functions need not have to cope with multiprocessing. Of course, the programmer can use multiple processes, but CLIM neither directly supports nor precludes doing so.
To write an application that uses the standard interaction loop provided by CLIM, an application programmer does the following:
Note that this definition of the standard interaction loop does not constrain the interaction style to be a command-line interface. The input sentence may be entered via single keystrokes, pointer input, menu selection, dialogs, or by typing full command lines.
28.2 Defining and Creating Application Frames
| application-frame | [Protocol Class] |
| application-frame-p | object | [Predicate] |
| :name | [Init arg] |
| :pretty-name | [Init arg] |
| :command-table | [Init arg] |
| :disabled-commands | [Init arg] |
| :panes | [Init arg] |
| :menu-bar | [Init arg] |
| :calling-frame | [Init arg] |
| :state | [Init arg] |
| :properties | [Init arg] |
| standard-application-frame | [Class] |
| define-application-frame | name superclasses slots &rest options | [Macro] |
options is a list of defclass -style options, and can include the usual defclass options, plus any of the following:
| make-application-frame | frame-name &rest options &key pretty-name frame-manager enable state left top right bottom width height save-under frame-class &allow-other-keys | [Function] |
The size options left , top , right , bottom , width , and height can be used to specify the initial size of the frame. If they are unsupplied and :geometry was supplied to define-application-frame , then these arguments default from the specified geometry.
options are passed as additional arguments to make-instance , after the pretty-name , frame-manager , enable , state , save-under , frame-class , and size options have been removed.
If save-under is true , then the sheets used to implement the user interface of the frame will have the ``save under'' property, if the host window system supports it.
If frame-manager is provided, then the frame is adopted by the specified frame manager. If the frame is adopted and either of enable or state are provided, the frame is pushed into the given state.
Once a frame has been create, run-frame-top-level can be called to make the frame visible and run its top-level function.
| *application-frame* | [Variable] |
| with-application-frame | (frame) &body body | [Macro] |
frame is a symbol; it is not evaluated. body may have zero or more declarations as its first forms.
| map-over-frames | function &key port frame-manager | [Function] |
function is a function of one argument, the frame. It has dynamic extent.
| destroy-frame | frame | [Generic function] |
| raise-frame | frame | [Generic function] |
| bury-frame | frame | [Generic function] |
28.2.1 Specifying the Panes of a Frame
The panes of a frame can be specified in one of two different ways. If the
frame has a single layout and no need of named panes, then the :pane option
can be used. Otherwise if named panes or multiple layouts are required, the
:panes and :layouts options can be used. Note that the :pane option is mutually exclusive with :panes and :layouts . It is
meaningful to define frames that have no panes at all; the frame will simply
serve as a repository for state and commands.
Panes and gadgets are discussed in detail in Chapter Panes and Chapter Gadgets .
The value of the :pane option is a form that is used to create a single (albeit arbitrarily complex) pane. For example:
(vertically ()
(tabling ()
((horizontally ()
(make-pane 'toggle-button)
(make-pane 'toggle-button)
(make-pane 'toggle-button))
(make-pane 'text-field))
((make-pane 'push-button :label "a button")
(make-pane 'slider)))
(scrolling ()
(make-pane 'application-pane
:display-function 'a-display-function))
(scrolling ()
(make-pane 'interactor-pane)))
If the :pane option is not used, a set of named panes can be specified with
the :panes option. Optionally, :layouts can also be used to describe
different layouts of the set of panes.
The value of the :panes option is a list, each entry of which is of the form (name . body) . name is a symbol that names the pane, and body specifies how to create the pane. body is either a list containing a single element that is itself a list, or a list consisting of a symbol followed by zero or more keyword-value pairs. In the first case, the body is a form exactly like the form used in the :pane option. In the second case, body is a pane abbreviation where the initial symbol names the type of pane, and the keyword-value pairs are pane options. For gadgets, the pane type is the class name of the abstract gadget (for example, slider or push-button ). For CLIM stream panes, the following abbreviations are defined:
An example of the use of :panes is:
(:panes
(buttons (horizontally ()
(make-pane 'push-button :label "Press me")
(make-pane 'push-button :label "Squeeze me")))
(toggle toggle-button
:label "Toggle me")
(interactor :interactor
:width 300 :height 300)
(application :application
:display-function 'another-display-function
:incremental-redisplay t))
The value of the :layouts option is a list, each entry of which is of the
form (name layout) . name is a symbol that names the layout, and
layout specifies the layout. layout is a form like the form used in
the :pane option, with the extension to the syntax such that the name of a
named pane can be used wherever a pane may appear. (This will typically be
implemented by using symbol-macrolet for each of the named panes.) For
example, assuming a frame that uses the :panes example above, the following
layouts could be specified:
(:layouts
(default
(vertically ()
button toggle
(scrolling () application)
interactor))
(alternate
(vertically ()
(scrolling () application)
(scrolling () interactor)
(horizontally ()
button toggle))))
The syntax for :layouts can be concisely expressed as:
layout-panes is layout-panes1 or (size-spec layout-panes1 ) .
layout-panes1 is a pane-name , or a layout-macro-form , or layout-code .
layout-code is lisp code that generates a pane, which may include the name of a named pane.
size-spec is a rational number less than 1, or :fill , or :compute .
layout-macro-form is (layout-macro-name (options ) . body ) .
layout-macro-name is one of the layout macros, such as outlining , spacing , labelling , vertically , horizontally , or tabling .
| frame-name | frame | [Generic function] |
| frame-pretty-name | frame | [Generic function] |
| (setf frame-pretty-name) | name frame | [Generic function] |
| frame-command-table | frame | [Generic function] |
| (setf frame-command-table) | command-table frame | [Generic function] |
| frame-standard-output | frame | [Generic function] |
| frame-standard-input | frame | [Generic function] |
| frame-query-io | frame | [Generic function] |
| frame-error-output | frame | [Generic function] |
| *pointer-documentation-output* | [Variable] |
| frame-pointer-documentation-output | frame | [Generic function] |
| frame-calling-frame | frame | [Generic function] |
| frame-parent | frame | [Generic function] |
| frame-panes | frame | [Generic function] |
| frame-top-level-sheet | frame | [Generic function] |
The value returned by frame-panes will be a descendents of the value of frame-top-level-sheet .
| frame-current-panes | frame | [Generic function] |
| get-frame-pane | frame pane-name | [Generic function] |
| find-pane-named | frame pane-name | [Generic function] |
| frame-current-layout | frame | [Generic function] |
| (setf frame-current-layout) | layout frame | [Generic function] |
Changing the layout of the frame must recompute what panes are used for the bindings of the standard stream variables (such as *standard-input* ). Some implementations of CLIM may cause the application to ``throw'' all the way back to run-frame-top-level in order to do this. After the new layout has been computed, the contents of each of the panes must be displayed to the degree necessary to ensure that all output is visible.
| frame-all-layouts | frame | [Generic function] |
| layout-frame | frame &optional width height | [Generic function] |
If width and height are provided, then this function resizes the frame to the specified size. It is an error to provide just width .
If no optional arguments are provided, this function resizes the frame to the preferred size of the top-level pane as determined by the space composition pass of the layout protocol.
In either case, after the frame is resized, the space allocation pass of the layout protocol is invoked on the top-level pane.
| frame-exit | [Condition] |
| frame-exit-frame | condition | [Generic function] |
| frame-exit | frame | [Generic function] |
| pane-needs-redisplay | pane | [Generic function] |
| (setf pane-needs-redisplay) | value pane | [Generic function] |
When value is nil , the pane will not require redisplay. When value is t , the pane will be cleared and redisplayed exactly once. When value is :command-loop , the pane will be cleared and redisplayed in each successive pass through the command loop. When value is :no-clear , the pane will be redisplayed exactly once without clearing it.
| redisplay-frame-pane | frame pane &key force-p | [Generic function] |
| redisplay-frame-panes | frame &key force-p | [Generic function] |
| frame-replay | frame stream &optional region | [Generic function] |
| notify-user | frame message &key associated-window title documentation exit-boxes name style text-style | [Generic function] |
associated-window is the window with which the notification will be associated, as it is for menu-choose . title is a title string to include in the notification. text-style is the text style in which to display the notification. exit-boxes is as for accepting-values ; it indicates what sort of exit boxes should appear in the notification. style is the style in which to display the notification, and is implementation-dependent.
| frame-properties | frame property | [Generic function] |
| (setf frame-properties) | value frame property | [Generic function] |
28.3.1 Interface with Presentation Types
This section describes the functions that connect application frames to the
presentation type system. All classes that inherit from application-frame must inherit or implement methods for all of these functions.
| frame-maintain-presentation-histories | frame | [Generic function] |
| frame-find-innermost-applicable-presentation | frame input-context stream x y &key event | [Generic function] |
The default method (on standard-application-frame ) will simply call find-innermost-applicable-presentation .
| frame-input-context-button-press-handler | frame stream button-press-event | [Generic function] |
The default implementation (on standard-application-frame ) unhighlights any highlighted presentations, finds the applicable presentation by calling frame-find-innermost-applicable-presentation-at-position , and then calls throw-highlighted-presentation to execute the translator on that presentation that corresponds to the user's gesture.
If frame-input-context-button-press-handler is called when the pointer is not over any applicable presentation, throw-highlighted-presentation must be called with a presentation of *null-presentation* .
| frame-document-highlighted-presentation | frame presentation input-context window x y stream | [Generic function] |
The default method (on standard-application-frame ) should produce documentation that corresponds to calling document-presentation-translator on all of the applicable translators in the input context input-context . presentation , window , x , y , and stream are as for document-presentation-translator .
Typically pointer documentation will consist of a brief description of each translator that is applicable to the specified presentation in the specified input context given the current modifier state for the window. For example, the following documentation might be produced when the pointer is pointing to a Lisp expression when the input context is form :
Left: '(1 2 3); Middle: (DESCRIBE '(1 2 3)); Right: Menu
| frame-drag-and-drop-feedback | frame presentation stream initial-x initial-y new-x new-y state | [Generic function] |
| frame-drag-and-drop-highlighting | frame presentation stream state | [Generic function] |
28.4 The Generic Command Loop
The default application command loop provided by CLIM performs the following
steps:
Reads a command. Each application frame has a command table that contains those commands that the author of the application wishes to allow the user to invoke at a given time. Since commands may be read in any number of ways, the generic command loop enforces no particular interface style.
Executes the command. The definition of each command may refer to (and update) the state variables of the frame, to which *application-frame* will be bound.
Runs the display function for each pane in the frame as necessary. The display function may refer to the frame's state variables. Display functions are usually written by the application writer, although certain display functions are supplied by CLIM itself. Note that an application frame is free to have no panes.
| run-frame-top-level | frame &key &allow-other-keys | [Generic function] |
| run-frame-top-level | (frame application-frame ) &key | [Method :around] |
| default-frame-top-level | frame &key command-parser command-unparser partial-command-parser prompt | [Generic function] |
default-frame-top-level will also establish a simple restart for abort , and bind the standard stream variables as follows. *standard-input* will be bound to the value returned by frame-standard-input . *standard-output* will be bound to the value returned by frame-standard-output . *query-io* will be bound to the value returned by frame-query-io . *error-output* will be bound to the value returned by frame-error-output . It is unspecified what *terminal-io* , *debug-io* , and *trace-output* will be bound to.
prompt is either a string to use as the prompt (defaulting to "Command: " ), or a function of two arguments, a stream and the frame.
command-parser , command-unparser , and partial-command-parser are the same as for read-command . command-parser defaults to command-line-command-parser if there is an interactor, otherwise it defaults to menu-only-command-parser . command-unparser defaults to command-line-command-unparser . partial-command-parser defaults to command-line-read-remaining-arguments-for-partial-command if there is an interactor, otherwise it defaults to menu-only-read-remaining-arguments-for-partial-command . default-frame-top-level binds *command-parser* , *command-unparser* , and *partial-command-parser* to the values of command-parser , command-unparser , and partial-command-parser .
| read-frame-command | frame &key (stream *standard-input* ) | [Generic function] |
The default method (on standard-application-frame ) for read-frame-command simply calls read-command , supplying frame 's current command table as the command table.
| execute-frame-command | frame command | [Generic function] |
The default method (on standard-application-frame ) for execute-frame-command simply applies the command-name of command to command-arguments of command .
If process that execute-frame-command is invoked in is not the same process the one frame is running in, CLIM may need to make special provisions in order for the command to be correctly executed, since as queueing up a special ``command event'' in frame 's event queue. The exact details of how this should work is left unspecified.
| command-enabled | command-name frame | [Generic function] |
Whether or not a particular command is currently enabled is stored independently for each instance of an application frame; this status can vary between frames that share a single command table.
| (setf command-enabled) | enabled command-name frame | [Generic function] |
If command-name is not accessible to the command table being used by frame , using setf on command-enabled does nothing.
| display-command-menu | frame stream &key command-table initial-spacing row-wise max-width max-height n-rows n-columns (cell-align-x :left ) (cell-align-y :top ) | [Generic function] |
initial-spacing , max-width , max-height , n-rows , n-columns , row-wise , cell-align-x , and cell-align-y are as for formatting-item-list .
28.5 Frame Managers
Frames may be adopted by a frame manager, which involves invoking a
protocol for generating the pane hierarchy of the frame. This protocol provides
for selecting pane types for abstract gadget panes based on the style
requirements imposed by the frame manager. That is, the frame manager is
responsible for the ``look and feel'' of a frame.
After a frame is adopted it can be in any of the three following states: enabled , disabled , or shrunk . An enabled frame is visible unless it is occluded by other frames or the user is browsing outside of the portion of the frame manager's space that the frame occupies. A shrunken frame provides a cue or handle for the frame, but generally will not show the entire contents of the frame. For example, the frame may be iconified or an item for the frame may be placed in a special suspended frame menu. A disabled frame is not visible, nor is there any user accessible handle for enabling the frame.
Frames may also be disowned , which involves releasing the frame's panes as well as all associated foreign resources.
| frame-manager | [Protocol Class] |
| frame-mananger-p | object | [Predicate] |
28.5.1 Finding Frame Managers
Most frames need only deal directly with frame managers to the extent that they
need to find a frame manager into which they can insert themselves. Since
frames will usually be invoked by some user action that is handled by some frame
manager, finding an appropriate frame manager is usually straightforward.
Some frames will support the embedding of other frames within themselves. Such frames would not only use frames but also act as frame managers, so that other frames could insert frames. In this case, the embedded frames are mostly unaware that they are nested within other frames, but only know that they are controlled by a particular frame manager.
An application can establish an application default frame manager using with-frame-manager . A frame's top-level loop automatically establishes the frame's frame manager.
The programmer or user can influence what frame manager is found by setting *default-frame-manager* or *default-server-path* .
Each frame manager is associated with one specific port. However, a single port may have multiple frame managers managing various frames associated with the port.
| find-frame-manager | &rest options &key port &allow-other-keys | [Function] |
port defaults to the value returned by find-port applied to the remaining options.
A frame manager is found using the following rules in the order listed:
If *default-frame-manager* is bound to a currently active frame manager and it conforms to the options, it is returned.
If port is nil , a port is found and an appropriate frame manager is constructed using *default-server-path* .
| *default-frame-manager* | [Variable] |
| with-frame-manager | (frame-manager) &body body | [Macro] |
28.5.2 Frame Manager Operations
| frame-manager | frame | [Generic function] |
| (setf frame-manager) | frame-manager frame | [Generic function] |
| frame-manager-frames | frame-manager | [Generic function] |
| adopt-frame | frame-manager frame | [Generic function] |
| disown-frame | frame-manager frame | [Generic function] |
| port | (frame standard-application-frame ) | [Method] |
| port | (frame-manager standard-frame-manager ) | [Method] |
| frame-state | frame | [Generic function] |
| enable-frame | frame | [Generic function] |
| disable-frame | frame | [Generic function] |
| shrink-frame | frame | [Generic function] |
These functions call the notification functions describe below to notify the frame manager that the state of the frame changed.
| note-frame-enabled | frame-manager frame | [Generic function] |
| note-frame-disabled | frame-manager frame | [Generic function] |
| note-frame-iconified | frame-manager frame | [Generic function] |
| note-frame-deiconified | frame-manager frame | [Generic function] |
| note-command-enabled | frame-manager frame command-name | [Generic function] |
| note-command-disabled | frame-manager frame command-name | [Generic function] |
| frame-manager-notify-user | framem message-string &key frame associated-window title documentation exit-boxes name style text-style | [Generic function] |
| generate-panes | frame-manager frame | [Generic function] |
It is the responsibility of this method to call setf on frame-panes on the frame in order to set the current
| find-pane-for-frame | frame-manager frame | [Generic function] |
28.5.3 Frame Manager Settings
CLIM provides frame manager settings in order to allow a frame to communicate
information to its frame manager.
| (setf client-setting) | value frame setting | [Generic function] |
| reset-frame | frame &rest client-settings | [Generic function] |
28.6 Examples of Applications
The following is an example that outlines a simple 4-by-4 sliding piece puzzle:
(define-application-frame puzzle ()
((puzzle-array :initform (make-array '(4 4))))
(:menu-bar t)
(:panes
(display
(outlining ()
(make-pane 'application-pane
:text-cursor nil
:width :compute
:height :compute
:incremental-redisplay T
:display-function 'draw-puzzle))))
(:layouts
(:default display)))
(defmethod run-frame-top-level :before ((puzzle puzzle))
;; Initialize the puzzle
...)
(define-presentation-type puzzle-cell ()
:inherit-from '(integer 1 15))
(defmethod draw-puzzle ((puzzle puzzle) stream &key max-width max-height)
(declare (ignore max-width max-height))
;; Draw the puzzle, presenting each cell as a PUZZLE-CELL
...)
(define-puzzle-command com-move-cell
((cell 'puzzle-cell :gesture :select))
;; Move the selected cell to the adjacent open cell,
;; if there is one
...)
(define-puzzle-command (com-scramble :menu t)
()
;; Scramble the pieces of the puzzle
...)
(define-puzzle-command (com-exit-puzzle :menu "Exit")
()
(frame-exit *application-frame*))
(defun puzzle ()
(let ((puzzle
(make-application-frame 'puzzle
:width 80 :height 80)))
(run-frame-top-level puzzle)))
The following is an application frame with two layouts:
(define-application-frame test-frame () ()
(:panes
(a (horizontally ()
(make-pane 'push-button :label "Press me")
(make-pane 'push-button :label "Squeeze me")))
(b toggle-button)
(c slider)
(d text-field)
(e :interactor-pane
:width 300 :max-width +fill+
:height 300 :max-height +fill+))
(:layouts
(default
(vertically ()
a b c (scrolling () e)))
(other
(vertically ()
a (scrolling () e) b d))))
(define-test-frame-command (com-switch :name t :menu t)
()
(setf (frame-current-layout *application-frame*)
(ecase (frame-current-layout *application-frame*)
(default other)
(other default))))
(let ((test-frame
(make-application-frame 'test-frame)))
(run-frame-top-level test-frame))
| Command Processing | Contents | Index | Panes |