Introduction Chapter 1 Chapter 2 Chapter 3 Chapter 4 Chapter 5 Chapter 6 CLOS Intro
Allegro CL version 8.0

Chapter 6: Add Color Options to the Dialogs

  1. Start Allegro CL if you exited in Chapter 5. Open the gui-builder-tutorial project we left in Chapter 5. If you have restarted Allegro CL, a dialog may appear asking if you want to open that project, along with any other projects recently worked on. If not, use File | Open Project and choose the file gui-builder-tutorial.lpr in your tutorial project directory, which is C:\Program Files\allegro-projects\gui-builder-tutorial\ or allegro-projects/gui-builder-tutorial/ on Linux if you used the defaults. The Recent menu may also provide a way to open the project. You should now be where you stopped at the end of Chapter 5.
  2. You will create a Background color palette and modify the coefficient-dialog to add a color palette to it also. It will look like the following figure.

ch6-1-bgcoef.bmp (276790 bytes)

Create a color-mixin Class

Creating a Shared Class

Since the palette in the Coefficients dialog and the Background palette are going to use the same controls and functions for color, a new shared class called color-mixin will encapsulate their shared behaviors. The -mixin suffix means that you will never create an instance of this class, but that you can create subclasses of it.

  1. Click on the New button on the Standard toolbar or choose File | New. You are placed in an Untitled buffer in the Editor Workbook.
  2. In the Untitled buffer, type in the following:
(in-package :common-graphics-user)

(defclass color-mixin ()
   ())

There is a nickname in the system for the :common-graphics-user package called :cg-user. You may see this in some of the already compiled files in the final directory.

  1. Evaluate the defclass. Save the Untitled buffer as colorx.cl in your tutorial directory.
  2. You should have been queried (with a pop-up dialog) about adding the file to the current project. If not, or if you answered No, add it now. If you did add it, skip to the next step. In the Project Manager dialog, click on the Add button (it looks like a big +). Choose Code from the dialog that appears (meaning you are adding a source file) and add colorx.cl from your tutorial directory by selecting its name from the list of files and clicking Open. The dialog will close and you will see colorx.cl appear in the Project Manager.
  3. In the Project Manager dialog, select coefficient-dialog and click on the View Selected Form button.
  4. In the coefficient-dialog form, sketch in a multi-picture-button control below the coefficient controls. Refer to the diagram in Step 2 for the position of the control. (Make the group-box surrounding the existing controls bigger if necessary. You want the multi-picture-button in the group box.)

mpb-icon.bmp (30994 bytes)

  1. Inspect the new multi-picture-button control. Switch to the Properties tab (if necessary) and change its name to :color-list and range to nil. The buttons on the control will disappear, but you will see the sizing handles while the control is selected.
  2. On the coefficient-dialog form, sketch in a button control to the right of the multi-picture-button. See the illustration at the beginning of this section for reference.

When Are Invisible Controls Being Overlapped?

If you are concerned about overlapping your new button control with the now-invisible multi-picture-button, try dragging it slowly to the left and intentionally overlapping the two. Look for the visual cues.

ch6-2-coef.bmp (341374 bytes)

  1. Inspect the new button and change the name to :color-button and the title to Other Color... (note the three trailing dots).
  2. Save the files by clicking on the Save All button on the standard toolbar.
  3. In the Project Manager dialog, select colorx and click on the View Selected Code button.
  4. In the colorx buffer, add the initialize-instance :after method and the default-color-range function.
(defmethod initialize-instance :after 
    ((dialog color-mixin) &rest initargs)
  (declare (ignore initargs))
  (let ((color-list 
         (find-component :color-list dialog)))
    (when color-list
       (setf (range color-list) 
             (default-color-range))
       (setf (recessed color-list) t))))

(defun default-color-range ()
  (list 
    (make-instance 'button-info 
      :name :black 
      :image #S(rgb red 0 green 0 blue 0))
    (make-instance 'button-info 
      :name :red 
      :image #S(rgb red 255 green 0 blue 0))
    (make-instance 'button-info 
      :name :green 
      :image #S(rgb red 0 green 255 blue 0))
    (make-instance 'button-info 
      :name :blue 
      :image #S(rgb red 0 green 0 blue 255))
    (make-instance 'button-info 
      :name :cyan 
      :image #S(rgb red 0 green 255 blue 255))
    (make-instance 'button-info 
      :name :magenta 
      :image #S(rgb red 255 green 0 blue 255))
    (make-instance 'button-info 
      :name :yellow 
      :image #S(rgb red 255 green 255 blue 0))
    (make-instance 'button-info 
      :name :white 
      :image #S(rgb red 255 green 255 blue 255))))

What’s going on?

This enables both the Coefficients and the Background dialogs to fill their multi-picture-button wells with color. You won’t see the colors until Step 59 at the end of the Chapter.

initialize-instance Customizes the Two Dialog Palettes

Initially, the list of colors in the Background dialog and the Coefficient dialog palettes are the same. Therefore, whenever you create a subclass of color-mixin, the range of the color list control should be initialized.

In CLOS, you use initialize-instance to customize initialization for all instances of a class. You usually add an :after method to initialize-instance to avoid overriding the default CLOS initialization process.

In this case, the initialize-instance sets the range of the :color-list multi-picture-button and the style of the control’s display. Since this multi-picture-button control holds color like a real paint box, you’re using recessed wells for the buttons.

  1. Save the colorx buffer.
  2. In the General tab of the Project Manager dialog, select coefficient-dialog and click on the View Selected Code button.
  3. Change the defclass to match the following. In this case, it doesn’t matter whether color-mixin is the first or second superclass. By convention, a –mixin is the first superclass.
(defclass coefficient-dialog (color-mixin dialog)
   ())
  1. Click on Save All to save your changes.
  2. Run the Doodler using the Run button. In the Doodler, display the Coefficients dialog and verify that the multi-picture-button's color list is displayed.

You won't be able to add color to the curve yet, but you can manipulate the control and select different portions of it. The Other Color... button does nothing as yet.

  1. When satisfied, click the Stop button to stop running the Doodler.

Adding Color to the Curve Drawing

  1. Select the colorx buffer from the Editor Workbook to display the code. Add the draw-curve :around method.
(defmethod draw-curve :around 
    ((window basic-pane)
     (curve cycloidal-curve))
  (with-foreground-color (window (color curve))
  (call-next-method)))
  1. In the General tab of the Project Manager dialog, select curve-dialog and click on the View Selected Code button. Change the show-coefficient-dialog method to match the following:
(defmethod show-coefficient-dialog 
    ((dialog curve-dialog)
     &optional (curve 
                (make-instance 'cycloidal-curve)))
  (let* ((coefficient-dialog 
          (get-coefficient-dialog dialog))
          (a-widget 
           (find-component 
             :a-coefficient-control
             coefficient-dialog))
          (b-widget 
           (find-component 
             :b-coefficient-control
             coefficient-dialog))
          (c-widget 
           (find-component 
             :c-coefficient-control
             coefficient-dialog))
          (color-list (find-component :color-list
                        coefficient-dialog))
          (color-name 
            (find-color-name 
              coefficient-dialog (color curve))))
    (move-window coefficient-dialog 
       (window-to-screen-units 
         dialog (make-position 10 10)))

    ;; initialize the value of the widgets
    (setf (value a-widget) (a-coefficient curve))
    (setf (value b-widget) (b-coefficient curve))
    (setf (value c-widget) (c-coefficient curve))
    (setf (value color-list)
          (when color-name 
            (list color-name)))
    ;; display the dialog as modal
    (when (pop-up-modal-dialog coefficient-dialog
             :stream (owner dialog))
       ;; if the user click on OK, change 
       ;; the new curve
       ;; to reflect the values shown in 
       ;; the dialog

       (setf (a-coefficient curve) (value a-widget))
       (setf (b-coefficient curve) (value b-widget))
       (setf (c-coefficient curve) (value c-widget))
       (setf (color curve)
             (current-color coefficient-dialog))
       curve)))
  1. Now select the colorx buffer to bring it forward in the Editor Workbook. Add the current-color and find-color-name methods to the buffer.
(defmethod current-color ((dialog color-mixin))
  (let* ((color-list 
          (find-component :color-list dialog))
         (value (first (value color-list))))
    (if value
      (color (find value (range color-list)
               :key #'name))
    ;; else
     black)))

(defmethod find-color-name ((dialog color-mixin) 
                             &optional (color black))
  (let ((color-list 
         (find-component :color-list dialog)))
    (name (find color (range color-list)
                :key #'color
                :test #'rgb-equal))))
  1. Click on the coefficient-dialog buffer tab in the Editor Workbook to display the code. Edit the test-curve method to match the following:
(defmethod test-curve ((dialog coefficient-dialog))
  (let* ((curve 
          (make-instance 'cycloidal-curve
            :a-coefficient 
            (value (find-component 
                     :a-coefficient-control
                     dialog))
            :b-coefficient
             (value (find-component 
                      :b-coefficient-control
                      dialog))
            :c-coefficient 
             (value (find-component 
                      :c-coefficient-control
                      dialog))
            :color (current-color dialog)))
         (curve-dialog (owner dialog))
         (doodler (owner curve-dialog)))
    (draw-curve (frame-child doodler) curve)))
  1. Save all files by clicking on the Save All button.
  2. Run the Doodler using the Run Project button. In the running Doodler, draw and center the curve in the Curves dialog, and then use the Edit button in the Curves dialog to edit that curve or the Add button to add another curve. In the Coefficients dialog, choose a new color and try using the Test button to see that the color is displayed correctly. The Other Color… button on the Curves dialog still won’t work.
  3. When you are satisfied, click the Stop button to stop running the Doodler.

Activating the Other Color Button

  1. In the Project Manager dialog, select coefficient-dialog and click on the View Selected Form button.
  2. In the coefficient-dialog form, inspect the Other Color… button.
  3. In the Other Color… button Inspector (it will say :color-button button in the history window at the top), select the Events tab. Click on the Extended Editor button (the one with the three dots) for the on-change property.
  4. In the coefficient-dialog buffer, modify the on-change (coefficient-dialog-button-on-change) function to match the following.
(defun coefficient-dialog-color-button-on-change 
    (widget new-value old-value) 
  (declare 
    (ignore-if-unused widget new-value old-value)) 
  (when new-value 
     (add-other-color (parent widget))) 
  (not new-value))
  1. Switch to the colorx buffer and add the add-other-color and new-color-name methods.
(defmethod add-other-color ((dialog color-mixin))
  (let* ((new-color (ask-user-for-color
                      :initial-color
                      (current-color dialog)))
         (color-list 
          (find-component :color-list dialog))
         (color-name nil))
    ;; do nothing if user canceled
    (when new-color
       ;; do not add color if it already 
       ;; is on the list.
       (when 
         (not 
          (setf color-name 
               (find-color-name dialog new-color)))
          (setf color-name (new-color-name dialog))

          (let* ((colors (range color-list)))
            (setf (range color-list)
                  (append colors
                    (list 
                      (make-instance 'button-info
                        :name color-name
                        :image new-color))))))

       ;; change which color is pressed
       (setf (value color-list) 
             (list color-name)))))

(defmethod new-color-name ((dialog color-mixin))
  (let ((range 
         (range (find-component :color-list dialog)))
        (name nil))
    (do ((index 1 (1+ index)))
        (nil)
       (setf name (intern
                     (format nil 
                             "CUSTOM-COLOR-~d" index)
                            (find-package :keyword)))
       ;; make sure no other colors already have
       ;; the same name
       (unless (find name range :key #'name)
       (return name)))))
  1. Save all files by clicking on the Save All button.
  2. Run the Doodler using the Run Project button. Try using the Other Color… button on the Coefficients dialog to choose another color for the curve you are adding.
  3. When you are satisfied, stop the running Doodler by clicking the Stop button.

Create the Background Color Form

  1. Click on the New Form button (or File | New Form) on the standard toolbar and select dialog from the list of options. Click OK to create the new form. You are creating the Background color dialog that will modify the Doodler’s background color.
  2. Inspect the new form. Change (where necessary) its border to :palette, close-button to nil, minimize-button to nil, maximize-button to nil, name to :background-palette, child-p to nil, resizable to nil, and title to Background.

Reshape the new form to look like the illustration.

ch6-3-back.bmp (126126 bytes)

Changing the Location of a Form

At any point, you can choose a new location to display the form by dragging it around on your screen. Once you've saved the project, Lisp will remember the new location of the form. It will display in that location when you run the project. This is true for any non-modal dialog.

Drag the Background dialog over to the right of the Doodler window now and it will reappear there when you reopen the project next time. (You may have to move the Doodler form to the left.)

  1. In the Project Manager dialog, select background-palette and click on the View Selected Code button.
  2. In the Editor buffer that comes up, add the following defclass:
(defclass background-palette (color-mixin dialog)
   ())
  1. Evaluate the defclass.
  2. Save the buffer to background-palette.cl in your tutorial project directory.
  3. Inspect the background-palette form and change the class to background-palette.
  4. Add a multi-picture-button to the Background form.
  5. Inspect the new multi-picture-button. Change its name to :color-list and range to nil.
  6. In the color-list Inspector, select the Events tab. Select the on-change event and click on the Extended Editor button (the one with three dots). The background-palette buffer will display with some skeleton code for the on-change event (background-palette-color-list-on-change).
  7. Modify the on-change function in the buffer to match the following:
(defun background-palette-color-list-on-change 
    (widget new-value old-value)
  (declare 
    (ignore-if-unused widget new-value old-value))
  (when new-value
    (change-background-color (parent widget)))
  (not new-value))
  1. Now add the change-background-color method to the same buffer.
(defmethod change-background-color 
    ((palette background-palette))
  (let ((color (current-color palette))
        (doodler (owner palette)))
    (setf 
      (background-color (frame-child doodler)) 
      color)
    (erase-window doodler)))
  1. On the background-palette form, add a button control below the multi-picture-button.
  2. Inspect the new button and change its name to :color-button and title to Other Color....

This is exactly like the button you put on the Coefficients dialog (in Step 10) since background-palette also inherits from the color–mixin class.

  1. In the inspector for the new button, select the Events tab. Select the on-change event and click on the Extended Editor button (the one with three dots). The background-palette buffer of the Editor Workbook will appear with some skeleton code for the on-change event (background-palette-color-button-on-change).
  2. In the background-palette buffer, modify the on-change function to match the following.
(defun background-palette-color-button-on-change 
    (widget new-value old-value)
  (declare 
    (ignore-if-unused widget new-value old-value))
  (when new-value
     (add-other-color (parent widget)))
  (not new-value))
  1. Now add the initialize-instance :after method to the same buffer.
(defmethod initialize-instance :after 
    ((palette background-palette) &rest initargs)
  (declare (ignore initargs))
  (let* ((pane (frame-child (owner palette)))
         (color-name (find-color-name palette
                       (or (background-color pane)
         (default-background-color pane)))))
    (initialize-value 
     (find-component :color-list palette)
     (list color-name))))
  1. In the Project Manager dialog, select doodler and click on the View Selected Code button.
  2. In the doodler buffer, add the initialize-instance :after method. This method allows Doodler to bring up the Background dialog.
(defmethod initialize-instance :after 
    ((window doodler) &rest initargs)
  (declare (ignore initargs))
  (show-background-palette window)
  (select-window window))
  1. Modify the doodler defclass to add the slot and the accessor for the new dialog Doodler needs to open.
(defclass doodler (bitmap-window)
  ((doodler-curve-dialog
    :accessor doodler-curve-dialog
    :initform nil)
   (doodler-background-palette
    :initform nil
    :accessor doodler-background-palette)))
  1. Now add the show-background-palette method to the same buffer.
(defmethod show-background-palette 
    ((window doodler))
  (let ((palette 
         (doodler-background-palette window)))
    (unless palette
      (setq palette
            (make-background-palette 
              :owner window))
      (select-window palette)
      (setf 
        (doodler-background-palette window) 
        palette))
      (select-window palette)))
  1. In the doodler buffer, modify the close :before method to match the following. This completes the specialization of Doodler to accommodate, open, and close the background-palette.
(defmethod close :before ((window doodler) &key)
  (let ((curve-dialog (doodler-curve-dialog window))
        (background-palette
          (doodler-background-palette window)))
    (when (and (windowp curve-dialog)
               curve-dialog)
       (close curve-dialog))
    (setf (doodler-curve-dialog window) nil)
    (when (and (windowp background-palette)
               background-palette)
       (close background-palette))
    (setf (doodler-background-palette window) nil)))
  1. Save all files by clicking on the Save All button on the standard toolbar.
  2. Run the Doodler by clicking the Run button.
  3. Try everything. Congratulations! The tutorial is done. Stop the Doodler when done testing.

Suggestions for further reading

Included with this tutorial is the CLOS chapter from this book. Many thanks to Paul Graham and Prentice Hall for their excellent contribution.
This book introduces Common Lisp with special attention to topics that have grown more important recently including macros, optimization, object-oriented programming, and generating HTML. Included are discussions and examples illustrating unique capabilities of Common Lisp. Finally, it has a 100-page reference manual for ANSI CL that most people find sufficient for everyday use.

Provides a good introduction to CLOS and the use of CLOS in object oriented programming. An advanced book that teaches you how to extend CLOS and design your own language using CLOS.

Designed to introduce Common Lisp to readers who have experience with other programming languages.

An excellent advanced book with many detailed real-world examples. Provides an in-depth exposition of advanced AI programming techniques. It focuses on the programming techniques necessary for building large AI systems, including object-oriented programming. This book also serves as an advanced introduction to Common Lisp, with sections on the Loop macro, CLOS and sequences, and some coverage of error handling, series, and the package facility.

A complete reference for almost-ANSI Common Lisp and CLOS.

An introductory text for readers who want to learn the basics of Common Lisp.

An introductory text on the basics of Common Lisp for non-programmers.

Copyright © 2001-2004 Franz Inc. All rights reserved.

Introduction Chapter 1 Chapter 2 Chapter 3 Chapter 4 Chapter 5 Chapter 6 CLOS Intro
Allegro CL version 8.0