From: cheetha (Ken Cheetham)

Subject: Re: [pcspr4288] Custom event handling of a widget

Date: 1997-1-1 1:41


  Does anyone know how to define the keyboard/mouse behavior of a
  widget, say, single-item-list? What I want to do is to add functions
  for mouse left single click, left double click and right single click.

We appreciate everyone's comments regarding event handling for widgets
(controls) in Allegro CL for Windows.  We are investigating making the
programmatic interface more consistant and improving the documentation
for the next major release.  We especially need to explicitly list the
set of event handlers (and properties) that are defined for each type
of control, and then document all event handlers and properties in a
single place rather than each one under some arbitrary type of widget
for which it is defined.

Part of the confusion, though, stems from the fact that most of the
controls are built into the OS with special behavior and are not
intended to (typically) be handled in the same low-level way as
"regular" windows.  While coming up with an explanation for this
I've ended up writing a whole primer (below), which I'll submit
for official documentation.

You will probably need to refer to subclass-widget toward the end of
this for intercepting arbitrary single clicks on an item-list widget.

[This request has been assigned the tracking number pcspr4288.
For efficiency, please mention this number in the title of future
messages concerning it.  Also please cc: <franz.com at pc-support> so
that someone else can handle your message if I am away.]

Ken Cheetham                          <franz.com at cheetham>
Franz Inc.                            Voice: (510) 548-3600
1995 University Avenue, Suite 275     Fax:   (510) 548-8253
Berkeley, CA  94704                   http:  //www.franz.com/
ACL Windows FAQ: ftp.franz.com:/pub/acl4w-faq
ACL Unix    FAQ: ftp.franz.com:/pub/faq

---------------------------------------------------------------------------
Handling Widget Events

For regular non-widget windows, Allegro CL receives a variety of
low-level messages (or events).  All mouse click, mouse movement, and
keypress messages are passed to the EVENT method for the window, while
other messages are passed to particular methods that are set up for
them, such as the window's SET-FOCUS method which is called whenever
the window receives the keyboard focus.

For widgets (also known as controls), on the other hand, Allegro
normally does not receive these low-level events.  Most of these
controls are defined in the Windows OS, and the low-level window
events are sent directly to the controls' own event handlers within
the OS, and each control sends a smaller higher-level set of messages
to the application (Allegro) as defined by the particular control.

For example, when the user clicks on an item-list, Allegro does not
receive any mouse click message, and so we cannot call EVENT for
either the widget or its dialog-item-window.  Instead the control
internally receives the event and determines which item was selected
and so on, and then sends Allegro a "change" message if the click
results in a different item in the list being selected, and so we
invoke the set-value-fn of the widget at that time.

A special way to intercept all low-level messages for controls is
described further below, but first we will discuss the higher-level
event-handling interface that each control defines for itself, as
implemented in Common Graphics.

---------------------------------------------------------------------------
Event Handlers On Widget Instances

Controls typically send out high-level messages to indicate such
occurrences as when their value changes, when they receive the
keyboard focus, and so on.  For many of these higher-level messages,
Common Graphics allows you to assign an arbitrary function to be run
whenever a particular widget message is received.  For example, an
item-list does not send us a message for a single click, but it DOES
send a message for a double-click, and so you can set up a function to
run whenever item-list-1 is double-clicked:

(defun my-double-click-fn (dialog widget)
     (print "You successfully double-clicked!"))

(setf (dialog-item-double-click-fn item-list-1)
      'my-double-click-fn)

This technique is object-oriented, but you may notice that it is not
CLOS-style since it locally attaches a "method" to a particular
object, while a CLOS method is independent of any particular objects
(and may even "specialize" different parameters on different classes
or objects, a feature known as multimethods).  We still provide these
instance-based "slot methods" for widget event handlers, though,
because (1) widget instances typically have their own individual event
handlers, and it would be inconvenient to specialize CLOS methods on
each one, and (2) such "slot methods" can be readily manipulated with
interface builders such as ours, which work primarily with individual
controls.

A specific point of confusion is that there is not always a one-to-one
correspondence between widget messages and the Common Graphics
handlers for them.  Two cases in particular: (1) when an editable-text
widget has the "delayed" attribute (as it does by default), its
set-value-fn is run when it loses the keyboard focus rather than when
the widget sends us a "change" message as each character is typed, and
(2) clicking a button widget invokes its set-value-fn even though the
button's "value" is not meaningful and is toggled between t and NIL as
it is clicked simply to cause the set-value-fn to be invoked.  It
would have been more sensible to provide a specific handler for the
button-click rather than to artificially map the click onto the
general set-value-fn, but that feature may be difficult to enhance in
a backwardly-compatible way.

---------------------------------------------------------------------------
Event Handlers On Widget Classes

While the above instance-based event-handling should usually suffice,
Allegro allows you to alternately define CLOS methods to handle
particular events for all controls of some subclass.  In order to do
this, you first need to create a (CLOS) subclass of some widget class.

This is somewhat awkward due to the design where (1) a dialog-item and
its dialog-item-window are separate objects and (2) events are passed
through the dialog-item-window rather than the dialog-item itself.
Therefore you need to subclass both of those classes and then add a
special method that points one to the other.
 
(We are looking into combining these two classes into a single class,
which would make this more straighforward.)

This example creates a subclass of single-item-list and its
associated dialog-item-window class:

(defclass my-item-list (single-item-list)())
(defclass my-item-list-pane (pc::single-item-list-pane)())
(defmethod widget-device ((item my-item-list)(dialog basic-pane))
  'my-item-list-pane)

This method then defines a double-click handler for
all instances of the my-item-list subclass:

(defmethod dialog-item-double-click ((dialog t)(widget my-item-list))
   (format t "You double-clicked ~a" (object-name widget))
   (call-next-method))

Since the default dialog-item-double-click method simply invokes the
widget's personal dialog-item-double-click-FN (if it has one), the
call-next-method above would invoke that instance's own double-click
handler as well.

---------------------------------------------------------------------------
Intercepting Low-Level Events on Widgets

So far we have discussed handling the high-level events that are
defined by a particular control, either at the instance level (simple)
or at the class level (somewhat awkward).  This higher-level interface
to the built-in controls is recommended whenever it is sufficient.
But if you really need to intercept the low-level mouse or keyboard
events for a widget, then you can call

(subclass-widget a-widget-instance)

Subclass-widget has nothing to do with CLOS subclasses, but rather
performs a standard Windows technique where we tell the OS to send all
low-level events for a control to Allegro's event handler instead of
to the control's own event handler.  (Windows imaginatively regards
this as "subclassing".)  This technique allows Allegro to intercept
all events and then optionally pass them on to the control's own event
handler in the OS if we want to retain the usual effect that's built
into the control.  Once you have called subclass-widget on a widget
instance, then the EVENT method will be called for each low-level
mouse and keyboard event for the widget.  (Other low-level events will
be passed only to the pc:control-procedure generic function, which is
similar to the pc:window-procedure generic function through which all
non-control messages are passed.)  You might want to go this route if,
for example, you really need to know when the user has clicked the
item that is already selected in an item-list, since the control sends
us no high-level message in that case.

Note that widgets of type pc::lisp-widget-mixin are written entirely
in lisp using "regular" (non-control) windows, and so the
dialog-item-window of the widget receives the low-level window events
already.  So there is no need to call subclass-widget on a lisp-widget
in order to receive these events.

Since the EVENT method is called on the dialog-item-window of a
"Windows subclassed" widget, you could intercept all left-clicks on
the my-item-list subclass defined above like this:

(defmethod event ((window my-item-list-pane)(message (eql mouse-left-down))
                  buttons data time)
   (format t "You clicked on ~a at position ~a,~a~%"
     (object-name (window-dialog-item window))
     (position-x data)(position-y data))

   ;; This call-next-method passes the event back to the control's own
   ;; event handler built into the OS so that it will select the
   ;; clicked list item as usual.  Removing this line would prevent
   ;; the OS from selecting the clicked item.
   (call-next-method))

(subclass-widget a-my-item-list-instance)

Note that the "data" parameter above indicates the mouse position at
the time of the click.  This meaning is specific to mouse-click
messages, and the data parameter means other things for keypress and
mouse movement messages.  (In the next major release we may replace
EVENT with individual generic functions for the various mouse and
keyboard messages (though retaining EVENT for backward compatibility)
so that the meaning of the function parameters is more explicit.