[Prev][Next][Index][Thread]

Re: Custom gadgets in a frame



In article <JPLCPLKHFGBBBAAA@mailcity.com>,
  andrewa@functionalobjects.com wrote:
> Hi Dustin,
>
> On Wed, 19 Apr 2000 21:15:02   Dustin Voss wrote:
> >Has anybody run into this problem?

[Cannot subclass a DUIM gadget]

> >Anybody know why this is?  This *severely* limits the extensibility
of
> >DUIM.
> >
> >-- Dustin Voss
>
> This should be in a DUIM FAQ, it is a counterintuitive part of the
> DUIM design. Basically, the gadget classes are all abstract, so you
> are subclassing a class that isn't a complete implementation. So
> typically you would only subclass a gadget to build a completely
> new implementation.
>
> There are two ways in which you can effectively subclass gadgets
> as you were expecting:
>
> 1. Use the module win32-duim, and subclass <win32-list-box>.
>
>   This subclasses the concrete Win32 representation of <list-box>,
>   but obviously limits your code to only working on Windows.

Well, since the only implementation of DUIM is on Windows, this isn't a
problem...

> 2. Use 'define pane' to wrap the list box.
>
>   Something like this (untested):
>
>     define pane <my-list-box> (<list-box>)
>       pane inner-list-box (pane)
>         make(<list-box>, ...);
>       layout (pane)
>         vertically ()
>           pane.inner-list-box
>         end;
>     end pane <my-list-box>;
>
>   I think this has been discussed before, so you might be able to
>   find more detail in the archives.
>
> I typically find that subclassing a gadget is something you don't
> need to do, in languages such as Java you have to do it in order
> to add new methods, but in Dylan you can add methods without having
> a new class. The whole Functional Developer IDE is written in DUIM,
> and we didn't find a need to subclass gadgets.
>
> If you'd like to tell me what you're trying to do, I'd be happy to
> offer some advice on how I might solve such a problem.

Sure...I'm sub-classing the control to support drag & drop operations.
This is as fine a time as any to reveal the (limited) extent of my
genius.

My original plan was to handle drag & drop like I would in a C app.  In
the handler for some event (<button-press-event> or <pointer-drag-
event>), I write an event loop which takes over from the standard
loop.  I track the mouse pointer and pass other events off to the
standard event dispatcher.  I could not do that in Dylan, because there
are no functions to fetch and enqueue an OS event.

I briefly considered using a separate thread running the with-pointer-
grabbed macro, and ended up doing that, but only after my other attempt
failed.  I had tried to write a handle-event method on <sheet> and
<button-release-event> to learn when the drag & drop operation was
completed, but alas, there was already such a method and I couldn't
override it.

So I ended up with the threaded version.  When the user starts a drag
in a drag & drop-capable control (i.e. any control that inherits from
the <drag&droppable> class [a Java naming convention I like]), the
control's <pointer-drag-event> handler starts a 'monitor' thread.  The
thread runs a with-pointer-grabbed macro and, in the middle of the
macro, waits on a <synchronization> object that indicates the drag &
drop is complete.

Meanwhile, back in the main thread, all pointer events are getting
trapped and routed to <drag&droppable> event handlers, courtesy of the
still-in-dynamic-scope with-pointer-grabbed macro.  The <pointer-drag-
event> handler notifies drag & drop-capable controls that something is
being dragged over them.  A <button-release-event> handler is keeping
an eye out for the end of the drag & drop.  Since ALL pointer events,
even those over other sheets, are being trapped, this handler can
monitor what's going on over foreign sheets.  When the drag & drop
ends, the handler notifies the appropriate controls.

Notifications, in true DUIM fashion, are via callbacks.  I've provided
some additional slots to specify the callbacks in the layout code just
like other gadgets do.

Now, I say this in past tense, but I haven't been able to test it
because of the subclassing problem.  I need to subclass for three
reasons:

1) I don't want to rewrite DUIM.  I must use the existing event and
sheet architecture, but I must extend the behavior for drag & drop-
capable controls.  That means I must subclass controls and write
methods on the new classes.

2) I want drag & drop controls to be as seamless as possible, hence the
new callback slots and corresponding init keywords.

3) Finally, this subclassing must be done via mixin so the programmers
(meaning me) can use drag & drop without a god-awful amount of work.

So there it is.  Source code follows.  Comments?

-- Dustin Voss


/****************************
 *  Mixin class (exported)  *
 ****************************/

define class <drag&droppable> (<object>)

  // Source callbacks
  slot drag-begin-callback    :: <function>, init-value: #f, init-
keyword: #"drag-begin-callback";
  slot drag-complete-callback :: <function>, init-value: #f, init-
keyword: #"drag-complete-callback";
  slot drag-cancel-callback   :: <function>, init-value: #f, init-
keyword: #"drag-cancel-callback";

  // Target callbacks
  slot drag-over-callback     :: <function>, init-value: #f, init-
keyword: #"drag-over-callback";
  slot drag-target-callback   :: <function>, init-value: #f, init-
keyword: #"drag-target-callback";

end class <drag&droppable>;


/******************************
 *  Internal state variables  *
 ******************************/

define variable *drag-object* = make (<object-table>);  // keyed by
pointer

define variable *drag-monitor-thread* = make (limited (<object-table>,
of: <thread>));  // keyed by pointer
define variable *drag-monitor-sync* = make (limited (<object-table>,
of: <synchronization>));
  // keyed by thread, not by pointer


/*****************************************************
 *  object-being-dragged setter/getter on <pointer>  *
 *****************************************************/

define method object-being-dragged (pointer :: <pointer>) => (object ::
<object>);
  element (*drag-object*, pointer, default: #f);
end method;

define method object-being-dragged-setter (object :: false-or
(<object>), pointer :: <pointer>)
  if (object)
    *drag-object*[pointer] := object;
  else
    remove-key! (*drag-object*, pointer);
  end if
end method;


/********************
 *  Event handlers  *
 ********************

As the mouse moves, the sheets' handlers monitor the mouse button to
make sure it is still
pressed.  They call drag-over on the drag&drop sheets under the mouse.
The non-drag&drop
sheets are ignored.  When the mouse button is released, the handler
calls the drag-drop
callback on the target sheet and the drag-complete callback on the
source sheet.  If the
target sheet isn't a drag&drop sheet, the drag-cancel callback is
called on the source sheet.

How does this divvy up drag&drop control?

- Source sheets need the drag-source, -complete, and -cancel callbacks.
- Destination sheets need the drag-over and -target callbacks. */


// This is called when starting or continuing a drag

define method handle-event (source :: <drag&droppable>, event ::
<pointer-drag-event>) => ()
  let pointer = event.event-pointer;

  if (element (*drag-monitor-thread*, pointer, default: #f))
    // This drag is being monitored...continuing to drag
    if (instance? (pointer.pointer-sheet, <drag&droppable>))
      drag-over-callback (pointer.pointer-sheet);
    end if

  else
    // Not being monitored, so this is a new drag!  Start the monitor
    let sync-for-thread = make (<synchronization>, name: "Release me to
kill drag-monitor");
    let drag-monitor-method = method () => ()
                                // Grab the pointer until the sync
becomes available
                                with-pointer-grabbed (source)
                                  wait-for (sync-for-thread);
                                end;
                              end method;

    wait-for (sync-for-thread);  // Make the sync unavailable (I assume
it is created available)
    let monitor-thread = make (<thread>, function: drag-monitor-method);

    // The drag is now official.  Store for later reference
    *drag-monitor-thread*[pointer] := monitor-thread;
    *drag-monitor-sync*[monitor-thread] := sync-for-thread;

    // Notify the source that the drag has started
    drag-begin-callback (source);
  end if;

  next-method();
end method;


// This handler determines when the drag is finished, and cleans up

define method handle-event (source :: <drag&droppable>, event ::
<button-release-event>) => ()
  let pointer = event.event-pointer;
  let monitor-thread = element (*drag-monitor-thread*, pointer,
default: #f);

  if (monitor-thread)
    // A drag operation is in progress...but it's done now

    // Dismantle the thread stuff
    release (*drag-monitor-sync*[monitor-thread]); // This should end
the pointer-grab thread
    join-thread (monitor-thread);
    remove-key! (*drag-monitor-thread*, pointer);
    remove-key! (*drag-monitor-sync*, monitor-thread);

    // Call the call-backs
    let destination = pointer.pointer-sheet;
    if (instance? (destination, <drag&droppable>))
      // Drop was completed successfully.
      drag-target-callback (destination);
      drag-complete-callback (source);
    else
      // Couldn't complete drop.
      drag-cancel-callback (source);
    end if;
  end if;

  next-method();
end method;


--
A.K.A. d_voss@mindspring.net
       dvoss@xypoint.com
(Welcome to account Madness!)


Sent via Deja.com http://www.deja.com/
Before you buy.



References: