|
Allegro CL version 11.0 |
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, and on the Mac.
CG applications and the IDE run inside web browsers on all three platforms. This is referred to as web browser mode or CG/JS mode, where the "JS" stands for the JavaScript/HTML5 port of CG. On Microsoft Windows, they can alternately be run as Windows desktop applications, which is referred to as Windows desktop mode or simply Windows mode. (An earlier GTK-based port for running as desktop apps on Mac and Linux is no longer supported.)
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 entry in the relevant documentation file (cg-ops.html, cg-vars.html, or cg-classes.html). The index (index.html) 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 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. These are all in the single file cg-menus-and-dialogs.html.
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 finish-output or force-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.html.
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 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.html 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.html.
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:
If the command line that started the lisp contains the -batch
flag (see Command line arguments in startup.html), then start-ide exits immediately, returning nil
, and nothing else is done.
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.
The system object is created (or recreated if this is a dumplisp'ed image).
Common Graphics is initialized, and the main IDE owner window (see development-main-window) and the IDE menu-bar window are created.
If a user options named *allegro-ide-options.cl*
exists in your home directory (on Windows in your personal documents directory) exists, it is loaded to restore configuration options that were saved most recently in an earlier IDE session. If no such file exists in your home or personal documents directory, the file is looked for in the main Allegro directory and loaded if there. If no user options file exists is any of the listed locations, default settings are used. See The user options file allegro-ide-options.cl and also see save-options-to-user-specific-file.
The IDE toolbars and status-bar are added according to the display-toolbars and display-status-bar configuration options.
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.
Additional IDE windows are created if the following configuration options are true:
new-project-show-form exposes the initial form window of the project and creates an inspector window inspecting that form.
new-project-show-project-manager shows the Project Manager dialog.
new-project-show-editor creates and displays an Editor Workbook.
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 menu | 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.
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.
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.
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.
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.
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.
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.
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):
Start Allegro CL and the IDE. (The initial package will be the common-graphics-user
package).
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, and press that dialog's OK or Apply button.
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:
The information on the desired value for initial-package is stored in the prefs.cl file. If that file is deleted, the value returned by initial-package reverts to common-graphics-user
and this process must be repeated.
The value of initial-package must be a keyword whose symbol-name is the name of the desired package. It must not be a package object (as returned by find-package).
When not starting the IDE, see The package on startup in [startup.html] 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.html 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"
namestring (translate-logical-pathname
:build-executable ("sys:mlisp8.exe"))
t
:include-ide
:restart-init-function 'ide:start-ide
:case-mode :case-sensitive-lower)"sys:mlisp8.exe" "sys:allegro8.exe"
(sys:copy-file :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 menu | 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.)
The options file is written when the Tools menu | Save Options Now menu choice is selected and automatically upon exit when the Tools menu | 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 menu | Save Options Now. (Just disabling Tools menu | 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 menu | Save Options Now.
The full pathname of the options file can be found by calling 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:
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.)
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.
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 the Run Project commands (both on the Run: 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.
Because the :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.
In earlier releases, passing the :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
(or parent (screen *system*)))
(&key parent (owner (256 149 960 519)) (name :form1)
(exterior (make-box "Form1") (border :frame) (child-p nil) form-p)
(title let ((owner
(
(make-window name
:owner owner
:class 'dialog
:exterior exterior
:border border
:child-p child-pt
:close-button
:cursor-name :arrow-cursor"MS Sans Serif / ANSI" 11 nil)
:font (make-font-ex :swiss
:form-state :normalt
:maximize-button t
:minimize-button :name :form1
nil
:form-package-name "C:\\Program Files\\acl70\\form1.bil"
:path #pnil
:help-string nil
:pop-up t
:resizable nil
:scrollbars
:state :normalnil
:status-bar t
:system-menu
:title titlet
:title-bar
:dialog-items (make-form1-widgets)nil
:toolbar
:form-p form-pnil)))
:help-string 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
(or parent (screen *system*)))
(&key (owner (256 149 960 519)) (name :form1)
(exterior (make-box "Form1") (border :frame) (child-p nil) form-p)
(title let ((owner
(
(make-window name
:owner owner
:class 'dialog
:exterior exterior
:border border
:child-p child-pt
:close-button
:cursor-name :arrow-cursor"MS Sans Serif / ANSI" 11 nil)
:font (make-font-ex :swiss
:form-state :normalt
:maximize-button t
:minimize-button :name :form1
nil
:form-package-name "C:\\Program Files\\acl70\\form1.bil"
:path #pnil
:help-string nil
:pop-up t
:resizable nil
:scrollbars
:state :normalnil
:status-bar t
:system-menu
:title titlet
:title-bar
:dialog-items (make-form1-widgets)nil
:toolbar
:form-p form-pnil)))
:help-string
owner))
defun make-form1-widgets ()
(list (make-instance 'button :font
(nil "Tahoma / ANSI" 11 nil) :left
(make-font-ex 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
(nil "Tahoma / ANSI" 11 nil) :left
(make-font-ex 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
(nil "Tahoma / ANSI" 11 nil) :left
(make-font-ex 114 :name :button4 :on-click
"Here" :top 80
'form1-button4-on-click :title 33))) :width
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.
Microsoft Windows Note: 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. This does not apply to CG/JS mode.
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:
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.
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, 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-functionlist :name "My dummy thread"
(
:initial-bindings cg:*default-cg-bindings*)lambda ()
#'(let* ((win (cg:make-window :my-window
(
:owner (cg:screen cg:*system*)"A window in its own thread.")))
:title (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)
(nil :accessor circle-centers)))
((circle-centers :initform
defmethod redisplay-window ((window my-frame) &optional box)
(declare (ignore box))
(call-next-method) ;; Clear the window
(dolist (center (circle-centers window))
(50)))
(draw-circle window center
defmethod mouse-left-down ((window my-frame) buttons cursor-pos)
(declare (ignore buttons))
(push cursor-pos (circle-centers window)) ;; Add a new circle
(;; Redraw the window to include the new circle
(invalidate window))
(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*)"Click to give me three circles")))
:title
(event-loop :window window
:exit-testlambda (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.html.
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 in About using multiple windowing processes in a Common Graphics application above. See also the section Using the IDE when user code is busy.
Multiple Listeners may be used. The 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 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 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 Allegro 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:
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:
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 iaclwin
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
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.
"My Windows App" 'my-windows-app))
(mp:process-run-function
;;; 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*)"My Window")
(with-native-string (native-window-name "x")
(with-native-string (dummy-string
(win:|CreateWindowEx|0 ;; extended style
native-class-name
native-window-name
;; Specify the style of this window.
logior
#.(;; title bar
win:|WS_CAPTION| ;; system menu and close button
win:|WS_SYSMENU| ;; maximize button
win:|WS_MAXIMIZEBOX|
win:|WS_MINIMIZEBOX|)
100 ;; left
200 ;; top
400 ;; width
300 ;; height
0 ;; parent window (0 is the screen)
0 ;; system menu
(lisp-hinstance);; value to pass to WM_CREATE
dummy-string))))))
;; 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.
0)
(win:|PostQuitMessage|
;; 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))
(0 255 0)))
(brush (win:|CreateSolidBrush| (win-color 1
(pen (win:|CreatePen| win:|PS_SOLID| 0 0 128)))
(win-color
old-brush old-pen)unwind-protect
(progn
(setq old-brush (win:|SelectObject| hdc brush))
(setq old-pen (win:|SelectObject| hdc pen))
(20 20 100 100))
(win:|Rectangle| hdc
(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
(
&keylogior win:|CS_OWNDC| win:|CS_DBLCLKS|))
(style-flags #.(
(large-icon-handle (franz-icon-handle))0))
(small-icon-handle 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)))
(class 'win:|wndclassex|)
(ff:with-stack-fobject (
;; 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)
(class))
(win:|GetClassInfo| hinstance native 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
(0)
:|lpszMenuName|) setf (ff:fslot-value-typed 'win:|wndclassex| nil class
(
:|lpszClassName|)class-name))
(string-to-native 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 ()
("aclicon")
(with-native-string (native
(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.
vector '(:array :long 4))
(ff:with-stack-fobject (vector)
(win:|GetWinMainArgs| :array :long 4) nil vector 0)))
(ff:fslot-value-typed '(
defun win-color (red green blue)
(+ (ash blue 16)
(ash green 8)
( red))
Copyright (c) Franz Inc. Lafayette, CA., USA. All rights reserved.
|
Allegro CL version 11.0 |