The index for the Allegro CL Documentation is in index.htm. The documentation is described in introduction.htm.
This document contains the following sections:1.0 About Common Graphics and IDE documentation
This document provides an introduction to Common Graphics (a windowing system used with Allegro CL on Windows) and the Integrated Development Environment (the set of application-building tools available with Allegro CL on Windows). Note that Common Graphics and the IDE are only available on Windows.
The Common Graphics and IDE documentation has been rearranged in Allegro CL release 6.0. We no longer use a WinHelp file for function, variable, and class definitions. Instead, every file (except for the tutorial documentation) is in HTML, like all other Allegro CL documentation.
Each object (operator, variable or constant, class) has an HTML page in pages/operators/common-graphics/, pages/variables/common-graphics/, or pages/classes/common-graphics/, as appropriate. The index (index.htm) includes Common Graphics symbols.
There are a number of essays, including the IDE User Guide, which are also provided. They are listed here.
*system*variable and its value.
This section describes the menus on the Allegro CL IDE menu bar and the various dialogs that present information about the running Lisp and about your project. Note that the IDE is available on Windows only.
Find in Files
Allegro Tree of Knowledge
Common Graphics streams are now simple-streams. Simple-streams are the new stream implementation in Allegro CL 6.0. They are described in streams.htm.
Because Common Graphics streams are simple-streams, Common Graphics uses the buffered I/O used by simple-streams. This can require force-output to be called to make still-buffered text output visible. Force-output is called internally by Common Graphics after any redisplay-window method runs and before any graphical output is done, to ensure that any text that is drawn onto a Common Graphics window by calling a Common Lisp text output function is always visible when appropriate under normal circumstances. But if an application performs Common Lisp text output to a Common Graphics stream in some other way, then it may need to add explicit calls to force-output.
Another consequence of this change is that a closed Common Graphics stream no longer changes class, but windowp still returns nil for a closed stream for backward compatibility (this name may change later). The print name of a closed Common Graphics stream now includes the string "(closed)".
still exported for creating printer streams and bitmap-streams, but
the location and direction arguments no longer need be specified for
these classes. The arguments are now optional rather than required.
And bitmap-streams now have a default page size, which is equal to the
size of the screen. So a printer stream could be created simply with
(open-stream 'printer) and a bitmap-stream
could be created with
But if additional initargs are to be passed, the old location and
direction arguments still need to be passed as placeholders for
backward compatibility, though the value will be ignored. Examples:
(open-stream 'printer nil nil :orientation :vertical) (open-stream 'bitmap-stream nil nil :page-width 200 :page-height 300)
Note that stream-device is now deprecated. type-of may be used instead.
There are pre-built images that, when invoked, start the IDE
automatically. These images are allegro.dxl and
allegro-ansi.dxl (the first is a modern --
case-sensitive -- image and the second an ANSI -- case-insensitive --
image. Both use 16-bit characters. There are no pre-built 8-bit
character images that contain the IDE. (If you want to run the IDE in
an 8-bit image, start an 8-bit image, evaluate
:ide), and call start-ide with no arguments.)
There are menu items on the Allegro CL 6.0 submenu of the Windows Start menu for Modern Images and ANSI Images. Each has an `Allegro CL (w IDE)' item. Choosing one of those starts Allegro CL and automatically starts the IDE. See Starting on Windows machines in startup.htm for more information on starting Allegro CL on Windows.
The IDE is started by calling start-ide with no arguments. This
function is called automatically when running one of the IDE images,
or can be called explicitly in a base lisp after requiring the
:ide module by evaluating
:ide). When start-ide is called, it performs
the following steps:
-batchflag (see Command line arguments in startup.htm), then start-ide exits immediately, returning nil, and nothing else is done.
*starting-ide*is set to t.
*system*object is created (or recreated it if this is a dumplisp'ed image).
*session-startup-hook*is a function or a function name, then it is funcalled with no arguments.
(session-variables *system*)are set to nil (see session-variables).
(session-init-functions *system*)is funcalled with no arguments (see session-init-functions). (Any members that are not fbound are skipped.) process-pending-events is called before each funcall. This list of functions does the following:
new-project-show-form exposes the initial form window of the project and creates an inspector window inspecting that form.
new-project-show-project-manager shows the Project Manager dialog.
new-project-show-editor creates an editor workbook.
*after-session-init-functions-hook*is non-nil, then its value is expected to be a function or function name and is funcalled with no arguments.
*default-cg-bindings*is passed as the :initial-bindings argument to mp:process-run-function when creating this thread (this should always be done when creating a thread that may create Common Graphics windows or to allow debugging that thread in the IDE). The user may create additional similar listeners later by using the View | New Listener command.
Most of the
*default-cg-bindings* are for
internal CG variables that need to have a distinct binding in each
thread, but one binding of special note is that *package* is bound to
the common-graphics-user package (nickname cg-user). The cg-user
package uses the cg package and associated packages in addition to the
packages used by the common-lisp-user package. This convention differs
from the traditional use of the common-lisp-user package in lisp
listeners so that IDE users do not need to add package qualifiers to
Common Graphics symbols. If a different initial IDE listener package
is desired, it could be set up by evaluating a form such as the
following (which sets the initial package to
common-lisp-user) in one of the earlier startup
steps (such as placing it in the startup.cl file
or having the function that is the value of
(setf (cdr (assoc '*package* *default-cg-bindings* :test #'eq)) '(find-package :cl-user))
(ide-evaluator-listener *system*)(see ide-evaluator-listener) is set to this initial listener pane. The Listener 1 thread passes top-level-read-eval-print-loop to start-interactive-top-level to enter a read-eval-print loop.
*starting-ide*to nil. The thread that called start-ide could check this variable to know when the IDE has completely finished starting up.
This page describes how to generate an automatic textual bug report when an error occurs in the IDE environment. If the error appears to be due to an Allegro bug, emailing this report to Franz will often help us to debug the error.
When an error occurs and the Restarts dialog (shown on the Debug Windows after an error page) appears with options for proceeding from the error, click the Debug button. This will show the current function stack as a graphical outline control. (The control will appear as a pane in the Debug Window if that window is tall enough, and otherwise in its own top-level window. Again see Debug Windows after an error) If the keyboard focus is not already in the stack outline control, move the focus there by clicking anywhere in the stack outline or perhaps by using the View | Manage menu commands for selecting windows that are near the top. Then invoke either the File | Save command or the File | Save As command. When the modal dialog appears asking for a pathname, select a pathname to write the bug report to. The entire function stack will be saved textually to that file, with brief platform information at the top of the file, and complete dribble-bug (as generated by dribble-bug) information at the bottom. The stack information will include all of the arguments and local variables for each stack frame, regardless of whether you have opened the outline items to show the arguments and variables in the IDE. The bug report that was written will then be shown in the IDE editor for your review.
The stack information will include the normally "hidden" frames only if the "Include Hidden Frames" button on the stack outline's toolbar is currently pressed. Though the additional information is not always necessary, it is best to click on this button before generating the bug report.
Since a window can be either a child window or a top-level window, and can also be either an owned window or not, there are four possible combinations of these attributes. But since a child window is always also an owned window (where the parent is also the owner), that leaves three actual types of windows, in terms of their relationships to a parent or owner:
:ownerkeyword argument to make-window, and true as the value of the
:child-pargument. (Note that the
:child-pargument defaults to t, so it's not necessary to specify a value when creating a child window.)
:ownerargument to make-window, and passing
nilas the value of the
:child-pargument. Note that the owner of an owned top-level window must always be a top-level window; if a child window is passed as the owner, its top-level parent (or ancestor) will become the owner, not the child window specified.
(screen *system*)as the value of the
:ownerargument to make-window (in which case the
:child-p argument will be essentially ignored).
In the IDE, the default value of the
argument to make-window is
(development-main-window *system*), which is the
invisible owner window of the various IDE dialogs (see development-main-window
*system*). This default allows a
user-created window to access the IDE menubar commands by using the
menubar's keyboard shortcuts when the user-created window has the
keyboard focus, and also allows the window to intermingle with the
various IDE windows, rather than being either behind all of the IDE
windows or in front of all of them. In a generated standalone
application, on the other hand, the default value of the
:owner argument is the screen. So to test a
top-level window just as it would behave in a standalone application,
(screen *system*) as the owner of the
window rather than letting the owner default to the IDE owner window.
These two alternatives are used by the Run Form and Run
Project commands (both on the Run menu): The
Run Form command places the
running window on the IDE owner window for easy access within the IDE,
whereas the Run
Project command creates the main window of the project
on the screen to more closely emulate the standalone application that
would be created from the project.
In 6.0, where the IDE is multi-threaded, you may find that a user window created on the IDE owner window leads to "message timeout" errors if it interacts with IDE windows in certain ways, due to the windows having been created in different threads and therefore handling their messages in those different threads. If this should happen, the workaround is to not create those user windows on the IDE owner window.
Some related functions: parent returns the parent of a window, while owner returns the owner. windows returns a list of all of the child or owned windows of a window. child-p returns whether a window is a child window. top-level-window returns the top-level ancestor window of a window.
:ownerargument was called
:parent; this was inappropriate since the argument was only an owner (and not a parent) when creating an owned top-level window. The
:parentargument still works for compatibility, but
:owneris now preferred.
:pop-upinitarg to make-window was passed as true to indicate that the ":parent" argument was really only an owner, thereby creating a top-level window. These arguments still work for compatibility, but passing
nilis now preferred.
:pop-upargument to make-window as true simply created a top-level window. Now it more specifically creates a top-level window appropriate for use as a modal dialog, by coercing
All controls (buttons, single-item-lists, combo-boxes, multiline-editable-text controls, etc.) are represented in the Allegro CL Integrated Development Environment (IDE) as classes. Instances are created with make-instance, which takes a class name and initialization arguments.
Using the IDE, you can add a control to a form. The code for creating an instance of the control is generated automatically. That automatically generated code provides examples of code that creates instances of controls (and also windows).
For example, when you bring up the IDE the first time, you have a blank form labeled form1. If you click Run | Run Project, files named form1.cl and form1.bil are saved (along with project1.lpr, which does not concern us here). Here is the contents of form1.bil (slightly edited, note: do not try to run this code as it has pathnames likely invalid on your machine):
;;; ;;; Define :form1 (in-package :common-graphics-user) ;; Return the window, creating it the first time or when it's closed. ;; Use only this function if you need only one instance. (defun form1 () (find-or-make-application-window :form1 'make-form1)) ;; Create an instance of the window. ;; Use this if you need more than one instance. (defun make-form1 (&key (parent (development-main-window *system*)) (exterior (make-box 256 149 960 519)) (name :form1) (title "Form1") form-p) (let ((parent (make-window name :parent parent :device 'dialog :exterior exterior :border :frame :close-button t :cursor-name :arrow-cursor :form-state :normal :maximize-button t :minimize-button t :name :form1 :package-name :common-graphics-user :pop-up nil :resizable t :scrollbars nil :state :normal :status-bar nil :system-menu t :title title :title-bar t :toolbar nil :form-p form-p :path #p"C:\\Program Files\\acl60\\form1.bil" :help-string nil :package-name :common-graphics-user))) parent))
Note in the definition of the function make-form1 is a call to make-window suitable for
creating a dialog window (that is, an instance of class
dialog). Note that
only some of the possible arguments are included. Now, stop running
the form and place a button on it by clicking on the button icon on
the component toolbar and clicking on the blank form1. Run the form
again, saving form1.cl. Again.look at the
;;; ;;; Define :form1 (in-package :common-graphics-user) ;; Return the window, creating it the first time or when it's closed. ;; Use only this function if you need only one instance. (defun form1 () (find-or-make-application-window :form1 'make-form1)) ;; Create an instance of the window. ;; Use this if you need more than one instance. (defun make-form1 (&key (parent (development-main-window *system*)) (exterior (make-box 256 149 960 519)) (name :form1) (title "Form1") form-p) (let ((parent (make-window name :parent parent :device 'dialog :exterior exterior :border :frame :close-button t :cursor-name :arrow-cursor :widgets (list (make-instance 'button :font (make-font-ex nil "MS Sans Serif" 11 nil) :left 263 :name :button4 :top 160)) :form-state :normal :maximize-button t :minimize-button t :name :form1 :package-name :common-graphics-user :pop-up nil :resizable t :scrollbars nil :state :normal :status-bar nil :system-menu t :title title :title-bar t :toolbar nil :form-p form-p :path #p"C:\\Program Files\\acl60\\form1.bil" :help-string nil :package-name :common-graphics-user))) parent))
Note that the call to make-window now has another
argument provided, :widgets. Its value is a list of one element, an
instance of the class
button, created with this call to
(make-instance 'button :font (make-font-ex nil "MS Sans Serif" 11 nil) :left 263 :name :button4 :top 160))
If you further customize the button, additional arguments will be provided. Here is the call after we have changed the title to "Here", added an on-click event handler, and modified the width from their default values:
(make-instance 'button :font (make-font-ex nil "MS Sans Serif" 11 nil) :left 263 :name :button4 :on-click 'form1-button4-on-click :title "Here" :top 160 :width 33))
The title, width, and on-click arguments have all been added.
You can reasonably easily generate similar examples creating forms of various classes and by adding controls to a form, running the form, and looking at the resulting .bil file.
Event-handling is a critical part of any visual application, where users communicate with the application through dialogs and other windows. Events are handled differently for controls than for general windows. Events on controls are discussed in the IDE User Guide, chapter 8 (Chapter 8 of the IDE User Guide). Here we introduce the more general type of event-handling for "regular" windows (non-controls), and point to a set of functions that can be looked up for more detailed information. A simple application may need to supply event-handling code only for controls, while a more complex application will probably need to supply code for non-control windows as well.
The operating system sends messages to individual Common Graphics windows (either in the IDE or in a standalone application) when events such as mouse clicks and focus movement occur. To respond to these events, an application can supply code that will be called whenever these messages are received.
The type of code to be supplied depends on whether the message is being received by a control or by a regular window. A message to a control is usually handled by an event-handler function that is written especially for a particular control instance. These event-handlers for controls (which are discussed in IDE User Guide, chapter 8) appear on the Events tab of the inspector when a control is being inspected, and skeleton code for an event handler can be generated interactively by clicking on the extended editor button to the right of the event-handler in the inspector.
For non-control windows, on the other hand, messages are handled by methods that typically apply to a whole window class rather than to an individual window instance. The IDE does not have an interface for adding these methods interactively (like the inspector's list of event-handlers for a control), and so you must know what generic function to modify in order to handle a particular message, and write a method from scratch. Normally an application should first define its own subclass of basic-pane (or one of its subclasses), and then define an event-handling method for that subclass. The method will then be called by Common Graphics whenever a window of that subclass receives the corresponding message from the operating system. Here is a list of many of the event-handling generic functions for non-control windows:
when the mouse moves into a window
mouse-moved, when the mouse moves within a window
mouse-out, when the mouse moves out of a window
mouse-double-click, this is for the left mouse button only
when a key is pressed down
virtual-key-up, when a key is released
character-message, when a keypress indicates a graphic character
when the user invokes a menu
menu-item-highlighted, when the user moves over a menu-item
handle-menu-selection, when the user chooses a menu-item
user-scroll, when the user scrolls a window
when a window is uncovered
set-focus, when the keyboard focus moves to a window
user-close, when the user attempts to close a window
move-window, when the user moves a window
resize-window, when the user resizes a window
The generic functions in the Application-Callable section above may also be called programmatically by an application, in addition to being called by Common Graphics when an interactive event occurs. For example, set-focus may be called by an application to move the keyboard focus to a window, and is also called by Common Graphics when the focus is otherwise moved to a window, such as by clicking in it. An application should keep in mind that when its method for one of these generic functions is called, that it may be due to an interactive gesture by the user or due to a programmatic call.
Any primary methods added to these generic functions should call
(call-next-method) unless you are sure that you
want to override the default Common Graphics behavior for the event.
Here's an example that handles the virtual-key-down event, since its
arguments are a little tricky. This code will create a window that
will change its size when the user types control-L, control-semicolon,
control-shift-L, or control-shift-semicolon. The buttons argument is
some subset of the values of the constants control-key, shift-key, and
alt-key logior'ed together (each one is a bit flag). The data
argument is an integer for the key that was pressed, expressed either
as the char-int of the character that is printed on the key or as the
value of one of the "vk-..." constants that are the value of the
(in-package :cg-user) (defclass my-window (frame-window)()) (defmethod virtual-key-down ((window my-window) buttons data) (case buttons (#.control-key (case data (#.vk-semicolon (incf (width window) 50)) (#.(char-int #\L) (decf (width window) 50)) (t (call-next-method)))) (#.(logior control-key shift-key) (case data (#.vk-semicolon (incf (height window) 50)) (#.(char-int #\L) (decf (height window) 50)) (t (call-next-method)))) (t (call-next-method)))) (make-window 'herbert :device 'my-window :parent (screen *system*))
Note that if the example window above were created on
*system*) instead of on the screen,
then the virtual-key-down method would not get called for the defined
keystrokes, because the keystrokes would be overridden by IDE menubar
shortcuts. In general, a menubar shortcut will override a virtual-key-down method,
and a custom virtual-key-down method will override a comtab binding
(since comtab events are implemented as a virtual-key-down method on
the general comtab-mixin class).
The Common Graphics event-handling generic functions listed above provide a high-level interface to many of the messages sent by the operating system. But since an application may still need to handle low-level Windows events, generic functions also exist for each Windows API message that is handled at all by Common Graphics, and an application may add methods to these functions to handle raw Windows messages.
The name of each generic function is the same as the Windows API constant for the message being received. The functions are in the windows package, and their parameters are
(window wparam lparam)
The arguments are what would be received by a standard Windows "window procedure", except without a parameter for the message name itself, since we have a generic function for each message. We do not document these WinAPI symbols, and provide these generic functions only for programmers who are familiar with the Windows API and can consult Microsoft's documentation on their use. Any primary methods added to these generic functions should call (call-next-method) so that the default method will call the DefaultWindowProc, unless you are sure that you really want to override the operating system's own handling of the event. Here is an example method that would be called whenever an application is activated:
(in-package :cg-user) (defmethod win:WM_ACTIVATEAPP ((window my-top-level-window-class) wparam lparam) (declare (ignore lparam)) (call-next-method) (when (eq wparam win:TRUE) (beep) (lisp-message "The user has returned to my application!")))
The generic functions listed above for non-control windows are not called for controls, as is sometimes expected. There are a few of reasons for this:
lisp-widget, on the other hand, are defined in lisp, and so they do receive all low-level window events.
For example, the on-change property of a control holds a function that is called whenever the value of the control has changed. Such properties are called event-handlers, and are accessible in the inspector on the Events tab. When an event-handler is selected in the inspector, pressing F1 (to invoke the Help | Help On Selected Symbol command) will display help on that event-handler (rather than on its current value, as would happen in the Internals tab).
(To be precise, there are also exported generic functions for a few of the events received by controls. These are click-event, double-click-event, set-focus-event, and kill-focus-event. The default method for each of these generic functions will call the corresponding event-handler function of the individual widget, but an application could override the default method to define behavior for a whole control subclass. Usually it is preferable to stick with individual event-handlers.)
A palette holds an arbitrary set of colors for drawing in one or more windows. An application needs to use a palette only if
The end user's Control Panel color scheme
normally includes the 16 standard VGA colors
dark-magenta, plus four
special colors for button background and 3d edges and so on. The
linked names are to constants whose values are the named colors.
By default, calling the function palette on a Common Graphics window
returns the symbol :RGB, which indicates that the window has not been
given a custom palette. To draw in color on a window that has no
custom palette, you simply set the foreground-color or background-color to be an
RGB color object (as created by make-rgb named by constants such as
red), and then
call various drawing functions.
But if Windows is not in true color mode (that is, less than 24-bit color) and the requested RGB color is not one of the 20 system colors (either a VGA color or one of the other four colors in the current Control Panel color scheme), then this actual color will not be used. Instead, for a line-drawing operation, one of the 20 system colors that is nearest to the requested color will be substituted; and for a space-filling operation, each pixel that is drawn will be one of two system colors in such a proportion as to approximate the requested color (this approximation technique is called dithering, and does not look as nice as every pixel being the actual requested color).
The alternative to the above is to define and use a custom color palette. The basic procedure to set up a custom palette is to
To then draw in the window,
A palette should be no larger than the number of colors in which the end user is running the Windows operating system. If Windows is running in 256-color mode (8-bit color), for example, then no more than 256 different colors may appear on the screen at any time, and a palette may contain no more than 256 colors.
If multiple windows (including those in other applications) are using palettes, and Windows is running in 256-color mode, then any given window that is using a palette will typically not display the correct colors except when it is the most recently selected window that uses a palette, since the combined palettes will typically contain more colors than the whole system can display.
The set of colors currently being displayed system-wide is sometimes referred to as the system palette. Common Graphics knows to force a window's full palette into the system palette whenever it is selected.
When Windows is running in 256-color mode, it is recommended that a palette have somewhat less than 256 colors, so that when the window is selected, the basic colors used in other windows will still have room in the system palette and so other windows will not switch to odd colors borrowed from your custom palette. A palette of 236 colors or less would still leave room for the user's 20 colors from their Control Panel color scheme.
The Windows OS may be run in 1-bit (monochrome) mode, 4-bit (16-color / VGA) mode, 8-bit (256-color) mode, 16-bit (65,536-color) mode, or 24- or 32-bit (true color) mode, depending on the end user's settings in Control Panel. True color never uses a palette because the 24 bits or more for each pixel is large enough to contain an accurate RGB (red-green-blue) specification directly rather than serving as an index into a smaller table of color definitions. Though most any color computer that is sold nowadays is capable of true color, the end user may still elect to run in 256-color mode either to achieve a greater screen resolution (width by height) or to achieve greater speed of graphical output. So in general it should not be assumed that an end user will run your application in true color unless that is an explicit requirement of the application.
A special case is 16-bit color mode, where the OS appears to use a default palette with millions of colors that approximate true color. In this mode, arbitrary RGB colors may be used for foreground and background colors of Common Graphics windows without using custom palettes, but the colors actually used will still only be approximate (though a much better approximation than dithering). A custom palette may still be needed even in this mode for better color accuracy.
DDE stands for Dynamic Data Exchange. Quoting from the Windows API Bible (a standard reference for programming with Windows): "Dynamic Data Exchange is a message protocol that allows Windows applications to exchange data."
Here we briefly describe the functionality available in Lisp for DDE. Note that we do not describe DDE in much detail. We assume you are familiar with using DDE between Windows applications. If you are not, please refer to standard Windows programming manuals. Here are some principles of using DDE in Lisp:
There is a simple example illustrating the DDE functionality in examples\cg\dde\examples.cl. Also in that directory is ddedoc.txt which contains most of the information in this entry.
Create a client-port instance by calling make-instance on the client-port class. Available initargs are:
These attributes can be changed after the instance is created using setf and the accessors port-name, port-application, and port-topic (all are setf'able). The new values will be used if the port is closed and re-opened.
;; Create a port with the program manager acting as the DDE ;; server (setq port1 (make-instance 'client-port :application :progman :topic :progman))
Any single DDE port (either a client or server port) will work only within a single thread during the time that it is open, and so all of the application code that uses a particular DDE port should run only in one thread. (A port could be opened and closed in one thread and then later opened and closed in another, but it would likely be tricky to ensure that the threads do not have the port open at the same time.)
The various DDE-related global variables are now bound per-thread, so an application that sets the value of any of these variables should do so in any thread that uses DDE.
In earlier releases, the DDE facility would have worked only in the first thread that attempted to use it, but now separate DDE ports work in separate threads. Allegro CL will continue to automatically initialize DDE as needed whenever a first port is opened, though now this is handled separately in each thread that begins to use DDE.
A timer can be used to cause an arbitrary piece of application code to be asynchronously invoked after a specified amount of time has elapsed. A timer can then be stopped in order to run its code a single time only, or it can be allowed to continue running in order to run its code an indefinite number of times at a regular time interval.
Common Graphics has a
timer class, which can
be instantiated to create a timer object. A single timer can be
started and stopped a number of times, using various time intervals,
to time a number of different activities. If multiple activities need
to be timed simultaneously, then multiple timers can be created and
run alongside each other. It is also convenient to create a separate
timer for each piece of code that is to be invoked by timers, even
when they do not need to run simultaneously.
A timer is an instance of the
timer class, which has
the following properties. All of these except for id may be set by an
application. See the page for the
timer class or for the
individual properties for more detailed information.
An on-timer function may call stop-timer on the timer (or set the timer's active property to nil) if the on-timer function should be invoked only a single time after the timer is started. Otherwise the on-timer function will be called each time the timer's interval has elapsed until stop-timer is called on the timer (or its active property is set to nil).
When a timer is started, this property is set to zero. Each time a timer's interval has elapsed, the default timer method increments the timer's timer-count by one and then calls its on-timer function. If a timer count reaches 50 million, it is reset to zero to prevent the consing of bignums.
class can of course be subclassed in order to add custom slots for
this or any other purpose, but this single timer-info slot is provided
since such a handy place is often needed due to the asynchronous
nature of timers, and a single slot is often sufficient.)
;; Define an on-timer event handler (defun my-on-timer (timer) ;; Show a status bar message each time the timer fires. (lisp-message "Timer event ~a for ~s" (timer-count timer)(name timer))) ;; Create a timer that uses the above on-timer event-handler. (setq tim (make-instance 'timer :on-timer 'my-on-timer)) (active tim) ==> nil ;; the new timer is not active yet ;; Start the timer, telling it to fire every half second (start-timer tim :interval 500) (active tim) ==> t ;; now it's active (stop-timer tim) ;; stop it for a while (setf (active tim) t) ;; this is another way to start it back up (setf (interval tim) 800) ;; slow it down even while it's running (setf (active tim) nil) ;; another way to stop it
Common Graphics uses various terms to describe different ways that it measures coordinates in various contexts, such as window units and stream units. These terms denote coordinates that differ in two ways:
The following two sections, 12.1 Coordinate System Unit of Size, and 12.2 Coordinate System Location of Origin, explain each type of difference.
Distances, offsets, and sizes in common graphics are usually measured in pixels. For example, the left property of a window is expressed as the number of pixels that fit in a horizontal line between the window's exterior left edge and the interior left edge of its parent window. These pixel-based measurements are referred to as being in device units, since their size depends on the resolution of the display device.
One alternative to measuring in pixel units is to use dialog units, where one dialog unit is equal to one-fourth of a system font character width or one-eighth of a system font character height. Common Graphics allows for the size of a control to be specified in dialog units (by passing the :dialog-units-p initarg to make-instance (or make-window) when creating the control), but we discourage the use of dialog units since the system font (provided by the video display adapter driver) is no longer commonly used, and because common graphics measures arbitrary drawing positions and font sizes and so on in pixels, and it's easier to fit all parts of a graphical interface together if the same units are used for everything.
The other alternative to pixel-size device units is to use a
scaling-stream. A scaling stream class is any class that is a subclass
windows-graphics and also a
subclass of the
scaling-stream mixin class. The
basic unit of distance of a scaling stream is initially device units
but may be set to any arbitrary size by calling (setf stream-units-per-inch) or
The only scaling stream class that common graphics supplies is the
but an application may create and use scaling window classes if it is
desired to draw in windows using coordinates expressed in some unit
other than pixels. It is particularly useful to scale a printer stream
to use the same stream-units-per-inch
as a window. Then the same drawing code can be used to draw on both
the window and the printer at the same size, without the application
code needing to scale its own coordinates for each call to a drawing
function. This can be achieved by evaluating
(setf (stream-units-per-inch my-printer-stream) (stream-units-per-inch my-window))
Note: this technique may cause fatter-than-desired lines since line thicknesses and so on are still limited to integral stream-unit values even though 1 stream-unit of thickness may now be many device pixels thick. Drawing glitches might also occur due to floating-point round-off in the scaling conversion. You also may need to add a fudge factor to this expression, though, since the operating system does not actually know how big the monitor is, and typically estimates it badly. Multiplying by 0.85 is suggested.
The functions device-to-scaling-units and scaling-to-device-units can be used to translate between scaled and non-scaled positions and boxes.
Stream coordinates are the application-level coordinates that are normally used for specifying drawing positions on a drawing "stream", which could be either a window or a printer stream or some other two-dimensional output stream. Stream coordinates are also used for specifying child window locations and other application-level positions except where otherwise noted.
Each coordinate indicates the distance rightward or downward from an arbitrary stream-origin. For example, if the stream-origin of a stream is (100, 200), then the leftmost x coordinate in the entire "page" (or canvas) that can be drawn into for the stream will be -100, the topmost y coordinate will be -200, the rightmost x coordinate will be
(- (page-width stream) 100)
and the bottommost y coordinate will be
(- (page-height stream) 200)
The function stream-to-stream-units translates coordinates in one stream to be relative to another stream; this can be done only if the two streams have a common ancestor, such as with windows on a screen, since otherwise no relationship between the two coordinate systems is defined.
Page coordinates are stream coordinates with the arbitrary stream-origin added on, and so each coordinate indicates a distance rightward or downward from the upper-left corner of the entire "page" (or canvas) that is being drawn on. The stream-origin is usually left at its default position of (0, 0) and so page coordinates are usually the same as stream coordinates. But an application may, for example, find it useful to move the stream origin in order to draw a polygon at various places on the page without modifying the individual vertices of the polygon.
Only the origin itself is specified in page coordinates; elsewhere stream coordinates should suffice for "user" positions and device coordinates (introduced next) for "physical" positions.
Device coordinates are page coordinates unscaled and unscrolled; that
is, they are always measured in pixels (which are what a "device" such
as a screen or printer uses itself), and are relative to the
upper-left corner of some "physical" device such as a printed page or
window interior. (Actually stream and page coordinates are usually
measured in pixels as well, except when the stream is a
which an arbitrary unit of size may be established. A
printer stream is always
a scaling-stream, and a window is a scaling-stream if an application
has mixed the
scaling-stream class into the
The functions stream-to-device-units and device-to-stream-units translate between stream and device coordinates.
When working in a window, device coordinates are also called window coordinates and are relative to the upper-left of the physical window interior rather than to the upper-left of the virtual canvas (or page) on which the application draws.
The functions stream-to-device-units and device-to-stream-units translate between stream and window coordinates just as they do with device coordinates generally. The function window-to-window-units translates window coordinates of one window into window coordinates of another window.
When working in a window or on the screen itself, screen coordinates are relative to the upper-left corner of the physical screen. The functions window-to-screen-units and screen-to-window-units translate between window and screen coordinates.
command and select rich-edit-dialog from the list of choices. This
will make a rich-edit-dialog form and will automatically add a
rich-edit control and an associated
just above it to the form, and also add auxiliary widgets in a toolbar
for use with the rich-edit control.
This dialog will act as a complete WordPad™-like application. If you would like to add additional controls to the form or toolbar, you can do so. The rich-edit controls appear at the right end of the component toolbar. Note that if multiple rich-edit controls are added to a single form, the single set of rich-edit helper controls works for all of the rich-edit controls on the same parent window, reflecting the rich-edit that most recently had the keyboard focus.
Try running the initial rich-edit form (with that form selected, click on Run | Run Form), then clicking the Open button in the toolbar and selecting the file cg\rich-edit-sample.rtf (in the cg subdirectory of the main Allegro directory). This should show some sample rich text.
To test making your own rich edit dialog from scratch, invoke the File | New Form command, and select dialog from the list of window classes that you can create. Double click the interior of the new form to inspect it.
If you would like to start the menu-bar off with the special rich-edit commands, then go to the menu property of the form in the inspector and enter #.(rich-edit-menubar) in the inspector line for the menu property. (The #. reader macro is a trick to evaluate the expression typed directly into a line of the inspector.) This should add the standard rich-edit menu-bar to the form, and you can further edit this menu as you like.
Back on the inspector, toggle the toolbar property on to give the form
a toolbar. Also toggle the status-bar property to give the form a
status-bar for messages. Next, click on the Rich-Edit button of the
Component Toolbar (the one with a big green "R"), and then click in
the main interior of your form to create a rich-edit control. You may
want to size the rich-edit control larger, since the default size is
rather small. Now click on each of the next three rich-edit "helper"
controls on the Component Toolbar and instantiate those widgets from
left to right on the toolbar of your new form. (The helper controls
consist of the
click on the Ruler button on the Component Toolbar, position it just
above the rich-edit control, and then drag it or stretch it partly
over the rich-edit as needed to make it snap into place along the top.
Having done this, you now have a dialog that is functionally similar to the one created by using the rich-edit-dialog class. You can customize either however you like by adding additional controls and editing the initial rich-edit menubar. Note that only a rich-edit-dialog has the built-in feature of prompting the user to save any unsaved rich-edit controls when a closing gesture is made.
While the rich edit functionality basically allows for custom WordPad™-like applications, there are a couple of features that are somewhat unique:
Multiple editor panes. The rich-edit helper controls will automatically keep track of which rich-edit control most recently had the keyboard focus, and apply any editing commands to that rich-edit. Also, as the focus moves from one rich-edit to another, the rich edit helper controls will update themselves to reflect the current rich-edit, just as they update to reflect the currently selected text within a single rich-edit.
Copying formatting. The Edit menu (on the rich-edit-dialog) has a couple of items called Copy Format and Paste Format that allow you to easily copy character formatting rather than text. Just select some text (or position the text cursor), invoke Edit | Copy Format, then select some other text, and invoke Edit | Paste Format (again, these commands are on the Edit menu on the rich-edit-dialog, not on the Allegro CL Edit menu) The second text range will now have the character formatting of the first text range.
All symbols documented here are exported from the common-graphics (cg) package, since this is an extension to common graphics.
Most of these commands work on a window (the new rich-edit-pane and/or
text-edit-pane) rather than on a
widget. If you are using a widget instead (the new rich-edit control
or the older
control), then you first need to call the function
window on the
control to retrieve the window of the control, and then pass that to
the function that expects a window.
Much of this functionality works using the pre-existing
controls. The corresponding newer classes, rich-edit-pane and the
rich-edit control, are provided mostly for automatically linking up
with text-formatting controls, for determining which type of text is
pasted into a given control, and other potential circumstances in
which the controls will need to default either to rich or plain
text. Thus, anything that's documented here to work on a
also work on a rich-edit-pane, and anything that works on a
will also work on a rich-edit. But note that the converse is not true.
Most measurements here are in points. A point is approximately 1 /
72nd of an inch. This applies even to font sizes, which are usually
measured in pixels in common graphics, but there does not appear to be
a feasible way to convert character formatting measurements to pixels,
and so this difference needs to be kept in mind. Therefore the
(setf font) should not be used to change the
font of a rich-edit-pane, instead the rich-edit functions that change
individual font attributes should be used, such as (setf face) and set-character-format. Note that a
list of available faces is returned by
*system*)). See font-faces, screen, and
character-formatinstance representing the character format of the character just before the current text cursor position in a rich-edit-pane.
character-formatinstance reflecting the character format of the character just before the current text cursor position in a window.
character-formatinstance that is currently the value of the global variable
(setf face)sets the font face of the selected range of text-edit-pane to a specified font face.
(font-faces (screen *system*))returns the available face names that may be passed to
(setf face). See face, font-faces, screen, and
on-format-change is an event handler for rich-edit controls. Its value should be a function that will be called when formatting changes are made.
text-edit-panethat will automatically link up with any rich-edit formatting controls that are either on the same parent or on a toolbar of the parent.
rich-editis the control built on
rich-edit-dialogis a dialog intended for use with rich-edit controls.
multi-picture-buttonthat will automatically link up with any rich-edit controls that are on the same dialog, in order to edit the character and paragraph formatting of the rich-edit control(s).
combo-boxthat will automatically link up with any rich-edit controls that are on the same dialog, in order to edit the font face of the rich-edit control(s).
combo-boxthat will automatically link up with any rich-edit controls that are on the same dialog, in order to edit the font size of the rich-edit control(s). The font-size-combo-box may optionally be on a toolbar.
rich-edit-ruleris a widget that reflects and controls the indentation and tabstops of the selected paragraph(s) of its associated rich-edit control.
character-formatis an object that contains a set of character format parameters.
rich-edit-panethat determines whether jumping to links is enabled.
The ACL Multimedia extension provides a high-level CLOS-based programmatic interface to the standard Windows Media Control Interface (MCI) functionality. Various multimedia devices such as the audio CD player, digital sound files (wave audio), MIDI files, and animation files, can be operated using a small amount of very straightforward lisp code.
Each type of multimedia device is represented by a CLOS class, and each particular multimedia device by a CLOS instance. A CLOS method is supplied for each operation that a device can perform, such as opening, playing, seeking to a new position, or asking about the current state of the device.
The following simple example is all the code that's needed in order to play an audio CD disc from its current position to the end:
(use-package :mci) (setq cd (make-instance 'mci-cd-audio)) (mci-open cd) (mci-play cd)
While cd-audio is a "simple device" which always plays whatever is loaded into the physical cd player, other types of multimedia devices (called "compound devices") can play sounds or animations from various files. For these devices, you can specify the filename to play when opening the device.
The next example would use one logical wave-audio device to play two sound files in succession:
(setq wav (make-instance 'mci-wave-audio)) (mci-open wav :file "c:\\windows\\chord.wav") (mci-play wav :wait-p t) (mci-close wav) (mci-open wav :file "c:\\windows\\tada.wav") (mci-play wav :wait-p t)
The ":wait-p" flag above causes the mci-play function to not return until the sound is finished playing, so that the second file will not be played until the first one is done. By default, mci functions will return asynchronously so that you can continue other processing while the activity requested by the function call is being performed.
A related argument is the ":notify-p" flag. When this is passed, the mci-notify generic function will be called when the operation is complete, with a value indicating whether the operation completed successfully, failed, or was interrupted by the user. You can redefine the mci-notify method for your own subclasses or instances in order to look at the notification and act on it however you wish.
For finer control over device operation, various optional keyword arguments are provided by the methods. For example, to play the first minute of the third track on the cd:
(mci-device-set-time-format cd :tmsf) (mci-play cd :from (tmsf-to-integer 3 0 0 0) :to (tmsf-to-integer 3 1 0 0))
The above code first tells the device to use the track-minute-second-frame (:tmsf) time format for describing its current location, and then tells the CD to play from "track 3 minute 0" to "track 3 minute 1". (The function tmsf-to-integer is required because there are various time formats that use different numbers of components.) To determine the current position of the CD device while it is playing, use:
(multiple-value-setq (track minute second frame) (integer-to-tmsf (mci-device-current-position cd)))
If mci-open is called when no device of the requested class is available, an error occurs with an error message from the operating system indicating why the device could not be opened. A device may not be available either because there is no such device installed on the computer, or because this application or other applications have all such devices open already. Since there is no way to know whether a device is available before attempting to open it, an application may want to trap the error and decide how to proceed rather than simply breaking. For example, the following function will either return an open cd-audio device, or nil to indicate that a cd-audio device could not be opened, along with the explanatory error message as a second value.
(defun get-a-cd-device () (let* ((cd (make-instance 'mci-cd-audio))) (handler-case (mci-open cd) (error (c) (values nil (princ-to-string c))))))
To ensure that devices are available when needed, it is prudent to always close devices by calling mci-close when they are no longer needed. An error will occur if an already-closed device is closed again, so an application needs to either keep track of whether it has closed a device, or else check whether a device is still open. If a device's mci-device-id is 0, then the device is closed; otherwise the device is open, and the mci-device-id will be a positive integer (that value is of no interest to the application other than indicating the device is open).
This section outlines the available set of MCI methods:
Most of these functions return true or nil as the device has or does not have the indicated capability or can or cannot perform the indicated task. Some return more specific information.
These functions return information on the current state of a device.
These functions modify the state of a device.
Multiple application threads can now create windows and handle events and can be debugged from the IDE. In earlier releases, only the single CG/IDE thread could create windows and only a single one could be debugged. Now multiple threads can create independent hierarchies of windows, each in its own thread, without needing to coordinate the activities of each thread in order for one to be responsive when the other is busy.
We advise against creating windows in multiple threads within a single window hierarchy, though, because deadlocks may occur when messages are sent from a window in one thread to a window in another thread within the hierarchy.
The function set-foreground-window makes the thread that created the specified window be the foreground thread, and selects the specified window.
A thread that is to create windows must be set up as follows:
*default-cg-bindings*as the value of the :initial-bindings keyword argument. If other bindings are needed, a union of those bindings with
*default-cg-bindings*may be passed, but of course do not modify the
Note: These steps are not necessary when using the project system to create an application with a single windowing thread (which is typical), because these steps are done automatically for the thread created by the Run | Run Project command in the IDE and by the corresponding initial thread of the generated standalone application.
;; This example simply starts up a thread to create a window, ;; and exits its event-loop (and therefore the thread) when ;; the user closes the window. (mp:process-run-function (list :name "My dummy thread" :initial-bindings cg:*default-cg-bindings*) #'(lambda () (let* ((win (cg:make-window :my-window :owner (cg:screen cg:*system*) :title "A window in its own thread."))) (event-loop :window win))))
;; This example lets the user click the window to specify a position. ;; A list is kept of the positions, and the window draws a circle ;; at each one. As soon as the user adds the third circle, the ;; event-loop exit-test causes the event-loop to exit, and so ;; the thread dies and its window is therefore closed (this ;; will happen before you actually see the third circle). (defclass my-frame (frame-window) ((circle-centers :initform nil :accessor circle-centers))) (defmethod redisplay-window ((window my-frame) &optional box) (declare (ignore box)) (call-next-method) ;; Clear the window (dolist (center (circle-centers window)) (draw-circle window center 50))) (defmethod mouse-left-down ((window my-frame) buttons cursor-pos) (declare (ignore buttons)) (push cursor-pos (circle-centers window)) ;; Add a new circle (invalidate window)) ;; Redraw the window to include the new circle (mp:process-run-function `(:name "Three Circles" :initial-bindings ,*default-cg-bindings*) #'(lambda () (let* ((window (make-window :three-circles :class 'my-frame :owner (screen *system*) :title "Click to give me three circles"))) (event-loop :window window :exit-test #'(lambda (win) (>= (length (circle-centers win)) 3))))))
When the Run | Run Project command in the IDE is invoked, a new thread is created automatically to run the project, and is set up as described above for debugging in the IDE and for handling events.
DDE can now work in multiple threads. See the section About DDE Support.
Multiple threads may simultaneously invoke modal dialogs without interference, even if the two dialogs are the "same" CG utility dialog, such as the ask-user-for-choice-from-list dialog.
Various global objects have been modified to avoid re-entrancy problems when multiple threads enter the same CG functions simultaneously. Among the things modified are many box and position constants. The functions with-boxes, with-positions, and with-positions-and-boxes are provided for applications that similarly need to remove box and position constants.
When the break key is pressed, the restarts dialog will be created and presented in a new thread that exists solely for handling the break; this may avoid problems with interrupting another thread that is in a problematic state.
Before the restarts dialog appears, the process-quantum of every thread is set to 0.1 seconds, to make any threads that are used for debugging more responsive if another thread is in a busy loop. The process quanta are set back to their earlier values when the break thread goes away, which happens when you either abort from the break key's Restarts Dialog or from the backtrace pane that is created for the break in the Debug Window if you select Debug from the restarts dialog.
Also before the restarts dialog appears, any modal dialogs that are currently invoked will be brought to the front. This may help to recover from a possible problem where a modal dialog gets buried and then prevents further work due to its modality.
This break key functionality exists in generated CG applications as
well as in the IDE. If it is not appropriate for a delivered
application, it could be disabled by the application at startup time
with this form which uses remove-global-keyboard-accelerator
and the constant
Multiple threads may be debugged in the IDE. In releases prior to 6.0,
the IDE's backtrace would only be invoked for breaks that occur in the
single CG/IDE thread, while breaks in other cg-related threads would
be handled in the console window or an Emacs window. Now any thread
can be debugged in the IDE if it is set up using
bindings as described above.
Multiple threads are used to prevent user code from hanging the IDE. Instead of a single CG/IDE thread, there is now one thread (named "IDE GUI") for handling user gestures in the IDE and the tools that they invoke, while a second thread (named "Listener 1") is used to evaluate user code. A hang in the evaluator thread will not make the GUI unable to respond, though it may still be rather sluggish if the evaluator is in a very tight loop.
Multiple Listeners may be used. A new View | New Listener menu command allows for the creation of additional all-purpose lisp listeners. Each listener uses an independent thread for evaluations, and has its own command history and backtrace window (when needed). All of the listeners are grouped into a single frame window, with a tab for each listener. The name of the thread and its listener will be "EvaluatorX", where X is a number to make the name unique.
The "Listener 1" listener window always exists while the IDE is running, along with the "Listener 1" thread. Forms evaluated in this listener or elsewhere in the IDE are evaluated in the Listener 1 thread, which is distinct from the "IDE GUI" thread, which handles the actual user gestures in the IDE such as mouse clicks and keypresses (since the IDE windows are created in the IDE GUI thread). The main implication of this is that if the evaluation of a user form is taking a while, the IDE GUI itself will still respond to interactive gestures since these are handled in a different thread (though it may be very sluggish if the evaluation is in a tight loop). To print output to the Listener 1 listener from any thread, the expression
(frame-child (ide-evaluator-listener *system*))
will return the debug-pane that can be printed to as a stream [see below].
The additional listeners may be closed with the File | Close Pane command (control-F4). The initial IDE Evaluator listener, named Listener 1, cannot be closed.
Each thread being debugged will have its own listener and backtrace pane. If a break occurs in one of the Evaluator threads, the listener that already exists for that thread will be used for the backtrace if the debugger is selected from the restarts dialog. For other threads, a new listener will be created, assuming that IDE debugging has been enabled for the thread.
Listeners that are created for debugging a break will go away automatically when the break is aborted or entirely popped out of. When a thread is debugged by clicking the Debug button of the Processes dialog, the listener that is created does evaluations in a new separate "proxy" process, similar to focusing on a thread in non-IDE listener; this is unlike listeners created when a break occurs and is debugged, which do evaluations in the broken thread itself.
Shortcut keystrokes can be used for moving amongst the different listeners and break levels with the keyboard. Shortcut keys are shown on the right-button shortcut menus of the listener tabs.
Dialog modality in user threads will not disable IDE interaction. Only modal dialogs invoked by the IDE itself in the IDE GUI thread will prevent further interaction with the IDE while the modal dialog is present. Modal dialogs invoked by user code will run independently.
The trace dialog reports which thread each call was made in. When a function call is selected in the trace dialog's outline control, the thread in which that call occurred is displayed in the titlebar of the trace dialog.
The View | Debug Window command will generate its new prompt in the main IDE Evaluator listener unless the focus is in a listener already, in which case the prompt is generated in that listener.
The Debug button on the Processes dialog will arrest the selected thread for debugging, and create a new thread with a listener that is focused on the selected thread. The new thread and its listener tab will be named "Proxy for FOO", where FOO is the thread that is focused on. Aborting out of the new listener will unarrest the focused thread.
Because of a design flaw which is turns out to be hard to back out of,
there is a class named
position which is the class
of position objects in Common Graphics. This is a design flaw because
position is a Common Lisp symbol (naming a sequence
function). It is actually outside the ANSI spec to overload Common
Lisp symbols with additional functionality. However, because the
violation is not very serious and because changing it would involve
substantial costs, we have decided to leave the class
position rather than renaming it.
position class is documented here because we
arrange our documentation by package and documenting the
position class with other Common Lisp symbols is
position class is the class of position
objects. A position is created with make-position, and indicates a
location in some coordinate system by specifying its x and y
coordinates. Positions are useful for determining such things as a
window's location or where to draw something on a graphical stream.
Copyright (c) 1998-2000, Franz Inc. Berkeley, CA., USA. All rights reserved. Created 2000.10.5.