ToCDocOverviewCGDocRelNotesIndexPermutedIndex
Allegro CL version 6.2
Moderately revised from 6.1

Common Graphics Integrated Development Environment

This document contains the following sections:

1.0 About Common Graphics and IDE documentation
2.0 About Menus and Dialogs in the IDE
3.0 Common Graphics and Simple Streams
4.0 About IDE startup
   4.1 How to create an 8-bit image which starts the IDE
5.0 About submitting a bug report from a break in the IDE
6.0 About child, parent, and owner windows
7.0 About how to get sample code for creating controls
8.0 About using multiple windowing threads in a Common Graphics application
   8.1 Modal CG utility dialogs are not shared between threads
   8.2 CG re-entrancy
   8.3 Enhanced Break Key functionality
   8.4 Debugging Multiple Threads in the IDE
   8.5 Using the IDE while user code is busy
9.0 About design considerations for event-driven applications
   9.1 Message-handling routines that run for a long time
   9.2 Message-handling routines that block
10.0 About the position class

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.



1.0 About Common Graphics and IDE 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.

The essays on projects, forms, and menus (formerly in doc/project/project.htm, doc/form/form.htm, doc/menu/menu.htm) have been merged into the Ide User Guide, as follows:



2.0 About Menus and Dialogs in the IDE

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.

Dialogs:

Menus:



3.0 Common Graphics and Simple Streams

Common Graphics streams are simple-streams. Simple-streams 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)".

open-stream is 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 the form (open-stream 'printer) and a bitmap-stream could be created with (open-stream 'bitmap-stream). 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.



4.0 About IDE startup

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. (See Section 4.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.)

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.

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 (require :ide). When start-ide is called, it performs the following steps:

  1. If the command line that started the lisp contains the -batch flag (see Command line arguments in startup.htm), then start-ide exits immediately, returning nil, and nothing else is done.
  2. The global variable *starting-ide* is set to t.
  3. Multiprocessing is started (if necessary), and the "IDE GUI" process is created. start-ide returns the new IDE GUI thread. The rest of the startup procedure is performed in the IDE GUI thread.
  4. The *system* object is created (or recreated it if this is a dumplisp'ed image).
  5. Common Graphics is initialized, and the main IDE owner window (see development-main-window) and the IDE menu-bar window are created.
  6. If a user prefs file exists, or if no user prefs file exists, if the file prefs.cl exists in the main Allegro directory, it is loaded to restore configuration options that were saved most recently in an earlier IDE session. (See the description of save-options-to-user-specific-file for information on user prefs files.) If no prefs file exists, default settings are used.
  7. The IDE toolbars and status-bar are added according to the display-toolbars and display-status-bar configuration options.
  8. If an initial project was specified either by using the "-project" command line argument or by double-clicking a .lpr project definition file to start the IDE, then open-project is called to open that existing project. If no initial project was specified, then a new empty project is created instead.
  9. Additional IDE windows are created if the following configuration options are true:
  10. The "Listener 1" thread is created to evaluate expressions in the initial lisp listener pane of the Debug Window, and to evaluate user code when commands such as Tools | Incremental Evaluation and File | Load are invoked when this listener is the selected one. *default-cg-bindings* is passed as the :initial-bindings argument to 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.
  11. The IDE GUI thread enters an event-handling loop to handle events for IDE windows, which are created in this thread. An abort restart around the loop ensures that the IDE GUI thread re-enters this event-handling loop when it is reset or otherwise aborted.
  12. The Listener 1 thread tells the IDE GUI thread to create its listener pane. This follows the convention where all IDE windows are created in the IDE GUI thread so that their events are all received and handled in that thread. The value of (ide-evaluator-listener *system*) (see ide-evaluator-listener) is set to this initial listener pane.
  13. If the file startup.cl exists in the main allegro directory, then the Listener 1 thread loads this file. Users may create this file to evaluate arbitrary forms whenever the IDE is started. (If the file startup.fasl exists and is not older than startup.cl, then startup.fasl is loaded instead.)

    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 thread 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.

  14. If the value of *ide-startup-hook* is non-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 thread.
  15. The Listener 1 thread sets the variable *starting-ide* to nil. The thread that called start-ide could check this variable to know when the IDE has completely finished starting up.
  16. The Listener 1 thread passes top-level-read-eval-print-loop to start-interactive-top-level to enter a read-eval-print loop. As with other lisp listeners, an abort restart ensures that the read-eval-print loop continues whenever this thread is reset or otherwise aborted.

Note on the initial package

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.

One of the entries in the list of *default-cg-bindings* (which establishes bindings for listeners started by the IDE) binds *package* to value returned by initial-package. As we said, that value is initially the common-graphics-user 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 -- replace :mypackage with a keyword naming the package you actually want):

  1. Start Allegro CL and the IDE. (The initial package will be the common-graphics-user package).
  2. Evaluate the following form:
    (setf (initial-package (configuration *system*)) :mypackage)
    
  3. Choose the menu command Tools | Save Options Now.

The next time you start Allegro CL with the IDE, all IDE listeners will have (find-package :my-package) as the initial value of *package*.

Note that:


4.1 How to create an 8-bit image which starts the IDE

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 6.2 | ANSI Images | Allegro CL (non Int'l, ANSI) or Start | Programs | Allegro CL 6.2 | 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 image with Common Graphics and the IDE already loaded which invokes the IDE on startup. You do that by loading a file named by #p"sys:cg;build-allegro8.cl" (i.e. the file [Allegro directory]/cg/build-allegro8.cl) and invoking the function excl::build-allegro8 defined in that file.

To build a modern (case-sensitive) image, evaluate (excl::build-allegro8). To build an ANSI (case-insensitive) image, evaluate (excl::build-allegro8 :ansi t).

Once excl::build-allegro8 completes, the files allegro8.exe and allegro8.dxl (if you built a modern image) or allegro8-ansi.exe and allegro8-ansi.dxl (if you built a ANSI image) will be in the Allegro directory. You can invoke them in by double-clicking on either the exe or the dxl file in the Windows explorer, or in any standard Windows way of starting a program.



5.0 About submitting a bug report from a break in the IDE

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 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.



6.0 About child, parent, and owner windows

Some definitions

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:

  1. Child windows, where the parent is a window. Such windows are created by passing an existing window as the value of the :owner keyword argument to make-window, and true as the value of the :child-p argument. (Note that the :child-p argument defaults to t, so it's not necessary to specify a value when creating a child window.)
  2. Owned top-level windows, where the owner is a window but the parent is the screen. These windows appear to be independent, moving freely about on the desktop (screen), but still have an owner window with which they shrink and so on. Such windows are created by passing an existing window as the value of the :owner argument to make-window, and passing nil as the value of the :child-p argument. 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.
  3. Non-owned top-level windows, where both the owner and parent are the screen. These windows are truly independent, and have their own icons in the Windows taskbar and alt-tab window (unless their border property is :palette). Create a non-owned top-level window by passing (screen *system*) as the value of the :owner argument to make-window (in which case the :child-p argument will be essentially ignored).

In the IDE, the default value of the :owner argument to make-window is (development-main-window *system*), which is the invisible owner window of the various IDE dialogs (see development-main-window and *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, specify (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 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.

Some Notes



7.0 About how to get sample code for creating controls

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))
 
;; 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\\acl62\\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 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 form1.bil file:

;;;
;;; 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\\acl62\\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.



8.0 About using multiple windowing threads in a Common Graphics application

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 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 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:

  1. When creating the thread by calling process-run-function, pass *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 *default-cg-bindings* list.
  2. At the end of the preset-function passed to process-run-function, enter an event-handling loop by calling event-loop. This allows any messages that are sent to windows that are created in this thread to be handled. Typically a "main window" is passed to event-loop so that the event-loop and its process will exit when the user has closed the specified window.

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.

Trivial example

;; 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))))

Simple example

;; 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 cg-dde.htm.


8.1 Modal CG utility dialogs are not shared between threads

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.


8.2 CG re-entrancy

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.


8.3 Enhanced Break Key functionality

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 vk-pause:

(cg:remove-global-keyboard-accelerator cg:vk-pause)

8.4 Debugging Multiple Threads in the IDE

Multiple threads may be debugged in the IDE. Any thread can be debugged in the IDE if it is set up using *default-cg-bindings* initial bindings as described above. See also the section Section 8.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 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.


8.5 Using the IDE while user code is busy

Multiple threads 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" thread, but evaluating user code in an IDE Listener thread such as the initial "Listener 1" thread, which is associated with the Debug window. The listener threads 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 thread is busy, however, if there are any open user windows (windows that were created in a listener thread) 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 thread, 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 (screen *system*). See screen and *system*.

As an alternative, see process-pending-events on how to use cooperative multitasking, which allows other threads to run in any situation.



9.0 About design considerations for event-driven applications

As documented in Section 8.0 About using multiple windowing threads 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.


9.1 Message-handling routines that run for a long time

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 8.0 About using multiple windowing threads 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 thread, 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.


9.2 Message-handling routines that block

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-enterred 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-enterred 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.



10.0 About the position class

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.

The position class is documented here because we arrange our documentation by package and documenting the position class with other Common Lisp symbols is inappropriate.

The 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-2002, Franz Inc. Oakland, CA., USA. All rights reserved.
Documentation for Allegro CL version 6.2. This page has had moderate revisions compared to the 6.1 page.
Created 2002.2.26.

ToCDocOverviewCGDocRelNotesIndexPermutedIndex
Allegro CL version 6.2
Moderately revised from 6.1