MyDocument_Pasteboard.m
/* |
File: MyDocument_Pasteboard.m |
Abstract: This category reads and writes data from the pasteboard to support copy/paste and dragging. |
Version: 1.2 |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple |
Inc. ("Apple") in consideration of your agreement to the following |
terms, and your use, installation, modification or redistribution of |
this Apple software constitutes acceptance of these terms. If you do |
not agree with these terms, please do not use, install, modify or |
redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following |
text and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Inc. may |
be used to endorse or promote products derived from the Apple Software |
without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or |
implied, are granted by Apple herein, including but not limited to any |
patent rights that may be infringed by your derivative works or by other |
works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE |
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION |
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS |
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND |
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL |
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED |
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), |
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE |
POSSIBILITY OF SUCH DAMAGE. |
Copyright (C) 2012 Apple Inc. All Rights Reserved. |
*/ |
#import "MyDocument_Pasteboard.h" |
#import "Transaction.h" |
@implementation MyDocument(Pasteboard) |
/* Formatting methods in support of writing to the pasteboard */ |
- (NSString *)stringFromTransactions:(NSArray *)transactions { |
// When we are writing out NSStringPboardType, we create a string with one line per transaction and tabs between items in the transaction |
NSMutableString *result = [NSMutableString string]; |
NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease]; |
[dateFormatter setDateStyle:NSDateFormatterShortStyle]; |
for (Transaction *transaction in transactions) { |
NSString *descriptionString = transaction.descriptionString, *type = transaction.type, *accountType = transaction.accountType; |
[result appendFormat:@"%@\t%.2f\t%@\t%@\t%@\n", [dateFormatter stringFromDate:transaction.date], transaction.amount, (descriptionString ? descriptionString : @""), (type ? type : @""), (accountType ? accountType : @"")]; |
} |
return result; |
} |
- (void)addCell:(NSString *)contents table:(NSTextTable *)table row:(NSInteger)row column:(NSInteger)col alignment:(NSTextAlignment)alignment toText:(NSMutableAttributedString *)text { |
// This is an auxiliary method to append a new cell to the attributed string we are creating in the following method |
NSUInteger textLength = [text length]; |
NSTextTableBlock *block = [[NSTextTableBlock alloc] initWithTable:table startingRow:row rowSpan:1 startingColumn:col columnSpan:1]; |
NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; |
[block setWidth:1.0f type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockBorder]; |
[block setWidth:5.0f type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMinXEdge]; |
[block setWidth:5.0f type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMaxXEdge]; |
[block setVerticalAlignment:NSTextBlockMiddleAlignment]; |
[block setBorderColor:[NSColor colorWithCalibratedWhite:0.75f alpha:1.0f]]; |
[style setTextBlocks:[NSArray arrayWithObject:block]]; |
[style setAlignment:alignment]; |
[text replaceCharactersInRange:NSMakeRange(textLength, 0) withString:[NSString stringWithFormat:@"%@\n", (contents ? contents : @"")]]; |
[text addAttribute:NSParagraphStyleAttributeName value:style range:NSMakeRange(textLength, [text length] - textLength)]; |
[style release]; |
[block release]; |
} |
- (NSAttributedString *)attributedStringFromTransactions:(NSArray *)transactions { |
// When we are writing out NSRTFPboardType, we create a table with one row per transaction and one cell per item in the transaction |
NSMutableAttributedString *result = [[[NSMutableAttributedString alloc] initWithString:@"\n"] autorelease]; |
NSAttributedString *returnString = [[[NSAttributedString alloc] initWithString:@"\n"] autorelease]; |
NSUInteger i, count = [transactions count]; |
Transaction *transaction; |
NSTextTable *table = [[[NSTextTable alloc] init] autorelease]; |
NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease]; |
[dateFormatter setDateStyle:NSDateFormatterShortStyle]; |
[table setNumberOfColumns:5]; |
[table setLayoutAlgorithm:NSTextTableAutomaticLayoutAlgorithm]; |
[table setCollapsesBorders:YES]; |
[table setHidesEmptyCells:NO]; |
for (i = 0; i < count; i++) { |
transaction = [transactions objectAtIndex:i]; |
[self addCell:[dateFormatter stringFromDate:transaction.date] table:table row:i column:0 alignment:NSLeftTextAlignment toText:result]; |
[self addCell:[NSString stringWithFormat:@"%.2f", transaction.amount] table:table row:i column:1 alignment:NSRightTextAlignment toText:result]; |
[self addCell:transaction.descriptionString table:table row:i column:2 alignment:NSLeftTextAlignment toText:result]; |
[self addCell:transaction.type table:table row:i column:3 alignment:NSLeftTextAlignment toText:result]; |
[self addCell:transaction.accountType table:table row:i column:4 alignment:NSLeftTextAlignment toText:result]; |
} |
[result appendAttributedString:returnString]; |
return result; |
} |
/* Methods for writing to the pasteboard */ |
- (NSArray *)writablePasteboardTypes { |
return [NSArray arrayWithObjects:kSpendDocumentType, NSFilesPromisePboardType, NSFilenamesPboardType, NSRTFPboardType, NSStringPboardType, nil]; |
} |
- (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard types:(NSArray *)types { |
BOOL result = NO; |
NSMutableArray *typesToDeclare = [NSMutableArray array]; |
NSArray *writableTypes = [self writablePasteboardTypes]; |
NSString *type; |
for (type in writableTypes) { |
if ([types containsObject:type]) [typesToDeclare addObject:type]; |
} |
if ([typesToDeclare count] > 0) { |
[pboard declareTypes:typesToDeclare owner:self]; |
for (type in typesToDeclare) { |
if ([self writeSelectionToPasteboard:pboard type:type]) result = YES; |
} |
} |
return result; |
} |
- (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard type:(NSString *)type { |
BOOL result = NO; |
NSArray *transactions = [_transactionController selectedObjects]; |
if (transactions && [transactions count] > 0) { |
if ([type isEqualToString:kSpendDocumentType]) { |
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:transactions]; |
if (data && [data length] > 0) result = [pboard setData:data forType:kSpendDocumentType]; |
} else if ([type isEqualToString:NSFilesPromisePboardType]) { |
result = [pboard setPropertyList:[NSArray arrayWithObject:kSpendExtension] forType:NSFilesPromisePboardType]; |
} else if ([type isEqualToString:NSFilenamesPboardType]) { |
// we do not have a file already in existence, so we wish to handle this type lazily to delay file creation until actually requested |
result = YES; |
} else if ([type isEqualToString:NSRTFPboardType]) { |
NSAttributedString *attrStr = [self attributedStringFromTransactions:transactions]; |
if (attrStr && [attrStr length] > 0) result = [pboard setData:[attrStr RTFFromRange:NSMakeRange(0, [attrStr length]) documentAttributes:nil] forType:NSRTFPboardType]; |
} else if ([type isEqualToString:NSStringPboardType]) { |
NSString *string = [self stringFromTransactions:transactions]; |
if (string && [string length] > 0) result = [pboard setString:string forType:NSStringPboardType]; |
} |
} |
return result; |
} |
- (NSURL *)writeSelectionToDestination:(NSURL *)destination { |
// This is the method that we call when our file promise is being redeemed |
// We write out a file to the directory specified, and return the file's URL (or nil in case of failure) |
NSArray *transactions = [_transactionController selectedObjects]; |
NSIndexSet *indexSet = [_transactionController selectionIndexes]; |
NSString *name = [NSString stringWithFormat:@"iSpend[%ld..%ld].%@", (long)[indexSet firstIndex], (long)[indexSet lastIndex], kSpendExtension], *path = [[destination path] stringByAppendingPathComponent:name]; |
NSURL *fileURL = [NSURL fileURLWithPath:path]; |
NSDocumentController *controller = [NSDocumentController sharedDocumentController]; |
NSError *error = nil; |
MyDocument *newDocument = [controller openUntitledDocumentAndDisplay:NO error:&error]; |
BOOL succeeded = NO; |
if (newDocument) { |
[newDocument setTransactions:[NSArray arrayWithArray:transactions]]; |
if ([newDocument writeToURL:fileURL ofType:kSpendDocumentType error:&error]) succeeded = YES; |
[controller removeDocument:newDocument]; |
} |
return (succeeded ? fileURL : nil); |
} |
- (void)pasteboard:(NSPasteboard *)pboard provideDataForType:(NSString *)type { |
// We expect that -tableView:namesOfPromisedFilesDroppedAtDestination:forDraggedRowsWithIndexes: will usually be called instead, |
// but we implement this method to create a file if NSFilenamesPboardType is ever requested directly |
if ([type isEqualToString:NSFilenamesPboardType]) { |
NSURL *fileURL = [self writeSelectionToDestination:[NSURL fileURLWithPath:NSTemporaryDirectory()]]; |
if (fileURL) [pboard setPropertyList:[NSArray arrayWithObject:[fileURL path]] forType:NSFilenamesPboardType]; |
} |
} |
- (void)copy:(id)sender { |
[self writeSelectionToPasteboard:[NSPasteboard generalPasteboard] types:[self writablePasteboardTypes]]; |
} |
/* Parsing methods in support of reading from the pasteboard */ |
- (BOOL)addTransactionsFromString:(NSString *)string { |
// If we are reading in NSStringPboardType, we parse the string into lines and create a transaction per line |
BOOL result = NO; |
NSUInteger length = [string length], location = 0; |
NSRange lineRange; |
Transaction *transaction; |
while (location < length) { |
lineRange = [string lineRangeForRange:NSMakeRange(location, 1)]; |
transaction = [[Transaction alloc] initWithString:[string substringWithRange:lineRange]]; |
if (transaction) { |
[_transactionController addObject:transaction]; |
[transaction release]; |
result = YES; |
} |
location = NSMaxRange(lineRange); |
} |
return result; |
} |
- (BOOL)addTransactionsFromAttributedString:(NSAttributedString *)attributedString { |
// If we are reading in NSRTFPboardType, we parse the string into lines and create a transaction per line, |
// unless there is a table present, in which case we parse the string into table rows and create a transaction per row |
BOOL result = NO; |
NSString *string = [attributedString string]; |
NSUInteger length = [string length], location = 0, rowLocation; |
NSRange lineRange, tableRange, blockRange; |
NSArray *textBlocks; |
NSTextTableBlock *block, *nextBlock; |
NSTextTable *table; |
Transaction *transaction; |
while (location < length) { |
lineRange = [string lineRangeForRange:NSMakeRange(location, 1)]; |
textBlocks = [[attributedString attribute:NSParagraphStyleAttributeName atIndex:location effectiveRange:NULL] textBlocks]; |
table = nil; |
if (textBlocks && [textBlocks count] > 0) { |
block = [textBlocks objectAtIndex:0]; |
if ([block isKindOfClass:[NSTextTableBlock class]]) table = [block table]; |
} |
if (table) { |
// If a table is present, parse it into rows |
tableRange = [attributedString rangeOfTextTable:table atIndex:location]; |
rowLocation = location; |
while (location < NSMaxRange(tableRange)) { |
// Go through the table by cells, looking for row boundaries |
block = [[[attributedString attribute:NSParagraphStyleAttributeName atIndex:location effectiveRange:NULL] textBlocks] objectAtIndex:0]; |
blockRange = [attributedString rangeOfTextBlock:block atIndex:location]; |
nextBlock = (NSMaxRange(blockRange) < NSMaxRange(tableRange)) ? [[[attributedString attribute:NSParagraphStyleAttributeName atIndex:NSMaxRange(blockRange) effectiveRange:NULL] textBlocks] objectAtIndex:0] : nil; |
if (!nextBlock || [nextBlock startingRow] != [block startingRow]) { |
// This is the last cell in a row |
transaction = [[Transaction alloc] initWithString:[string substringWithRange:NSMakeRange(rowLocation, NSMaxRange(blockRange) - rowLocation)]]; |
if (transaction) { |
[_transactionController addObject:transaction]; |
[transaction release]; |
result = YES; |
} |
rowLocation = NSMaxRange(blockRange); |
} |
location = NSMaxRange(blockRange); |
} |
} else { |
// If no table is present, create a transaction per line as in the string case |
transaction = [[Transaction alloc] initWithString:[string substringWithRange:lineRange]]; |
if (transaction) { |
[_transactionController addObject:transaction]; |
[transaction release]; |
result = YES; |
} |
location = NSMaxRange(lineRange); |
} |
} |
return result; |
} |
- (BOOL)addTransactionsFromPasteboardData:(NSData *)data { |
// If we are reading in our custom pasteboard type, we use NSKeyedUnarchiver |
BOOL result = NO; |
NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithData:data]; |
if (array && [array isKindOfClass:[NSArray class]]) { |
[_transactionController addObjects:array]; |
result = YES; |
} |
return result; |
} |
- (BOOL)addTransactionsFromFileData:(NSData *)data { |
// If we are reading in our custom file type, we create a new document and extract its transactions |
BOOL result = NO; |
NSDocumentController *controller = [NSDocumentController sharedDocumentController]; |
NSError *error = nil; |
MyDocument *newDocument = [controller openUntitledDocumentAndDisplay:NO error:&error]; |
if (newDocument) { |
if ([newDocument readFromData:data ofType:kSpendDocumentType error:&error]) { |
[_transactionController addObjects:[newDocument transactions]]; |
result = YES; |
} |
[controller removeDocument:newDocument]; |
} |
return result; |
} |
/* Methods for reading from the pasteboard */ |
- (NSArray *)readablePasteboardTypes { |
return [NSArray arrayWithObjects:kSpendDocumentType, NSFilenamesPboardType, NSRTFPboardType, NSStringPboardType, nil]; |
} |
- (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pboard { |
// We go through the available types in our preferred order, and return after the first one that succeeds |
BOOL result = NO; |
NSArray *availableTypes = [pboard types], *readableTypes = [self readablePasteboardTypes]; |
NSEnumerator *enumerator = [readableTypes objectEnumerator]; |
NSString *type; |
while (!result && (type = [enumerator nextObject])) { |
if ([availableTypes containsObject:type]) { |
result = [self readSelectionFromPasteboard:pboard type:type]; |
} |
} |
return result; |
} |
- (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pboard type:(NSString *)type { |
BOOL result = NO; |
if ([type isEqualToString:kSpendDocumentType]) { |
NSData *data = [pboard dataForType:kSpendDocumentType]; |
if (data && [data length] > 0) result = [self addTransactionsFromPasteboardData:data]; |
} else if ([type isEqualToString:NSFilenamesPboardType]) { |
NSArray *files = [pboard propertyListForType:NSFilenamesPboardType]; |
for (NSString *filePath in files) { |
if ([[filePath pathExtension] isEqualToString:kSpendExtension]) { |
// This is a file of our custom type |
NSData *data = [NSData dataWithContentsOfFile:filePath]; |
if (data && [data length] > 0 && [self addTransactionsFromFileData:data]) result = YES; |
} else { |
// Treat the file as a text file |
NSAttributedString *attrStr = [[[NSAttributedString alloc] initWithPath:filePath documentAttributes:nil] autorelease]; |
if (attrStr && [attrStr length] > 0 && [self addTransactionsFromAttributedString:attrStr]) result = YES; |
} |
} |
} else if ([type isEqualToString:NSRTFPboardType]) { |
NSData *data = [pboard dataForType:NSRTFPboardType]; |
NSAttributedString *attrStr = [[[NSAttributedString alloc] initWithRTF:data documentAttributes:NULL] autorelease]; |
if (attrStr && [attrStr length] > 0) result = [self addTransactionsFromAttributedString:attrStr]; |
} else if ([type isEqualToString:NSStringPboardType]) { |
NSString *string = [pboard stringForType:NSStringPboardType]; |
if (string && [string length] > 0) result = [self addTransactionsFromString:string]; |
} |
return result; |
} |
- (void)paste:(id)sender { |
[self readSelectionFromPasteboard:[NSPasteboard generalPasteboard]]; |
} |
/* Method for enabling services use */ |
- (id)validRequestorForSendType:(NSString *)sendType returnType:(NSString *)returnType { |
if ((!sendType || [[self writablePasteboardTypes] containsObject:sendType]) && (!returnType || [[self readablePasteboardTypes] containsObject:returnType]) && (!sendType || [[_transactionController selectedObjects] count] > 0)) return self; |
// We are not actually a subclass of NSResponder; if we were, we would pass this on to super. |
// In this particular application, we know that no responder above the document level handles copy/paste; if there were one, we would pass this on to it. |
return nil; |
} |
/* Methods for providing services */ |
+ (void)importData:(NSPasteboard *)pboard userData:(NSString *)data error:(NSString **)error { |
// -[NSWindowController currentDocument] works only when app is active, so we use this alternative means of finding the front document |
MyDocument *document = [[[NSApp makeWindowsPerform:@selector(windowController) inOrder:YES] windowController] document]; |
if (document) [document readSelectionFromPasteboard:pboard]; |
} |
+ (void)exportData:(NSPasteboard *)pboard userData:(NSString *)data error:(NSString **)error { |
MyDocument *document = [[[NSApp makeWindowsPerform:@selector(windowController) inOrder:YES] windowController] document]; |
if (document) [document writeSelectionToPasteboard:pboard types:[document writablePasteboardTypes]]; |
} |
@end |
Copyright © 2012 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2012-06-07