Drag and Drop¶
In a Cocoa application, NSView is naturally the receiver of dragged data. DrawKit allows the general dragging model (NSDraggingDestination) to be extended down into its layers and objects within layers, much as it does for menu commands etc. The default behaviour is for dragged objects to target the active layer, and then as the drag location changes, to allow objects under the mouse to become selected if they are able to receive the dragged data. This allows you for example to drop image files from the Finder on top of an individual object, and have it adopt that image in a meaningful manner. In fact, DrawKit’s standard objects are able to receive drags of strings, images, and colours, and layers are also able to receive drags of images, creating DKImageShapes, and text, creating DKTextShapes. In addition, DrawKit’s native objects are also draggable between different views and documents.
The first object that needs to deal with a drag is DKDrawingView, because a view must be the initial destination of any drag. When the active layer changes, DKViewController gathers the drag types that the layer and its objects are able to respond to, and sets these types as the registered drag types for its view. Thus any drags matching the given types are first handled by the view. Then the same NSDraggingDestination methods are called on the active layer - so any layer is able to implement the NSDraggingDestination protocol exactly as if it were a view.
DKObjectDrawingLayer takes this one step further and targets the drawable object under the mouse (drag location). For simplicity a drawable object does not implement the full NSDraggingDestination protocol - instead the layer handles the majority of it and the object is required only to do two things:- 1. supply the drag flavours it knows how to respond to, and 2. handle the drop when the user drops the item. The layer will not pass a drop to an object that can’t receive the data, is locked or hidden, nor allow it to be selected. You can disable the passing on of drags to objects by using the - setAllowsObjectsToBeTargetedByDrags: method. If NO, the layer can still receive drags.
Standard objects in DrawKit respond to dragged data in the following ways:
- Any object can receive a colour item. If the object has one or more fills, the topmost fill is set to the colour, otherwise if it has one or more strokes the topmost stroke is set to the colour. If it has neither, a fill is added with the given colour.
- Any object is able to receive an image. If the object already has an image or path adornment or pattern fill, the image sets the adornment’ or pattern’s image. Otherwise if the object is a shape, an image is added as a DKImageAdornment. If the object is a path, a DKPathDecorator is added having the dragged image. If the object is a DKImageShape, the dragged image sets the shape’s image.
- Any object is able to accept a dragged string. If the object already has a DKTextAdornment, the strings sets the adornment’s text. If the object is a DKTextShape, the string sets the object’s text. If it doesn’t have any text, a DKTextAdornment is added, having the block layout for a shape, and the path layout for a path, and having the style’s text attributes, if any (otherwise Helvetica 12, left aligned).
- If an image is dropped into the layer (and not an object within it), a new DKImageShape is created with the image and added at the drop location.
- If text is dropped into the layer a new DKTextShape is created with the text and added at the drop location.
The choice of these behaviours is to do the most useful, expected thing with dragged-in data. Naturally you can customise this in many ways including disabling it altogether. Each class of drawable object supplies a list of the drag data types it is willing to respond to, so your subclass can modify this list how it likes. Its implementation of -performDragOperation can do whatever makes sense for the data being dropped. The layer itself returns the union of all of the drag types from all drawable classes when it registers the drag types, and this code is forward-compatible with any new classes of DKDrawableObject you might create.
Note that the above default implementation of handling dragged data works by modifying a copy of the receiver object’s current style. Thus even if the object is using a locked and/or registered style, it still responds to the drag and ends up with a new style assigned. Style’s are never modified “in place” though if the object is the sole client of a style, it will be replaced and the old one discarded, so it is effectively equivalent. The point is that the drag/drop behaviour does not change the style of any object other than the one receiving the drag.