Common Graphics Integrated Development Environment

The index for the Allegro CL Documentation is in index.htm. The documentation is described in introduction.htm.

This document contains the following sections:

1.0 About Common Graphics and IDE documentation
2.0 About Menus and Dialogs in the IDE
3.0 Common Graphics and Simple Streams
4.0 About IDE startup
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 event-handling in the IDE
9.0 About color palettes
10.0 About DDE support
11.0 About the Common Graphics timer facility
12.0 About coordinate systems in Common Graphics
   12.1 Coordinate System Unit of Size
   12.2 Coordinate System Location of Origin
13.0 About Rich Text Editing in Common Graphics
   13.1 The Rich Text Interactive Interface
   13.2 The Rich Text Programmatic Interface
14.0 About MCI support
15.0 About using multiple windowing threads in a CG application
   15.1 Modal CG utility dialogs are not shared between threads
   15.2 CG re-entrancy
   15.3 Enhanced Break Key functionality
   15.4 Debugging Multiple Threads in the IDE
16.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

The Common Graphics and IDE documentation has been rearranged in Allegro CL release 6.0. We no longer use a WinHelp file for function, variable, and class definitions. Instead, every file (except for the tutorial documentation) is in HTML, like all other Allegro CL documentation.

Each object (operator, variable or constant, class) has an HTML page in pages/operators/common-graphics/, pages/variables/common-graphics/, or pages/classes/common-graphics/, as appropriate. The index (index.htm) includes Common Graphics symbols.

There are a number of essays, including the IDE User Guide, which are also provided. They are listed here.

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:

Project Manager
Debug Window
Editor Workbook
Process
Trace
Profile
Class Browser
Window List
Clipboard
Package List
Apropos
Find
Replace
Find in Files
Definitions
Options
Menu Editor
Navigator
Inspect
Shortcut Keys
Allegro Tree of Knowledge

Menus:

File menu
Edit menu
Search menu
View menu
Windows menu
Tools menu
Run menu
Form menu
Recent menu
Help menu

3.0 Common Graphics and Simple Streams

Common Graphics streams are now simple-streams. Simple-streams are the new stream implementation in Allegro CL 6.0. They are described in streams.htm.

Because Common Graphics streams are simple-streams, Common Graphics uses the buffered I/O used by simple-streams. This can require force-output to be called to make still-buffered text output visible. Force-output is called internally by Common Graphics after any redisplay-window method runs and before any graphical output is done, to ensure that any text that is drawn onto a Common Graphics window by calling a Common Lisp text output function is always visible when appropriate under normal circumstances. But if an application performs Common Lisp text output to a Common Graphics stream in some other way, then it may need to add explicit calls to force-output.

Another consequence of this change is that a closed Common Graphics stream no longer changes class, but windowp still returns nil for a closed stream for backward compatibility (this name may change later). The print name of a closed Common Graphics stream now includes the string "(closed)".

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. (If you want to run the IDE in an 8-bit image, start an 8-bit image, evaluate (require :ide), and call start-ide with no arguments.)

There are menu items on the Allegro CL 6.0 submenu of the Windows Start menu for Modern Images and ANSI Images. Each has an `Allegro CL (w IDE)' item. Choosing one of those starts Allegro CL and automatically starts the IDE. See Starting on Windows machines in startup.htm for more information on starting Allegro CL on Windows.

The IDE is started by calling start-ide with no arguments. This function is called automatically when running one of the IDE images, or can be called explicitly in a base lisp after requiring the :ide module by evaluating (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, 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. If the value of *session-startup-hook* is a function or a function name, then it is funcalled with no arguments.
  6. All of the variables in (session-variables *system*) are set to nil (see session-variables).
  7. Each function name in (session-init-functions *system*) is funcalled with no arguments (see session-init-functions). (Any members that are not fbound are skipped.) process-pending-events is called before each funcall. This list of functions does the following:
  8. If the value of *after-session-init-functions-hook* is non-nil, then its value is expected to be a function or function name and is funcalled with no arguments.
  9. The "Listener 1" thread is created to evaluate forms in the initial listener pane of the Debug Window, and to evaluate user code when commands such as "Tools | Incremental Eval" and "File | Load" are invoked when this listener is the selected one. *default-cg-bindings* is passed as the :initial-bindings argument to mp:process-run-function when creating this thread (this should always be done when creating a thread that may create Common Graphics windows or to allow debugging that thread in the IDE). The user may create additional similar listeners later by using the View | New Listener command.

    Most of the *default-cg-bindings* are for internal CG variables that need to have a distinct binding in each thread, but one binding of special note is that *package* is bound to the common-graphics-user package (nickname cg-user). The cg-user package uses the cg package and associated packages in addition to the packages used by the common-lisp-user package. This convention differs from the traditional use of the common-lisp-user package in lisp listeners so that IDE users do not need to add package qualifiers to Common Graphics symbols. If a different initial IDE listener package is desired, it could be set up by evaluating a form such as the following (which sets the initial package to common-lisp-user) in one of the earlier startup steps (such as placing it in the startup.cl file or having the function that is the value of *after-session-init-functions-hook* evaluate it):

    (setf (cdr (assoc '*package* *default-cg-bindings* :test #'eq))
          '(find-package :cl-user))
    
  10. 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.
  11. 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. The Listener 1 thread passes top-level-read-eval-print-loop to start-interactive-top-level to enter a read-eval-print loop.
  12. Once the IDE GUI thread has handled the request from the Listener 1 thread to create its listener pane, it 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.

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 invoke either the File | Save command or the File | Save As command. When the modal dialog appears asking for a pathname, select a pathname to write the bug report to. The entire function stack will be saved textually to that file, with brief platform information at the top of the file, and complete dribble-bug (as generated by dribble-bug) information at the bottom. The stack information will include all of the arguments and local variables for each stack frame, regardless of whether you have opened the outline items to show the arguments and variables in the IDE. The bug report that was written will then be shown in the IDE editor for your review.

The stack information will include the normally "hidden" frames only if the "Include Hidden Frames" button on the stack outline's toolbar is currently pressed. Though the additional information is not always necessary, it is best to click on this button before generating the bug report.

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.

In 6.0, where the IDE is multi-threaded, you may find that a user window created on the IDE owner window leads to "message timeout" errors if it interacts with IDE windows in certain ways, due to the windows having been created in different threads and therefore handling their messages in those different threads. If this should happen, the workaround is to not create those user windows on the IDE owner window.

Some related functions: parent returns the parent of a window, while owner returns the owner. windows returns a list of all of the child or owned windows of a window. child-p returns whether a window is a child window. top-level-window returns the top-level ancestor window of a window.

Notes on changes in release 6.0

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))
 
;; Create an instance of the window.
;; Use this if you need more than one instance.
(defun make-form1
    (&key (parent (development-main-window *system*))
     (exterior (make-box 256 149 960 519)) (name :form1)
     (title "Form1") form-p)
  (let ((parent
         (make-window name
           :parent parent
           :device 'dialog
           :exterior exterior
           :border :frame
           :close-button t
           :cursor-name :arrow-cursor
           :form-state :normal
           :maximize-button t
           :minimize-button t
           :name :form1
           :package-name :common-graphics-user
           :pop-up nil
           :resizable t
           :scrollbars nil
           :state :normal
           :status-bar nil
           :system-menu t
           :title title
           :title-bar t
           :toolbar nil
           :form-p form-p
           :path #p"C:\\Program Files\\acl60\\form1.bil"
           :help-string nil
           :package-name :common-graphics-user)))
    parent))

Note in the definition of the function make-form1 is a call to make-window suitable for creating a dialog window (that is, an instance of class dialog). Note that only some of the possible arguments are included. Now, stop running the form and place a button on it by clicking on the button icon on the component toolbar and clicking on the blank form1. Run the form again, saving form1.cl. Again.look at the 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))
 
;; Create an instance of the window.
;; Use this if you need more than one instance.
(defun make-form1
    (&key (parent (development-main-window *system*))
     (exterior (make-box 256 149 960 519)) (name :form1)
     (title "Form1") form-p)
  (let ((parent
         (make-window name
           :parent parent
           :device 'dialog
           :exterior exterior
           :border :frame
           :close-button t
           :cursor-name :arrow-cursor
           :widgets
           (list (make-instance 'button
                   :font
                   (make-font-ex nil "MS Sans Serif" 11 nil)
                   :left 263
                   :name :button4
                   :top 160))
           :form-state :normal
           :maximize-button t
           :minimize-button t
           :name :form1
           :package-name :common-graphics-user
           :pop-up nil
           :resizable t
           :scrollbars nil
           :state :normal
           :status-bar nil
           :system-menu t
           :title title
           :title-bar t
           :toolbar nil
           :form-p form-p
           :path #p"C:\\Program Files\\acl60\\form1.bil"
           :help-string nil
           :package-name :common-graphics-user)))
    parent))

Note that the call to make-window now has another argument provided, :widgets. Its value is a list of one element, an instance of the class button, created with this call to make-instance:

(make-instance 'button
               :font
                 (make-font-ex nil "MS Sans Serif" 11 nil)
               :left 263
               :name :button4
               :top 160))

If you further customize the button, additional arguments will be provided. Here is the call after we have changed the title to "Here", added an on-click event handler, and modified the width from their default values:

(make-instance 'button
               :font
                 (make-font-ex nil "MS Sans Serif" 11 nil)
               :left 263
               :name :button4
               :on-click 'form1-button4-on-click
               :title "Here"
               :top 160
               :width 33))

The title, width, and on-click arguments have all been added.

You can reasonably easily generate similar examples creating forms of various classes and by adding controls to a form, running the form, and looking at the resulting .bil file.

8.0 About event-handling in the IDE

Event-handling is a critical part of any visual application, where users communicate with the application through dialogs and other windows. Events are handled differently for controls than for general windows. Events on controls are discussed in the IDE User Guide, chapter 8 (Chapter 8 of the IDE User Guide). Here we introduce the more general type of event-handling for "regular" windows (non-controls), and point to a set of functions that can be looked up for more detailed information. A simple application may need to supply event-handling code only for controls, while a more complex application will probably need to supply code for non-control windows as well.

Event-Handling for Non-Control Windows

The operating system sends messages to individual Common Graphics windows (either in the IDE or in a standalone application) when events such as mouse clicks and focus movement occur. To respond to these events, an application can supply code that will be called whenever these messages are received.

The type of code to be supplied depends on whether the message is being received by a control or by a regular window. A message to a control is usually handled by an event-handler function that is written especially for a particular control instance. These event-handlers for controls (which are discussed in IDE User Guide, chapter 8) appear on the Events tab of the inspector when a control is being inspected, and skeleton code for an event handler can be generated interactively by clicking on the extended editor button to the right of the event-handler in the inspector.

For non-control windows, on the other hand, messages are handled by methods that typically apply to a whole window class rather than to an individual window instance. The IDE does not have an interface for adding these methods interactively (like the inspector's list of event-handlers for a control), and so you must know what generic function to modify in order to handle a particular message, and write a method from scratch. Normally an application should first define its own subclass of basic-pane (or one of its subclasses), and then define an event-handling method for that subclass. The method will then be called by Common Graphics whenever a window of that subclass receives the corresponding message from the operating system. Here is a list of many of the event-handling generic functions for non-control windows:

For mouse movement:

mouse-in, when the mouse moves into a window
mouse-moved, when the mouse moves within a window
mouse-out, when the mouse moves out of a window

For mouse clicks:

mouse-left-down
mouse-left-up
mouse-middle-down
mouse-middle-up
mouse-right-down
mouse-right-up
mouse-double-click, this is for the left mouse button only
mouse-right-double-click

For non-client-area (window border) mouse movement and clicks:

nc-mouse-moved
nc-mouse-left-down
nc-mouse-left-up
nc-mouse-middle-down
nc-mouse-middle-up
nc-mouse-right-down
nc-mouse-right-up

For keypresses:

virtual-key-down, when a key is pressed down
virtual-key-up, when a key is released
character-message, when a keypress indicates a graphic character

For menus:

about-to-show-menu, when the user invokes a menu
menu-item-highlighted, when the user moves over a menu-item
handle-menu-selection, when the user chooses a menu-item

Miscellaneous:

user-scroll, when the user scrolls a window

Application-Callable:

redisplay-window, when a window is uncovered
set-focus, when the keyboard focus moves to a window
user-close, when the user attempts to close a window
move-window, when the user moves a window
resize-window, when the user resizes a window

The generic functions in the Application-Callable section above may also be called programmatically by an application, in addition to being called by Common Graphics when an interactive event occurs. For example, set-focus may be called by an application to move the keyboard focus to a window, and is also called by Common Graphics when the focus is otherwise moved to a window, such as by clicking in it. An application should keep in mind that when its method for one of these generic functions is called, that it may be due to an interactive gesture by the user or due to a programmatic call.

Any primary methods added to these generic functions should call (call-next-method) unless you are sure that you want to override the default Common Graphics behavior for the event.

Here's an example that handles the virtual-key-down event, since its arguments are a little tricky. This code will create a window that will change its size when the user types control-L, control-semicolon, control-shift-L, or control-shift-semicolon. The buttons argument is some subset of the values of the constants control-key, shift-key, and alt-key logior'ed together (each one is a bit flag). The data argument is an integer for the key that was pressed, expressed either as the char-int of the character that is printed on the key or as the value of one of the "vk-..." constants that are the value of the constant key-names.

(in-package :cg-user)

(defclass my-window (frame-window)())

(defmethod virtual-key-down ((window my-window) buttons data)
   (case buttons
     (#.control-key
      (case data
        (#.vk-semicolon
         (incf (width window) 50))
        (#.(char-int #\L)
         (decf (width window) 50))
        (t (call-next-method))))
     (#.(logior control-key shift-key)
      (case data
        (#.vk-semicolon
         (incf (height window) 50))
        (#.(char-int #\L)
         (decf (height window) 50))
        (t (call-next-method))))
     (t (call-next-method))))

(make-window 'herbert :device 'my-window 
             :parent (screen *system*))

Note that if the example window above were created on (main-development-window *system*) instead of on the screen, then the virtual-key-down method would not get called for the defined keystrokes, because the keystrokes would be overridden by IDE menubar shortcuts. In general, a menubar shortcut will override a virtual-key-down method, and a custom virtual-key-down method will override a comtab binding (since comtab events are implemented as a virtual-key-down method on the general comtab-mixin class).

Handling Low-Level WinAPI Messages

The Common Graphics event-handling generic functions listed above provide a high-level interface to many of the messages sent by the operating system. But since an application may still need to handle low-level Windows events, generic functions also exist for each Windows API message that is handled at all by Common Graphics, and an application may add methods to these functions to handle raw Windows messages.

The name of each generic function is the same as the Windows API constant for the message being received. The functions are in the windows package, and their parameters are

(window wparam lparam)

The arguments are what would be received by a standard Windows "window procedure", except without a parameter for the message name itself, since we have a generic function for each message. We do not document these WinAPI symbols, and provide these generic functions only for programmers who are familiar with the Windows API and can consult Microsoft's documentation on their use. Any primary methods added to these generic functions should call (call-next-method) so that the default method will call the DefaultWindowProc, unless you are sure that you really want to override the operating system's own handling of the event. Here is an example method that would be called whenever an application is activated:

(in-package :cg-user)

(defmethod win:WM_ACTIVATEAPP 
           ((window my-top-level-window-class) wparam lparam)
   (declare (ignore lparam))
   (call-next-method)
   (when (eq wparam win:TRUE)
      (beep)
      (lisp-message "The user has returned to my application!")))

Event-Handling for Controls

The generic functions listed above for non-control windows are not called for controls, as is sometimes expected. There are a few of reasons for this:

For example, the on-change property of a control holds a function that is called whenever the value of the control has changed. Such properties are called event-handlers, and are accessible in the inspector on the Events tab. When an event-handler is selected in the inspector, pressing F1 (to invoke the Help | Help On Selected Symbol command) will display help on that event-handler (rather than on its current value, as would happen in the Internals tab).

(To be precise, there are also exported generic functions for a few of the events received by controls. These are click-event, double-click-event, set-focus-event, and kill-focus-event. The default method for each of these generic functions will call the corresponding event-handler function of the individual widget, but an application could override the default method to define behavior for a whole control subclass. Usually it is preferable to stick with individual event-handlers.)

9.0 About color palettes

A palette holds an arbitrary set of colors for drawing in one or more windows. An application needs to use a palette only if

  1. The end user might be running the Windows operating system in 256-color mode or (perhaps) 65,536-color mode; and
  2. Colors other than the 20 colors in the end user's Control Panel color scheme are needed.

The end user's Control Panel color scheme normally includes the 16 standard VGA colors black, white, gray, light-gray, red, green, blue, yellow, cyan, magenta, dark-red, dark-green, dark-blue, dark-yellow, dark-cyan, and dark-magenta, plus four special colors for button background and 3d edges and so on. The linked names are to constants whose values are the named colors.

By default, calling the function palette on a Common Graphics window returns the symbol :RGB, which indicates that the window has not been given a custom palette. To draw in color on a window that has no custom palette, you simply set the foreground-color or background-color to be an RGB color object (as created by make-rgb named by constants such as blue and red), and then call various drawing functions.

But if Windows is not in true color mode (that is, less than 24-bit color) and the requested RGB color is not one of the 20 system colors (either a VGA color or one of the other four colors in the current Control Panel color scheme), then this actual color will not be used. Instead, for a line-drawing operation, one of the 20 system colors that is nearest to the requested color will be substituted; and for a space-filling operation, each pixel that is drawn will be one of two system colors in such a proportion as to approximate the requested color (this approximation technique is called dithering, and does not look as nice as every pixel being the actual requested color).

The alternative to the above is to define and use a custom color palette. The basic procedure to set up a custom palette is to

  1. Create a vector of RGB color objects.
  2. Create a palette by calling open-palette on that RGB vector.
  3. Assign the palette handle returned by open-palette to a window by calling setf of palette.

To then draw in the window,

  1. setf the current foreground-color and/or background-color of the window to be the index of the desired color in the color vector that you defined earlier.
  2. Call drawing functions on the window.

A palette should be no larger than the number of colors in which the end user is running the Windows operating system. If Windows is running in 256-color mode (8-bit color), for example, then no more than 256 different colors may appear on the screen at any time, and a palette may contain no more than 256 colors.

If multiple windows (including those in other applications) are using palettes, and Windows is running in 256-color mode, then any given window that is using a palette will typically not display the correct colors except when it is the most recently selected window that uses a palette, since the combined palettes will typically contain more colors than the whole system can display.

The set of colors currently being displayed system-wide is sometimes referred to as the system palette. Common Graphics knows to force a window's full palette into the system palette whenever it is selected.

When Windows is running in 256-color mode, it is recommended that a palette have somewhat less than 256 colors, so that when the window is selected, the basic colors used in other windows will still have room in the system palette and so other windows will not switch to odd colors borrowed from your custom palette. A palette of 236 colors or less would still leave room for the user's 20 colors from their Control Panel color scheme.

The Windows OS may be run in 1-bit (monochrome) mode, 4-bit (16-color / VGA) mode, 8-bit (256-color) mode, 16-bit (65,536-color) mode, or 24- or 32-bit (true color) mode, depending on the end user's settings in Control Panel. True color never uses a palette because the 24 bits or more for each pixel is large enough to contain an accurate RGB (red-green-blue) specification directly rather than serving as an index into a smaller table of color definitions. Though most any color computer that is sold nowadays is capable of true color, the end user may still elect to run in 256-color mode either to achieve a greater screen resolution (width by height) or to achieve greater speed of graphical output. So in general it should not be assumed that an end user will run your application in true color unless that is an explicit requirement of the application.

A special case is 16-bit color mode, where the OS appears to use a default palette with millions of colors that approximate true color. In this mode, arbitrary RGB colors may be used for foreground and background colors of Common Graphics windows without using custom palettes, but the colors actually used will still only be approximate (though a much better approximation than dithering). A custom palette may still be needed even in this mode for better color accuracy.

10.0 About DDE support

DDE stands for Dynamic Data Exchange. Quoting from the Windows API Bible (a standard reference for programming with Windows): "Dynamic Data Exchange is a message protocol that allows Windows applications to exchange data."

Here we briefly describe the functionality available in Lisp for DDE. Note that we do not describe DDE in much detail. We assume you are familiar with using DDE between Windows applications. If you are not, please refer to standard Windows programming manuals. Here are some principles of using DDE in Lisp:

Example

There is a simple example illustrating the DDE functionality in examples\cg\dde\examples.cl. Also in that directory is ddedoc.txt which contains most of the information in this entry.

Creating a port where Lisp is the DDE client

Create a client-port instance by calling make-instance on the client-port class. Available initargs are:

These attributes can be changed after the instance is created using setf and the accessors port-name, port-application, and port-topic (all are setf'able). The new values will be used if the port is closed and re-opened.

Example of creating a port:

;; Create a port with the program manager acting as the DDE 
;; server
(setq port1 (make-instance 'client-port
              :application :progman
              :topic :progman))

DDE and Multithreading

Any single DDE port (either a client or server port) will work only within a single thread during the time that it is open, and so all of the application code that uses a particular DDE port should run only in one thread. (A port could be opened and closed in one thread and then later opened and closed in another, but it would likely be tricky to ensure that the threads do not have the port open at the same time.)

The various DDE-related global variables are now bound per-thread, so an application that sets the value of any of these variables should do so in any thread that uses DDE.

In earlier releases, the DDE facility would have worked only in the first thread that attempted to use it, but now separate DDE ports work in separate threads. Allegro CL will continue to automatically initialize DDE as needed whenever a first port is opened, though now this is handled separately in each thread that begins to use DDE.

DDE Functionality

*active-client-ports*
*active-server-ports*
answer-request
*case-sensitive-dde*
close-port
close-server
convert-returned-dde-buffer
convert-returned-dde-string
execute-command
list-to-tabbed-string
open-port
open-server
port-open-p
post-advice
receive-advice
receive-value
send-command
send-request
send-value
*server-active-p*
*service-name*
*service-topics*
*sysitems*
tabbed-string-to-list

11.0 About the Common Graphics timer facility

A timer can be used to cause an arbitrary piece of application code to be asynchronously invoked after a specified amount of time has elapsed. A timer can then be stopped in order to run its code a single time only, or it can be allowed to continue running in order to run its code an indefinite number of times at a regular time interval.

Common Graphics has a timer class, which can be instantiated to create a timer object. A single timer can be started and stopped a number of times, using various time intervals, to time a number of different activities. If multiple activities need to be timed simultaneously, then multiple timers can be created and run alongside each other. It is also convenient to create a separate timer for each piece of code that is to be invoked by timers, even when they do not need to run simultaneously.

A timer is an instance of the timer class, which has the following properties. All of these except for id may be set by an application. See the page for the timer class or for the individual properties for more detailed information.

Functions that apply to all timers

find-timer
start-timer
stop-timer
timer

Functions apply to particular built-in timers

A trivial timer example

;; Define an on-timer event handler 
(defun my-on-timer (timer)
   ;; Show a status bar message each time the timer fires.
   (lisp-message "Timer event ~a for ~s"
     (timer-count timer)(name timer)))

;; Create a timer that uses the above on-timer event-handler.
(setq tim (make-instance 'timer :on-timer 'my-on-timer))

(active tim) ==> nil        ;; the new timer is not active yet

;; Start the timer, telling it to fire every half second
(start-timer tim :interval 500)

(active tim) ==> t          ;; now it's active

(stop-timer tim)            ;; stop it for a while

(setf (active tim) t)       ;; this is another way to start it back up

(setf (interval tim) 800)   ;; slow it down even while it's running

(setf (active tim) nil)     ;; another way to stop it

12.0 About coordinate systems in Common Graphics

Common Graphics uses various terms to describe different ways that it measures coordinates in various contexts, such as window units and stream units. These terms denote coordinates that differ in two ways:

  1. the size of the basic unit of distance
  2. the location of the origin point, where x and y are both zero

The following two sections, 12.1 Coordinate System Unit of Size, and 12.2 Coordinate System Location of Origin, explain each type of difference.

12.1 Coordinate System Unit of Size

Distances, offsets, and sizes in common graphics are usually measured in pixels. For example, the left property of a window is expressed as the number of pixels that fit in a horizontal line between the window's exterior left edge and the interior left edge of its parent window. These pixel-based measurements are referred to as being in device units, since their size depends on the resolution of the display device.

One alternative to measuring in pixel units is to use dialog units, where one dialog unit is equal to one-fourth of a system font character width or one-eighth of a system font character height. Common Graphics allows for the size of a control to be specified in dialog units (by passing the :dialog-units-p initarg to make-instance (or make-window) when creating the control), but we discourage the use of dialog units since the system font (provided by the video display adapter driver) is no longer commonly used, and because common graphics measures arbitrary drawing positions and font sizes and so on in pixels, and it's easier to fit all parts of a graphical interface together if the same units are used for everything.

The other alternative to pixel-size device units is to use a scaling-stream. A scaling stream class is any class that is a subclass of windows-graphics and also a subclass of the scaling-stream mixin class. The basic unit of distance of a scaling stream is initially device units but may be set to any arbitrary size by calling (setf stream-units-per-inch) or (setf stream-units-per-mm).

The only scaling stream class that common graphics supplies is the printer class, but an application may create and use scaling window classes if it is desired to draw in windows using coordinates expressed in some unit other than pixels. It is particularly useful to scale a printer stream to use the same stream-units-per-inch as a window. Then the same drawing code can be used to draw on both the window and the printer at the same size, without the application code needing to scale its own coordinates for each call to a drawing function. This can be achieved by evaluating

(setf (stream-units-per-inch my-printer-stream)
      (stream-units-per-inch my-window))

Note: this technique may cause fatter-than-desired lines since line thicknesses and so on are still limited to integral stream-unit values even though 1 stream-unit of thickness may now be many device pixels thick. Drawing glitches might also occur due to floating-point round-off in the scaling conversion. You also may need to add a fudge factor to this expression, though, since the operating system does not actually know how big the monitor is, and typically estimates it badly. Multiplying by 0.85 is suggested.

The functions device-to-scaling-units and scaling-to-device-units can be used to translate between scaled and non-scaled positions and boxes.

12.2 Coordinate System Location of Origin

Stream coordinates are the application-level coordinates that are normally used for specifying drawing positions on a drawing "stream", which could be either a window or a printer stream or some other two-dimensional output stream. Stream coordinates are also used for specifying child window locations and other application-level positions except where otherwise noted.

Each coordinate indicates the distance rightward or downward from an arbitrary stream-origin. For example, if the stream-origin of a stream is (100, 200), then the leftmost x coordinate in the entire "page" (or canvas) that can be drawn into for the stream will be -100, the topmost y coordinate will be -200, the rightmost x coordinate will be

(- (page-width stream) 100)

and the bottommost y coordinate will be

(- (page-height stream) 200)

The function stream-to-stream-units translates coordinates in one stream to be relative to another stream; this can be done only if the two streams have a common ancestor, such as with windows on a screen, since otherwise no relationship between the two coordinate systems is defined.

Page coordinates are stream coordinates with the arbitrary stream-origin added on, and so each coordinate indicates a distance rightward or downward from the upper-left corner of the entire "page" (or canvas) that is being drawn on. The stream-origin is usually left at its default position of (0, 0) and so page coordinates are usually the same as stream coordinates. But an application may, for example, find it useful to move the stream origin in order to draw a polygon at various places on the page without modifying the individual vertices of the polygon.

Only the origin itself is specified in page coordinates; elsewhere stream coordinates should suffice for "user" positions and device coordinates (introduced next) for "physical" positions.

Device coordinates are page coordinates unscaled and unscrolled; that is, they are always measured in pixels (which are what a "device" such as a screen or printer uses itself), and are relative to the upper-left corner of some "physical" device such as a printed page or window interior. (Actually stream and page coordinates are usually measured in pixels as well, except when the stream is a scaling-stream, for which an arbitrary unit of size may be established. A printer stream is always a scaling-stream, and a window is a scaling-stream if an application has mixed the scaling-stream class into the window's class.)

The functions stream-to-device-units and device-to-stream-units translate between stream and device coordinates.

When working in a window, device coordinates are also called window coordinates and are relative to the upper-left of the physical window interior rather than to the upper-left of the virtual canvas (or page) on which the application draws.

The functions stream-to-device-units and device-to-stream-units translate between stream and window coordinates just as they do with device coordinates generally. The function window-to-window-units translates window coordinates of one window into window coordinates of another window.

When working in a window or on the screen itself, screen coordinates are relative to the upper-left corner of the physical screen. The functions window-to-screen-units and screen-to-window-units translate between window and screen coordinates.

13.0 About Rich Text Editing in Common Graphics

13.1 The Rich Text Interactive Interface

Getting Started

Invoke the File | New Form menu command and select rich-edit-dialog from the list of choices. This will make a rich-edit-dialog form and will automatically add a rich-edit control and an associated rich-edit-ruler just above it to the form, and also add auxiliary widgets in a toolbar for use with the rich-edit control.

This dialog will act as a complete WordPad™-like application. If you would like to add additional controls to the form or toolbar, you can do so. The rich-edit controls appear at the right end of the component toolbar. Note that if multiple rich-edit controls are added to a single form, the single set of rich-edit helper controls works for all of the rich-edit controls on the same parent window, reflecting the rich-edit that most recently had the keyboard focus.

Try running the initial rich-edit form (with that form selected, click on Run | Run Form), then clicking the Open button in the toolbar and selecting the file cg\rich-edit-sample.rtf (in the cg subdirectory of the main Allegro directory). This should show some sample rich text.

Building Rich Edit Forms from Scratch

To test making your own rich edit dialog from scratch, invoke the File | New Form command, and select dialog from the list of window classes that you can create. Double click the interior of the new form to inspect it.

If you would like to start the menu-bar off with the special rich-edit commands, then go to the menu property of the form in the inspector and enter #.(rich-edit-menubar) in the inspector line for the menu property. (The #. reader macro is a trick to evaluate the expression typed directly into a line of the inspector.) This should add the standard rich-edit menu-bar to the form, and you can further edit this menu as you like.

Back on the inspector, toggle the toolbar property on to give the form a toolbar. Also toggle the status-bar property to give the form a status-bar for messages. Next, click on the Rich-Edit button of the Component Toolbar (the one with a big green "R"), and then click in the main interior of your form to create a rich-edit control. You may want to size the rich-edit control larger, since the default size is rather small. Now click on each of the next three rich-edit "helper" controls on the Component Toolbar and instantiate those widgets from left to right on the toolbar of your new form. (The helper controls consist of the rich-edit-multipic, the font-face-combo-box, and the font-size-combo-box. Finally, click on the Ruler button on the Component Toolbar, position it just above the rich-edit control, and then drag it or stretch it partly over the rich-edit as needed to make it snap into place along the top.

Having done this, you now have a dialog that is functionally similar to the one created by using the rich-edit-dialog class. You can customize either however you like by adding additional controls and editing the initial rich-edit menubar. Note that only a rich-edit-dialog has the built-in feature of prompting the user to save any unsaved rich-edit controls when a closing gesture is made.

Special Features

While the rich edit functionality basically allows for custom WordPad™-like applications, there are a couple of features that are somewhat unique:

Multiple editor panes. The rich-edit helper controls will automatically keep track of which rich-edit control most recently had the keyboard focus, and apply any editing commands to that rich-edit. Also, as the focus moves from one rich-edit to another, the rich edit helper controls will update themselves to reflect the current rich-edit, just as they update to reflect the currently selected text within a single rich-edit.

Copying formatting. The Edit menu (on the rich-edit-dialog) has a couple of items called Copy Format and Paste Format that allow you to easily copy character formatting rather than text. Just select some text (or position the text cursor), invoke Edit | Copy Format, then select some other text, and invoke Edit | Paste Format (again, these commands are on the Edit menu on the rich-edit-dialog, not on the Allegro CL Edit menu) The second text range will now have the character formatting of the first text range.

13.2 The Rich Text Programmatic Interface

Package of symbols

All symbols documented here are exported from the common-graphics (cg) package, since this is an extension to common graphics.

Widgets (controls) versus windows

Most of these commands work on a window (the new rich-edit-pane and/or the older text-edit-pane) rather than on a widget. If you are using a widget instead (the new rich-edit control or the older multi-line-editable-text control), then you first need to call the function window on the control to retrieve the window of the control, and then pass that to the function that expects a window.

Rich-edit-specific classes versus plain-text classes

Much of this functionality works using the pre-existing text-edit-pane windows and multi-line-editable-text controls. The corresponding newer classes, rich-edit-pane and the rich-edit control, are provided mostly for automatically linking up with text-formatting controls, for determining which type of text is pasted into a given control, and other potential circumstances in which the controls will need to default either to rich or plain text. Thus, anything that's documented here to work on a text-edit-pane will also work on a rich-edit-pane, and anything that works on a multi-line-editable-text will also work on a rich-edit. But note that the converse is not true.

Units of measurement

Most measurements here are in points. A point is approximately 1 / 72nd of an inch. This applies even to font sizes, which are usually measured in pixels in common graphics, but there does not appear to be a feasible way to convert character formatting measurements to pixels, and so this difference needs to be kept in mind. Therefore the functions font and (setf font) should not be used to change the font of a rich-edit-pane, instead the rich-edit functions that change individual font attributes should be used, such as (setf face) and set-character-format. Note that a list of available faces is returned by (font-faces (screen *system*)). See font-faces, screen, and *system*.

Text-Formatting Functions --- Multiple Format Parameters

Text-Formatting Functions --- Individual Format Parameters

Responding to Format Changes

on-format-change is an event handler for rich-edit controls. Its value should be a function that will be called when formatting changes are made.

Rich Edit Classes

Accessing Rich Text Strings

Functions for reading and Writing Rich Text Files

Clipboard-Related Functions

Support for Hypertext Links

Miscellaneous Functionality

14.0 About MCI support

The ACL Multimedia extension provides a high-level CLOS-based programmatic interface to the standard Windows Media Control Interface (MCI) functionality. Various multimedia devices such as the audio CD player, digital sound files (wave audio), MIDI files, and animation files, can be operated using a small amount of very straightforward lisp code.

Each type of multimedia device is represented by a CLOS class, and each particular multimedia device by a CLOS instance. A CLOS method is supplied for each operation that a device can perform, such as opening, playing, seeking to a new position, or asking about the current state of the device.

The following simple example is all the code that's needed in order to play an audio CD disc from its current position to the end:

(use-package :mci)
(setq cd (make-instance 'mci-cd-audio))
(mci-open cd)
(mci-play cd)

While cd-audio is a "simple device" which always plays whatever is loaded into the physical cd player, other types of multimedia devices (called "compound devices") can play sounds or animations from various files. For these devices, you can specify the filename to play when opening the device.

The next example would use one logical wave-audio device to play two sound files in succession:

(setq wav (make-instance 'mci-wave-audio))
(mci-open wav :file "c:\\windows\\chord.wav")
(mci-play wav :wait-p t)
(mci-close wav)
(mci-open wav :file "c:\\windows\\tada.wav")
(mci-play wav :wait-p t)

The ":wait-p" flag above causes the mci-play function to not return until the sound is finished playing, so that the second file will not be played until the first one is done. By default, mci functions will return asynchronously so that you can continue other processing while the activity requested by the function call is being performed.

A related argument is the ":notify-p" flag. When this is passed, the mci-notify generic function will be called when the operation is complete, with a value indicating whether the operation completed successfully, failed, or was interrupted by the user. You can redefine the mci-notify method for your own subclasses or instances in order to look at the notification and act on it however you wish.

For finer control over device operation, various optional keyword arguments are provided by the methods. For example, to play the first minute of the third track on the cd:

(mci-device-set-time-format cd :tmsf)
(mci-play cd :from (tmsf-to-integer 3 0 0 0)
             :to (tmsf-to-integer 3 1 0 0))

The above code first tells the device to use the track-minute-second-frame (:tmsf) time format for describing its current location, and then tells the CD to play from "track 3 minute 0" to "track 3 minute 1". (The function tmsf-to-integer is required because there are various time formats that use different numbers of components.) To determine the current position of the CD device while it is playing, use:

(multiple-value-setq (track minute second frame)
  (integer-to-tmsf (mci-device-current-position cd)))

If mci-open is called when no device of the requested class is available, an error occurs with an error message from the operating system indicating why the device could not be opened. A device may not be available either because there is no such device installed on the computer, or because this application or other applications have all such devices open already. Since there is no way to know whether a device is available before attempting to open it, an application may want to trap the error and decide how to proceed rather than simply breaking. For example, the following function will either return an open cd-audio device, or nil to indicate that a cd-audio device could not be opened, along with the explanatory error message as a second value.

(defun get-a-cd-device ()
  (let* ((cd (make-instance 'mci-cd-audio)))
    (handler-case (mci-open cd)
      (error (c)
        (values nil (princ-to-string c))))))

To ensure that devices are available when needed, it is prudent to always close devices by calling mci-close when they are no longer needed. An error will occur if an already-closed device is closed again, so an application needs to either keep track of whether it has closed a device, or else check whether a device is still open. If a device's mci-device-id is 0, then the device is closed; otherwise the device is open, and the mci-device-id will be a positive integer (that value is of no interest to the application other than indicating the device is open).

This section outlines the available set of MCI methods:

Basic operations

Capability inquiries

Most of these functions return true or nil as the device has or does not have the indicated capability or can or cannot perform the indicated task. Some return more specific information.

Status inquiries

These functions return information on the current state of a device.

Setting device status

These functions modify the state of a device.

15.0 About using multiple windowing threads in a CG 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 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 the section About DDE Support.

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

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

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

15.4 Debugging Multiple Threads in the IDE

Multiple threads may be debugged in the IDE. In releases prior to 6.0, the IDE's backtrace would only be invoked for breaks that occur in the single CG/IDE thread, while breaks in other cg-related threads would be handled in the console window or an Emacs window. Now any thread can be debugged in the IDE if it is set up using *default-cg-bindings* initial bindings as described above.

Multiple threads are used to prevent user code from hanging the IDE. Instead of a single CG/IDE thread, there is now one thread (named "IDE GUI") for handling user gestures in the IDE and the tools that they invoke, while a second thread (named "Listener 1") is used to evaluate user code. A hang in the evaluator thread will not make the GUI unable to respond, though it may still be rather sluggish if the evaluator is in a very tight loop.

Multiple Listeners may be used. A new View | New Listener menu command allows for the creation of additional all-purpose lisp listeners. Each listener uses an independent thread for evaluations, and has its own command history and backtrace window (when needed). All of the listeners are grouped into a single frame window, with a tab for each listener. The name of the thread and its listener will be "EvaluatorX", where X is a number to make the name unique.

The "Listener 1" listener window always exists while the IDE is running, along with the "Listener 1" thread. Forms evaluated in this listener or elsewhere in the IDE are evaluated in the Listener 1 thread, which is distinct from the "IDE GUI" thread, which handles the actual user gestures in the IDE such as mouse clicks and keypresses (since the IDE windows are created in the IDE GUI thread). The main implication of this is that if the evaluation of a user form is taking a while, the IDE GUI itself will still respond to interactive gestures since these are handled in a different thread (though it may be very sluggish if the evaluation is in a tight loop). To print output to the Listener 1 listener from any thread, the expression

(frame-child (ide-evaluator-listener *system*)) 

will return the debug-pane that can be printed to as a stream [see below].

The additional listeners may be closed with the File | Close Pane command (control-F4). The initial IDE Evaluator listener, named Listener 1, cannot be closed.

Each thread being debugged will have its own listener and backtrace pane. If a break occurs in one of the Evaluator threads, the listener that already exists for that thread will be used for the backtrace if the debugger is selected from the restarts dialog. For other threads, a new listener will be created, assuming that IDE debugging has been enabled for the thread.

Listeners that are created for debugging a break will go away automatically when the break is aborted or entirely popped out of. When a thread is debugged by clicking the Debug button of the Processes dialog, the listener that is created does evaluations in a new separate "proxy" process, similar to focusing on a thread in non-IDE listener; this is unlike listeners created when a break occurs and is debugged, which do evaluations in the broken thread itself.

Shortcut keystrokes can be used for moving amongst the different listeners and break levels with the keyboard. Shortcut keys are shown on the right-button shortcut menus of the listener tabs.

Dialog modality in user threads will not disable IDE interaction. Only modal dialogs invoked by the IDE itself in the IDE GUI thread will prevent further interaction with the IDE while the modal dialog is present. Modal dialogs invoked by user code will run independently.

The trace dialog reports which thread each call was made in. When a function call is selected in the trace dialog's outline control, the thread in which that call occurred is displayed in the titlebar of the trace dialog.

The View | Debug Window command will generate its new prompt in the main IDE Evaluator listener unless the focus is in a listener already, in which case the prompt is generated in that listener.

The Debug button on the Processes dialog will arrest the selected thread for debugging, and create a new thread with a listener that is focused on the selected thread. The new thread and its listener tab will be named "Proxy for FOO", where FOO is the thread that is focused on. Aborting out of the new listener will unarrest the focused thread.

16.0 About the position class

Because of a design flaw which is turns out to be hard to back out of, there is a class named position which is the class of position objects in Common Graphics. This is a design flaw because position is a Common Lisp symbol (naming a sequence function). It is actually outside the ANSI spec to overload Common Lisp symbols with additional functionality. However, because the violation is not very serious and because changing it would involve substantial costs, we have decided to leave the class position rather than renaming it.

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-2000, Franz Inc. Berkeley, CA., USA. All rights reserved. Created 2000.10.5.