Drag-and-Drop

Drag-and-drop is another way for applications to interoperate: the user simply drags information from one application into another. Drag-and-drop requires more work on the part of the user than the Services system, because the data must be manually dragged across application boundaries. Drag-and-drop is also less powerful than Services because it does not offer the bidirectional interaction of services that receive information, act on it, and return a result. Nevertheless, drag-and-drop is easier than Services for many people to understand, largely because drag-and-drop is more familiar: it is present both in Windows and in previous versions of the Macintosh operating system.

Although we’ve already done a lot in this chapter, with just a little more work we can implement drag-and-drop functionality as well. So let’s do it!

Being a Drag-and-Drop Source

We can make GraphPaper a drag-and-drop source by making a few small changes to the GraphView class. Unfortunately, one aspect of this process will be a little awkward because of our decision earlier in this chapter and the previous one to have the PDF- and TIFF-generation functionality centralized in the Controller class. (Fixing this design flaw is left to the user as an exercise.)

  1. Add the following three new method declarations to the GraphView.h interface file:

                            -(BOOL)acceptsFirstMouse:(NSEvent *)theEvent;
                            -(NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)flag;
                            -(void)mouseDragged:(NSEvent *)theEvent;
  2. Insert the #import directive shown here in bold at the beginning of GraphView.m:

    #import "GraphView.h"
    #import "Segment.h"
    #import "Label.h"#import "Controller.h"
  3. Add the acceptsFirstMouse: method to GraphView.m:

                            - (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
                            {
                                return YES;
                            }

The Macintosh operating system is a click-to-focus window environment. This means that you click on a window to focus the keyboard on that window. But this can lead to confusing behavior. Sometimes you want a view to react immediately to an activating click, such as when you are clicking on a button of an application that is not active. Other times you do not want the application to react to this activating click. Whether or not an NSView reacts to an activating click is controlled by the acceptsFirstMouse: method. If this method returns YES, the NSWindow object will both activate and pass the event along to the NSView in which an activating click occurs. This is the behavior that we want for dragging operations, because, for example, the GraphPaper application will probably not be the active application when you are attempting to drag an image out of it.

You must override two methods to implement drag-and-drop in your view. The first method, draggingSourceOperationMaskForLocal: , tells Cocoa which drag-and-drop operations you support. The flag argument allows you to specify whether you support these operations only for other applications, or also within your own application.

The second method, mouseDragged: , is used to initiate a drag-and-drop operation. Your NSView subclass is sent this message when the mouse is dragged.

  1. Add the following draggingSourceOperationMaskForLocal: method to GraphView.m:

                            - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)flag
                            {
                                if (flag==YES) return NSDragOperationNone;
                                return NSDragOperationCopy;
                            }

This method returns an NSDragOperation type that indicates what kind of operation is supported. If flag is YES, we are being asked about drag operations within the same application. Otherwise, we are being asked about drag operations into other applications. This method tells the drag-and-drop system that we do not support dragging into the same application; we only support the copy operation into other applications.

Finally, we need to implement the method that does the actual dragging. Instead of hooking onto the mouseDown: event, we’ll actually hook onto the mouseDragged: event, so that merely clicking in the GraphView will not initiate the dragging operation. (We tried the application both ways and decided that this way was better.)

  1. Add the mouseDragged: method to GraphView.m:

                            - (void)mouseDragged:(NSEvent *)theEvent
                            {
                                NSImage *pdfImage = [[NSWorkspace sharedWorkspace]
                                                      iconForFileType:@"pdf"];
                                NSPasteboard *pboard;
                             
       pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
                                [[NSApp delegate] copyToPasteboard:pboard];
                            
        [self dragImage:pdfImage
                                             at:[self convertPoint:[theEvent locationInWindow]
                                                 fromView:nil]
                                         offset:NSMakeSize(0,0)
                                          event:theEvent
                                     pasteboard:pboard
                                         source:self
                                      slideBack:YES];
                            }

This method first gets an image of a PDF file’s icon. We don’t really know what that image looks like, but the operating system knows, so we ask it and store the results in the pdfImage variable. We then ask the NSPasteboard class for the NSDragPboard and ask the Controller class to copy to this pasteboard. (This is the inelegant part, by the way. Ideally, the copyToPasteboard code should have been put in the GraphView class, rather than in the Controller class.)

Finally, we use the NSView dragImage:at:offset:event:pasteboard:source:slideBack: method to initiate the dragging operation. This method takes the arguments listed in Table 20-5.

Table 20-5. Method arguments of dragImage:at:offset:event:pasteboard:source:slideBack:

Argument

Meaning

at:

Location where the dragging should start.

offset:

Offset into the image for the dragging. We make this (0,0), but it could be a point within the image itself.

event:

Event that initiated the dragging operation.

pasteboard:

Pasteboard to use for the dragging operation.

source:

Source of the dragging operation; it is usually self, but it doesn’t have to be.

slideBack:

If this argument is true, a released drag icon will appear to slide back to its source.

  1. Build and run GraphPaper, saving all files first.

  2. Start the TextEdit application and make sure the active window is in Rich Text mode.

  3. Click the Graph button to graph a function.

  4. Press your mouse on the graph and drag. A PDF icon should appear, as shown in Figure 20-9.

Dragging a PDF icon from GraphPaper

Figure 20-9. Dragging a PDF icon from GraphPaper

  1. Drop the PDF icon into the TextEdit application. The graph should appear!

  2. Quit GraphPaper.

Pretty neat, isn’t it? Cocoa programming is actually lots of fun once you get the hang of it.

Being a Drag-and-Drop Receiver

As long as we are implementing drag-and-drop, we should implement the ability to receive drag-and-drop events as well. Of course, just what an application such as GraphPaper should do when it receives a drag-and-drop event might be subject to some debate. After all, what would it mean to drop something into a GraphView?

A drag-and-drop event contains both the data being dragged in and a type associated with that data. This information is on the pasteboard that is provided to the drag-and-drop receiver. (You should always interrogate the incoming drag-and-drop event for its pasteboard, rather than simply getting the global NSDragPboard.) We’re not sure what it means to drag an image into the GraphPaper application. However, there are two obvious drag-and-drop types to which the GraphPaper application could respond:

  • If the user drags in a color and drops it on an item in the GraphView, we should set the item to be that color.

  • If the user drags in a piece of text and drops it on the GraphView, we should set the formula to be that text and initiate a graph.

We’ll implement both of these.

Drag-and-drop receiving applications must implement one or more of the NSDraggingDestination category methods. These methods are implemented as categories of NSObject, rather than as informational protocols or categories of NSResponder. Because they are implemented as categories of NSObject, you can send the messages to any object without first checking to see whether that object responds to them. And they are probably categories of NSObject, rather than NSResponder, so both responders and cells can be used as drag-and-drop destinations. The methods that drag-and-drop receivers need to implement are listed in Table 20-6.

Table 20-6. NSObject (NSDraggingDestination) methods implemented by drag-and-drop receivers

Method

Purpose

- (NSDragOperation)draggingEntered: (id <NSDraggingInfo>)sender

Sent to a potential drag-and-drop receiver. NSObject returns NSDragOperationNone, which indicates that the object cannot receive drag-and-drop events.

- (NSDragOperation)draggingUpdated: (id <NSDraggingInfo>)sender

Sent periodically while a drag-and-drop object is held over a potential receiver. The receiver can use this to implement some sort of animation.

- (void)draggingExited: (id <NSDraggingInfo>)sender

Sent to the potential drag-and-drop receiver if no drag-and-drop took place.

- (BOOL)prepareForDragOperation: (id <NSDraggingInfo>)sender

Sent to the potential receiver when the drag-and-drop image is released. The receiver should return YES if it can receive the drag-and-drop object and NO if it cannot.

- (BOOL)performDragOperation: (id <NSDraggingInfo>)sender

Sent after the drag-and-drop object has been released. This is where the receiver should do the actual work of receiving the drag-and-drop object. Returns YES if the action was successful.

- (void)concludeDragOperation: (id <NSDraggingInfo>)sender

Sent when the drag-and-drop operation is finished.

- (void)draggingEnded: (id <NSDraggingInfo>)sender

According to Apple’s documentation, this is sent when the drag-and-drop operation concludes in some other destination. In fact, though, it isn’t sent, because the document also indicates that this method hasn’t been implemented.

Table 20-7. NSDraggingInfo protocol accessor methods

Method

Purpose

- (NSWindow *)draggingDestinationWindow

Returns the destination window of the drag operation.

- (NSDragOperation)draggingSourceOperationMask

Returns the dragging source operation mask, which indicates what sort of dragging operation the source supports.

- (NSPoint)draggingLocation

Returns the cursor’s current location in the dragging operation, in the destination window’s coordinate system.

- (NSPoint)draggedImageLocation

Returns the location of the dragged image.

- (NSImage *)draggedImage

Returns the actual image being dragged.

- (NSPasteboard *)draggingPasteboard

Returns the pasteboard that contains the data that is being dragged in.

- (id)draggingSource

Returns the source of the dragging operation.

- (int)draggingSequenceNumber

Returns the integer that uniquely identifies the dragging session.

- (void)slideDraggedImageTo:(NSPoint)screenPoint

Slides the image to screenPoint. Use this method to snap the image down to a particular location. Read the documentation for details.

We’re going to implement the methods to receive a drag operation by modifying the ColorGraphView class.

  1. Add the following method declarations to the ColorGraphView.h interface definition:

                            // Dragging support 
                            - (int)tagAtPoint:(NSPoint)pt;
                            - (unsigned int)draggingEntered:(id <NSDraggingInfo>)sender;
                            - (unsigned int)draggingUpdated:(id <NSDraggingInfo>)sender;
                            - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender;
    @end
  2. Modify the initWithFrame: method in ColorGraphView.m by inserting the lines shown here in bold:

    - initWithFrame:(NSRect)frame
    {
        [super initWithFrame:frame];
        axesColor = [[NSColor darkGrayColor] retain];
        graphColor = [[NSColor blackColor] retain];
        labelColor = [[NSColor blackColor] retain];
        [self registerForDraggedTypes:
                                      [NSArray arrayWithObjects:NSStringPboardType,
                                       NSColorPboardType,nil]];
        return self;
    }

The tagAtPoint: method allows the methods that implement drag receiving to determine what object is underneath the cursor’s hot spot.

  1. Add the following tagAtPoint: method to the ColorGraphView.m class implementation file:

                            - (int)tagAtPoint:(NSPoint)pt
                            {
                                NSEnumerator *en;
                                id obj;
                            
        en = [displayList objectEnumerator];
                                while (obj = [en nextObject]) {
                                    if (NSPointInRect(pt,[obj bounds])) {
                                        return [obj tag];
                                    }
                                }
                                return 0;
                            }

The draggingEntered: method simply informs the dragging system that we accept dragging only for copy operations.

  1. Add the draggingEntered: method to ColorGraphView.m:

                            - (unsigned int)draggingEntered:(id <NSDraggingInfo>)sender
                            {
                                return NSDragOperationCopy;
                            }
  2. Add the draggingUpdated: method to ColorGraphView.m:

                            - (unsigned int)draggingUpdated:(id <NSDraggingInfo>)sender
                            {
                                NSPasteboard *pboard = [sender draggingPasteboard];
                            
        // If it is a string, we can take it here
                                if ([pboard stringForType:NSStringPboardType]) {
                                    return NSDragOperationCopy;
                                }
                            
        // If is a color, we support dropping only 
                                // on objects in the display list 
                                if ([pboard dataForType:NSColorPboardType]) {
                            
           // Get the dragging location in the view's coordinates
                                   NSPoint pt = [self convertPoint:[sender draggingLocation]
                                                          fromView:nil];
                            
           // See if there is an intersection.
                                   // If so, say we support a copy.
                                    if ([self tagAtPoint:pt]) {
                                        return NSDragOperationCopy;
                                    }
                                }
                                return NSDragOperationNone;
                            }

This method is more complicated than the draggingEntered: method. If we are receiving a text drag, we tell the dragging system that we can accept it anywhere. But if we are receiving a color drag, we can receive it only at points where we actually have something drawn (that is, on a piece of the graph, on the labels, or on the axes).

The next method that will actually implement the drag operation.

  1. Add the performDragOperation: to the ColorGraphView.m class implementation file:

                            - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
                            {
                                NSPasteboard *pboard = [sender draggingPasteboard];
                                NSString *str =0 ;
                                NSColor  *color=0;
                            
        // If there is text, do the graph
                                [pboard types];
                                str = [pboard stringForType:NSStringPboardType];
                                if (str) {
                                    [self setFormula:str];
                                    [self graph:nil];
                                    return YES;
                                }
                            
        // If there is color, find the tag that the user is dragging
                                // onto and set the objects with that tag to be that color
                                color = [NSColor colorFromPasteboard:pboard];
                            
        if (color) {
                                    NSPoint pt = [self convertPoint:[sender draggingLocation]
                                                           fromView:nil];
                                    int tag = [self tagAtPoint:pt];
                                    if (tag!=-1) {
                                        [self setObjectsToColor:color forTag:tag];
                                        return YES;
                                    }
                                }
                                return NO;
                            }

This method first sees if there is a string on the dragging pasteboard. If there is, the method sets the formula to be the value and performs a graph operation. If a color is passed in, the method determines the tag over which the color chip was dropped, then sets all of the objects with that tag to be that color.

  1. Build and run GraphPaper, saving all files first. Don’t graph a function yet.

  2. Open up the TextEdit application. Type sin(x) (or any function that Evaluator can handle) into an empty window and select the text by typing Command-A.

  3. Now drag the selected text (i.e., the text expression sin(x)) and let it hover over the GraphPaper graphing area, as shown in Figure 20-10.

    Note that a plus sign (+) accompanies the arrow cursor, indicating that the view will accept a copy of the dropped item (the plus sign is not shown in Figure 20-10).

    Drag-and-drop from TextEdit into GraphPaper’s graphing area

    Figure 20-10. Drag-and-drop from TextEdit into GraphPaper’s graphing area

  4. Now drop the dragged text into GraphPaper’s graphing area, and voila — GraphPaper graphs the dropped function sin(x)! See Figure 20-11.

The resulting graph of the dropped function (bottom)

Figure 20-11. The resulting graph of the dropped function (bottom)

Next we’ll test the color drag-and-drop feature.

  1. Back in the TextEdit application, choose Format Font Colors (or type Command-Shift-C) to display the Colors panel.

  2. Choose a color and drag a color chip to the actual graph (not just the graphing area) in GraphPaper, as shown in Figure 20-12.

    Dragging a color chip from the Colors dialog and dropping it on GraphPaper’s graph

    Figure 20-12. Dragging a color chip from the Colors dialog and dropping it on GraphPaper’s graph

    Note that the arrow cursor changes to the arrow cursor with the plus sign only when the mouse is over an object in the window that can receive a color. (Again, the plus sign doesn’t appear in the screen shot.)

  3. Drop the color chip on top of the graph and see it change color.

  4. Now drag-and-drop another color chip on the function name (label) and see it change color as well! (We didn’t write code for the axes to accept drag-and-drop color chips.)

  5. Quit GraphPaper and rejoice!

This completes our implementation of pasteboard-related services.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.17.154