Chapter 18. Pasteboards and Nil-Targeted Actions

There is a process running on your Mac called the pasteboard server (/System/Library/CoreServices/pbs). Applications use the NSPasteboard class to write data into that process and to read data from that process. The pasteboard server makes operations like copying, cutting, and pasting between applications possible.

An application can copy the same data onto the pasteboard in several formats. For example, an image can be copied onto the pasteboard as a PDF document and as a bitmapped image. Then the application that reads the data can choose the format that it likes most.

When putting data on the pasteboard, your application typically declares the types it will put on the pasteboard and then immediately copies those data to the pasteboard. The receiving application will first ask the pasteboard what types are available and then read the data in its preferred format.

You can also copy data to the pasteboard in a lazy manner. To do so, simply declare all the types of data you could put on the pasteboard and then supply the data when it is requested. We will talk about lazy copying at the end of the chapter.

Multiple pasteboards are available. There is a pasteboard for copy and paste operations and another for drag-and-drop tasks. There is a pasteboard that stores the last string that the user searched for. There is a pasteboard for copying rulers and another pasteboard for copying fonts.

In this section, you will add cut, copy, and paste capabilities to your BigLetterView. First, you will implement the methods that will read from and write to the pasteboard. Then we will discuss how those methods get called.

NSPasteboard

As mentioned earlier, the NSPasteboard class acts as an interface to the pasteboard server. Following are some of the commonly used methods of NSPasteboard:

  • + (NSPasteboard *)generalPasteboard
    
  • Returns the general NSPasteboard. You will use this pasteboard to copy, cut, and paste.

    + (NSPasteboard *)pasteboardWithName:(NSString *)name
    
  • Returns the pasteboard identified by name. Here are the global variables that contain the names of the standard pasteboards:

    NSGeneralPboard
    NSFontPboard
    NSRulerPboard
    NSFindPboard
    NSDragPboard
    
    - (int)declareTypes:(NSArray *)types owner:(id)theOwner
    
  • Clears whatever was on the pasteboard before and declares the types of data that theOwner will put on the pasteboard. Here are the global variables for the standard types:

    NSColorPboardType
    NSFileContentsPboardType
    NSFilenamesPboardType
    NSFontPboardType
    NSPDFPboardType
    NSPICTPboardType
    NSPostScriptPboardType
    NSRulerPboardType
    NSRTFPboardType
    NSRTFDPboardType
    NSStringPboardType
    NSTabularTextPboardType
    NSVCardPboardType
    NSTIFFPboardType
    NSURLPboardType
    
  • You can also create your own pasteboard types.

    - (BOOL)setData:(NSData *)aData forType:(NSString *)dataType 
    
    
    - (BOOL)setString:(NSString *)s forType:(NSString *)dataType
    
  • Write data to the pasteboard.

    - (NSArray *)types
    
  • Returns an array containing the types of data that are available to be read from the pasteboard.

    - (NSString *)availableTypeFromArray:(NSArray *)types
    
  • Returns the first type found in types that is available for reading from the pasteboard. types should be a list of all types that you would be able to read.

    - (NSData *)dataForType:(NSString *)dataType 
    
    
    - (NSString *)stringForType:(NSString *)dataType
    
  • Read data from the pasteboard.

Add Cut, Copy, and Paste to BigLetterView

You will create methods named cut:, copy:, and paste: in the BigLetterView class. To make these methods easier to write, you will first create methods for putting data onto and reading data off of a pasteboard. Add these methods to BigLetterView.m:

- (void)writeStringToPasteboard:(NSPasteboard *)pb {
    // Declare types
    [pb declareTypes:
          [NSArray arrayWithObject:NSStringPboardType] 
               owner:self];
    // Copy data to the pasteboard
    [pb setString:string forType:NSStringPboardType];
}

- (BOOL)readStringFromPasteboard:(NSPasteboard *)pb {
    NSString *value;
    NSString *type;

    // Is there a string on the pasteboard?
    type = [pb availableTypeFromArray:
                [NSArray arrayWithObject:NSStringPboardType]];
    if (type) {
        // Read the string from the pasteboard
        value = [pb stringForType:NSStringPboardType];
        // Our view can handle only one letter
        if ([value length] == 1) {
           [self setString:value];
           return YES;
        }
    }
    return NO;
}

Add cut:, copy:, and paste: to BigLetterView.m:

- (IBAction)cut:(id)sender
{
    [self copy:sender];
    [self setString:@" "];
}

- (IBAction)copy:(id)sender
{
    NSPasteboard *pb = [NSPasteboard generalPasteboard];
    [self writeStringToPasteboard:pb];
}

- (IBAction)paste:(id)sender
{
    NSPasteboard *pb = [NSPasteboard generalPasteboard];
    if(![self readStringFromPasteboard:pb]) {
        NSBeep();
    }
}

Declare these methods in BigLetterView.h:

#import <Cocoa/Cocoa.h>

@interface BigLetterView : NSView
{
    NSColor *bgColor;
    NSString *string;
    NSMutableDictionary *attributes;
}
- (void)prepareAttributes;
- (IBAction)savePDF:(id)sender;
- (void)didEnd:(NSSavePanel *)sheet
    returnCode:(int)code
   contextInfo:(void *)contextInfo;
- (void)drawStringCenteredIn:(NSRect)bounds;
- (void)setBgColor:(NSColor *)c;
- (NSColor *)bgColor;
- (void)setString:(NSString *)c;
- (NSString *)string;
- (IBAction)cut:(id)sender;
- (IBAction)copy:(id)sender;
- (IBAction)paste:(id)sender;
- (void)writeStringToPasteboard:(NSPasteboard *)pb;
- (BOOL)readStringFromPasteboard:(NSPasteboard *)pb;

@end

Nil-Targeted Actions

How is the right view sent the cut:, copy:, or paste: message? After all, there are many, many views. If you select a text field, it should get the message. When you select another view and then choose the Copy or Paste menu item, the message should go to the newly selected view.

To solve this problem, the clever engineers at NeXT came up with nil-targeted actions. If you set the target of a control to nil, the application will try to send the action message to several objects until one of them responds. The application first tries to send the message to the first responder of the key window. This is exactly the behavior that you want for Cut and Paste. You can have several windows, each of which can have several views. The active view on the active window gets sent the cut-and-paste messages.

The beauty of targeted actions doesn't end there. NSView, NSApplication, and NSWindow all inherit from NSResponder. NSResponder has an instance variable called nextResponder. If an object doesn't respond to a nil-targeted action, its nextResponder gets a chance. The nextResponder for a view is usually its superview. The nextResponder of the content view of the window is the window. Thus the responders are linked together in what we call the responder chain.

Note that nextResponder has nothing to do with nextKeyView.

For example, one menu item closes the key window. It has a target of nil. The action is performClose:. None of the standard objects respond to performClose: except NSWindow. Thus the selected text field, for example, refuses to respond to performClose:. Then the superview of the text field refuses, and on up the view hierarchy. Ultimately, the window (the key window) accepts the performClose: method. So, to the user, the “active” window is closed.

As was mentioned in Chapter 7, a panel can become the key window but not the main window. If the key window and the main window are different, both windows get a chance to respond to the nil-targeted action.

Your question at this point should be: In what order will the objects be tested before a nil-targeted action is discarded?

  1. The firstResponder of the keyWindow and its responder chain. The responder chain would typically include the superviews and, finally, the key window.

  2. The delegate of the key window.

  3. If it is a document-based application, the NSWindowController and then NSDocument object for the key window.

  4. If the main window is different from the key window, it then goes through the same ritual with the main window:

    • The firstResponder of the main window and its responder chain (including the main window itself)

    • The main window's delegate.

    • The NSWindowController and then NSDocument object for the main window.

  5. The instance of NSApplication.

  6. The delegate of the NSApplication.

  7. The NSDocumentController.

This series of objects is known as the responder chain. Figure 18.1 presents an example. The numbers indicate the order in which the objects would be asked if they respond to the nil-targeted action.

An Example of the Order in Which Responders Get a Chance to Respond

Figure 18.1. An Example of the Order in Which Responders Get a Chance to Respond

Note that in document-based applications (such as RaiseMan), the NSDocument object gets a chance to respond to the nil-targeted action. It receives the messages from the following menu items: Save, Save As…, Revert To Saved, Print…, and Page Layout….

Looking at the Nib File

Open the nib file. Notice that the cut, copy, and paste items are connected to the icon that is labeled First Responder. The First Responder icon represents nil. It gives you something to drag to when you want an object to have a nil target (Figure 18.2).

Check Menu Item

Figure 18.2. Check Menu Item

The actions that appear in the inspector when you drag to the First Responder are in the class browser in Interface Builder. If you want an action to appear there, simply add it in the class browser.

Build and run your application. Note that cut, copy, and paste now work with your view. The keyboard equivalents also work. You can only copy strings that have one character into the BigLetterView.

For the More Curious: Which Object Really Sends the Action Message?

The target on the cut, copy, and paste menu items is nil. We know that sending a message to nil will not do anything. Actually, all target-action messages are handled by NSApplication. It has the following method:

- (BOOL)sendAction:(SEL)anAction to:(id)aTarget from:(id)sender

When the target is nil, NSApplication knows to try to send messages to the objects in the responder chain.

For the More Curious: Lazy Copying

An application can implement copying to a pasteboard in a lazy manner. For example, imagine a graphics application that copies large images to the pasteboard in several formats: PICT, TIFF, PDF, and so on. You can imagine that copying all these formats onto the pasteboard would be hard on the application and the pasteboard server. Instead, such an application might do a lazy copy. That is, it will declare all the types that it could put on the pasteboard but will put off actually copying those data until another application asks for them.

Essentially, the application puts an “IOU” (instead of the data) on the pasteboard and gives an object that will provide the data when they are needed. When another application actually asks for the data, the pasteboard server calls back for the data.

The declaration works the same as earlier:

- (int)declareTypes:(NSArray *)types owner:(id)theOwner

But theOwner must implement the following method:

- (void)pasteboard:(NSPasteboard *)sender
         provideDataForType:(NSString *)type

When another application needs the data, this method will be called. At that point, the application must copy the data it promised onto the supplied pasteboard.

As you can imagine, a problem would arise if the pasteboard server asked for the data after the application had terminated. When the application is terminating, if it has an “IOU” currently on the pasteboard, it will be asked to supply all the data that were promised before terminating. Thus, it is not uncommon for an “IOU” owner to be sent pasteboard:provideDataForType: several times while the application is in the process of terminating.

The trickiest part of a lazy copy is that when the user copies data to the pasteboard and later pastes it into another application, he doesn't want the most recent state of the data. Rather, the user wants it the way it was when the user copied it. Most developers when implementing a lazy copy will take some sort of a snapshot of the information when declaring the types. When providing the data, the developer will copy the snapshot, instead of the current state, onto the pasteboard.

Challenge

You are putting the string on the pasteboard. Create the PDF for the view and put that on the pasteboard, too. Now you will be able to copy the image of the letter into graphics programs. Test it using Preview's New from Clipboard menu item.

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

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