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.
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
- (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
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
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?
The firstResponder
of the keyWindow
and its responder chain. The responder chain would typically include the superviews and, finally, the key window.
The delegate
of the key window.
If it is a document-based application, the NSWindowController
and then NSDocument
object for the key window.
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.
The instance of NSApplication
.
The delegate
of the NSApplication
.
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.
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….
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).
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
.
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.
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.