|Allegro CL version 8.2|
Moderately revised from 8.1.
This document contains the following sections:1.0 About Common Graphics and IDE documentation
This document provides a high-level set of links to documentation for the Common Graphics (CG) windowing system and the Integrated Development Environment (IDE). It also includes a number of miscellaneous CG and IDE topics that have no other home in the documentation.
Common Graphics is a code library that allows writing windowized graphical user interfaces (GUIs) in applications. The IDE is a set of integrated tools that facilitate the writing and debugging of applications, including CG applications. Both of these tools are available only on Windows, on x86-based Linux platforms with GTK, and on the Mac.
Each object (operator, variable or constant, class) has an HTML page in operators/cg/, variables/cg/, or classes/cg/, 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.
Links to the various CG widget and window classes can be found below in Section 11.0 Widget and window classes.
Information on projects, forms, and menus is found in the Ide User Guide, as follows:
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, Linux (with Intel x86 processors), and the Mac only.
Common Graphics and the IDE runs on Windows, Linux, and Mac OS X. While broadly, CG and the IDE are same on all three platforms, there are some differences. Both Linux and the Mac use GTK. Differences between Windows and GTK are described in cggtk-relnotes.html.
See Functionality to handle differences between Windows and GTK in release-notes.htm.
In addition to being a window for displaying graphical output and
accepting user gestures, a Common Graphics window is also a Common
Lisp stream. This means that text can be printed to a window by
calling CL stream output functions like print and
format, as an alternative to calling a CG function like draw-string-in-box. (And CL stream
input functions work with a few classes such as
Common Graphics streams are interactive streams (see interactive-stream-p), and so you typically will not need to call finish-output or force-output to make printed output appear immediately in a Common Graphics window. But if you call lower-level Common Lisp stream output functions (basically write-string, write-char, and write-sequence) then no force-output is done internally even for interactive streams. After calling such functions on a Common Graphics stream, you should call force-output or finish-output on the stream before subsequently calling any graphics-related Common Graphics functions, or else the sequencing of graphical output may be incorrect. For more information, see Force-output and finish-output policy in streams.htm.
The pre-built lisp images allegro.dxl and allegro-ansi.dxl contain CG and the IDE. (The image supplied with Allegro CL Express is allegro-express and is equivalent to allegro-ansi.dxl.) The IDE (with CG) may be started directly by running the associated executable files, whose names on Windows are allegro.exe and allegro-ansi.exe (allegro-express.exe for Allegro CL Express), and on Linux and the Mac are allegro and allegro-ansi. The "allegro-ansi" images are case-insensitive, as in the ANSI Common Lisp specification, while the "allegro" images are case-sensitive to be more compatible with other modern software. Both use 16-bit characters. There are no pre-built 8-bit character images that contain the IDE. (See Section 5.1 How to create an 8-bit image which starts the IDE for information on starting the IDE in an 8-bit image and information on building an 8-bit image that includes Common Graphics and the IDE and starts the IDE when invoked.)
On Windws, there are menu items on the Allegro CL 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. Starting on Linux and the Mac is similar to starting on any UNIX machine; see Starting on UNIX machines in startup.htm.
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). (You cannot load the IDE into an image on Linux
Express Edition. You must use either the allegro or the
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.
*system*object is created (or recreated if this is a dumplisp'ed image).
*default-cg-bindings*is passed as the :initial-bindings argument to process-run-function when creating this process (this should always be done when creating a process that may create Common Graphics windows or to allow debugging that process in the IDE). The user may create additional similar listeners later by using the View | New Listener command.
(ide-evaluator-listener *system*)(see ide-evaluator-listener) is set to this initial listener pane.
If an error is signaled while loading this file (or the prefs.cl file above), a modal dialog informs you of this and then the IDE continues to start up without loading the rest of the file. Debugging is not possible at this point because the process is not yet in an event-loop to which it can return when aborting from the debugger. You can, however, load the file explicitly after the IDE is running in order to debug it.
nil, then its value is expected to be a list of function names and/or function objects. Each function in the list is funcalled with no arguments in the Listener 1 process.
t. The process that called start-ide could check this variable to know when the IDE has completely finished starting up.
By default, the initial package when the IDE starts up is the
common-graphics-user package, nicknamed
cg-user. The IDE starts with this as the initial
package rather than the more traditional
common-lisp-user package (nicknamed
cl-user) so that IDE users do not need to add
package qualifiers to external Common Graphics symbols, since the
cg-user package uses the cg package in addition to
the packages used by the
cl-user package. On
startup the Debug window prints [changing package from
"common-lisp-user" to "common-graphics-user"], which notes the
change of package. This message is normal and no user action is
necessary. The cg-user package does not use the ide package
starting in release 8.1. ide package symbols must be
One of the entries in the list of
establishes bindings for listeners started by the IDE) binds
*package* to the value returned by
initial-package. As we said, that
value is initially the
package. If you would rather have IDE listeners start up in a
different package, you can set the initial-package configuration
option. You do this with the following steps (we assume you want the
initial package to be the package named
:mypackage with a keyword naming the
package you actually want):
(setf (initial-package (configuration *system*)) :mypackage)
(Alternately, edit the value of the Initial Package widget on the IDE 1 tab of the Options dialog, and press that dialog's OK or Apply button.)
The next time you start Allegro CL with the IDE, all IDE listeners
(find-package :my-package) as the initial
common-graphics-userand this process must be repeated.
As delivered, Allegro CL only provides 16-bit character size images that contain the IDE and start the IDE when invoked. These images are allegro.dxl (and its associated executable allegro.exe), which is modern (case-sensitive), and allegro-ansi.dxl (and its associated executable allegro-ansi.exe), which is ANSI (case-insensitive). See Allegro CL Executables in startup.htm for a general discussion of execuatble and image names.
If you want to run an 8-bit character size image with the IDE, you can start an 8-bit image (alisp8.exe/alisp8.dxl or mlisp8.exe/mlisp8.dxl, that is Start | Programs | Allegro CL 8.0 | ANSI Images | Allegro CL (non Int'l, ANSI) or Start | Programs | Allegro CL 8.0 | Modern Images | Allegro CL (non Int'l, Modern)), and then load the IDE and call start-ide, by evaluating these forms:
(require :ide) (start-ide)
You can also build an 8-bit image that has Common Graphics and the IDE already loaded into it, and that starts up the IDE automatically. Evaluating the following expression, for example, would build an 8-bit Modern IDE:
(progn (build-lisp-image "sys:allegro8.dxl" :build-executable (namestring (translate-logical-pathname "sys:mlisp8.exe")) :include-ide t :restart-init-function 'ide:start-ide :case-mode :case-sensitive-lower) (sys:copy-file "sys:mlisp8.exe" "sys:allegro8.exe" :overwrite t))
That example builds a Modern (case-sensitive) IDE. To build an ANSI (case-insenstive) IDE instead, then you would need to specify the :case-mode argument as :case-insensitive-upper. And to match the names of the 16-bit IDE images, you might want to use the name "allegro8-ansi" in place of "allegro8". On Linux/Unix you should exclude the ".exe" suffixes in either case.
After the build completes, the files allegro8.exe and allegro8.dxl will be in the Allegro directory. You can use the new 8-bit IDE by running allegro8.exe. (As usual, you could run this file by double-clicking on allegro8.exe in the file explorer, or by entering "allegro8" in the proper directory in a command window, or by setting up a shortcut that invokes allegro8.exe).
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.
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 write the bug information to a file. This can be done in several ways:
Whichever you choose, a modal dialog appears asking for a pathname (even if you have chosen Save or Save As before). 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) and prefs.cl 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
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.
The IDE is multi-threaded, so 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 processes and therefore handling their messages in those different processes. 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 inappropriately called
:parentin earlier releases (inappropriate because the argument was only an owner and not a parent when creating an owned top-level window), the
:parentargument still works for compatibility, but
: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).
You can add a form to a project with the File | New Form command. The first form added will typically be 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)) ;; The maker-function, which always creates a new window. ;; Call this function if you need more than one copy, ;; or the single copy should have a parent or owner window. ;; (Pass :owner to this function; :parent is for compatibility.) (defun make-form1 (&key parent (owner (or parent (screen *system*))) (exterior (make-box 256 149 960 519)) (name :form1) (title "Form1") (border :frame) (child-p nil) form-p) (let ((owner (make-window name :owner owner :class 'dialog :exterior exterior :border border :child-p child-p :close-button t :cursor-name :arrow-cursor :font (make-font-ex :swiss "MS Sans Serif / ANSI" 11 nil) :form-state :normal :maximize-button t :minimize-button t :name :form1 :form-package-name nil :path #p"C:\\Program Files\\acl70\\form1.bil" :help-string nil :pop-up nil :resizable t :scrollbars nil :state :normal :status-bar nil :system-menu t :title title :title-bar t :dialog-items (make-form1-widgets) :toolbar nil :form-p form-p :help-string nil))) owner))
Note in the definition of the function make-form1 is a call to
suitable for creating a dialog window (that is, an instance of class
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)) ;; The maker-function, which always creates a new window. ;; Call this function if you need more than one copy, ;; or the single copy should have a parent or owner window. ;; (Pass :owner to this function; :parent is for compatibility.) (defun make-form1 (&key (owner (or parent (screen *system*))) (exterior (make-box 256 149 960 519)) (name :form1) (title "Form1") (border :frame) (child-p nil) form-p) (let ((owner (make-window name :owner owner :class 'dialog :exterior exterior :border border :child-p child-p :close-button t :cursor-name :arrow-cursor :font (make-font-ex :swiss "MS Sans Serif / ANSI" 11 nil) :form-state :normal :maximize-button t :minimize-button t :name :form1 :form-package-name nil :path #p"C:\\Program Files\\acl70\\form1.bil" :help-string nil :pop-up nil :resizable t :scrollbars nil :state :normal :status-bar nil :system-menu t :title title :title-bar t :dialog-items (make-form1-widgets) :toolbar nil :form-p form-p :help-string nil))) owner)) (defun make-form1-widgets () (list (make-instance 'button :font (make-font-ex nil "Tahoma / ANSI" 11 nil) :left 114 :name :button4 :top 80)))
Note that the call to make-window now has another argument provided, :dialog-items. Its value is a call to make-form1-widgets, which is defined (in the file, appearing above as well) as:
(defun make-form1-widgets () (list (make-instance 'button :font (make-font-ex nil "Tahoma / ANSI" 11 nil) :left 114 :name :button4 :top 80)))
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:
(defun make-form1-widgets () (list (make-instance 'button :font (make-font-ex nil "Tahoma / ANSI" 11 nil) :left 114 :name :button4 :on-click 'form1-button4-on-click :title "Here" :top 80 :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.
Multiple application processes can create CG windows, where each process will handle all events for the windows that it creates.
We advise against creating windows in multiple processes within a single window hierarchy, though, because deadlocks may occur when messages are sent from a window in one process to a window in another process within the hierarchy.
The generic function creation-process applied to a window returns the process that called make-window to create the window.
The function set-foreground-window makes the process that created the specified window be the foreground process, and selects the specified window.
A process 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
A convenient way to do the above two steps is to use the macro in-cg-process.
These steps are not necessary when using the project system to create an application with a single windowing process (which is typical), because these steps are done automatically for the process created by the Run | Run Project command in the IDE and by the corresponding initial process of the generated standalone application.
To print debugging output to an IDE listener from a CG process that is neither a listener nor a Run | Run Project process, see format-debug and with-output-to-ide-listener. (Otherwise standard output will be directed to the console window, which is typically hidden.)
;; This example simply starts up a process to create a window, ;; and exits its event-loop (and therefore the process) 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 process 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 process 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 processes. See dde.htm.
Multiple processes 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 processes 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 in the IDE, the IDE will look for a listener process that is currently busy evaluating user code. If it finds such a process, then it calls process-interrupt on that process to tell that process to call break. This allows you to interrupt and debug user code that may be in an infinite loop, for example, or is otherwise taking longer than expected. The rest of this section describes what is done only when a busy listener process is not found.
When the break key is pressed and no busy listener process is found, the Restarts dialog will be created and presented in a new process that exists solely for handling the break; this may avoid problems with interrupting another process that is in a problematic state.
Before the Restarts dialog appears, the process-quantum of every process is set to 0.1 seconds, to make any processes that are used for debugging more responsive if another process is in a busy loop. The process quanta are set back to their earlier values when the break process 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.
Multiple processes may be debugged in the IDE. Any process can be
debugged in the IDE if it is set up using
bindings as described above. See also the section
Section 9.5 Using the IDE while user code is busy.
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 process 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 process and its listener will be "Listener X", 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" process. Forms evaluated in this listener or elsewhere in the IDE are evaluated in the Listener 1 process, which is distinct from the "IDE GUI" process, 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 process). 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 process (though it may be very sluggish if the evaluation is in a tight loop).
The additional listeners may be closed with the File | Close Pane command (control-alt-X). The initial IDE listener, named Listener 1, cannot be closed.
Each process being debugged will have its own listener and backtrace pane. If a break occurs in one of the listener processes, the listener that already exists for that process will be used for the backtrace if the debugger is selected from the Restarts dialog. For other processes, a new listener will be created, assuming that IDE debugging has been enabled for the process.
Listeners that are created for debugging a break will go away automatically when the break is aborted or entirely popped out of. When a process 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 process in non-IDE listener; this is unlike listeners created when a break occurs and is debugged, which do evaluations in the broken process 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 processes will not disable IDE interaction. Only modal dialogs invoked by the IDE itself in the IDE GUI process 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 process each call was made in. When a function call is selected in the trace dialog's outline control, the process 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 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 process for debugging, and create a new process with a listener that is focused on the selected process. The new process and its listener tab will be named "Proxy for FOO", where FOO is the process that is focused on. Aborting out of the new listener will unarrest the focused process.
Multiple processes are used by the IDE to allow IDE windows to be responsive while arbitrary user code is busy executing. This is accomplished by creating all of the IDE windows in the "IDE GUI" process, but evaluating user code in an IDE Listener process such as the initial "Listener 1" process, which is associated with the Debug window. The listener processes are used not only for evalutions in the listener pane itself, but also by IDE commands that involve user code, such as the Tools | Incremental Evaluation and File | Load commands.
The IDE windows will not respond at all while a listener process is busy, however, if there are any open user windows (windows that were created in a listener process) on the IDE owner window (see development-main-window). There are two cases (described below) where a user window may commonly end up on the IDE owner window, and so these cases should be avoided (and any existing user windows on the IDE owner should be closed) at times when it is desirable to use IDE windows while user code is busy running.
The first case is the Run | Run Form command. Run Form creates the running window in an IDE listener process, with the IDE owner window as the owner. This is done so that particular IDE windows may be used alongside the running window without bringing the entire IDE in front of the running window, and so that keyboard shortcuts for IDE commands may be used while the running window is selected. Run Project (also on the Run menu), on the other hand, does not use the IDE owner window, since its purpose is to simulate the final standalone application more closely. So if a widget on a dialog of the current project initiates a long procedure, and it is desirable to use the IDE while this procedure is running, you should use Run Project rather than Run Form. (If you need to interrupt something that is running and the IDE windows are not responsive, you can still do so by right-clicking the Franz icon in the Windows Tray and selecting "Interrupt Lisp".)
The second case involves arbitrary user calls to make-window where no :owner
argument is specified. When this is done in the IDE, the owner of the
new window defaults to the IDE owner window. This default is used for
the same reasons as Run Form above. If it is desirable to use the
IDE while such a window is open and while user code is busy running,
then the window should be created with the screen as its owner by
specifying the value of the :owner argument to make-window as
*system*). See screen and
As an alternative, see process-pending-events on how to use cooperative multitasking, which allows other processes to run in any situation.
As documented in Section 9.0 About using multiple windowing processes in a Common Graphics application above, any process that is set up to create windows and handle the messages that are sent to them needs to call event-loop at the end of its process-run-function preset-function. The process will then spend its remaining time inside event-loop, running code that is triggered by the messages that are sent to any windows that are created in that process. The messages include user mouse and keyboard events as well as messages sent by code that the application is running and messages from the operating system.
This interactive event-driven model requires an application to be designed somewhat differently than one that simply runs from start to end to complete a pre-defined task. We describe two kinds of potential problems that are good to keep in mind when designing an interactive Common Graphics application: problems with message-handling routines that run for a long time and problems with message-handling routines that block.
When mouse and keyboard events (and other messages) are sent to various windows of a Common Graphics application, the messages are held in a queue, and generally the application code that handles each message is run completely before the next message in the queue is handled. This allows the code to run in a predictable order, even though the messages themselves are queued asynchronously.
This can be a problem when the code that handles a message runs for a long time, because no other messages for windows in the same process will be handled until that code returns, and so end users will see no response to their gestures in the meantime. If future user actions might depend on the current routine completing, then not much can be done about this except showing an hourglass cursor (see with-hourglass) or using other cues to tell the user to wait. In this default case, further messages will be queued and handled later in order, and this is how most windowing applications work.
But sometimes it is desirable for the user to be able to go ahead and perform other independent actions (when there are any). One way to do this is to call process-run-function to create a new process that performs the time-consuming operation. Note that if the new process needs to create windows that will handle messages, then it needs to follow the guidelines for creating a Common Graphics process described in Section 9.0 About using multiple windowing processes in a Common Graphics application above; otherwise any ordinary process may be created and used. Another general option is to hand off a command to an existing process, perhaps with process-interrupt or using a custom queue of some sort.
A different approach is to call process-pending-events at frequent intervals in the long-running code, which handles subsequent queued messages at that time. This function can cause unknown messages to be handled in a different order than usual, though, and so it should be used with care.
process-pending-events should not be called when an exclusive resource (such as a process lock) is currently being held, if it is possible that the processing of subsequent events may lead to a request for that resource that will block until the resource is no longer held. If it is the same process that requests the resource again, then this would always cause a deadlock, since the process will not unwind to the earlier use of the resource in order to free it (unless the second request knows how to see that that process already has the resource, and then either proceed or return, as appropriate). Even if the second request is from another process, complex interactions could still lead to deadlock unless this is carefully avoided in the application design.
Similarly, a Common Graphics process that an application creates normally should not grab an exclusive resource in its process-run-function preset-function and not release it before it calls event-loop, as this would hoard the resource for the entire life of the process. Likewise, a project's on-initialization function should avoid returning while holding an exclusive resource.
With either of the above approaches (handing a long-running command off to another process, or calling process-pending-events frequently), it is up to the application to prevent the user from doing an action that depends on the result of an earlier action that has not yet completed.
A less obvious kind of problem may arise due to the fact that there are certain other exceptions to the general rule where one message is handled completely before the next message is handled. In particular, there are certain functions such as process-wait that wait an arbitrary amount of time until some condition is met (this is usually called blocking). When such functions are called in a Common Graphics process, any messages that were already queued or that occur during the waiting period are handled while the call is blocking.
This handling of further messages during blocking is done to avoid failing to respond to arbitrary messages for long periods, including messages sent by other processes, or messages that the operating system may send to all top-level windows, expecting a timely reply. Also, the process may be waiting on a condition that will not be reached until further messages are handled by that same process, and so the process would be hung if further messages were not handled while blocking.
This design means that when a blocking function is called in code that is itself handling a message, later messages are handled while the code handling the current message is still running, and so the messages are not handled totally in the usual order. In particular, if the same type of message occurs again during the waiting period, the code that handles the message may be re-entered a number of times, which the application may not be written to handle.
And normally almost all Common Graphics code in an application is message-handling code (namely everything except the on-initialization function of a standalone application, or setup code that a newly-created Common Graphics process calls before it calls event-loop). So this warning applies to nearly all Common Graphics code in a typical application, if it calls blocking functions that process intervening messages.
The functions and macros that cause intervening messages to be handled immediately include the following:
Other functions (either in Allegro itself or in the application) that call the above functions would exhibit this behavior as well, and there may be other primitive functions in Allegro that behave this way but are not noted here.
A particular kind of deadlock can result if one of these functions is called while holding an exclusive resource of some kind, such as a process lock. For example, say an application has a mouse-in method that grabs a process lock and then calls either process-wait or process-pending-events. If the user moves the mouse into a window a second time and that event is handled while the call to the method for the first mouse-in is still inside the call to process-wait or process-pending-events, then the mouse-in method will be re-entered and wait for the lock. This will deadlock (with the default arguments to with-process-lock) because that event-handling process will wait for the lock forever and therefore never unwind to the first call to the mouse-in method, as it would need to do to release the lock.
The function post-funcall-in-cg-process has been supplied as a general single-process solution to this kind of problem. If code that calls any of the blocking functions listed above is passed to this function instead of being called directly, then the code will be run later after all window messages in this process have been handled and all code that was queued by earlier calls to this function have run and returned. The important point is that all function calls that are passed to post-funcall-in-cg-process for a given process are guarranteed to run sequentially (in the order of posting), eliminating problems that might arise if the calls overlapped. See post-funcall-in-cg-process for more information and an example. Further window messages are handled as usual by the process whenever a queued function is not running, and users will still see response to their interactive gestures while posted function calls are queued or blocking.
The Tree of Knowledge displays the classes associated with windows and widgets: follow Common Graphics | Interface Objects | Windows | Window Classes or Common Graphics | Interface Objects | Controls | Control Classes (and look at the subentries). Here we list the classes for windows and widgets:
A Common Graphics application is typically built as a project in the IDE, and then turned into a standalone application with the IDE's File | Build Project Distribution. See Chapter 4: Projects of the Ide User Guide for information on using projects to create appplications.
You can also create a standalone Common Graphics application with a
direct call to generate-application, but you must make sure
you do everything that needs to be done correctly. Most importantly,
note that it is not sufficient to specify as a value for the
:restart-init-function argument (to generate-application) a function
that would work as the on-initialization function of a project.
Instead, the restart function must do additional setup that would have
been handled automatically by the project system. Generally, the
restart function should do the following:
In addition, the :pre-load-form argument to generate-application should require any needed Common Graphics modules, since these will not be included automatically as the project system would do.
Because of a design flaw which is 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.
An artifact left over from a very early release of Common Graphics is
various mathematical functions named
i<cl-math-function>. In the very earliest release, these
were quite fast on Windows machines because they used 16-bit
arithmetic. With the release of 5.0, the 16-bit operators were
replaced by corresponding regular Common Lisp math functions with
wrapped declarations identifying the arguments as fixnums. These were
named by symbols in the
aclwin package and were not
documented. These symbols are now
in the cg package. Because we document all exported symbols in that
package, we document the i<cl-math-function> operators
here. They are defined for backward compatibility only. Please do not
use them in new code. Instead, use the Common Lisp function determined
by removing the initial i from the symbol name.
i*, i+, i-, i/, i/=, i1+, i1-, i>, i<, i<=, i>=, i=, iabs, iceiling, idecf, ievenp, ifloor, iincf, ilogand, ilogandc1, ilogandc2, ilogbitp, ilogior, ilognand, ilognor, ilognot, ilogorc1, ilogorc2, ilogtest, ilogxor, imin, iminusp, imod, ioddp, iplusp, irem, iround, isquare, itruncate, izerop.
The winapi module contains certain Windows-related functionality that does not need Common Graphics to work. Among the functions are:
It is, of course, possible to write a Windows program that makes direct calls to the Windows API and does not use Common Graphics. Below is an example of a trivial but complete program written using Allegro CL's foreign function interface to the Windows API. Writing directly in the Windows API may be useful if you want to port an existing Windows program from C, or if you want the application to be smaller than it would be if it included Common Graphics. Note that many functions are defined but not documented. Use standard tools (like do-external-symbols and symbol-function) to find the available operators.
This program simply creates a window that draws a box inside itself, but it illustrates how to set up the usual basic structure of a Windows program in Allegro CL. This example runs in a base lisp that does not include Common Graphics. (Note that the package is the cl-user package, not the cg-user package -- which may not exist in the base Lisp. If you run this program in the IDE, be sure to use the cl-user:: package qualifier when necessary.)
The functions above will work in either modern (case-sensitive) Lisp or in ANSI (case-insensitive) Lisp. However, the symbols naming other functions named by symbols in the win package must be escaped if they are to work properly in an ANSI Lisp, as is done in the example below.
;; We have escaped all WIN package symbol names are also all ;; mixed-case keyword in this example. Therefore this code will work in ;; either an ANSI (case-insensitive) Lisp or a modern (case-sensitive) ;; Lisp. The escaping could also be avoided by placing (in-case-mode :local) ;; at the very beginning of the file. (in-package :cl-user) (eval-when (compile load eval) (require :winapi) (require :winapi-dev)) ;;; An arbitrary Windows class name to use for our windows. (defconstant *my-window-class-name* "My Window Class") ;;; Call this function to run the application in a development lisp. (defun run-my-windows-app () ;; Create a new process for this app since it will ;; remain tied up in its message-handling loop. (mp:process-run-function "My Windows App" 'my-windows-app)) ;;; Call this function to run the application as a standalone app. (defun my-windows-app () ;; Register a window class with Microsoft for all of the ;; windows of this application. Their messages will be ;; received by my-window-procedure-callback. (register-window-class *my-window-class-name* 'my-window-procedure-callback) ;; Make an initial top-level window. (let* ((window-handle (with-native-string (native-class-name *my-window-class-name*) (with-native-string (native-window-name "My Window") (with-native-string (dummy-string "x") (win:|CreateWindowEx| 0 ;; extended style native-class-name native-window-name ;; Specify the style of this window. #.(logior win:|WS_CAPTION| ;; title bar win:|WS_SYSMENU| ;; system menu and close button win:|WS_MAXIMIZEBOX| ;; maximize button win:|WS_MINIMIZEBOX|) 100 ;; left 200 ;; top 400 ;; width 300 ;; height 0 ;; parent window (0 is the screen) 0 ;; system menu (lisp-hinstance) dummy-string)))))) ;; value to pass to WM_CREATE ;; Expose the window. (win:|ShowWindow| window-handle win:|SW_SHOWNOACTIVATE|) ;; Give the window the keyboard focus. (win:|SetForegroundWindow| window-handle) ;; Process incoming messages in a loop. (ff:with-stack-fobject (message 'win:|msg|) (loop (unless (win:|GetMessage| message 0 0 0) ;; Exit the application when a WM_QUIT message is ;; received, causing GetMessage to return nil. (return)) ;; Dispatch each message so that it is passed ;; to our window procedure callback function. (win:|DispatchMessage| message)) ;; Return an exit code to the operating system ;; (if this is a standalone app). This is the ;; exit code that we passed to PostQuitMessage. (ff:fslot-value-typed 'win:|msg| :foreign message :wparam)))) (ff:defun-foreign-callable my-window-procedure-callback (window-handle message-number wparam lparam) ;; This is the window procedure that is called for all ;; messages that are sent to "My Window Class" windows. ;; It is very important to use the :stdcall convention for ;; winapi callback functions. Otherwise lisp will crash. (declare (:convention :stdcall)) ;; Make this foreign callback function call a generic function ;; that we can specialize for various messages. (my-window-procedure window-handle message-number wparam lparam)) (defmethod my-window-procedure (handle message wparam lparam) ;; This default message-handling method passes the message back ;; to the operating system to let it do its default behavior. ;; It needs to return whatever the DefWindowProc returns. (win:|DefWindowProc| handle message wparam lparam)) (defmethod my-window-procedure (handle (message (eql win:|WM_DESTROY|)) wparam lparam) (declare (ignore handle wparam lparam)) ;; When our only window is being closed, post a WM_QUIT message. ;; Our call to GetMessage will return nil when it reads this ;; WM_QUIT message, and then we exit our event-handling loop. (win:|PostQuitMessage| 0) ;; Call the default message-handling method to pass ;; the message back to the OS for default behavior. ;; (If we didn't call call-next-method, then we would need ;; to return the proper type of value for this message, ;; which is usually win:|FALSE|.) (call-next-method)) (defmethod my-window-procedure (handle (message (eql win:|WM_PAINT|)) wparam lparam) (declare (ignore wparam lparam)) (declare (optimize (speed 3)(safety 1))) ;; for with-stack-fobject ;; The WM_PAINT message is sent by the operating system whenever ;; we need to redraw all or part of our window. ;; Do nothing if the window currently has no update region. (unless (eq (win:|GetUpdateRect| handle 0 0) 0) ;; Do the standard preparation for painting. (ff:with-stack-fobject (ps 'win:|paintstruct|) (win:|BeginPaint| handle ps) (unwind-protect ;; Find the area of the window that needs to be redrawn. (let* ((left (ff:fslot-value-typed 'win:|paintstruct| :foreign ps :rcPaint :left)) (top (ff:fslot-value-typed 'win:|paintstruct| :foreign ps :rcPaint :top)) (right (ff:fslot-value-typed 'win:|paintstruct| :foreign ps :rcPaint :right)) (bottom (ff:fslot-value-typed 'win:|paintstruct| :foreign ps :rcPaint :bottom))) ;; We're not actually using the refresh area in this ;; simple example, so just ignore it. A real app can ;; be made more efficient by not drawing anything that ;; doesn't intersect this refresh rectangle. (declare (ignore left top right bottom)) ;; Draw and fill a simple rectangle in our window. ;; This illustrates the convolutions required in Windows ;; to simply draw colored lines and areas. (let* ((hdc (win:|GetDC| handle)) (brush (win:|CreateSolidBrush| (win-color 0 255 0))) (pen (win:|CreatePen| win:|PS_SOLID| 1 (win-color 0 0 128))) old-brush old-pen) (unwind-protect (progn (setq old-brush (win:|SelectObject| hdc brush)) (setq old-pen (win:|SelectObject| hdc pen)) (win:|Rectangle| hdc 20 20 100 100)) (win:|SelectObject| hdc old-brush) (win:|SelectObject| hdc old-pen) (win:|DeleteObject| brush) (win:|DeleteObject| pen) (win:|ReleaseDC| handle hdc)))) ;; Do the standard painting cleanup. (win:|EndPaint| handle ps) ;; Always return zero to the OS for WM_PAINT mesages. 0)))) ;;; ------------------------------------------------ ;;; Utility Functions --- These could be used as is. (defun register-window-class (class-name window-procedure &key (style-flags #.(logior win:|CS_OWNDC| win:|CS_DBLCLKS|)) (large-icon-handle (franz-icon-handle)) (small-icon-handle 0)) (declare (optimize (speed 3)(safety 1))) ;; for with-stack-fobject ;; Registers a window class in the Windows OS. ;; Messages sent to windows of this class will call ;; the specified window-procedure callback function. (let* ((hinstance (lisp-hinstance))) (ff:with-stack-fobject (class 'win:|wndclassex|) ;; If we have already registered this Windows class name ;; in this lisp session, then don't don't do so again. (when (with-native-string (native class-name) (win:|GetClassInfo| hinstance native class)) (return-from register-window-class)) ;; Fill the WNDCLASSEX foreign structure with the ;; attributes for our window class. (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :cbSize) (ff:sizeof-fobject 'win:|wndclassex|)) (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :style) style-flags) (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|lpfnWndProc|) (ff:register-foreign-callable window-procedure :reuse :return-value)) (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|cbClsExtra|) 0) (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|cbWndExtra|) win:DLGWINDOWEXTRA) (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|hInstance|) hinstance) (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|hIcon|) large-icon-handle) (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|hCursor|) 0) (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|hbrBackground|) (1+ win:COLOR_WINDOW)) (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|lpszMenuName|) 0) (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|lpszClassName|) (string-to-native class-name)) (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|hIconSm|) small-icon-handle) ;; Register the class, signaling an error on failure. (when (zerop (win:|RegisterClassEx| class)) (error "Failed to register the window class ~s." class-name))))) (defun franz-icon-handle () (with-native-string (native "aclicon") (win:|LoadIcon| (lisp-hinstance) native))) (defun lisp-hinstance () (declare (optimize (speed 3)(safety 1))) ;; for with-stack-fobject ;; Returns the handle of the lisp executable that's running. (ff:with-stack-fobject (vector '(:array :long 4)) (win:|GetWinMainArgs| vector) (ff:fslot-value-typed '(:array :long 4) nil vector 0))) (defun win-color (red green blue) (+ (ash blue 16) (ash green 8) red))
Copyright (c) 1998-2012, Franz Inc. Oakland, CA., USA. All rights reserved.
This page has had moderate revisions compared to the 8.1 page.
|Allegro CL version 8.2|
Moderately revised from 8.1.