| Allegro CL version 10.1 Unrevised from 10.0 to 10.1. 10.0 version |
This document contains the following sections:
1.0 About Common Graphics and IDE documentationThe 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 10.1 on the Mac and on Linux. They are supported on the non-SMP version of Allegro CL 10.1 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.
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.
chart-widget
.
object-editor
and the
class-grid
.
*system*
variable and its value.
Links to the various CG widget and window classes can be found below in Section 12.0 Widget and window classes.
Information on projects, forms, and menus is found in the Ide User Guide, as follows:
This section describes the menus on the Allegro CL IDE menu bar and the various dialogs that present information about the running Lisp and about your project. Note that the IDE is available on Windows, Linux (with Intel x86 processors), and the Mac only.
Common Graphics and the IDE runs on Windows, Linux, and Mac OS X. While broadly, CG and the IDE are same on all three platforms. Both Linux and the Mac use GTK. Differences between Windows and GTK are described in cggtk-relnotes.html.
We have tested the GTK version of the IDE on Red Hat Enterprise Linux 6.x and 7.x and on CentOS 6.x and 7.x (which are based on the RHEL versions of the same number). The IDE works properly on those Linux versions. However, there are many different Linux distributions and we cannot guarantee the GTK version of the IDE is compatible with all of them. Our ability to support different flavors of Linux is limited.
See Functionality to handle differences between Windows and GTK in release-notes.htm.
If you run XQuartz in Full-screen mode, it may happen that when you exit Allegro CL's IDE, the screen will go black and keys will seem not to work. This is apparently a feature of XQuartz. Avoid it by turning off Full-screen mode in the X11 Preferences. If it happens, the key sequence Command-Option-A is documented to take you out of Full-screen mode.
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.
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.2 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:
-batch
flag (see Command line arguments in
startup.htm), then start-ide exits immediately,
returning nil, and nothing else is done.
*system*
object is created (or recreated if this is a dumplisp'ed image).
*default-cg-bindings*
is passed as
the :initial-bindings argument to process-run-function when creating
this process (this should always be done when creating a process that
may create Common Graphics windows or to allow debugging that process
in the IDE). The user may create additional similar listeners later by
using the View | New Listener command.
(ide-evaluator-listener
*system*)
(see ide-evaluator-listener) is set to
this initial listener pane.
If an error is signaled while loading this file (or the prefs.cl file above), a modal dialog informs you of this and then the IDE continues to start up without loading the rest of the file. Debugging is not possible at this point because the process is not yet in an event-loop to which it can return when aborting from the debugger. You can, however, load the file explicitly after the IDE is running in order to debug it.
*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.
*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.
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):
common-graphics-user
package).
(setf (initial-package (configuration *system*)) :mypackage)
Alternately, edit the value of the Initial Package widget on the IDE 1 tab of the Options dialog, and press that dialog's OK or Apply button.
The next time you start Allegro CL with the IDE, all IDE listeners
will have (find-package :my-package)
as the initial
value of *package*
.
Note that:
common-graphics-user
and this process must be
repeated.
When not starting the IDE, see The package on startup in startup.htm for information on specifying the initial package.
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).
The user options file saves the various options which can be set on the various options dialogs displayed by the Tools | Options menu choice. The file is named allegro-ide-options.cl is is typically located in the user's home directory (on UNIX) or in the personal documents directory on Windows. It can also be located in the main Allegro directory. (The user-specific location if for an individual user. The Allegro directory location affects all users who do not have personal versions.)
In releases prior to 10.0, the options file had a complicated name which included the work prefs and also typically information about the version number. Starting in 10.0, the file has a new structure which is version-independent and so the same file can be used when the version is upgraded (with any newly irrevalent entries ignored).
The options file is written when the Tools | Save Options Now menu choice is selected and automatically upon exit when the Tools | Save Options on Exit choice is selected (as indicated by a checkmark -- it is initially enabled by default). To suppress saving on exit, disable the option by clicking on the menu item and then choose Tools | Save Options Now. (Just disabling Tools | Save Options on Exit means that options will not be saved when the current Allegro CL is exited but unless that option is saved, it will be enabled when Allegro CL is next started up.)
Do not edit the options file by hand. Your edits may be overwritten and an error in editing may cause loading the file on startup to fail. (Errors in the options file are protected against, so Allegro CL will start when the options file causes an error, but you will not get the settings you want. Instead, modify the file by selecting the desired options from the Options dialog and selecting Tools | Save Options Now.
The full pathname of the options file can be found by calling ide:options-path.
This page describes how to generate an automatic textual bug report when an error occurs in the IDE environment. If the error appears to be due to an Allegro bug, emailing this report to Franz will often help us to debug the error.
When an error occurs and the Restarts dialog (shown on the Debug Windows after an error page) appears with options for proceeding from the error, click the Debug button. This will show the current function stack as a graphical outline control.
If the keyboard focus is not already in the stack outline control, move the focus there by clicking anywhere in the stack outline or perhaps by using the View | Manage menu commands for selecting windows that are near the top.
Then write the bug information to a file. This can be done in several ways:
Whichever you choose, a modal dialog appears asking for a pathname (even if you have chosen Save or Save As before). Select a pathname to write the bug report to. The entire function stack will be saved textually to that file, with brief platform information at the top of the file, and complete dribble-bug (as generated by dribble-bug) and prefs.cl information at the bottom. The stack information will include all of the arguments and local variables for each stack frame, regardless of whether you have opened the outline items to show the arguments and variables in the IDE. The bug report that was written will then be shown in the IDE editor for your review.
The stack information will include the normally "hidden" frames only if the "Include Hidden Frames" button on the stack outline's toolbar is currently pressed. Though the additional information is not always necessary, it is best to click on this button before generating the bug report.
Since a window can be either a child window or a top-level window, and can also be either an owned window or not, there are four possible combinations of these attributes. But since a child window is always also an owned window (where the parent is also the owner), that leaves three actual types of windows, in terms of their relationships to a parent or owner:
: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.)
: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.
(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.
:owner
argument was inappropriately
called :parent
in earlier releases (inappropriate
because the argument was only an owner and not a parent when creating
an owned top-level window), the :parent
argument
still works for compatibility, but :owner
is
preferred.
:pop-up
argument
to make-window as true
simply created a top-level window. Now it more specifically creates a
top-level window appropriate for use as a modal dialog, by coercing
:child-p
to nil
,
:state
to :shrunk
,
:minimize-button
and
:maximize-button
to nil
,
and :scrollbars
to nil
.
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.
Multiple application processes can create CG windows, where each process will handle all events for the windows that it creates.
We advise against creating windows in multiple processes within a single window hierarchy, though, because deadlocks may occur when messages are sent from a window in one process to a window in another process within the hierarchy.
The generic function creation-process applied to a window returns the process that called make-window to create the window.
The function set-foreground-window makes the process that created the specified window be the foreground process, and selects the specified window.
A process that is to create windows must be set up as follows:
*default-cg-bindings*
as the value
of the :initial-bindings keyword argument. If other bindings are
needed, a union of those bindings with *default-cg-bindings*
may be
passed, but of course do not modify the *default-cg-bindings*
list.
A convenient way to do the above two steps is to use the macro in-cg-process.
These steps are not necessary when using the project system to create an application with a single windowing process (which is typical), because these steps are done automatically for the process created by the Run | Run Project command in the IDE and by the corresponding initial process of the generated standalone application.
To print debugging output to an IDE listener from a CG process that is neither a listener nor a Run | Run Project process, see format-debug and with-output-to-ide-listener. (Otherwise standard output will be directed to the console window, which is typically hidden.)
;; This example simply starts up a process to create a window, ;; and exits its event-loop (and therefore the process) when ;; the user closes the window. (mp:process-run-function (list :name "My dummy thread" :initial-bindings cg:*default-cg-bindings*) #'(lambda () (let* ((win (cg:make-window :my-window :owner (cg:screen cg:*system*) :title "A window in its own thread."))) (event-loop :window win))))
;; This example lets the user click the window to specify a position. ;; A list is kept of the positions, and the window draws a circle ;; at each one. As soon as the user adds the third circle, the ;; event-loop exit-test causes the event-loop to exit, and so ;; the process dies and its window is therefore closed (this ;; will happen before you actually see the third circle). (defclass my-frame (frame-window) ((circle-centers :initform nil :accessor circle-centers))) (defmethod redisplay-window ((window my-frame) &optional box) (declare (ignore box)) (call-next-method) ;; Clear the window (dolist (center (circle-centers window)) (draw-circle window center 50))) (defmethod mouse-left-down ((window my-frame) buttons cursor-pos) (declare (ignore buttons)) (push cursor-pos (circle-centers window)) ;; Add a new circle (invalidate window)) ;; Redraw the window to include the new circle (mp:process-run-function `(:name "Three Circles" :initial-bindings ,*default-cg-bindings*) #'(lambda () (let* ((window (make-window :three-circles :class 'my-frame :owner (screen *system*) :title "Click to give me three circles"))) (event-loop :window window :exit-test #'(lambda (win) (>= (length (circle-centers win)) 3))))))
When the Run | Run Project command in the IDE is invoked, a new process is created automatically to run the project, and is set up as described above for debugging in the IDE and for handling events.
DDE can now work in multiple processes. See dde.htm.
Multiple processes may simultaneously invoke modal dialogs without interference, even if the two dialogs are the "same" CG utility dialog, such as the ask-user-for-choice-from-list dialog.
Various global objects have been modified to avoid re-entrancy problems when multiple processes enter the same CG functions simultaneously. Among the things modified are many box and position constants. The functions with-boxes, with-positions, and with-positions-and-boxes are provided for applications that similarly need to remove box and position constants.
When the break key is pressed in the IDE, the IDE will look for a listener process that is currently busy evaluating user code. If it finds such a process, then it calls process-interrupt on that process to tell that process to call break. This allows you to interrupt and debug user code that may be in an infinite loop, for example, or is otherwise taking longer than expected. The rest of this section describes what is done only when a busy listener process is not found.
When the break key is pressed and no busy listener process is found, the Restarts dialog will be created and presented in a new process that exists solely for handling the break; this may avoid problems with interrupting another process that is in a problematic state.
Before the Restarts dialog appears, the process-quantum of every process is set to 0.1 seconds, to make any processes that are used for debugging more responsive if another process is in a busy loop. The process quanta are set back to their earlier values when the break process goes away, which happens when you either abort from the break key's Restarts dialog or from the backtrace pane that is created for the break in the Debug Window if you select Debug from the Restarts dialog.
Also before the Restarts dialog appears, any modal dialogs that are currently invoked will be brought to the front. This may help to recover from a possible problem where a modal dialog gets buried and then prevents further work due to its modality.
Multiple processes may be debugged in the IDE. Any process can be
debugged in the IDE if it is set up using *default-cg-bindings*
initial
bindings as described above. See also the section
Section 10.5 Using the IDE while user code is busy.
Multiple Listeners may be used. A new View | New Listener menu command allows for the creation of additional all-purpose lisp listeners. Each listener uses an independent process for evaluations, and has its own command history and backtrace window (when needed). All of the listeners are grouped into a single frame window, with a tab for each listener. The name of the process and its listener will be "Listener X", where X is a number to make the name unique.
The "Listener 1" listener window always exists while the IDE is running, along with the "Listener 1" process. Forms evaluated in this listener or elsewhere in the IDE are evaluated in the Listener 1 process, which is distinct from the "IDE GUI" process, which handles the actual user gestures in the IDE such as mouse clicks and keypresses (since the IDE windows are created in the IDE GUI process). The main implication of this is that if the evaluation of a user form is taking a while, the IDE GUI itself will still respond to interactive gestures since these are handled in a different process (though it may be very sluggish if the evaluation is in a tight loop).
The additional listeners may be closed with the File | Close Pane command (control-alt-X). The initial IDE listener, named Listener 1, cannot be closed.
Each process being debugged will have its own listener and backtrace pane. If a break occurs in one of the listener processes, the listener that already exists for that process will be used for the backtrace if the debugger is selected from the Restarts dialog. For other processes, a new listener will be created, assuming that IDE debugging has been enabled for the process.
Listeners that are created for debugging a break will go away automatically when the break is aborted or entirely popped out of. When a process is debugged by clicking the Debug button of the Processes dialog, the listener that is created does evaluations in a new separate "proxy" process, similar to focusing on a process in non-IDE listener; this is unlike listeners created when a break occurs and is debugged, which do evaluations in the broken process itself.
Shortcut keystrokes can be used for moving amongst the different listeners and break levels with the keyboard. Shortcut keys are shown on the right-button shortcut menus of the listener tabs.
Dialog modality in user processes will not disable IDE interaction. Only modal dialogs invoked by the IDE itself in the IDE GUI process will prevent further interaction with the IDE while the modal dialog is present. Modal dialogs invoked by user code will run independently.
The trace dialog reports which process each call was made in. When a function call is selected in the trace dialog's outline control, the process in which that call occurred is displayed in the titlebar of the trace dialog.
The View | Debug Window command will generate its new prompt in the main IDE listener unless the focus is in a listener already, in which case the prompt is generated in that listener.
The Debug button on the Processes dialog will arrest the selected process for debugging, and create a new process with a listener that is focused on the selected process. The new process and its listener tab will be named "Proxy for FOO", where FOO is the process that is focused on. Aborting out of the new listener will unarrest the focused process.
Multiple processes are used by the IDE to allow IDE windows to be responsive while arbitrary user code is busy executing. This is accomplished by creating all of the IDE windows in the "IDE GUI" process, but evaluating user code in an IDE Listener process such as the initial "Listener 1" process, which is associated with the Debug window. The listener processes are used not only for evalutions in the listener pane itself, but also by IDE commands that involve user code, such as the Tools | Incremental Evaluation and File | Load commands.
The IDE windows will not respond at all while a listener process is busy, however, if there are any open user windows (windows that were created in a listener process) on the IDE owner window (see development-main-window). There are two cases (described below) where a user window may commonly end up on the IDE owner window, and so these cases should be avoided (and any existing user windows on the IDE owner should be closed) at times when it is desirable to use IDE windows while user code is busy running.
The first case is the Run | Run Form command. Run Form creates the running window in an IDE listener process, with the IDE owner window as the owner. This is done so that particular IDE windows may be used alongside the running window without bringing the entire IDE in front of the running window, and so that keyboard shortcuts for IDE commands may be used while the running window is selected. Run Project (also on the Run menu), on the other hand, does not use the IDE owner window, since its purpose is to simulate the final standalone application more closely. So if a widget on a dialog of the current project initiates a long procedure, and it is desirable to use the IDE while this procedure is running, you should use Run Project rather than Run Form. (If you need to interrupt something that is running and the IDE windows are not responsive, you can still do so by right-clicking the Franz icon in the Windows Tray and selecting "Interrupt Lisp".)
The second case involves arbitrary user calls to make-window where no :owner
argument is specified. When this is done in the IDE, the owner of the
new window defaults to the IDE owner window. This default is used for
the same reasons as Run Form above. If it is desirable to use the
IDE while such a window is open and while user code is busy running,
then the window should be created with the screen as its owner by
specifying the value of the :owner argument to make-window as (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.
As documented in Section 10.0 About using multiple windowing processes in a Common Graphics application above, any process that is set up to create windows and handle the messages that are sent to them needs to call event-loop at the end of its process-run-function preset-function. The process will then spend its remaining time inside event-loop, running code that is triggered by the messages that are sent to any windows that are created in that process. The messages include user mouse and keyboard events as well as messages sent by code that the application is running and messages from the operating system.
This interactive event-driven model requires an application to be designed somewhat differently than one that simply runs from start to end to complete a pre-defined task. We describe two kinds of potential problems that are good to keep in mind when designing an interactive Common Graphics application: problems with message-handling routines that run for a long time and problems with message-handling routines that block.
When mouse and keyboard events (and other messages) are sent to various windows of a Common Graphics application, the messages are held in a queue, and generally the application code that handles each message is run completely before the next message in the queue is handled. This allows the code to run in a predictable order, even though the messages themselves are queued asynchronously.
This can be a problem when the code that handles a message runs for a long time, because no other messages for windows in the same process will be handled until that code returns, and so end users will see no response to their gestures in the meantime. If future user actions might depend on the current routine completing, then not much can be done about this except showing an hourglass cursor (see with-hourglass) or using other cues to tell the user to wait. In this default case, further messages will be queued and handled later in order, and this is how most windowing applications work.
But sometimes it is desirable for the user to be able to go ahead and perform other independent actions (when there are any). One way to do this is to call process-run-function to create a new process that performs the time-consuming operation. Note that if the new process needs to create windows that will handle messages, then it needs to follow the guidelines for creating a Common Graphics process described in Section 10.0 About using multiple windowing processes in a Common Graphics application above; otherwise any ordinary process may be created and used. Another general option is to hand off a command to an existing process, perhaps with process-interrupt or using a custom queue of some sort.
A different approach is to call process-pending-events at frequent intervals in the long-running code, which handles subsequent queued messages at that time. This function can cause unknown messages to be handled in a different order than usual, though, and so it should be used with care.
process-pending-events should not be called when an exclusive resource (such as a process lock) is currently being held, if it is possible that the processing of subsequent events may lead to a request for that resource that will block until the resource is no longer held. If it is the same process that requests the resource again, then this would always cause a deadlock, since the process will not unwind to the earlier use of the resource in order to free it (unless the second request knows how to see that that process already has the resource, and then either proceed or return, as appropriate). Even if the second request is from another process, complex interactions could still lead to deadlock unless this is carefully avoided in the application design.
Similarly, a Common Graphics process that an application creates normally should not grab an exclusive resource in its process-run-function preset-function and not release it before it calls event-loop, as this would hoard the resource for the entire life of the process. Likewise, a project's on-initialization function should avoid returning while holding an exclusive resource.
With either of the above approaches (handing a long-running command off to another process, or calling process-pending-events frequently), it is up to the application to prevent the user from doing an action that depends on the result of an earlier action that has not yet completed.
A less obvious kind of problem may arise due to the fact that there are certain other exceptions to the general rule where one message is handled completely before the next message is handled. In particular, there are certain functions such as process-wait that wait an arbitrary amount of time until some condition is met (this is usually called blocking). When such functions are called in a Common Graphics process, any messages that were already queued or that occur during the waiting period are handled while the call is blocking.
This handling of further messages during blocking is done to avoid failing to respond to arbitrary messages for long periods, including messages sent by other processes, or messages that the operating system may send to all top-level windows, expecting a timely reply. Also, the process may be waiting on a condition that will not be reached until further messages are handled by that same process, and so the process would be hung if further messages were not handled while blocking.
This design means that when a blocking function is called in code that is itself handling a message, later messages are handled while the code handling the current message is still running, and so the messages are not handled totally in the usual order. In particular, if the same type of message occurs again during the waiting period, the code that handles the message may be re-entered a number of times, which the application may not be written to handle.
And normally almost all Common Graphics code in an application is message-handling code (namely everything except the on-initialization function of a standalone application, or setup code that a newly-created Common Graphics process calls before it calls event-loop). So this warning applies to nearly all Common Graphics code in a typical application, if it calls blocking functions that process intervening messages.
The functions and macros that cause intervening messages to be handled immediately include the following:
Other functions (either in Allegro itself or in the application) that call the above functions would exhibit this behavior as well, and there may be other primitive functions in Allegro that behave this way but are not noted here.
A particular kind of deadlock can result if one of these functions is called while holding an exclusive resource of some kind, such as a process lock. For example, say an application has a mouse-in method that grabs a process lock and then calls either process-wait or process-pending-events. If the user moves the mouse into a window a second time and that event is handled while the call to the method for the first mouse-in is still inside the call to process-wait or process-pending-events, then the mouse-in method will be re-entered and wait for the lock. This will deadlock (with the default arguments to with-process-lock) because that event-handling process will wait for the lock forever and therefore never unwind to the first call to the mouse-in method, as it would need to do to release the lock.
The function post-funcall-in-cg-process has been supplied as a general single-process solution to this kind of problem. If code that calls any of the blocking functions listed above is passed to this function instead of being called directly, then the code will be run later after all window messages in this process have been handled and all code that was queued by earlier calls to this function have run and returned. The important point is that all function calls that are passed to post-funcall-in-cg-process for a given process are guarranteed to run sequentially (in the order of posting), eliminating problems that might arise if the calls overlapped. See post-funcall-in-cg-process for more information and an example. Further window messages are handled as usual by the process whenever a queued function is not running, and users will still see response to their interactive gestures while posted function calls are queued or blocking.
The Tree of Knowledge displays the classes associated with windows and widgets: follow Common Graphics | Interface Objects | Windows | Window Classes or Common Graphics | Interface Objects | Controls | Control Classes (and look at the subentries). Here we list the classes for windows and widgets:
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
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
On the Windows platform, Common Graphics provides an interface to
Microsoft's support for touchscreen gestures, which was new in Windows
7 and works the same way in Windows 8. The Common Graphics facility
corresponds closely to the Microsoft API and uses similar names for
functions and arguments, though adpated for use in Common Graphics and
Lisp. We also provide a higher-level but less general facility that is
not discussed here; see two-stroke-mixin
.
There are two separate interfaces, one at a somewhat higher level than the other. Any window can use only one of the interfaces at any given time, though you can switch a window back and forth between the two interfaces.
Microsoft generally speaks of the higher-level interface as the gesture interface and the lower-level one as the touch interface, so we will follow that convention even though they both deal with touch gestures.
In Windows, the gesture interface uses the WM_GESTURE message, which in Common Graphics results in calls to the gesture-event generic function. The touch interface uses the WM_TOUCH message, which in Common Graphics results in calls to the touch-event generic function. To handle touch gestures, a Common Graphics application needs to supply one or more methods for one (or possibly both) of these generic functions (but see note just below). By default the gesture interface is used, but an application can call register-touch-window on any window to use the touch interface for that window instead.
Note: actually a few gestures automatically map to pre-existing Windows events by default, without needing to write any touch-gesture-specific code at all. These include a single-finger tap to emulate a left-click, a single-finger press and hold to emulate a right-click, and a two-finger drag to scroll.
Both interfaces send a series of messages for a single gesture, one message for each different position of one or more fingers on the screen. (The interface also works for pens, but we will speak only of fingers here.) This results in a series of calls to either touch-event or gesture-event for a single gesture. The first and last calls for a single gesture will indicate that they are the first and last. It is up to an application to interpret the different positions in the multiple calls to decide things like how far to scroll a window or how much to zoom it by. This typically requires saving positions from earlier calls in order to compare the positions in later calls to them. It is up to the application to save this information in some way.
The main difference between the two interfaces is that the gesture interface will first determine which of several standard gestures the user is performing before it sends any messages. It provides that information along with the finger positions that are provided by both interfaces. These standard gesture types are:
To handle any other arbitrary gestures, the lower-level touch interface must be used instead.
There is a trade-off problem where if you want to handle one of the standard gestures like zoom and also some other gesture like a three-finger tap in the same window, then you would need to use the touch interface to handle the three-finger tap and therefore cannot use the gesture interface to figure out for you when a zoom gesture is being done. (The standard zoom gesture is a two-finger pinch to zoom out or a spread to zoom in). So in general if the standard gestures do not suffice, then you will need to use the touch interface where you will always need to first determine from the series of finger positions what kind of gesture is being performed. With either interface you will need to determine from the series of finger positions something like the size of the gesture to decide on the corresponding size of your response.
Below are two complete examples to demonstrate each of the two APIs. The first one is a simple finger-painting example that uses the lower-level touch API, and the second is a longer example that uses higher-level gesture API for typical panning, zooming, and rotating of a line drawing.
;;; ------------------- ;;; touch-event example ;;; ------------------- ;;; This example uses a custom touch-event method to handle ;;; lower-level touch messages. It emulates finger painting ;;; by simply drawing a thick line wherever any fingers are ;;; dragged across the screen. It changes the current color ;;; whenever all fingers have left the screen. Run the example by ;;; evaluating the commented-out call to touch-demo at the bottom. (in-package :cg-user) (defclass my-touch-window (bitmap-window) ((finger-positions :accessor finger-positions :initform nil))) (defclass my-touch-pane (bitmap-pane)()) (defmethod default-pane-class ((frame my-touch-window)) 'my-touch-pane) (defmethod touch-event ((pane my-touch-pane) touch-points count) ;; This is the method to define to use the lower-level ;; Microsoft touch API. That API simply sends the positions ;; of a number of fingers that are on the screen, every ;; time one of the fingers moves. (let* ((frame (parent pane)) ;; Anti-aliasing has weird behavior with very ;; short but very thick lines, so don't use it. (*antialiasing* nil)) ;; Handle each finger that's now on the screen. (dotimes (index count) ;; Retrieve the current state of this finger. (let* ((touch-point (aref touch-points index)) (id (touch-point-id touch-point)) (type (touch-point-type touch-point)) (x (touch-point-x touch-point)) (y (touch-point-y touch-point))) (case type ;; When a finger first makes contact with the screen, ;; save a new entry for remembering its previous ;; position. Initialize it to this first position. (:down (push (list id x y)(finger-positions frame))) ;; On subsequent messages for the same stroke of ;; this finger, find the existing entry for this ;; finger from its ID ... (t (let* ((previous (assoc id (finger-positions frame)))) ;; ... and draw a line segment from the previous ;; position of this finger to this new position. (draw-line-x-y pane (second previous)(third previous) x y) ;; Remember this position as the previous one ;; for this finger. (setf (second previous) x) (setf (third previous) y)))) ;; When the last finger is removed from the screen, ;; clear the finger entries to clean up garbage ... (when (and (eq type :up)(= count 1)) (setf (finger-positions frame) nil) ;; ... and select a new random drawing color ;; for the next (possibly multi-finger) stroke. (setf (foreground-color pane) (make-rgb :red (random 256) :green (random 256) :blue (random 256)))))))) (defun touch-demo (&key (line-width 24)) (let* ((frame (make-window :painting :class 'my-touch-window :title "Paint Me" :exterior (make-box-relative 100 50 800 600))) (pane (frame-child frame))) ;; This must be called to switch any window from the ;; higher-level "gesture" API to the lower-level "touch" API. (register-touch-window pane) (setf (line-width pane) line-width) frame)) #+test (touch-demo) ;;; --------------------- ;;; gesture-event example ;;; --------------------- ;;; This example uses a custom gesture-event method to handle ;;; higher-level gesture messages for panning, zooming, and ;;; rotating a line drawing. Run the example by evaluating ;;; the commented-out call to gesture-demo at the bottom. ;;; Perhaps this could be turned into a general Common Graphics ;;; facility, though it's not clear that it could be ;;; generalized sufficiently for any application. But you ;;; may be able to start with this code and adapt it to ;;; your own application. (in-package :cg-user) (defclass my-gesture-window (frame-window) ;; Define some custom slots for remembering the state of things. ;; This includes state that has been established by earlier ;; gestures, plus the state at the beginning of a gesture to be ;; referenced later during that same gesture. The latter slots ;; begin with "initial". ((initial-distance :accessor initial-distance :initform nil) (initial-x :accessor initial-x :initform nil) (initial-y :accessor initial-y :initform nil) (initial-scroll-x :accessor initial-scroll-x :initform nil) (initial-scroll-y :accessor initial-scroll-y :initform nil) (offset-x :accessor offset-x :initform 0) (offset-y :accessor offset-y :initform 0) (initial-offset-x :accessor initial-offset-x :initform 0) (initial-offset-y :accessor initial-offset-y :initform 0) (initial-page-width :accessor initial-page-width :initform nil) (initial-page-height :accessor initial-page-height :initform nil) (zoom-factor :accessor zoom-factor :initform 1) (initial-zoom-factor :accessor initial-zoom-factor :initform nil) (rotation-angle :accessor rotation-angle :initform 0) (initial-rotation-angle :accessor initial-rotation-angle :initform 0) ;; Add some slots for information about the thing to draw. (line-segments :accessor line-segments :initarg :line-segments :initform nil) (drawing-margin :accessor drawing-margin :initarg :drawing-margin :initform 0) (drawing-width :accessor drawing-width :initarg :drawing-width :initform 500) (drawing-height :accessor drawing-height :initarg :drawing-height :initform 500)) (:default-initargs ;; Double-buffering is important for smooth animation. :double-buffered t)) (defmethod redisplay-window ((window my-gesture-window) &optional box) ;; As usual, this method draws the complete picture at any time. (erase-contents-box window box) (let* ((zoom-factor (zoom-factor window)) (offset-x (offset-x window)) (offset-y (offset-y window)) (width (drawing-width window)) (height (drawing-height window)) (margin (drawing-margin window)) (middle-x (- (round width 2) margin)) (middle-y (- (round height 2) margin)) ;; Reverse the angle because Common Graphics increases ;; the Y coordinate downward rather than upward. (angle (- (rotation-angle window))) (cos (cos angle)) (sin (sin angle)) x1 y1 x2 y2) ;; Transform each coordinate pair according to the current ;; position, zoom factor, and rotation of the drawing. ;; Always rotate about the middle of the drawing. (flet ((transform (x y) (values (+ offset-x (round (* (+ middle-x (* cos (- x middle-x)) (- (* sin (- y middle-y))) margin) zoom-factor))) (+ offset-y (round (* (+ middle-y (* sin (- x middle-x)) (* cos (- y middle-y)) margin) zoom-factor)))))) ;; Draw each line segment after transforming its endpoints. (let* ((*antialiasing* t)) ;; for smoother lines (with-line-width (window 2) (dolist (line-segment (line-segments window)) (multiple-value-setq (x1 y1) (transform (first line-segment)(second line-segment))) (multiple-value-setq (x2 y2) (transform (third line-segment)(fourth line-segment))) (draw-line-x-y window x1 y1 x2 y2))))))) (defmethod gesture-event ((window basic-pane)(target my-gesture-window) gesture x y distance beginning ending inertia) (declare (ignore ending inertia)) ;; This is the method to define to use the higher-level Microsoft ;; gesture API. That API first figures out which of several ;; standard gestures is being done, and then starts calling this ;; method as the user's fingers move through the gesture. (case gesture ;; Handle the standard zoom gesture, which is placing two fingers ;; on the screen and then either pinching them closer together ;; to zoom out or spreading them apart to zoom in. (:zoom ;; When the first message of a new zoom gesture arrives, ;; save the state of the beginning of the gesture. ;; On subsequent messages for the same gesture, we will ;; compare the state at that time to this beginning state ;; to decide how to update the display. Some of this state ;; comes from the initial touch position of this gesture, ;; and some of it is the state that was left by earlier gestures. (cond (beginning ;; Save the initial distance between the two fingers and ;; the initial point that's midway between the two fingers. ;; Use these on subsequent messages for the same gesture ;; to decide how much to zoom by, and for centering the ;; zoom at this initial position of the fingers. (setf (initial-distance target)(max 1 distance)) (setf (initial-x target)(- x (offset-x target))) (setf (initial-y target)(- y (offset-y target))) ;; Also save the initial page size and scroll position. ;; This will be used later to adjust the page size so ;; the user can still scroll the entire drawing into view ;; when zoomed in, and to decide where to scroll the ;; window to in order to zoom around the point that's ;; midway between the initial two finger positions. (setf (initial-page-width target)(page-width target)) (setf (initial-page-height target)(page-height target)) (with-positions (pos) (nscroll-position target pos) (setf (initial-scroll-x target)(- (position-x pos) (offset-x target))) (setf (initial-scroll-y target)(- (position-y pos) (offset-y target)))) (setf (initial-offset-x target)(offset-x target)) (setf (initial-offset-y target)(offset-y target)) ;; Save the initial zoom factor that resulted from earlier ;; zoom gestures, so that we can multiply the current zoom ;; factor of this gesture by that one to arrive at the ;; combined zoom factor to use. (setf (initial-zoom-factor target)(zoom-factor target))) ;; When subsequent messages arrive for the same zoom gesture, ;; zoom the drawing according to how the current finger ;; positions compare to the initial positions for this gesture ;; that we saved above. (t ;; Regard the zoom factor for this gesture so far as being ;; the current distance between the two fingers divided by ;; the initial distance between them. This is just one ;; interpretation of the gesture, whereas an application ;; could use some other interpretation. (let* ((gesture-zoom-factor (/ distance (initial-distance target)))) ;; Calculate the current zoom factor by which the ;; redisplay-window method will draw the picture as the ;; current zoom factor from before this gesture multiplied ;; by the zoom factor of this gesture so far. (setf (zoom-factor target)(* (initial-zoom-factor target) gesture-zoom-factor)) ;; Avoid any jerkiness when the scrollbars get turned ;; on and off. (with-delayed-redraw (target :invalidate nil) ;; Multiply the page size by the zoom factor so that the ;; scrollable canvas will still hold a larger image. ;; The rounding is because CG positions must use integers. (set-page-size target (round (* gesture-zoom-factor (initial-page-width target))) (round (* gesture-zoom-factor (initial-page-height target)))) ;; Scroll the window to keep the central finger position ;; scrolled to the same place. Implementing the intuitive ;; behavior here seems to require some unintuitive code. (let* ((initial-x (initial-x target)) (initial-y (initial-y target)) (scroll-x (- (round (* gesture-zoom-factor initial-x)) (- initial-x (initial-scroll-x target)))) (scroll-y (- (round (* gesture-zoom-factor initial-y)) (- initial-y (initial-scroll-y target))))) (scroll-to target (make-position (max scroll-x 0) (max scroll-y 0))) ;; An offset is needed when the scrollbars are off ;; to still zoom out around the pinch position. ;; It essentially stores a logical negative scroll ;; position when the scrollbars themselves can't ;; scroll below zero. (multiple-value-bind (max-scroll-x max-scroll-y) (scroll-range target) (setf (offset-x target) (min (max 0 (- scroll-x)) ;; A negative offset seems to be needed when ;; zooming out would otherwise require ;; scrolling beyond the end of the range. (- max-scroll-x scroll-x))) (setf (offset-y target) (min (max 0 (- scroll-y)) (- max-scroll-y scroll-y)))))) ;; Redraw the window at the current mid-gesture state. (invalidate target)))) ;; Return true to say that we handled this zoom event ourselves, ;; to override any default bevavior. t) ;; Handle the standard panning gesture, which is simply dragging ;; one or two fingers in any direction. ;; Panning would work automatically using the default system ;; behavior when the window has scrollbars. But let's handle ;; it ourselves so that we can also move the drawn object around ;; even when the scrollbars are off (when zoomed out). (:pan ;; For the pan gesture we need to save only some of the ;; information at the beginning of the gesture that we save ;; for a zoom gesture above. (cond (beginning (setf (initial-x target) x) (setf (initial-y target) y) (with-positions (pos) (nscroll-position target pos) (setf (initial-scroll-x target)(- (position-x pos) (offset-x target))) (setf (initial-scroll-y target)(- (position-y pos) (offset-y target)))) (setf (initial-offset-x target)(offset-x target)) (setf (initial-offset-y target)(offset-y target))) ;; On subsequent messages for the same pan gesture, pan by ;; the difference between the current finger position and ;; the initial finger position that we saved above ;; at the beginning of the gesture. (t (let* ((scroll-x (- x (initial-x target))) (scroll-y (- y (initial-y target))) (zoom-factor (zoom-factor target))) ;; If either scrollbar is present (because we're zoomed ;; in enough for there to be content that's scrolled out ;; of view), then implement the pan gesture by adjusting ;; one or both scrollbars. (multiple-value-bind (scroll-range-x scroll-range-y) (scroll-range target) (cond ((or (plusp scroll-range-x) (plusp scroll-range-y)) (decf scroll-x (- (position-x (scroll-position target)) ;; A subtle point: For smooth scrolling, ;; we must subtract the initial scroll ;; position from the stream coordinates ;; (scrolled coordinates) that Common ;; Graphics passes in, and use the ;; resulting window coordinates ;; (unscrolled coordinates) that ;; correspond to the series of touch ;; points on the window itself. ;; Otherwise each incremental scroll ;; would adjust the coordinates that we ;; are using, leading to jerkiness. (initial-scroll-x target))) (decf scroll-y (- (position-y (scroll-position target)) (initial-scroll-y target))) (with-positions (pos) (scroll-to target (nmake-position pos (- (initial-scroll-x target) scroll-x) (- (initial-scroll-y target) scroll-y)) :delay-redraw t)) (invalidate target) t) ;; If neither scrollbar is present, then implement ;; the pan gesture by adjusting our offset values, ;; to move all of the content around in the window. (t (setf (offset-x target) ;; Don't let the user drag the object ;; out of the window. (max 0 (min (- (interior-width target) (round (* (drawing-width target) zoom-factor))) (+ (initial-offset-x target) scroll-x)))) (setf (offset-y target) (max 0 (min (- (interior-height target) (round (* (drawing-height target) zoom-factor))) (+ (initial-offset-y target) scroll-y)))) (invalidate target) t))))))) ;; Handle the standard rotate gesture, which is placing two fingers ;; on the screen and then rotating them about a central point. ;; We will always rotate around the center of the drawing, rather ;; than around the finger position, and so this operation is ;; relatively simple. Just save the pre-existing rotation angle ;; as well as the initial angle at the beginning of the gesture, ;; then add the current angle of rotation for this gesture to that ;; to find final angle at which to redraw everything. ;; For the :rotate gesture, the "distance" argument is actually ;; the angle between the two fingers, measured in radians. (:rotate (cond (beginning (setf (initial-rotation-angle target)(rotation-angle target)) (setf (initial-distance target) distance)) (t (setf (rotation-angle target) (+ (initial-rotation-angle target) distance)) (invalidate target)))) ;; For other gestures that we don't handle ourselves here, return ;; nil to allow Windows to pass the event up through any ancestor ;; windows, and finally to perform default bevavior if no ancestor ;; window handles the event. (Though most gestures have no ;; default behavior.) (t nil))) (defun gesture-demo (line-segments &key (margin 100)) (let* ((max-x (apply 'max (mapcar (lambda (line-segment) (max (first line-segment) (third line-segment))) line-segments))) (max-y (apply 'max (mapcar (lambda (line-segment) (max (second line-segment) (fourth line-segment))) line-segments))) (drawing-width (+ max-x (* 2 margin))) (drawing-height (+ max-y (* 2 margin))) (window (make-window :my-gesture-window :class 'my-gesture-window :line-segments line-segments :drawing-margin margin :drawing-width drawing-width :drawing-height drawing-height :exterior (make-box-relative 100 20 850 700))) ) (set-page-size window drawing-width drawing-height) ;; Request all of the built-in gestures, but turn off ;; pan-with-gutter so that the user can pan immediately ;; in any direction. (pan-with-gutter would restrict the ;; drag to only horizontal or vertical unless the user ;; also drags far enough in the other direction to ;; "break out of the gutter".) (configure-gestures window :zoom t :rotate t :two-finger-tap t :press-and-tap t :pan t :pan-with-gutter nil :pan-with-inertia t :pan-with-single-finger-horizontally t :pan-with-single-finger-vertically t) ;; Return the demo window. window)) #+test ;; Test a simple drawing of a house. (Or possibly a pencil.) (gesture-demo '((0 390 0 200) ;; lower left (0 200 200 250) (200 250 200 450) (200 450 0 390) (0 200 120 100) ;; upper left (120 100 200 250) (200 450 600 330) ;; lower right (600 330 600 150) (600 150 200 250) (600 150 480 30) ;; upper right (480 30 120 100)))
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:
In addition, the :pre-load-form argument to generate-application should require any needed Common Graphics modules, since these will not be included automatically as the project system would do.
Because of a design flaw which is hard to back out of,
there is a class named position
which is the class
of position objects in Common Graphics. This is a design flaw because
position
is a Common Lisp symbol (naming a sequence
function). It is actually outside the ANSI spec to overload Common
Lisp symbols with additional functionality. However, because the
violation is not very serious and because changing it would involve
substantial costs, we have decided to leave the class
position
rather than renaming it.
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.
An artifact left over from a very early release of Common Graphics is
various mathematical functions named
i<cl-math-function>. In the very earliest release, these
were quite fast on Windows machines because they used 16-bit
arithmetic. With the release of 5.0, the 16-bit operators were
replaced by corresponding regular Common Lisp math functions with
wrapped declarations identifying the arguments as fixnums. These were
named by symbols in the aclwin
package and were not
documented. These symbols are now
in the cg package. Because we document all exported symbols in that
package, we document the i<cl-math-function> operators
here. They are defined for backward compatibility only. Please do not
use them in new code. Instead, use the Common Lisp function determined
by removing the initial i from the symbol name.
i*, i+, i-, i/, i/=, i1+, i1-, i>, i<, i<=, i>=, i=, iabs, iceiling, idecf, ievenp, ifloor, iincf, ilogand, ilogandc1, ilogandc2, ilogbitp, ilogior, ilognand, ilognor, ilognot, ilogorc1, ilogorc2, ilogtest, ilogxor, imin, iminusp, imod, ioddp, iplusp, irem, iround, isquare, itruncate, izerop.
The winapi module contains certain Windows-related functionality that does not need Common Graphics to work. Among the functions are:
It is, of course, possible to write a Windows program that makes direct calls to the Windows API and does not use Common Graphics. Below is an example of a trivial but complete program written using Allegro CL's foreign function interface to the Windows API. Writing directly in the Windows API may be useful if you want to port an existing Windows program from C, or if you want the application to be smaller than it would be if it included Common Graphics. Note that many functions are defined but not documented. Use standard tools (like do-external-symbols and symbol-function) to find the available operators.
This program simply creates a window that draws a box inside itself, but it illustrates how to set up the usual basic structure of a Windows program in Allegro CL. This example runs in a base lisp that does not include Common Graphics. (Note that the package is the cl-user package, not the cg-user package -- which may not exist in the base Lisp. If you run this program in the IDE, be sure to use the cl-user:: package qualifier when necessary.)
The functions above will work in either modern (case-sensitive) Lisp or in ANSI (case-insensitive) Lisp. However, the symbols naming other functions named by symbols in the win package must be escaped if they are to work properly in an ANSI Lisp, as is done in the example below.
;; We have escaped all WIN package symbol names are also all ;; mixed-case keyword in this example. Therefore this code will work in ;; either an ANSI (case-insensitive) Lisp or a modern (case-sensitive) ;; Lisp. The escaping could also be avoided by placing (in-case-mode :local) ;; at the very beginning of the file. (in-package :cl-user) (eval-when (compile load eval) (require :winapi) (require :winapi-dev)) ;;; An arbitrary Windows class name to use for our windows. (defconstant *my-window-class-name* "My Window Class") ;;; Call this function to run the application in a development lisp. (defun run-my-windows-app () ;; Create a new process for this app since it will ;; remain tied up in its message-handling loop. (mp:process-run-function "My Windows App" 'my-windows-app)) ;;; Call this function to run the application as a standalone app. (defun my-windows-app () ;; Register a window class with Microsoft for all of the ;; windows of this application. Their messages will be ;; received by my-window-procedure-callback. (register-window-class *my-window-class-name* 'my-window-procedure-callback) ;; Make an initial top-level window. (let* ((window-handle (with-native-string (native-class-name *my-window-class-name*) (with-native-string (native-window-name "My Window") (with-native-string (dummy-string "x") (win:|CreateWindowEx| 0 ;; extended style native-class-name native-window-name ;; Specify the style of this window. #.(logior win:|WS_CAPTION| ;; title bar win:|WS_SYSMENU| ;; system menu and close button win:|WS_MAXIMIZEBOX| ;; maximize button win:|WS_MINIMIZEBOX|) 100 ;; left 200 ;; top 400 ;; width 300 ;; height 0 ;; parent window (0 is the screen) 0 ;; system menu (lisp-hinstance) dummy-string)))))) ;; value to pass to WM_CREATE ;; Expose the window. (win:|ShowWindow| window-handle win:|SW_SHOWNOACTIVATE|) ;; Give the window the keyboard focus. (win:|SetForegroundWindow| window-handle) ;; Process incoming messages in a loop. (ff:with-stack-fobject (message 'win:|msg|) (loop (unless (win:|GetMessage| message 0 0 0) ;; Exit the application when a WM_QUIT message is ;; received, causing GetMessage to return nil. (return)) ;; Dispatch each message so that it is passed ;; to our window procedure callback function. (win:|DispatchMessage| message)) ;; Return an exit code to the operating system ;; (if this is a standalone app). This is the ;; exit code that we passed to PostQuitMessage. (ff:fslot-value-typed 'win:|msg| :foreign message :wparam)))) (ff:defun-foreign-callable my-window-procedure-callback (window-handle message-number wparam lparam) ;; This is the window procedure that is called for all ;; messages that are sent to "My Window Class" windows. ;; It is very important to use the :stdcall convention for ;; winapi callback functions. Otherwise lisp will crash. (declare (:convention :stdcall)) ;; Make this foreign callback function call a generic function ;; that we can specialize for various messages. (my-window-procedure window-handle message-number wparam lparam)) (defmethod my-window-procedure (handle message wparam lparam) ;; This default message-handling method passes the message back ;; to the operating system to let it do its default behavior. ;; It needs to return whatever the DefWindowProc returns. (win:|DefWindowProc| handle message wparam lparam)) (defmethod my-window-procedure (handle (message (eql win:|WM_DESTROY|)) wparam lparam) (declare (ignore handle wparam lparam)) ;; When our only window is being closed, post a WM_QUIT message. ;; Our call to GetMessage will return nil when it reads this ;; WM_QUIT message, and then we exit our event-handling loop. (win:|PostQuitMessage| 0) ;; Call the default message-handling method to pass ;; the message back to the OS for default behavior. ;; (If we didn't call call-next-method, then we would need ;; to return the proper type of value for this message, ;; which is usually win:|FALSE|.) (call-next-method)) (defmethod my-window-procedure (handle (message (eql win:|WM_PAINT|)) wparam lparam) (declare (ignore wparam lparam)) (declare (optimize (speed 3)(safety 1))) ;; for with-stack-fobject ;; The WM_PAINT message is sent by the operating system whenever ;; we need to redraw all or part of our window. ;; Do nothing if the window currently has no update region. (unless (eq (win:|GetUpdateRect| handle 0 0) 0) ;; Do the standard preparation for painting. (ff:with-stack-fobject (ps 'win:|paintstruct|) (win:|BeginPaint| handle ps) (unwind-protect ;; Find the area of the window that needs to be redrawn. (let* ((left (ff:fslot-value-typed 'win:|paintstruct| :foreign ps :rcPaint :left)) (top (ff:fslot-value-typed 'win:|paintstruct| :foreign ps :rcPaint :top)) (right (ff:fslot-value-typed 'win:|paintstruct| :foreign ps :rcPaint :right)) (bottom (ff:fslot-value-typed 'win:|paintstruct| :foreign ps :rcPaint :bottom))) ;; We're not actually using the refresh area in this ;; simple example, so just ignore it. A real app can ;; be made more efficient by not drawing anything that ;; doesn't intersect this refresh rectangle. (declare (ignore left top right bottom)) ;; Draw and fill a simple rectangle in our window. ;; This illustrates the convolutions required in Windows ;; to simply draw colored lines and areas. (let* ((hdc (win:|GetDC| handle)) (brush (win:|CreateSolidBrush| (win-color 0 255 0))) (pen (win:|CreatePen| win:|PS_SOLID| 1 (win-color 0 0 128))) old-brush old-pen) (unwind-protect (progn (setq old-brush (win:|SelectObject| hdc brush)) (setq old-pen (win:|SelectObject| hdc pen)) (win:|Rectangle| hdc 20 20 100 100)) (win:|SelectObject| hdc old-brush) (win:|SelectObject| hdc old-pen) (win:|DeleteObject| brush) (win:|DeleteObject| pen) (win:|ReleaseDC| handle hdc)))) ;; Do the standard painting cleanup. (win:|EndPaint| handle ps) ;; Always return zero to the OS for WM_PAINT mesages. 0)))) ;;; ------------------------------------------------ ;;; Utility Functions --- These could be used as is. (defun register-window-class (class-name window-procedure &key (style-flags #.(logior win:|CS_OWNDC| win:|CS_DBLCLKS|)) (large-icon-handle (franz-icon-handle)) (small-icon-handle 0)) (declare (optimize (speed 3)(safety 1))) ;; for with-stack-fobject ;; Registers a window class in the Windows OS. ;; Messages sent to windows of this class will call ;; the specified window-procedure callback function. (let* ((hinstance (lisp-hinstance))) (ff:with-stack-fobject (class 'win:|wndclassex|) ;; If we have already registered this Windows class name ;; in this lisp session, then don't don't do so again. (when (with-native-string (native class-name) (win:|GetClassInfo| hinstance native class)) (return-from register-window-class)) ;; Fill the WNDCLASSEX foreign structure with the ;; attributes for our window class. (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :cbSize) (ff:sizeof-fobject 'win:|wndclassex|)) (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :style) style-flags) (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|lpfnWndProc|) (ff:register-foreign-callable window-procedure :reuse :return-value)) (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|cbClsExtra|) 0) (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|cbWndExtra|) win:DLGWINDOWEXTRA) (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|hInstance|) hinstance) (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|hIcon|) large-icon-handle) (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|hCursor|) 0) (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|hbrBackground|) (1+ win:COLOR_WINDOW)) (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|lpszMenuName|) 0) (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|lpszClassName|) (string-to-native class-name)) (setf (ff:fslot-value-typed 'win:|wndclassex| nil class :|hIconSm|) small-icon-handle) ;; Register the class, signaling an error on failure. (when (zerop (win:|RegisterClassEx| class)) (error "Failed to register the window class ~s." class-name))))) (defun franz-icon-handle () (with-native-string (native "aclicon") (win:|LoadIcon| (lisp-hinstance) native))) (defun lisp-hinstance () (declare (optimize (speed 3)(safety 1))) ;; for with-stack-fobject ;; Returns the handle of the lisp executable that's running. (ff:with-stack-fobject (vector '(:array :long 4)) (win:|GetWinMainArgs| vector) (ff:fslot-value-typed '(:array :long 4) nil vector 0))) (defun win-color (red green blue) (+ (ash blue 16) (ash green 8) red))
Copyright (c) 1998-2022, Franz Inc. Lafayette, CA., USA. All rights reserved.
This page was not revised from the 10.0 page.
Created 2019.8.20.
| Allegro CL version 10.1 Unrevised from 10.0 to 10.1. 10.0 version |