ToCDocOverviewCGDocRelNotesFAQIndexPermutedIndex
Allegro CL version 8.2
Moderately revised from 8.1.
8.1 version

Common Graphics and the 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 Platform-specific information
4.0 Common Graphics Streams
5.0 About IDE startup
   5.1 How to create an 8-bit image which starts the IDE
6.0 About submitting a bug report from a break in the IDE
7.0 About child, parent, and owner windows
8.0 About how to get sample code for creating controls
9.0 About using multiple windowing processes in a Common Graphics application
   9.1 Modal CG utility dialogs are not shared between processes
   9.2 CG re-entrancy
   9.3 Enhanced Break Key functionality
   9.4 Debugging Multiple Processes in the IDE
   9.5 Using the IDE while user code is busy
10.0 About design considerations for event-driven applications
   10.1 Message-handling routines that run for a long time
   10.2 Message-handling routines that block
11.0 Widget and window classes
12.0 About creating a Standalone Common Graphics Application without using the Project System
13.0 About the position class
14.0 The I[cl-math-function] functions
Appendix A. The Windows API and a Windows API program with windows but without CG

The IDE and Common Graphics are supported on the Windows SMP (Symmetric Multi Processing) Lisp but are not supported on the SMP version of Allegro CL 8.2 on the Mac and on Linux. They are supported on the non-SMP version of Allegro CL 8.2 on those platforms. The feature :smp is on the features list in the SMP version and not in the non-SMP version.

This document provides a high-level set of links to documentation for the Common Graphics (CG) windowing system and the Integrated Development Environment (IDE). It also includes a number of miscellaneous CG and IDE topics that have no other home in the documentation.

Common Graphics is a code library that allows writing windowized graphical user interfaces (GUIs) in applications. The IDE is a set of integrated tools that facilitate the writing and debugging of applications, including CG applications. Both of these tools are available only on Windows, on x86-based Linux platforms with GTK, and on the Mac.

Symbols in common-graphics are generally accessible in the :cg package, though their actual home package might be a subpackage (in the hierarchical package sense) of the :cg package. Symbols naming IDE functionality which are not intended to be present in applications are in the :ide package. One normally starts an image (named allegro.dxl or something similar) which includes both common-graphics and the IDE, but (require :ide) and (require :cg) will load the relevant modules into an image where they are not present.



1.0 About Common Graphics and IDE documentation

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

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

Links to the various CG widget and window classes can be found below in Section 11.0 Widget and window classes.

Information on projects, forms, and menus is found in the Ide User Guide, as follows:



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, Linux (with Intel x86 processors), and the Mac only.

Dialogs:

Menus:



3.0 Platform-specific information

Common Graphics and the IDE runs on Windows, Linux, and Mac OS X. While broadly, CG and the IDE are same on all three platforms, there are some differences. Both Linux and the Mac use GTK. Differences between Windows and GTK are described in cggtk-relnotes.html.

Linux-specific details

See Functionality to handle differences between Windows and GTK in release-notes.htm.

Mac-specific differences



4.0 Common Graphics Streams

In addition to being a window for displaying graphical output and accepting user gestures, a Common Graphics window is also a Common Lisp stream. This means that text can be printed to a window by calling CL stream output functions like print and format, as an alternative to calling a CG function like draw-string-in-box. (And CL stream input functions work with a few classes such as text-edit-pane.)

Common Graphics streams are interactive streams (see interactive-stream-p), and so you typically will not need to call finish-output or force-output to make printed output appear immediately in a Common Graphics window. But if you call lower-level Common Lisp stream output functions (basically write-string, write-char, and write-sequence) then no force-output is done internally even for interactive streams. After calling such functions on a Common Graphics stream, you should call force-output or finish-output on the stream before subsequently calling any graphics-related Common Graphics functions, or else the sequencing of graphical output may be incorrect. For more information, see Force-output and finish-output policy in streams.htm.



5.0 About IDE startup

The pre-built lisp images allegro.dxl and allegro-ansi.dxl contain CG and the IDE. (The image supplied with Allegro CL Express is allegro-express and is equivalent to allegro-ansi.dxl.) The IDE (with CG) may be started directly by running the associated executable files, whose names on Windows are allegro.exe and allegro-ansi.exe (allegro-express.exe for Allegro CL Express), and on Linux and the Mac are allegro and allegro-ansi. The "allegro-ansi" images are case-insensitive, as in the ANSI Common Lisp specification, while the "allegro" images are case-sensitive to be more compatible with other modern software. Both use 16-bit characters. There are no pre-built 8-bit character images that contain the IDE. (See Section 5.1 How to create an 8-bit image which starts the IDE for information on starting the IDE in an 8-bit image and information on building an 8-bit image that includes Common Graphics and the IDE and starts the IDE when invoked.)

On Windws, there are menu items on the Allegro CL submenu of the Windows Start menu for Modern Images and ANSI Images. Each has an `Allegro CL (w IDE)' item. Choosing one of those starts Allegro CL and automatically starts the IDE. See Starting on Windows machines in startup.htm for more information on starting Allegro CL on Windows. Starting on Linux and the Mac is similar to starting on any UNIX machine; see Starting on UNIX machines in startup.htm.

The IDE is started by calling start-ide with no arguments. This function is called automatically when running one of the IDE images, or can be called explicitly in a base lisp after requiring the :ide module by evaluating (require :ide). (You cannot load the IDE into an image on Linux Express Edition. You must use either the allegro or the allegro-ansi executable/image.)

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. Multiprocessing is started (if necessary), and the "IDE GUI" process is created. start-ide returns the new IDE GUI process. The rest of the startup procedure is performed in the IDE GUI process.
  3. The *system* object is created (or recreated if this is a dumplisp'ed image).
  4. Common Graphics is initialized, and the main IDE owner window (see development-main-window) and the IDE menu-bar window are created.
  5. 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.
  6. The IDE toolbars and status-bar are added according to the display-toolbars and display-status-bar configuration options.
  7. 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 the IDE starts without a current project. If there were recent projects previously open, a dialog appears asking if you want to open any of them. Canceling causes the IDE to open without a project.
  8. Additional IDE windows are created if the following configuration options are true:
  9. The "Listener 1" process 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 process (this should always be done when creating a process that may create Common Graphics windows or to allow debugging that process in the IDE). The user may create additional similar listeners later by using the View | New Listener command.
  10. The IDE GUI process enters an event-handling loop to handle events for IDE windows, which are created in this process. An abort restart around the loop ensures that the IDE GUI process re-enters this event-handling loop when it is reset or otherwise aborted.
  11. The Listener 1 process tells the IDE GUI process to create its listener pane. This follows the convention where all IDE windows are created in the IDE GUI process so that their events are all received and handled in that process. The value of (ide-evaluator-listener *system*) (see ide-evaluator-listener) is set to this initial listener pane.
  12. If the file startup.cl exists in the main allegro directory, then the Listener 1 process 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 process is not yet in an event-loop to which it can return when aborting from the debugger. You can, however, load the file explicitly after the IDE is running in order to debug it.

  13. 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 process.
  14. The Listener 1 process sets the variable *ide-is-running* to t. The process that called start-ide could check this variable to know when the IDE has completely finished starting up.
  15. The Listener 1 process 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 process 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. The cg-user package does not use the ide package starting in release 8.1. ide package symbols must be package-qualified.

One of the entries in the list of *default-cg-bindings* (which establishes bindings for listeners started by the IDE) binds *package* to the value returned by initial-package. As we said, that value is initially the 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)
    

    (Alternately, edit the value of the Initial Package widget on the IDE 1 tab of the Options dialog, and press that dialog's OK or Apply button.)

  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:


5.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 8.0 | ANSI Images | Allegro CL (non Int'l, ANSI) or Start | Programs | Allegro CL 8.0 | Modern Images | Allegro CL (non Int'l, Modern)), and then load the IDE and call start-ide, by evaluating these forms:

(require :ide)
(start-ide)

You can also build an 8-bit image that has Common Graphics and the IDE already loaded into it, and that starts up the IDE automatically. Evaluating the following expression, for example, would build an 8-bit Modern IDE:

(progn
  (build-lisp-image
   "sys:allegro8.dxl"
   :build-executable (namestring (translate-logical-pathname
                                  "sys:mlisp8.exe"))
   :include-ide t
   :restart-init-function 'ide:start-ide
   :case-mode :case-sensitive-lower)
  (sys:copy-file "sys:mlisp8.exe" "sys:allegro8.exe"
                 :overwrite t))

That example builds a Modern (case-sensitive) IDE. To build an ANSI (case-insenstive) IDE instead, then you would need to specify the :case-mode argument as :case-insensitive-upper. And to match the names of the 16-bit IDE images, you might want to use the name "allegro8-ansi" in place of "allegro8". On Linux/Unix you should exclude the ".exe" suffixes in either case.

After the build completes, the files allegro8.exe and allegro8.dxl will be in the Allegro directory. You can use the new 8-bit IDE by running allegro8.exe. (As usual, you could run this file by double-clicking on allegro8.exe in the file explorer, or by entering "allegro8" in the proper directory in a command window, or by setting up a shortcut that invokes allegro8.exe).



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

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.



7.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 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 processes and therefore handling their messages in those different processes. If this should happen, the workaround is to not create those user windows on the IDE owner window.

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

Some Notes



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

You can add a form to a project with the File | New Form command. The first form added will typically be labeled form1. If you click Run | Run Project, files named form1.cl and form1.bil are saved (along with project1.lpr, which does not concern us here). Here is the contents of form1.bil (slightly edited, note: do not try to run this code as it has pathnames likely invalid on your machine):

;;;
;;; Define :form1
 
(in-package :common-graphics-user)

;; Return the window, creating it the first time or when it's closed.
;; Use only this function if you need only one instance.
(defun form1 () (find-or-make-application-window :form1 'make-form1))
 
;; The maker-function, which always creates a new window.
;; Call this function if you need more than one copy,
;; or the single copy should have a parent or owner window.
;; (Pass :owner to this function; :parent is for compatibility.)
(defun make-form1
    (&key parent (owner (or parent (screen *system*)))
     (exterior (make-box 256 149 960 519)) (name :form1)
     (title "Form1") (border :frame) (child-p nil) form-p)
  (let ((owner
         (make-window name
           :owner owner
           :class 'dialog
           :exterior exterior
           :border border
           :child-p child-p
           :close-button t
           :cursor-name :arrow-cursor
           :font (make-font-ex :swiss "MS Sans Serif / ANSI" 11 nil)
           :form-state :normal
           :maximize-button t
           :minimize-button t
           :name :form1
           :form-package-name nil
           :path #p"C:\\Program Files\\acl70\\form1.bil"
           :help-string nil
           :pop-up nil
           :resizable t
           :scrollbars nil
           :state :normal
           :status-bar nil
           :system-menu t
           :title title
           :title-bar t
           :dialog-items (make-form1-widgets)
           :toolbar nil
           :form-p form-p
           :help-string nil)))
    owner))

Note in the definition of the function make-form1 is a call to 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\\acl70\\form1.bil"
           :help-string nil
           :pop-up nil
           :resizable t
           :scrollbars nil
           :state :normal
           :status-bar nil
           :system-menu t
           :title title
           :title-bar t
           :dialog-items (make-form1-widgets)
           :toolbar nil
           :form-p form-p
           :help-string nil)))
    owner))

(defun make-form1-widgets ()
  (list (make-instance 'button :font
                       (make-font-ex nil "Tahoma / ANSI" 11 nil) :left
                       114 :name :button4 :top 80)))

Note that the call to make-window now has another argument provided, :dialog-items. Its value is a call to make-form1-widgets, which is defined (in the file, appearing above as well) as:

(defun make-form1-widgets ()
  (list (make-instance 'button :font
                       (make-font-ex nil "Tahoma / ANSI" 11 nil) :left
                       114 :name :button4 :top 80)))

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

(defun make-form1-widgets ()
  (list (make-instance 'button :font
                       (make-font-ex nil "Tahoma / ANSI" 11 nil) :left
                       114 :name :button4 :on-click
                       'form1-button4-on-click :title "Here" :top 80
                       :width 33)))

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

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



9.0 About using multiple windowing processes in a Common Graphics application

Multiple application processes can create CG windows, where each process will handle all events for the windows that it creates.

We advise against creating windows in multiple processes within a single window hierarchy, though, because deadlocks may occur when messages are sent from a window in one process to a window in another process within the hierarchy.

The generic function creation-process applied to a window returns the process that called make-window to create the window.

The function set-foreground-window makes the process that created the specified window be the foreground process, and selects the specified window.

A process that is to create windows must be set up as follows:

  1. When creating the process 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 process 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.

A convenient way to do the above two steps is to use the macro in-cg-process.

These steps are not necessary when using the project system to create an application with a single windowing process (which is typical), because these steps are done automatically for the process created by the Run | Run Project command in the IDE and by the corresponding initial process of the generated standalone application.

To print debugging output to an IDE listener from a CG process that is neither a listener nor a Run | Run Project process, see format-debug and with-output-to-ide-listener. (Otherwise standard output will be directed to the console window, which is typically hidden.)

Trivial example

;; This example simply starts up a process to create a window,
;; and exits its event-loop (and therefore the process) when
;; the user closes the window.

(mp:process-run-function
 (list :name "My dummy thread"
       :initial-bindings cg:*default-cg-bindings*)
 #'(lambda ()
     (let* ((win (cg:make-window :my-window
                   :owner (cg:screen cg:*system*)
                   :title "A window in its own thread.")))
       (event-loop :window win))))

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 process dies and its window is therefore closed (this
;; will happen before you actually see the third circle).

(defclass my-frame (frame-window)
  ((circle-centers :initform nil :accessor circle-centers)))

(defmethod redisplay-window ((window my-frame) &optional box)
  (declare (ignore box))
  (call-next-method) ;; Clear the window
  (dolist (center (circle-centers window))
    (draw-circle window center 50)))

(defmethod mouse-left-down ((window my-frame) buttons cursor-pos)
  (declare (ignore buttons))
  (push cursor-pos (circle-centers window)) ;; Add a new circle
  (invalidate window)) ;; Redraw the window to include the new circle

(mp:process-run-function
 `(:name "Three Circles" :initial-bindings ,*default-cg-bindings*)
 #'(lambda ()
     (let* ((window (make-window :three-circles
                      :class 'my-frame
                      :owner (screen *system*)
                      :title "Click to give me three circles")))
       (event-loop :window window
                   :exit-test
                   #'(lambda (win)
                       (>= (length (circle-centers win)) 3))))))

When the Run | Run Project command in the IDE is invoked, a new process is created automatically to run the project, and is set up as described above for debugging in the IDE and for handling events.

DDE can now work in multiple processes. See dde.htm.


9.1 Modal CG utility dialogs are not shared between processes

Multiple processes may simultaneously invoke modal dialogs without interference, even if the two dialogs are the "same" CG utility dialog, such as the ask-user-for-choice-from-list dialog.


9.2 CG re-entrancy

Various global objects have been modified to avoid re-entrancy problems when multiple processes enter the same CG functions simultaneously. Among the things modified are many box and position constants. The functions with-boxes, with-positions, and with-positions-and-boxes are provided for applications that similarly need to remove box and position constants.


9.3 Enhanced Break Key functionality

When the break key is pressed in the IDE, the IDE will look for a listener process that is currently busy evaluating user code. If it finds such a process, then it calls process-interrupt on that process to tell that process to call break. This allows you to interrupt and debug user code that may be in an infinite loop, for example, or is otherwise taking longer than expected. The rest of this section describes what is done only when a busy listener process is not found.

When the break key is pressed and no busy listener process is found, the Restarts dialog will be created and presented in a new process that exists solely for handling the break; this may avoid problems with interrupting another process that is in a problematic state.

Before the Restarts dialog appears, the process-quantum of every process is set to 0.1 seconds, to make any processes that are used for debugging more responsive if another process is in a busy loop. The process quanta are set back to their earlier values when the break process goes away, which happens when you either abort from the break key's Restarts dialog or from the backtrace pane that is created for the break in the Debug Window if you select Debug from the Restarts dialog.

Also before the Restarts dialog appears, any modal dialogs that are currently invoked will be brought to the front. This may help to recover from a possible problem where a modal dialog gets buried and then prevents further work due to its modality.


9.4 Debugging Multiple Processes in the IDE

Multiple processes may be debugged in the IDE. Any process 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 9.5 Using the IDE while user code is busy.

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

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

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

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

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

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

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

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

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

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


9.5 Using the IDE while user code is busy

Multiple processes are used by the IDE to allow IDE windows to be responsive while arbitrary user code is busy executing. This is accomplished by creating all of the IDE windows in the "IDE GUI" process, but evaluating user code in an IDE Listener process such as the initial "Listener 1" process, which is associated with the Debug window. The listener processes are used not only for evalutions in the listener pane itself, but also by IDE commands that involve user code, such as the Tools | Incremental Evaluation and File | Load commands.

The IDE windows will not respond at all while a listener process is busy, however, if there are any open user windows (windows that were created in a listener process) on the IDE owner window (see development-main-window). There are two cases (described below) where a user window may commonly end up on the IDE owner window, and so these cases should be avoided (and any existing user windows on the IDE owner should be closed) at times when it is desirable to use IDE windows while user code is busy running.

The first case is the Run | Run Form command. Run Form creates the running window in an IDE listener process, with the IDE owner window as the owner. This is done so that particular IDE windows may be used alongside the running window without bringing the entire IDE in front of the running window, and so that keyboard shortcuts for IDE commands may be used while the running window is selected. Run Project (also on the Run menu), on the other hand, does not use the IDE owner window, since its purpose is to simulate the final standalone application more closely. So if a widget on a dialog of the current project initiates a long procedure, and it is desirable to use the IDE while this procedure is running, you should use Run Project rather than Run Form. (If you need to interrupt something that is running and the IDE windows are not responsive, you can still do so by right-clicking the Franz icon in the Windows Tray and selecting "Interrupt Lisp".)

The second case involves arbitrary user calls to make-window where no :owner argument is specified. When this is done in the IDE, the owner of the new window defaults to the IDE owner window. This default is used for the same reasons as Run Form above. If it is desirable to use the IDE while such a window is open and while user code is busy running, then the window should be created with the screen as its owner by specifying the value of the :owner argument to make-window as (screen *system*). See screen and *system*.

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



10.0 About design considerations for event-driven applications

As documented in Section 9.0 About using multiple windowing processes in a Common Graphics application above, any process that is set up to create windows and handle the messages that are sent to them needs to call event-loop at the end of its process-run-function preset-function. The process will then spend its remaining time inside event-loop, running code that is triggered by the messages that are sent to any windows that are created in that process. The messages include user mouse and keyboard events as well as messages sent by code that the application is running and messages from the operating system.

This interactive event-driven model requires an application to be designed somewhat differently than one that simply runs from start to end to complete a pre-defined task. We describe two kinds of potential problems that are good to keep in mind when designing an interactive Common Graphics application: problems with message-handling routines that run for a long time and problems with message-handling routines that block.


10.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 9.0 About using multiple windowing processes in a Common Graphics application above; otherwise any ordinary process may be created and used. Another general option is to hand off a command to an existing process, perhaps with process-interrupt or using a custom queue of some sort.

A different approach is to call process-pending-events at frequent intervals in the long-running code, which handles subsequent queued messages at that time. This function can cause unknown messages to be handled in a different order than usual, though, and so it should be used with care.

process-pending-events should not be called when an exclusive resource (such as a process lock) is currently being held, if it is possible that the processing of subsequent events may lead to a request for that resource that will block until the resource is no longer held. If it is the same process that requests the resource again, then this would always cause a deadlock, since the process will not unwind to the earlier use of the resource in order to free it (unless the second request knows how to see that that process already has the resource, and then either proceed or return, as appropriate). Even if the second request is from another process, complex interactions could still lead to deadlock unless this is carefully avoided in the application design.

Similarly, a Common Graphics process that an application creates normally should not grab an exclusive resource in its process-run-function preset-function and not release it before it calls event-loop, as this would hoard the resource for the entire life of the process. Likewise, a project's on-initialization function should avoid returning while holding an exclusive resource.

With either of the above approaches (handing a long-running command off to another process, or calling process-pending-events frequently), it is up to the application to prevent the user from doing an action that depends on the result of an earlier action that has not yet completed.


10.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-entered a number of times, which the application may not be written to handle.

And normally almost all Common Graphics code in an application is message-handling code (namely everything except the on-initialization function of a standalone application, or setup code that a newly-created Common Graphics process calls before it calls event-loop). So this warning applies to nearly all Common Graphics code in a typical application, if it calls blocking functions that process intervening messages.

The functions and macros that cause intervening messages to be handled immediately include the following:

Other functions (either in Allegro itself or in the application) that call the above functions would exhibit this behavior as well, and there may be other primitive functions in Allegro that behave this way but are not noted here.

A particular kind of deadlock can result if one of these functions is called while holding an exclusive resource of some kind, such as a process lock. For example, say an application has a mouse-in method that grabs a process lock and then calls either process-wait or process-pending-events. If the user moves the mouse into a window a second time and that event is handled while the call to the method for the first mouse-in is still inside the call to process-wait or process-pending-events, then the mouse-in method will be re-entered and wait for the lock. This will deadlock (with the default arguments to with-process-lock) because that event-handling process will wait for the lock forever and therefore never unwind to the first call to the mouse-in method, as it would need to do to release the lock.

The function post-funcall-in-cg-process has been supplied as a general single-process solution to this kind of problem. If code that calls any of the blocking functions listed above is passed to this function instead of being called directly, then the code will be run later after all window messages in this process have been handled and all code that was queued by earlier calls to this function have run and returned. The important point is that all function calls that are passed to post-funcall-in-cg-process for a given process are guarranteed to run sequentially (in the order of posting), eliminating problems that might arise if the calls overlapped. See post-funcall-in-cg-process for more information and an example. Further window messages are handled as usual by the process whenever a queued function is not running, and users will still see response to their interactive gestures while posted function calls are queued or blocking.



11.0 Widget and window classes

The Tree of Knowledge displays the classes associated with windows and widgets: follow Common Graphics | Interface Objects | Windows | Window Classes or Common Graphics | Interface Objects | Controls | Control Classes (and look at the subentries). Here we list the classes for windows and widgets:

Windows classes

cg-stream
  graphical-stream
    bitmap-stream
    printer
    screen-stream
      basic-pane
        bitmap-pane
        frame-window
          dialog
            html-browser
            object-editor-pane
            rich-edit-dialog
            status-bar
            toolbar
          form
          frame-with-single-child
            bitmap-window
            non-refreshing-window
            object-editor
            text-edit-window
              lisp-edit-window
        non-refreshing-pane
          transparent-pane
        widget-window
          lisp-widget-window
            grid-drawing-pane
              class-grid-drawing-pane
            lisp-widget-top-window
              chart-or-plot-pane
                chart-widget-pane
              drawable-pane
              grid-top-pane
                class-grid-top-pane
              group-box-pane
              lamp-pane
              multi-picture-button-pane
              outline-widget-pane
                outline-display-pane
                outline-pane
                  outline-dropping-pane
                  outline-top-pane
              rich-edit-ruler-pane
              scrolling-static-text-pane
          ocx-widget-window
            html-widget-pane
              html-widget-pane-for-browser
          os-widget-window
            combo-box-pane
            common-status-bar
            header-control-pane
            item-list-pane
              multi-item-list-pane
              single-item-list-pane
            list-view-pane
            progress-indicator-pane
            scroll-bar-pane
              horizontal-scroll-bar-pane
              vertical-scroll-bar-pane
            tab-control-pane
            text-edit-pane
              lisp-edit-pane
              rich-edit-pane
            text-widget-pane
              editable-text-pane
                lisp-text-pane
              static-text-pane
            toggling-widget-pane
              button-pane
              check-box-pane
              picture-widget-pane
                picture-button-pane
                static-picture-pane
              radio-button-pane
            trackbar-pane
            up-down-control-pane
      screen
  menu
    menu-bar
    pop-up-menu
      pull-down-menu
      shortcut-menu

Widget classes

dialog-item
  lisp-widget
    chart-or-plot
      chart-widget
    drawable
    grid-widget
      class-grid
        slot-editing-class-grid
    group-box
      lisp-group-box
    lamp
    multi-picture-button
      rich-edit-multipic
    outline
      dropping-outline
    rich-edit-ruler
    scrolling-static-text
  ocx-widget
    html-widget
      html-widget-for-browser
  os-widget
    combo-box
      rich-edit-combo-box
        font-face-combo-box
        font-size-combo-box
    header-control
    item-list
      multi-item-list
      single-item-list
    list-view
    progress-indicator
    scroll-bar
      horizontal-scroll-bar
      vertical-scroll-bar
    tab-control
    text-widget
      editable-text
        lisp-text
        multi-line-editable-text
          multi-line-lisp-text
          rich-edit
      static-text
    toggling-widget
      button
        cancel-button
        default-button
      check-box
      picture-widget
        picture-button
        static-picture
      radio-button
    trackbar
    up-down-control


12.0 About creating a Standalone Common Graphics Application without using the Project System

A Common Graphics application is typically built as a project in the IDE, and then turned into a standalone application with the IDE's File | Build Project Distribution. See Chapter 4: Projects of the Ide User Guide for information on using projects to create appplications.

You can also create a standalone Common Graphics application with a direct call to generate-application, but you must make sure you do everything that needs to be done correctly. Most importantly, note that it is not sufficient to specify as a value for the :restart-app-function or :restart-init-function argument (to generate-application) a function that would work as the on-initialization function of a project. Instead, the restart function must do additional setup that would have been handled automatically by the project system. Generally, the restart function should do the following:

  1. Call initialize-cg.
  2. Create any initial windows and other setup that would be done by a project's on-initialization function.
  3. Call event-loop to handle events until the application exits.
  4. Call excl:exit, passing the return code that was returned from the call to event-loop.

In addition, the :pre-load-form argument to generate-application should require any needed Common Graphics modules, since these will not be included automatically as the project system would do.



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



14.0 The I[cl-math-function] functions

An artifact left over from a very early release of Common Graphics is various mathematical functions named i<cl-math-function>. In the very earliest release, these were quite fast on Windows machines because they used 16-bit arithmetic. With the release of 5.0, the 16-bit operators were replaced by corresponding regular Common Lisp math functions with wrapped declarations identifying the arguments as fixnums. These were named by symbols in the aclwin package and were not documented. These symbols are now in the cg package. Because we document all exported symbols in that package, we document the i<cl-math-function> operators here. They are defined for backward compatibility only. Please do not use them in new code. Instead, use the Common Lisp function determined by removing the initial i from the symbol name.

i*, i+, i-, i/, i/=, i1+, i1-, i>, i<, i<=, i>=, i=, iabs, iceiling, idecf, ievenp, ifloor, iincf, ilogand, ilogandc1, ilogandc2, ilogbitp, ilogior, ilognand, ilognor, ilognot, ilogorc1, ilogorc2, ilogtest, ilogxor, imin, iminusp, imod, ioddp, iplusp, irem, iround, isquare, itruncate, izerop.



Appendix A: The Windows API and a Windows API program with windows but without CG

The winapi module contains certain Windows-related functionality that does not need Common Graphics to work. Among the functions are:

It is, of course, possible to write a Windows program that makes direct calls to the Windows API and does not use Common Graphics. Below is an example of a trivial but complete program written using Allegro CL's foreign function interface to the Windows API. Writing directly in the Windows API may be useful if you want to port an existing Windows program from C, or if you want the application to be smaller than it would be if it included Common Graphics. Note that many functions are defined but not documented. Use standard tools (like do-external-symbols and symbol-function) to find the available operators.

This program simply creates a window that draws a box inside itself, but it illustrates how to set up the usual basic structure of a Windows program in Allegro CL. This example runs in a base lisp that does not include Common Graphics. (Note that the package is the cl-user package, not the cg-user package -- which may not exist in the base Lisp. If you run this program in the IDE, be sure to use the cl-user:: package qualifier when necessary.)

The functions above will work in either modern (case-sensitive) Lisp or in ANSI (case-insensitive) Lisp. However, the symbols naming other functions named by symbols in the win package must be escaped if they are to work properly in an ANSI Lisp, as is done in the example below.


;; We have escaped all WIN package symbol names are also all
;; mixed-case keyword in this example. Therefore this code will work in
;; either an ANSI (case-insensitive) Lisp or a modern (case-sensitive)
;; Lisp.  The escaping could also be avoided by placing (in-case-mode :local)
;; at the very beginning of the file.

(in-package :cl-user)

(eval-when (compile load eval)
  (require :winapi)
  (require :winapi-dev))

;;; An arbitrary Windows class name to use for our windows.
(defconstant *my-window-class-name* "My Window Class")

;;; Call this function to run the application in a development lisp.
(defun run-my-windows-app ()
  
  ;; Create a new process for this app since it will
  ;; remain tied up in its message-handling loop.
  (mp:process-run-function "My Windows App" 'my-windows-app))

;;; Call this function to run the application as a standalone app.
(defun my-windows-app ()
  
  ;; Register a window class with Microsoft for all of the
  ;; windows of this application.  Their messages will be
  ;; received by my-window-procedure-callback.
  (register-window-class *my-window-class-name*
                         'my-window-procedure-callback)
  
  ;; Make an initial top-level window.
  (let* ((window-handle
          (with-native-string (native-class-name *my-window-class-name*)
            (with-native-string (native-window-name "My Window")
              (with-native-string (dummy-string "x")
                (win:|CreateWindowEx|
                 0       ;; extended style
                 native-class-name
                 native-window-name
                 
                 ;; Specify the style of this window.
                 #.(logior
                    win:|WS_CAPTION|     ;; title bar
                    win:|WS_SYSMENU|     ;; system menu and close button
                    win:|WS_MAXIMIZEBOX| ;; maximize button
                    win:|WS_MINIMIZEBOX|)
                 
                 100     ;; left
                 200     ;; top
                 400     ;; width
                 300     ;; height
                 0       ;; parent window (0 is the screen)
                 0       ;; system menu
                 (lisp-hinstance)
                 dummy-string)))))) ;; value to pass to WM_CREATE
    
    ;; Expose the window.
    (win:|ShowWindow| window-handle win:|SW_SHOWNOACTIVATE|)
    
    ;; Give the window the keyboard focus.
    (win:|SetForegroundWindow| window-handle)
    
    ;; Process incoming messages in a loop.
    (ff:with-stack-fobject (message 'win:|msg|)
      (loop (unless (win:|GetMessage| message 0 0 0)
            
              ;; Exit the application when a WM_QUIT message is
              ;; received, causing GetMessage to return nil.
              (return))
            
            ;; Dispatch each message so that it is passed
            ;; to our window procedure callback function.
            (win:|DispatchMessage| message))
    
      ;; Return an exit code to the operating system
      ;; (if this is a standalone app).  This is the
      ;; exit code that we passed to PostQuitMessage.
      (ff:fslot-value-typed 'win:|msg| :foreign message :wparam))))

(ff:defun-foreign-callable my-window-procedure-callback
    (window-handle message-number wparam lparam)
  
  ;; This is the window procedure that is called for all
  ;; messages that are sent to "My Window Class" windows.
  
  ;; It is very important to use the :stdcall convention for
  ;; winapi callback functions.  Otherwise lisp will crash.
  (declare (:convention :stdcall))
  
  ;; Make this foreign callback function call a generic function
  ;; that we can specialize for various messages.
  (my-window-procedure window-handle message-number wparam lparam))

(defmethod my-window-procedure (handle message wparam lparam)
  
  ;; This default message-handling method passes the message back
  ;; to the operating system to let it do its default behavior.
  ;; It needs to return whatever the DefWindowProc returns.
  (win:|DefWindowProc| handle message wparam lparam))

(defmethod my-window-procedure (handle (message (eql win:|WM_DESTROY|))
                                       wparam lparam)
  (declare (ignore handle wparam lparam))
  
  ;; When our only window is being closed, post a WM_QUIT message.
  ;; Our call to GetMessage will return nil when it reads this
  ;; WM_QUIT message, and then we exit our event-handling loop.
  (win:|PostQuitMessage| 0)
  
  ;; Call the default message-handling method to pass
  ;; the message back to the OS for default behavior.
  ;; (If we didn't call call-next-method, then we would need
  ;; to return the proper type of value for this message,
  ;; which is usually win:|FALSE|.)
  (call-next-method))

(defmethod my-window-procedure (handle (message (eql win:|WM_PAINT|))
                                       wparam lparam)
  (declare (ignore wparam lparam))
  (declare (optimize (speed 3)(safety 1))) ;; for with-stack-fobject
  
  ;; The WM_PAINT message is sent by the operating system whenever
  ;; we need to redraw all or part of our window.
  
  ;; Do nothing if the window currently has no update region.
  (unless (eq (win:|GetUpdateRect| handle 0 0) 0)
    
    ;; Do the standard preparation for painting.
    (ff:with-stack-fobject (ps 'win:|paintstruct|)
      (win:|BeginPaint| handle ps)
      (unwind-protect
          
          ;; Find the area of the window that needs to be redrawn.
          (let* ((left (ff:fslot-value-typed
                        'win:|paintstruct| :foreign ps :rcPaint :left))
                 (top (ff:fslot-value-typed
                       'win:|paintstruct| :foreign ps :rcPaint :top))
                 (right (ff:fslot-value-typed
                         'win:|paintstruct| :foreign ps :rcPaint :right))
                 (bottom (ff:fslot-value-typed
                          'win:|paintstruct| :foreign ps :rcPaint :bottom)))
            
            ;; We're not actually using the refresh area in this
            ;; simple example, so just ignore it.  A real app can
            ;; be made more efficient by not drawing anything that
            ;; doesn't intersect this refresh rectangle.
            (declare (ignore left top right bottom))
            
            ;; Draw and fill a simple rectangle in our window.
            ;; This illustrates the convolutions required in Windows
            ;; to simply draw colored lines and areas.
            (let* ((hdc (win:|GetDC| handle))
                   (brush (win:|CreateSolidBrush| (win-color 0 255 0)))
                   (pen (win:|CreatePen| win:|PS_SOLID| 1
                                       (win-color 0 0 128)))
                   old-brush old-pen)
              (unwind-protect
                  (progn
                    (setq old-brush (win:|SelectObject| hdc brush))
                    (setq old-pen (win:|SelectObject| hdc pen))
                    (win:|Rectangle| hdc 20 20 100 100))
                (win:|SelectObject| hdc old-brush)
                (win:|SelectObject| hdc old-pen)
                (win:|DeleteObject| brush)
                (win:|DeleteObject| pen)
                (win:|ReleaseDC| handle hdc))))
        
        ;; Do the standard painting cleanup.
        (win:|EndPaint| handle ps)
        
        ;; Always return zero to the OS for WM_PAINT mesages.
        0))))
  
;;; ------------------------------------------------
;;; Utility Functions --- These could be used as is.

(defun register-window-class
    (class-name window-procedure
                &key
                (style-flags #.(logior win:|CS_OWNDC| win:|CS_DBLCLKS|))
                (large-icon-handle (franz-icon-handle))
                (small-icon-handle 0))
  (declare (optimize (speed 3)(safety 1))) ;; for with-stack-fobject
  
  ;; Registers a window class in the Windows OS.
  ;; Messages sent to windows of this class will call
  ;; the specified window-procedure callback function.
  (let* ((hinstance (lisp-hinstance)))
    (ff:with-stack-fobject (class 'win:|wndclassex|)
      
      ;; If we have already registered this Windows class name
      ;; in this lisp session, then don't don't do so again.
      (when (with-native-string (native class-name)
              (win:|GetClassInfo| hinstance native class))
        (return-from register-window-class))
      
      ;; Fill the WNDCLASSEX foreign structure with the
      ;; attributes for our window class.
      (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :cbSize)
        (ff:sizeof-fobject 'win:|wndclassex|))
      (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :style)
        style-flags)
      (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|lpfnWndProc|)
        (ff:register-foreign-callable window-procedure
                                      :reuse :return-value))
      (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|cbClsExtra|)
        0)
      (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|cbWndExtra|)
        win:DLGWINDOWEXTRA)
      (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|hInstance|)
        hinstance)
      (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|hIcon|)
        large-icon-handle)
      (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|hCursor|) 0)
      (setf (ff:fslot-value-typed 'win:|wndclassex| nil class
                                  :|hbrBackground|)
        (1+ win:COLOR_WINDOW))
      (setf (ff:fslot-value-typed 'win:|wndclassex| nil class
                                  :|lpszMenuName|) 0)
      (setf (ff:fslot-value-typed 'win:|wndclassex| nil class
                                  :|lpszClassName|)
        (string-to-native class-name))
      (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|hIconSm|)
        small-icon-handle)
      
      ;; Register the class, signaling an error on failure.
      (when (zerop (win:|RegisterClassEx| class))
        (error "Failed to register the window class ~s."
          class-name)))))
  
(defun franz-icon-handle ()
  (with-native-string (native "aclicon")
    (win:|LoadIcon| (lisp-hinstance) native)))

(defun lisp-hinstance ()
  (declare (optimize (speed 3)(safety 1))) ;; for with-stack-fobject
  
  ;; Returns the handle of the lisp executable that's running.
  (ff:with-stack-fobject (vector '(:array :long 4))
    (win:|GetWinMainArgs| vector)
    (ff:fslot-value-typed '(:array :long 4) nil vector 0)))

(defun win-color (red green blue)
  (+ (ash blue 16)
     (ash green 8)
     red))


Copyright (c) 1998-2016, Franz Inc. Oakland, CA., USA. All rights reserved.
This page has had moderate revisions compared to the 8.1 page.
Created 2016.6.21.

ToCDocOverviewCGDocRelNotesFAQIndexPermutedIndex
Allegro CL version 8.2
Moderately revised from 8.1.
8.1 version