Squeak
  links to this page:    
View this PageEdit this PageUploads to this PageHistory of this PageTop of the SwikiRecent ChangesSearch the SwikiHelp Guide
Drag and Drop Explained
Last updated at 6:48 pm UTC on 27 January 2020
See also

Drag and Drop (PBE)

Dropping


The drop process starts with the HandMorph handling an event

This is supposed to find the right recipient morph and get it to deal with the drop. If no morph will handle the event (for example, rejecting it completely) then #rejectDropMorphEvent: is sent to the droppee to clear up.

MorphicEventDispatcher>dispatchDropEvent:with starts by checking if the current morph fullBounds (important distinction) contains the event’s cursor point; if not then there is no drop to consider and we return #rejected.
Next we ask if the recipient wants to reject the drop using #rejectDropEvent:.
Then we recurse down the submorphs, passing the entire dispatch down the tree using 'child processEvent:’ (which usually leads on to MorphicEventDispatcher>dispatchDropEvent:with: for each submorph). The return value that comes back from submorphs is extremely important; if #rejected is returned then the parent morph will keep trying its other submorphs. If any other value is returned then the parents’ looping through submorphs is broken out of; if the event was marked as handled then we return immediately and typically this will return through all the levels of Morph back to the root.
If no submorph actually deals with the event then the recipient is given a chance by sending #handleEvent:, which leads eventually leads to Morph>handleDropMorph: evt

Important drop related messages. (*) marks ones intended for you to over-ride


The Dropping process

Consider a fairly simple tree of Morphs; A contains B & C. We drop morph D somewhere over A and get to #dispatchDropEvent: event with: B.
In the case that B’s bounds do not include the event cursor point, we immediately return #rejected. This returns us to the middle of #dispatchDropEvent: event with: A and since the return value is == #rejected we simply move on to looking at C.
In the case of the event being within B’s fullBounds, we try ‘B rejectDropEvent:’.
All this complicated recursion is intended to allow the lowest level morph to get a chance to accept the drop. Note that submorphs are considered first; so a complex morph structure that you do not want to accept drops even if some interior morphs may be capable should implement #repelsMorph:event: to reject the drop immediately.

A very simple example of how this works -

Example for the #dropEnabled property (Morphic)

A little more complicated

Open the halo for the embedded CircleMorph and use its red menu to allow dropping on the CircleMorph. Drag another morph from the Objects viewer and drop it on top of the circle; if you check in the explorer you will see it as a submorph of the circle. So, now we can drop morphs into the RectangleMorph or the CircleMorph but what if we need to only allow dropping into the circle? All we need to do is (using the red menu) turn off ‘accept drops’ for the RectangleMorph - now dropping a morph on the rectangle will fail but on the circle will work.

Building a drop target morph.

A very basic drop target needs only two methods implementing
An example is provided, see below.

Dragging

When a mouse button is pressed, the HandMorph will create a MouseButtonEvent that gets sent to a Morph with #handleMouseDown:. which then sends itself #mouseDown: There are two distinct paths the code can follow at this point -
When next handling a mouse event, HandMorphhandleEvent: checks for an existing MouseClickState object and sends #handleEvent:from: to it. This is where the actual drag handling work is done.
MouseClickState#handleEvent:from: works out if the action is intended to be a drag. If there is a dragSelector set (see #on:send:to: and some versions of #mouseDown:) it sends #drag: to itself. (NB - this seems pointless, there are no other users, re-does a check for nil selector, should really be folded in).
The ‘clickClient’ set by the prior #on:send:to: or #mouseDown: invocation is sent the dragSelector. Usually that means the clickClient gets sent #startDrag: (simply because most people stick to the default). With either the event handler or the manually created MouseClickState it is possible to have a message sent to any object; there is no requirement that it be the morph involved in the drag operation. We could, for example, have a message logger object that simply reports the action for debugging or tracking purposes.
Finally the morph to drag has to be chosen and attached to the HandMorph. Usually this requires finding a morph at the event’s cursorPosition (#morphAt: helps, but provides all the submorphs intersecting that point which is a bit overwhelming. What is the simpler message?) and then sending the hand #grabMorph: with the chosen morph.
HandMorphgrabMorph: provides two more opportunities for the dragged morph to deal with the impending drag. The #aboutToBeGrabbedBy: method is used to do any pre-grab cleanup (see SelectionMorph for a simple example) and we should make sure to pass it on to super so that the default actions are followed. After the morph has been attached to the active Hand we send #justGrabbedFrom: to it to allow final clean-up or ex-owner edit saving etc. The only current implementors use it to save eToys scripts for Tile morphs that are being dragged out of an editor.

Important drag related messages. (*) marks ones intended for you to over-ride

A very simple example of how this works -

You still have that rectangle with the embedded circle, right? Using the explorer - it’s still open? - select the rectangle morph and then in the text view at the bottom of the explorer type -
self on: #startDrag send: #halt to: self
and DoIt.
Now if you try to drag the rectangle you should get a typical error notifier as the #halt gets sent to the rectangle. A quick look in the debugger will illustrate how the execution got there.
To turn off the action you can use
self removeLink: #startDrag
Now select the circlemorph and DoIt again on the #on:send:to: line; this time when you drag the rectangle it will move as you might expect. Drag the circle instead and you will get the notifier.
Here we are relying upon the event handler mechanism rather than subclassing and over-riding #mouseDown:

Building a drag source morph

To provide basic dragging capability is quite simple

An example pair of morphs

Both example classes are subclasses of Morph to keep this as simple as possible; the new morph can be added into some other Morph to provide borders, colours etc.

DragSourceMorph

All submorphs will be draggable, so only add items to be dragged. This example is only a source of morphs, so no handling of drop events is needed. Any morph dragged will provide a copy so that the source does not get emptied.
Since this is a class intended to be for parts bin use, we can over-ride #mouseDown: and build a MouseClickState directly rather than using the event handler per-instance mechanism.
#mouseDown: can check for a submorph that is under the event position and only create the MouseClickState when there is one; this should save having to check later. Since we are only interested in the dragging, the quad of selectors to pass to the #waitForClicksOrDrag:event:selectors:threshold: needs to be three nils and #startDrag:.
The #startDrag: code is very simple - find the target morph and have the hand grab a copy of it.
A side-effect of picking up a morph from our experiment is that you can’t put it back. It is possible to handle this and add some drop capability that checks for a morph recently taken from the source.

DragTargetMorph

The example will be only accept CircleMorphs, so the #wantsDroppedMorph:event: method simply returns whether the dropped morph class matches. In order to do something faintly interesting with the dropped morphs, the #acceptDroppingMorph:event: method
- checks for a layout policy and if one has been set we use it
- otherwise, works out a grid position within the target and puts the dropped there.

Play connect-5!

Load the Morphic-Demo-Drag package from SqueakSource
MCHttpRepository
    location: 'http://www.squeaksource.com/PlumbingDemo'
    user: 'tpr'
    password: ''


Then in a Workspace execute -
DragSourceMorph example1
DragTargetMorph example1

Drag the blue square target morph away a little so you can drag white or black circles to the target. Actually implementing rules of the game is left as an exercise for the reader.

Play with Plumbin'!

It's here- it's back! WardCunningham's famous ancient example for Squeak 5.x - Plumbin' - it is in the same repository as the above demo code.

[3]See #send:to:withEvent:fromMorph: for some explanation of details about what parameters can be sent with the message.
[2]The MouseClickState is created by sending #waitForClicksOrDrag:event:selectors:threshold: to a HandMorph (see PluggableListMorph>mouseDown: for an example) with a load of options as to what gets done. The general case is in HandMorphwaitForClicksOrDrag:event: but there are many options.
[1]http://www.poemhunter.com/poem/naming-of-parts/