1/** <title>NSDocument</title> 2 3 <abstract>The abstract document class</abstract> 4 5 Copyright (C) 1999 Free Software Foundation, Inc. 6 7 Author: Carl Lindberg <Carl.Lindberg@hbo.com> 8 Date: 1999 9 Modifications: Fred Kiefer <FredKiefer@gmx.de> 10 Date: June 2000, Dec 2006 11 12 This file is part of the GNUstep GUI Library. 13 14 This library is free software; you can redistribute it and/or 15 modify it under the terms of the GNU Lesser General Public 16 License as published by the Free Software Foundation; either 17 version 2 of the License, or (at your option) any later version. 18 19 This library is distributed in the hope that it will be useful, 20 but WITHOUT ANY WARRANTY; without even the implied warranty of 21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 22 Lesser General Public License for more details. 23 24 You should have received a copy of the GNU Lesser General Public 25 License along with this library; see the file COPYING.LIB. 26 If not, see <http://www.gnu.org/licenses/> or write to the 27 Free Software Foundation, 51 Franklin Street, Fifth Floor, 28 Boston, MA 02110-1301, USA. 29*/ 30 31#import <Foundation/NSData.h> 32#import <Foundation/NSError.h> 33#import <Foundation/NSException.h> 34#import <Foundation/NSFileManager.h> 35#import <Foundation/NSNotification.h> 36#import <Foundation/NSProcessInfo.h> 37#import <Foundation/NSUndoManager.h> 38#import <Foundation/NSURL.h> 39#import "AppKit/NSBox.h" 40#import "AppKit/NSDocument.h" 41#import "AppKit/NSFileWrapper.h" 42#import "AppKit/NSSavePanel.h" 43#import "AppKit/NSPageLayout.h" 44#import "AppKit/NSPopUpButton.h" 45#import "AppKit/NSPrintInfo.h" 46#import "AppKit/NSPrintOperation.h" 47#import "AppKit/NSView.h" 48#import "NSDocumentFrameworkPrivate.h" 49 50#import "GSGuiPrivate.h" 51 52static inline NSError* 53create_error(int code, NSString* desc) 54{ 55 return [NSError errorWithDomain: @"NSDocument" 56 code: code 57 userInfo: [NSDictionary 58 dictionaryWithObjectsAndKeys: desc, 59 NSLocalizedDescriptionKey, nil]]; 60} 61 62@implementation NSDocument 63 64+ (NSArray *) readableTypes 65{ 66 // FIXME: Should allow for filterable types 67 return [[NSDocumentController sharedDocumentController] 68 _readableTypesForClass: self]; 69} 70 71+ (NSArray *) writableTypes 72{ 73 // FIXME: Should allow for filterable types 74 return [[NSDocumentController sharedDocumentController] 75 _writableTypesForClass: self]; 76} 77 78+ (BOOL) isNativeType: (NSString *)type 79{ 80 return ([[self readableTypes] containsObject: type] && 81 [[self writableTypes] containsObject: type]); 82} 83 84/* 85 * Private helper macro to check, if the method given via the selector sel 86 * has been overridden in the current subclass. 87 */ 88#define OVERRIDDEN(sel) ([self methodForSelector: @selector(sel)] != [[NSDocument class] instanceMethodForSelector: @selector(sel)]) 89 90- (id) init 91{ 92 static int untitledCount = 1; 93 NSArray *fileTypes; 94 95 self = [super init]; 96 if (self != nil) 97 { 98 _document_index = untitledCount++; 99 _window_controllers = [[NSMutableArray alloc] init]; 100 fileTypes = [[self class] readableTypes]; 101 _doc_flags.has_undo_manager = YES; 102 103 /* Set our default type */ 104 if ([fileTypes count]) 105 { 106 [self setFileType: [fileTypes objectAtIndex: 0]]; 107 ASSIGN(_save_type, [fileTypes objectAtIndex: 0]); 108 } 109 } 110 return self; 111} 112 113/** 114 * Initialises the receiver with the contents of the document at fileName 115 * assuming that the type of data is as specified by fileType.<br /> 116 * Destroys the receiver and returns nil on failure. 117 */ 118- (id) initWithContentsOfFile: (NSString*)fileName ofType: (NSString*)fileType 119{ 120 self = [self init]; 121 if (self != nil) 122 { 123 // Setting these values first is contrary to the documentation, 124 // but mathces the reported behaviour on Cocoa. 125 [self setFileType: fileType]; 126 [self setFileName: fileName]; 127 if (![self readFromFile: fileName ofType: fileType]) 128 { 129 NSRunAlertPanel (_(@"Load failed"), 130 _(@"Could not load file %@."), 131 nil, nil, nil, fileName); 132 DESTROY(self); 133 } 134 } 135 return self; 136} 137 138/** 139 * Initialises the receiver with the contents of the document at url 140 * assuming that the type of data is as specified by fileType.<br /> 141 * Destroys the receiver and returns nil on failure. 142 */ 143- (id) initWithContentsOfURL: (NSURL*)url ofType: (NSString*)fileType 144{ 145 self = [self init]; 146 if (self != nil) 147 { 148 [self setFileType: fileType]; 149 [self setFileName: [url path]]; 150 if (![self readFromURL: url ofType: fileType]) 151 { 152 NSRunAlertPanel(_(@"Load failed"), 153 _(@"Could not load URL %@."), 154 nil, nil, nil, [url absoluteString]); 155 DESTROY(self); 156 } 157 } 158 return self; 159} 160 161- (id) initForURL: (NSURL *)forUrl 162withContentsOfURL: (NSURL *)url 163 ofType: (NSString *)type 164 error: (NSError **)error 165{ 166 self = [self initWithType: type error: error]; 167 if (self != nil) 168 { 169 [self setFileType: type]; 170 if (forUrl) 171 [self setFileURL: forUrl]; 172 if ([self readFromURL: url 173 ofType: type 174 error: error]) 175 { 176 if (![url isEqual: forUrl]) 177 { 178 [self setAutosavedContentsFileURL: url]; 179 [self updateChangeCount: NSChangeReadOtherContents]; 180 } 181 } 182 else 183 { 184 DESTROY(self); 185 } 186 } 187 return self; 188} 189 190- (id) initWithContentsOfURL: (NSURL *)url 191 ofType: (NSString *)type 192 error: (NSError **)error 193{ 194 if (OVERRIDDEN(initWithContentsOfFile:ofType:) && [url isFileURL]) 195 { 196 self = [self initWithContentsOfFile: [url path] ofType: type]; 197 } 198 else 199 { 200 self = [self initForURL: url 201 withContentsOfURL: url 202 ofType: type 203 error: error]; 204 } 205 206 [self setFileModificationDate: [NSDate date]]; 207 return self; 208} 209 210- (id) initWithType: (NSString *)type 211 error: (NSError **)error 212{ 213 self = [self init]; 214 if (self != nil) 215 { 216 [self setFileType: type]; 217 } 218 return self; 219} 220 221- (void) dealloc 222{ 223 [[NSNotificationCenter defaultCenter] removeObserver: self]; 224 RELEASE(_undo_manager); 225 RELEASE(_file_name); 226 RELEASE(_file_url); 227 RELEASE(_file_type); 228 RELEASE(_last_component_file_name); 229 RELEASE(_autosaved_file_url); 230 RELEASE(_file_modification_date); 231 RELEASE(_window_controllers); 232 RELEASE(_window); 233 RELEASE(_print_info); 234 RELEASE(_printOp_delegate); 235 RELEASE(_save_panel_accessory); 236 RELEASE(_spa_button); 237 RELEASE(_save_type); 238 [super dealloc]; 239} 240 241- (NSString *) fileName 242{ 243 return _file_name; 244} 245 246- (void) setFileName: (NSString *)fileName 247{ 248 NSURL *fileUrl; 249 250 if (fileName && ![fileName isAbsolutePath]) 251 { 252 NSString *dir = [[NSFileManager defaultManager] currentDirectoryPath]; 253 254 if (dir) 255 { 256 fileName = [dir stringByAppendingPathComponent: fileName]; 257 } 258 } 259 260 fileUrl = fileName ? [NSURL fileURLWithPath: fileName] : nil; 261 262 // This check is to prevent super calls from recursing. 263 if (!OVERRIDDEN(setFileName:)) 264 { 265 [self setFileURL: fileUrl]; 266 } 267 else 268 { 269 ASSIGN(_file_name, fileName); 270 ASSIGN(_file_url, fileUrl); 271 [self setLastComponentOfFileName: [_file_name lastPathComponent]]; 272 } 273} 274 275- (NSString *) fileType 276{ 277 return _file_type; 278} 279 280- (void) setFileType: (NSString *)type 281{ 282 ASSIGN(_file_type, type); 283} 284 285- (NSURL *) fileURL 286{ 287 if (OVERRIDDEN(fileName)) 288 { 289 NSString *fileName = [self fileName]; 290 291 return fileName ? [NSURL fileURLWithPath: fileName] : nil; 292 } 293 else 294 { 295 return _file_url; 296 } 297} 298 299- (void) setFileURL: (NSURL *)url 300{ 301 if (OVERRIDDEN(setFileName:) && 302 ((url == nil) || [url isFileURL])) 303 { 304 [self setFileName: [url path]]; 305 } 306 else 307 { 308 ASSIGN(_file_url, url); 309 ASSIGN(_file_name, (url && [url isFileURL]) ? [url path] : (NSString*)nil); 310 [self setLastComponentOfFileName: [[_file_url path] lastPathComponent]]; 311 } 312} 313 314- (NSDate *) fileModificationDate 315{ 316 return _file_modification_date; 317} 318 319- (void) setFileModificationDate: (NSDate *)date 320{ 321 ASSIGN(_file_modification_date, date); 322} 323 324- (NSString *) lastComponentOfFileName 325{ 326 return _last_component_file_name; 327} 328 329- (void) setLastComponentOfFileName: (NSString *)str 330{ 331 ASSIGN(_last_component_file_name, str); 332 333 [[self windowControllers] makeObjectsPerformSelector: 334 @selector(synchronizeWindowTitleWithDocumentName)]; 335} 336 337- (NSArray *) windowControllers 338{ 339 return _window_controllers; 340} 341 342- (void) addWindowController: (NSWindowController *)windowController 343{ 344 [_window_controllers addObject: windowController]; 345 if ([windowController document] != self) 346 { 347 [windowController setDocument: self]; 348 [windowController setDocumentEdited: [self isDocumentEdited]]; 349 } 350} 351 352- (void) removeWindowController: (NSWindowController *)windowController 353{ 354 if ([_window_controllers containsObject: windowController]) 355 { 356 [windowController setDocumentEdited: NO]; 357 [windowController setDocument: nil]; 358 [_window_controllers removeObject: windowController]; 359 } 360} 361 362- (NSString *) windowNibName 363{ 364 return nil; 365} 366 367// private; called during nib load. 368// we do not retain the window, since it should 369// already have a retain from the nib. 370- (void) setWindow: (NSWindow *)aWindow 371{ 372 _window = aWindow; 373} 374 375- (NSWindow *) windowForSheet 376{ 377 NSWindow *win; 378 379 if (([_window_controllers count] > 0) && 380 ((win = [[_window_controllers objectAtIndex: 0] window]) != nil)) 381 { 382 return win; 383 } 384 385 /* Note: While Apple's documentation says that this method returns 386 * [NSApp mainWindow] if the document has no window controllers, the 387 * actual implementation returns nil and, in fact, the header files 388 * on OS X also say so. Since it would be very unreasonable to attach a 389 * document modal sheet to a window that doesn't belong to this document, 390 * we do the same here, too. */ 391 return nil; 392} 393 394/** 395 * Creates the window controllers for the current document. Calls 396 * addWindowController: on the receiver to add them to the controller 397 * array. 398 */ 399- (void) makeWindowControllers 400{ 401 NSString *name = [self windowNibName]; 402 403 if (name != nil && [name length] > 0) 404 { 405 NSWindowController *controller; 406 407 controller = [[NSWindowController alloc] initWithWindowNibName: name 408 owner: self]; 409 [self addWindowController: controller]; 410 RELEASE(controller); 411 } 412 else 413 { 414 [NSException raise: NSInternalInconsistencyException 415 format: @"%@ must override either -windowNibName " 416 @"or -makeWindowControllers", NSStringFromClass([self class])]; 417 } 418} 419 420/** 421 * Makes all the documents windows visible by ordering them to the 422 * front and making them main or key.<br /> 423 * If the document has no windows, this method has no effect. 424 */ 425- (void) showWindows 426{ 427 [_window_controllers makeObjectsPerformSelector: @selector(showWindow:) 428 withObject: self]; 429} 430 431- (BOOL) isDocumentEdited 432{ 433 return _change_count != 0 || _doc_flags.permanently_modified; 434} 435 436- (void) updateChangeCount: (NSDocumentChangeType)change 437{ 438 int i, count = [_window_controllers count]; 439 BOOL isEdited; 440 441 switch (change) 442 { 443 case NSChangeDone: _change_count++; 444 _autosave_change_count++; 445 break; 446 case NSChangeUndone: _change_count--; 447 _autosave_change_count--; 448 break; 449 case NSChangeReadOtherContents: 450 _doc_flags.permanently_modified = 1; 451 break; 452 case NSChangeCleared: _change_count = 0; 453 _autosave_change_count = 0; 454 _doc_flags.permanently_modified = 0; 455 _doc_flags.autosave_permanently_modified = 0; 456 break; 457 case NSChangeAutosaved: _autosave_change_count = 0; 458 _doc_flags.autosave_permanently_modified = 0; 459 break; 460 } 461 462 /* 463 * NOTE: Apple's implementation seems to not call -isDocumentEdited 464 * here but directly checks to see if _changeCount == 0. It seems it 465 * would be better to call the method in case it's overridden by a 466 * subclass, but we may want to keep Apple's behavior. 467 */ 468 isEdited = [self isDocumentEdited]; 469 470 for (i = 0; i < count; i++) 471 { 472 [[_window_controllers objectAtIndex: i] setDocumentEdited: isEdited]; 473 } 474} 475 476- (BOOL) canCloseDocument 477{ 478 int result; 479 480 if (![self isDocumentEdited]) 481 return YES; 482 483 result = NSRunAlertPanel (_(@"Close"), 484 _(@"%@ has changed. Save?"), 485 _(@"Save"), _(@"Cancel"), _(@"Don't Save"), 486 [self displayName]); 487 488#define Save NSAlertDefaultReturn 489#define Cancel NSAlertAlternateReturn 490#define DontSave NSAlertOtherReturn 491 492 switch (result) 493 { 494 // return NO if save failed 495 case Save: 496 { 497 [self saveDocument: nil]; 498 return ![self isDocumentEdited]; 499 } 500 case DontSave: return YES; 501 case Cancel: 502 default: return NO; 503 } 504} 505 506- (void) canCloseDocumentWithDelegate: (id)delegate 507 shouldCloseSelector: (SEL)shouldCloseSelector 508 contextInfo: (void *)contextInfo 509{ 510 BOOL result = [self canCloseDocument]; 511 512 if (delegate != nil && shouldCloseSelector != NULL) 513 { 514 void (*meth)(id, SEL, id, BOOL, void*); 515 meth = (void (*)(id, SEL, id, BOOL, void*))[delegate methodForSelector: 516 shouldCloseSelector]; 517 if (meth) 518 meth(delegate, shouldCloseSelector, self, result, contextInfo); 519 } 520} 521 522- (BOOL) shouldCloseWindowController: (NSWindowController *)windowController 523{ 524 if (![_window_controllers containsObject: windowController]) return YES; 525 526 /* If it's the last window controller, pop up a warning */ 527 /* maybe we should count only loaded window controllers (or visible windows). */ 528 if ([windowController shouldCloseDocument] 529 || [_window_controllers count] == 1) 530 { 531 return [self canCloseDocument]; 532 } 533 534 return YES; 535} 536 537- (void) shouldCloseWindowController: (NSWindowController *)windowController 538 delegate: (id)delegate 539 shouldCloseSelector: (SEL)callback 540 contextInfo: (void *)contextInfo 541{ 542 /* If it's the last window controller, pop up a warning */ 543 /* maybe we should count only loaded window controllers (or visible windows). */ 544 if ([_window_controllers containsObject: windowController] 545 && ([windowController shouldCloseDocument] 546 || [_window_controllers count] == 1)) 547 { 548 [self canCloseDocumentWithDelegate: delegate 549 shouldCloseSelector: callback 550 contextInfo: contextInfo]; 551 return; 552 } 553 554 if (delegate != nil && callback != NULL) 555 { 556 void (*meth)(id, SEL, id, BOOL, void*); 557 meth = (void (*)(id, SEL, id, BOOL, void*))[delegate methodForSelector: 558 callback]; 559 560 if (meth) 561 meth(delegate, callback, self, YES, contextInfo); 562 } 563} 564 565- (NSString *) displayName 566{ 567 if ([self lastComponentOfFileName] != nil) 568 { 569 if ([self fileNameExtensionWasHiddenInLastRunSavePanel]) 570 { 571 return [[self lastComponentOfFileName] stringByDeletingPathExtension]; 572 } 573 else 574 { 575 return [self lastComponentOfFileName]; 576 } 577 } 578 else 579 { 580 return [NSString stringWithFormat: _(@"Untitled-%d"), _document_index]; 581 } 582} 583 584- (BOOL) keepBackupFile 585{ 586 return NO; 587} 588 589- (NSData *) dataRepresentationOfType: (NSString *)type 590{ 591 [NSException raise: NSInternalInconsistencyException format:@"%@ must implement %@", 592 NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; 593 return nil; 594} 595 596- (NSData *) dataOfType: (NSString *)type 597 error: (NSError **)error 598{ 599 if (OVERRIDDEN(dataRepresentationOfType:)) 600 { 601 if (error) 602 *error = nil; 603 return [self dataRepresentationOfType: type]; 604 } 605 606 [NSException raise: NSInternalInconsistencyException format:@"%@ must implement %@", 607 NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; 608 return nil; 609} 610 611- (BOOL) loadDataRepresentation: (NSData *)data ofType: (NSString *)type 612{ 613 [NSException raise: NSInternalInconsistencyException format:@"%@ must implement %@", 614 NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; 615 return NO; 616} 617 618- (NSFileWrapper *) fileWrapperRepresentationOfType: (NSString *)type 619{ 620 NSData *data = [self dataRepresentationOfType: type]; 621 622 if (data == nil) 623 return nil; 624 625 return AUTORELEASE([[NSFileWrapper alloc] initRegularFileWithContents: data]); 626} 627 628- (NSFileWrapper *) fileWrapperOfType: (NSString *)type 629 error: (NSError **)error 630{ 631 NSData *data; 632 633 if (OVERRIDDEN(fileWrapperRepresentationOfType:)) 634 { 635 if (error) 636 *error = nil; 637 return [self fileWrapperRepresentationOfType: type]; 638 } 639 640 data = [self dataOfType: type error: error]; 641 642 if (data == nil) 643 { 644 if (error && !(*error)) 645 *error = create_error(0, NSLocalizedString(@"Could not create data for type.", 646 @"Error description")); 647 return nil; 648 } 649 return AUTORELEASE([[NSFileWrapper alloc] initRegularFileWithContents: data]); 650} 651 652- (BOOL) loadFileWrapperRepresentation: (NSFileWrapper *)wrapper 653 ofType: (NSString *)type 654{ 655 if ([wrapper isRegularFile]) 656 { 657 return [self loadDataRepresentation: [wrapper regularFileContents] 658 ofType: type]; 659 } 660 661 /* 662 * This even happens on a symlink. May want to use 663 * -stringByResolvingAllSymlinksInPath somewhere, but Apple doesn't. 664 */ 665 NSLog(@"%@ must be overridden if your document deals with file packages.", 666 NSStringFromSelector(_cmd)); 667 668 return NO; 669} 670 671- (BOOL) writeToFile: (NSString *)fileName ofType: (NSString *)type 672{ 673 return [[self fileWrapperRepresentationOfType: type] 674 writeToFile: fileName atomically: YES updateFilenames: YES]; 675} 676 677- (BOOL) readFromFile: (NSString *)fileName ofType: (NSString *)type 678{ 679 NSFileWrapper *wrapper = AUTORELEASE([[NSFileWrapper alloc] initWithPath: fileName]); 680 return [self loadFileWrapperRepresentation: wrapper ofType: type]; 681} 682 683- (BOOL) revertToSavedFromFile: (NSString *)fileName ofType: (NSString *)type 684{ 685 return [self readFromFile: fileName ofType: type]; 686} 687 688- (BOOL) writeToURL: (NSURL *)url ofType: (NSString *)type 689{ 690 if ([url isFileURL]) 691 { 692 return [self writeToFile: [url path] ofType: type]; 693 } 694 695 return NO; 696} 697 698- (BOOL) readFromURL: (NSURL *)url ofType: (NSString *)type 699{ 700 if ([url isFileURL]) 701 { 702 return [self readFromFile: [url path] ofType: type]; 703 } 704 705 return NO; 706} 707 708- (BOOL) revertToSavedFromURL: (NSURL *)url ofType: (NSString *)type 709{ 710 return [self readFromURL: url ofType: type]; 711} 712 713- (BOOL) readFromData: (NSData *)data 714 ofType: (NSString *)type 715 error: (NSError **)error 716{ 717 if (OVERRIDDEN(loadDataRepresentation:ofType:)) 718 { 719 if (error) 720 *error = nil; 721 return [self loadDataRepresentation: data 722 ofType: type]; 723 } 724 725 [NSException raise: NSInternalInconsistencyException format:@"%@ must implement %@", 726 NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; 727 return NO; 728} 729 730- (BOOL) readFromFileWrapper: (NSFileWrapper *)wrapper 731 ofType: (NSString *)type 732 error: (NSError **)error 733{ 734 if (OVERRIDDEN(loadFileWrapperRepresentation:ofType:)) 735 { 736 if (error) 737 *error = nil; 738 return [self loadFileWrapperRepresentation: wrapper ofType: type]; 739 } 740 741 if ([wrapper isRegularFile]) 742 { 743 return [self readFromData: [wrapper regularFileContents] 744 ofType: type 745 error: error]; 746 } 747 748 if (error) 749 { 750 *error = create_error(0, NSLocalizedString(@"File wrapper is no file.", 751 @"Error description")); 752 } 753 return NO; 754} 755 756- (BOOL) readFromURL: (NSURL *)url 757 ofType: (NSString *)type 758 error: (NSError **)error 759{ 760 if ([url isFileURL]) 761 { 762 NSString *fileName = [url path]; 763 764 if (OVERRIDDEN(readFromFile:ofType:)) 765 { 766 if (error) 767 *error = nil; 768 return [self readFromFile: fileName ofType: type]; 769 } 770 else 771 { 772 NSFileWrapper *wrapper = AUTORELEASE([[NSFileWrapper alloc] initWithPath: fileName]); 773 774 return [self readFromFileWrapper: wrapper 775 ofType: type 776 error: error]; 777 } 778 } 779 else 780 { 781 return [self readFromData: [url resourceDataUsingCache: YES] 782 ofType: type 783 error: error]; 784 } 785} 786 787- (BOOL) revertToContentsOfURL: (NSURL *)url 788 ofType: (NSString *)type 789 error: (NSError **)error 790{ 791 if (OVERRIDDEN(revertToSavedFromURL:ofType:)) 792 { 793 return [self revertToSavedFromURL: url ofType: type]; 794 } 795 if (OVERRIDDEN(revertToSavedFromFile:ofType:) && [url isFileURL]) 796 { 797 return [self revertToSavedFromFile:[url path] ofType: type]; 798 } 799 return [self readFromURL: url 800 ofType: type 801 error: error]; 802} 803 804- (BOOL) writeToFile: (NSString *)fileName 805 ofType: (NSString *)type 806 originalFile: (NSString *)origFileName 807 saveOperation: (NSSaveOperationType)saveOp 808{ 809 return [self writeToFile: fileName ofType: type]; 810} 811 812 813- (NSString *) _backupFileNameFor: (NSString *)newFileName 814{ 815 NSString *extension = [newFileName pathExtension]; 816 NSString *backupFilename = [newFileName stringByDeletingPathExtension]; 817 818 backupFilename = [backupFilename stringByAppendingString:@"~"]; 819 return [backupFilename stringByAppendingPathExtension: extension]; 820} 821 822- (BOOL) _writeBackupForFile: (NSString *)newFileName 823 toFile: (NSString *)backupFilename 824{ 825 NSFileManager *fileManager = [NSFileManager defaultManager]; 826 827 /* NSFileManager movePath: will fail if destination exists */ 828 /* Save panel has already asked if the user wants to replace it */ 829 if ([fileManager fileExistsAtPath: backupFilename]) 830 { 831 [fileManager removeFileAtPath: backupFilename handler: nil]; 832 } 833 834 // Move or copy? 835 if (![fileManager movePath: newFileName toPath: backupFilename handler: nil] && 836 [self keepBackupFile]) 837 { 838 int result = NSRunAlertPanel(_(@"File Error"), 839 _(@"Can't create backup file. Save anyways?"), 840 _(@"Save"), _(@"Cancel"), nil); 841 842 if (result != NSAlertDefaultReturn) return NO; 843 } 844 845 return YES; 846} 847 848- (BOOL) writeWithBackupToFile: (NSString *)fileName 849 ofType: (NSString *)fileType 850 saveOperation: (NSSaveOperationType)saveOp 851{ 852 NSFileManager *fileManager = [NSFileManager defaultManager]; 853 NSString *backupFilename = nil; 854 BOOL isNativeType = [[self class] isNativeType: fileType]; 855 856 if (fileName && isNativeType) 857 { 858 if ([fileManager fileExistsAtPath: fileName]) 859 { 860 backupFilename = [self _backupFileNameFor: fileName]; 861 862 if (![self _writeBackupForFile: fileName 863 toFile: backupFilename]) 864 { 865 return NO; 866 } 867 } 868 869 if ([self writeToFile: fileName 870 ofType: fileType 871 originalFile: backupFilename 872 saveOperation: saveOp]) 873 { 874 // FIXME: Should set the file attributes 875 876 if (saveOp != NSSaveToOperation) 877 { 878 [self _removeAutosavedContentsFile]; 879 [self setFileName: fileName]; 880 [self setFileType: fileType]; 881 [self updateChangeCount: NSChangeCleared]; 882 } 883 884 if (backupFilename && ![self keepBackupFile]) 885 { 886 [fileManager removeFileAtPath: backupFilename handler: nil]; 887 } 888 889 return YES; 890 } 891 } 892 893 return NO; 894} 895 896- (BOOL) writeSafelyToURL: (NSURL *)url 897 ofType: (NSString *)type 898 forSaveOperation: (NSSaveOperationType)op 899 error: (NSError **)error 900{ 901 NSURL *original = [self fileURL]; 902 NSFileManager *fileManager = [NSFileManager defaultManager]; 903 NSString *backupFilename = nil; 904 BOOL isNativeType = [[self class] isNativeType: type]; 905 906 if (OVERRIDDEN(writeWithBackupToFile:ofType:saveOperation:)) 907 { 908 BOOL isAutosave = NO; 909 910 if (op == NSAutosaveOperation) 911 { 912 op = NSSaveToOperation; 913 isAutosave = YES; 914 } 915 916 if (error) 917 *error = nil; 918 if (![self writeWithBackupToFile: [url path] 919 ofType: type 920 saveOperation: op]) 921 { 922 if (error) 923 { 924 *error = create_error(0, NSLocalizedString(@"Could not write backup file.", 925 @"Error description")); 926 } 927 return NO; 928 } 929 930 if (isAutosave) 931 { 932 [self setAutosavedContentsFileURL: url]; 933 [self updateChangeCount: NSChangeAutosaved]; 934 } 935 return YES; 936 } 937 938 if (!isNativeType || (url == nil)) 939 { 940 if (error) 941 { 942 *error = create_error(0, NSLocalizedString(@"Not a writable type or no URL given.", 943 @"Error description")); 944 } 945 return NO; 946 } 947 948 if (op == NSSaveOperation) 949 { 950 if ([url isFileURL]) 951 { 952 NSString *fileName; 953 954 fileName = [url path]; 955 if ([fileManager fileExistsAtPath: fileName]) 956 { 957 backupFilename = [self _backupFileNameFor: fileName]; 958 959 if (![self _writeBackupForFile: fileName 960 toFile: backupFilename]) 961 { 962 if (error) 963 { 964 *error = create_error(0, NSLocalizedString(@"Could not write backup file.", 965 @"Error description")); 966 } 967 return NO; 968 } 969 } 970 } 971 } 972 973 if (![self writeToURL: url 974 ofType: type 975 forSaveOperation: op 976 originalContentsURL: original 977 error: error]) 978 { 979 return NO; 980 } 981 982 if ([url isFileURL]) 983 { 984 NSDictionary *attrs; 985 986 attrs = [self fileAttributesToWriteToURL: url 987 ofType: type 988 forSaveOperation: op 989 originalContentsURL: original 990 error: error]; 991 [fileManager changeFileAttributes: attrs atPath: [url path]]; 992 } 993 994 if (op == NSAutosaveOperation) 995 { 996 [self setAutosavedContentsFileURL: url]; 997 [self updateChangeCount: NSChangeAutosaved]; 998 } 999 else if (op != NSSaveToOperation) 1000 { 1001 [self _removeAutosavedContentsFile]; 1002 [self setFileURL: url]; 1003 [self setFileType: type]; 1004 [self updateChangeCount: NSChangeCleared]; 1005 } 1006 1007 if (backupFilename && ![self keepBackupFile]) 1008 { 1009 [fileManager removeFileAtPath: backupFilename handler: nil]; 1010 } 1011 1012 return YES; 1013} 1014 1015- (BOOL) writeToURL: (NSURL *)url 1016 ofType: (NSString *)type 1017 error: (NSError **)error 1018{ 1019 if ([url isFileURL]) 1020 { 1021 NSFileWrapper *wrapper; 1022 1023 if (OVERRIDDEN(writeToFile:ofType:)) 1024 { 1025 if (error) 1026 *error = nil; 1027 return [self writeToFile: [url path] ofType: type]; 1028 } 1029 1030 wrapper = [self fileWrapperOfType: type 1031 error: error]; 1032 if (wrapper == nil) 1033 { 1034 if (error && !(*error)) 1035 { 1036 *error = create_error(0, NSLocalizedString(@"Could not write file wrapper.", 1037 @"Error description")); 1038 } 1039 return NO; 1040 } 1041 1042 if (error) 1043 *error = nil; 1044 return [wrapper writeToFile: [url path] atomically: YES updateFilenames: YES]; 1045 } 1046 else 1047 { 1048 NSData *data = [self dataOfType: type error: error]; 1049 1050 if (data == nil) 1051 return NO; 1052 1053 return [url setResourceData: data]; 1054 } 1055} 1056 1057- (BOOL) writeToURL: (NSURL *)url 1058 ofType: (NSString *)type 1059 forSaveOperation: (NSSaveOperationType)op 1060originalContentsURL: (NSURL *)orig 1061 error: (NSError **)error 1062{ 1063 if (OVERRIDDEN(writeToFile:ofType:originalFile:saveOperation:)) 1064 { 1065 if (op == NSAutosaveOperation) 1066 { 1067 op = NSSaveToOperation; 1068 } 1069 1070 if (error) 1071 *error = nil; 1072 return [self writeToFile: [url path] 1073 ofType: type 1074 originalFile: [orig path] 1075 saveOperation: op]; 1076 } 1077 1078 return [self writeToURL: url 1079 ofType: type 1080 error: error]; 1081} 1082 1083- (IBAction) changeSaveType: (id)sender 1084{ 1085 NSDocumentController *controller = 1086 [NSDocumentController sharedDocumentController]; 1087 NSArray *extensions; 1088 1089 ASSIGN(_save_type, [[sender selectedItem] representedObject]); 1090 extensions = [controller fileExtensionsFromType: _save_type]; 1091 if ([extensions containsObject: @"*"]) 1092 extensions = nil; 1093 [(NSSavePanel *)[sender window] setAllowedFileTypes: extensions]; 1094} 1095 1096- (NSInteger) runModalSavePanel: (NSSavePanel *)savePanel 1097 withAccessoryView: (NSView *)accessoryView 1098{ 1099 NSString *directory, *file; 1100 1101 if (accessoryView) 1102 { 1103 [savePanel setAccessoryView: accessoryView]; 1104 } 1105 1106 if ([self fileName]) 1107 { 1108 directory = [[self fileName] stringByDeletingLastPathComponent]; 1109 file = [[self fileName] lastPathComponent]; 1110 if (![savePanel allowsOtherFileTypes]) 1111 { 1112 NSArray *exts = [savePanel allowedFileTypes]; 1113 if ([exts count] && ![exts containsObject: [file pathExtension]] && 1114 ![exts containsObject: @"*"]) 1115 { 1116 file = [file stringByDeletingPathExtension]; 1117 file = [file stringByAppendingPathExtension: 1118 [exts objectAtIndex: 0]]; 1119 } 1120 } 1121 return [savePanel runModalForDirectory: directory file: file]; 1122 } 1123 1124 return [savePanel runModal]; 1125} 1126 1127- (BOOL) prepareSavePanel: (NSSavePanel *)savePanel 1128{ 1129 return YES; 1130} 1131 1132- (BOOL) shouldRunSavePanelWithAccessoryView 1133{ 1134 return YES; 1135} 1136 1137- (void) _createPanelAccessory 1138{ 1139 if (_save_panel_accessory == nil) 1140 { 1141 NSRect accessoryFrame = NSMakeRect(0,0,380,70); 1142 NSRect spaFrame = NSMakeRect(115,14,150,22); 1143 1144 _save_panel_accessory = [[NSBox alloc] initWithFrame: accessoryFrame]; 1145 [(NSBox *)_save_panel_accessory setTitle: _(@"File Type")]; 1146 [_save_panel_accessory setAutoresizingMask: 1147 NSViewWidthSizable | NSViewHeightSizable]; 1148 _spa_button = [[NSPopUpButton alloc] initWithFrame: spaFrame]; 1149 [_spa_button setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable | NSViewMinYMargin | 1150 NSViewMaxYMargin | NSViewMinXMargin | NSViewMaxXMargin]; 1151 [_spa_button setTarget: self]; 1152 [_spa_button setAction: @selector(changeSaveType:)]; 1153 [_save_panel_accessory addSubview: _spa_button]; 1154 } 1155} 1156 1157- (void) _addItemsToSpaButtonFromArray: (NSArray *)types 1158{ 1159 NSString *type, *title; 1160 int i, count = [types count]; 1161 1162 [_spa_button removeAllItems]; 1163 for (i = 0; i < count; i++) 1164 { 1165 type = [types objectAtIndex: i]; 1166 title = [[NSDocumentController sharedDocumentController] 1167 displayNameForType: type]; 1168 [_spa_button addItemWithTitle: title]; 1169 [[_spa_button itemAtIndex: i] setRepresentedObject: type]; 1170 } 1171 1172 // if it's more than one, then 1173 [_spa_button setEnabled: (count > 0)]; 1174 1175 // if we have some items, select the current filetype. 1176 if (count > 0) 1177 { 1178 [_spa_button selectItemAtIndex: 1179 [_spa_button indexOfItemWithRepresentedObject: [self fileType]]]; 1180 } 1181} 1182 1183- (NSSavePanel *)_runSavePanelForSaveOperation: (NSSaveOperationType)saveOperation 1184{ 1185 NSString *title; 1186 NSString *directory; 1187 NSArray *types; 1188 NSDocumentController *controller; 1189 NSSavePanel *savePanel = [NSSavePanel savePanel]; 1190 1191 ASSIGN(_save_type, [self fileType]); 1192 controller = [NSDocumentController sharedDocumentController]; 1193 types = [self writableTypesForSaveOperation: saveOperation]; 1194 1195 if ([self shouldRunSavePanelWithAccessoryView]) 1196 { 1197 if (_save_panel_accessory == nil) 1198 [self _createPanelAccessory]; 1199 1200 [self _addItemsToSpaButtonFromArray: types]; 1201 1202 [savePanel setAccessoryView: _save_panel_accessory]; 1203 } 1204 1205 if ([types count] > 0) 1206 { 1207 NSArray *extensions = [controller fileExtensionsFromType: [self fileType]]; 1208 if ([extensions containsObject: @"*"]) 1209 extensions = nil; 1210 [savePanel setAllowedFileTypes: extensions]; 1211 } 1212 1213 switch (saveOperation) 1214 { 1215 case NSSaveAsOperation: title = _(@"Save As"); break; 1216 case NSSaveToOperation: title = _(@"Save To"); break; 1217 case NSSaveOperation: 1218 default: 1219 title = _(@"Save"); 1220 break; 1221 } 1222 1223 [savePanel setTitle: title]; 1224 1225 if ([self fileName]) 1226 directory = [[self fileName] stringByDeletingLastPathComponent]; 1227 else 1228 directory = [controller currentDirectory]; 1229 [savePanel setDirectory: directory]; 1230 1231 if (!OVERRIDDEN(runModalSavePanel:withAccessoryView:)) 1232 { 1233 if (![self prepareSavePanel: savePanel]) 1234 { 1235 return nil; 1236 } 1237 } 1238 if ([self runModalSavePanel: savePanel 1239 withAccessoryView: [savePanel accessoryView]]) 1240 { 1241 return savePanel; 1242 } 1243 1244 return nil; 1245} 1246 1247- (NSString *) fileNameFromRunningSavePanelForSaveOperation: (NSSaveOperationType)saveOperation 1248{ 1249 NSSavePanel *savePanel = [self _runSavePanelForSaveOperation: saveOperation]; 1250 1251 if (savePanel) 1252 { 1253 return [savePanel filename]; 1254 } 1255 1256 return nil; 1257} 1258 1259- (void) runModalSavePanelForSaveOperation: (NSSaveOperationType)saveOperation 1260 delegate: (id)delegate 1261 didSaveSelector: (SEL)didSaveSelector 1262 contextInfo: (void *)contextInfo 1263{ 1264 // FIXME: Commit registered editors 1265 NSSavePanel *savePanel = [self _runSavePanelForSaveOperation: saveOperation]; 1266 1267 if (savePanel) 1268 { 1269 if (OVERRIDDEN(saveToFile:saveOperation:delegate:didSaveSelector:contextInfo:)) 1270 { 1271 [self saveToFile: [savePanel filename] 1272 saveOperation: saveOperation 1273 delegate: delegate 1274 didSaveSelector: didSaveSelector 1275 contextInfo: contextInfo]; 1276 } 1277 else 1278 { 1279 [self saveToURL: [savePanel URL] 1280 ofType: [self fileTypeFromLastRunSavePanel] 1281 forSaveOperation: saveOperation 1282 delegate: delegate 1283 didSaveSelector: didSaveSelector 1284 contextInfo: contextInfo]; 1285 } 1286 } 1287} 1288 1289- (NSArray *) writableTypesForSaveOperation: (NSSaveOperationType)op 1290{ 1291 NSArray *types = [object_getClass(self) writableTypes]; 1292 NSMutableArray *muTypes; 1293 int i, len; 1294 1295 if (op == NSSaveToOperation) 1296 { 1297 return types; 1298 } 1299 1300 len = [types count]; 1301 muTypes = [NSMutableArray arrayWithCapacity: len]; 1302 for (i = 0; i < len; i++) 1303 { 1304 NSString *type; 1305 1306 type = [types objectAtIndex: i]; 1307 if ([[self class] isNativeType: type]) 1308 { 1309 [muTypes addObject: type]; 1310 } 1311 } 1312 1313 return muTypes; 1314} 1315 1316- (BOOL) fileNameExtensionWasHiddenInLastRunSavePanel 1317{ 1318 // FIXME 1319 return NO; 1320} 1321 1322- (NSString *) fileNameExtensionForType: (NSString *)typeName 1323 saveOperation: (NSSaveOperationType)saveOperation 1324{ 1325 NSArray *exts = [[NSDocumentController sharedDocumentController] 1326 fileExtensionsFromType: typeName]; 1327 1328 if ([exts count] && ![exts containsObject: @"*"]) 1329 return (NSString *)[exts objectAtIndex: 0]; 1330 1331 return @""; 1332} 1333 1334- (BOOL) shouldChangePrintInfo: (NSPrintInfo *)newPrintInfo 1335{ 1336 return YES; 1337} 1338 1339- (NSPrintInfo *) printInfo 1340{ 1341 return _print_info? _print_info : [NSPrintInfo sharedPrintInfo]; 1342} 1343 1344- (void) setPrintInfo: (NSPrintInfo *)printInfo 1345{ 1346 NSUndoManager *undoManager = [self undoManager]; 1347 1348 if (undoManager != nil) 1349 { 1350 [[undoManager prepareWithInvocationTarget: self] 1351 setPrintInfo: _print_info]; 1352 // FIXME undoManager -setActionName: 1353 } 1354 ASSIGN(_print_info, printInfo); 1355 if (undoManager == nil) 1356 { 1357 [self updateChangeCount: NSChangeDone]; 1358 } 1359} 1360 1361 1362// Page layout panel (Page Setup) 1363- (BOOL) preparePageLayout: (NSPageLayout *)pageLayout 1364{ 1365 return YES; 1366} 1367 1368- (NSInteger) runModalPageLayoutWithPrintInfo: (NSPrintInfo *)printInfo 1369{ 1370 NSPageLayout *pageLayout; 1371 1372 pageLayout = [NSPageLayout pageLayout]; 1373 if ([self preparePageLayout: pageLayout]) 1374 { 1375 return [pageLayout runModalWithPrintInfo: printInfo]; 1376 } 1377 else 1378 { 1379 return NSCancelButton; 1380 } 1381} 1382 1383- (void) _documentDidRunModalPageLayout: (NSDocument*)doc 1384 accepted: (BOOL)accepted 1385 contextInfo: (void *)contextInfo 1386{ 1387 NSPrintInfo *printInfo = (NSPrintInfo *)contextInfo; 1388 1389 if (accepted && [self shouldChangePrintInfo: printInfo]) 1390 { 1391 [self setPrintInfo: printInfo]; 1392 } 1393} 1394 1395- (IBAction) runPageLayout: (id)sender 1396{ 1397 NSPrintInfo *printInfo = AUTORELEASE([[self printInfo] copy]); 1398 1399 [self runModalPageLayoutWithPrintInfo: printInfo 1400 delegate: self 1401 didRunSelector: @selector(_documentDidRunModalPageLayout:accepted:contextInfo:) 1402 contextInfo: printInfo]; 1403} 1404 1405- (void) runModalPageLayoutWithPrintInfo: (NSPrintInfo *)info 1406 delegate: (id)delegate 1407 didRunSelector: (SEL)sel 1408 contextInfo: (void *)context 1409{ 1410 NSPageLayout *pageLayout; 1411 1412 pageLayout = [NSPageLayout pageLayout]; 1413 if ([self preparePageLayout: pageLayout]) 1414 { 1415 [pageLayout beginSheetWithPrintInfo: info 1416 modalForWindow: [self windowForSheet] 1417 delegate: delegate 1418 didEndSelector: sel 1419 contextInfo: context]; 1420 } 1421} 1422 1423/* This is overridden by subclassers; the default implementation does nothing. */ 1424- (void) printShowingPrintPanel: (BOOL)flag 1425{ 1426} 1427 1428- (IBAction) printDocument: (id)sender 1429{ 1430 [self printDocumentWithSettings: [NSDictionary dictionary] 1431 showPrintPanel: YES 1432 delegate: nil 1433 didPrintSelector: NULL 1434 contextInfo: NULL]; 1435} 1436 1437- (void) printDocumentWithSettings: (NSDictionary *)settings 1438 showPrintPanel: (BOOL)flag 1439 delegate: (id)delegate 1440 didPrintSelector: (SEL)sel 1441 contextInfo: (void *)context 1442{ 1443 NSPrintOperation *printOp; 1444 NSError *error; 1445 1446 if (OVERRIDDEN(printShowingPrintPanel:)) 1447 { 1448 // FIXME: More communication with the panel is needed. 1449 return [self printShowingPrintPanel: flag]; 1450 } 1451 1452 printOp = [self printOperationWithSettings: settings 1453 error: &error]; 1454 if (printOp != nil) 1455 { 1456 [printOp setShowsPrintPanel: flag]; 1457 [self runModalPrintOperation: printOp 1458 delegate: delegate 1459 didRunSelector: sel 1460 contextInfo: context]; 1461 } 1462 else 1463 { 1464 [self presentError: error]; 1465 1466 if (delegate != nil && sel != NULL) 1467 { 1468 void (*meth)(id, SEL, id, BOOL, void*); 1469 meth = (void (*)(id, SEL, id, BOOL, void*))[delegate methodForSelector: sel]; 1470 if (meth) 1471 meth(delegate, sel, self, NO, context); 1472 } 1473 } 1474} 1475 1476- (NSPrintOperation *) printOperationWithSettings: (NSDictionary *)settings 1477 error: (NSError **)error 1478{ 1479 if (error) 1480 *error = nil; 1481 return nil; 1482} 1483 1484- (void) runModalPrintOperation: (NSPrintOperation *)op 1485 delegate: (id)delegate 1486 didRunSelector: (SEL)sel 1487 contextInfo: (void *)context 1488{ 1489 ASSIGN(_printOp_delegate, delegate); 1490 _printOp_didRunSelector = sel; 1491 [op runOperationModalForWindow: [self windowForSheet] 1492 delegate: self 1493 didRunSelector: @selector(_runModalPrintOperationDidSucceed:contextInfo:) 1494 contextInfo: context]; 1495} 1496 1497- (void) _runModalPrintOperationDidSucceed: (BOOL)success 1498 contextInfo: (void *)context 1499{ 1500 id delegate = _printOp_delegate; 1501 SEL didRunSelector = _printOp_didRunSelector; 1502 void (*didRun)(id, SEL, NSDocument *, BOOL, id); 1503 1504 if (delegate && [delegate respondsToSelector: didRunSelector]) 1505 { 1506 didRun = (void (*)(id, SEL, NSDocument *, BOOL, id)) 1507 [delegate methodForSelector: didRunSelector]; 1508 didRun(delegate, didRunSelector, self, success, context); 1509 } 1510 DESTROY(_printOp_delegate); 1511} 1512 1513- (BOOL) validateMenuItem: (NSMenuItem *)anItem 1514{ 1515 return [self validateUserInterfaceItem: anItem]; 1516} 1517 1518- (BOOL) validateUserInterfaceItem: (id <NSValidatedUserInterfaceItem>)anItem 1519{ 1520 if (sel_isEqual([anItem action], @selector(revertDocumentToSaved:))) 1521 return ([self fileName] != nil && [self isDocumentEdited]); 1522 if (sel_isEqual([anItem action], @selector(saveDocument:))) 1523 return [self isDocumentEdited]; 1524 1525 // FIXME should validate spa popup items; return YES if it's a native type. 1526 1527 return YES; 1528} 1529 1530- (NSString *) fileTypeFromLastRunSavePanel 1531{ 1532 return _save_type; 1533} 1534 1535- (NSDictionary *) fileAttributesToWriteToFile: (NSString *)fullDocumentPath 1536 ofType: (NSString *)docType 1537 saveOperation: (NSSaveOperationType)saveOperationType 1538{ 1539 // FIXME: Implement. Should set NSFileExtensionHidden 1540 return [NSDictionary dictionary]; 1541} 1542 1543- (NSDictionary *) fileAttributesToWriteToURL: (NSURL *)url 1544 ofType: (NSString *)type 1545 forSaveOperation: (NSSaveOperationType)op 1546 originalContentsURL: (NSURL *)original 1547 error: (NSError **)error 1548{ 1549 // FIXME: Implement. Should set NSFileExtensionHidden 1550 if (error) 1551 *error = nil; 1552 1553 return [NSDictionary dictionary]; 1554} 1555 1556- (IBAction) saveDocument: (id)sender 1557{ 1558 [self saveDocumentWithDelegate: nil 1559 didSaveSelector: NULL 1560 contextInfo: NULL]; 1561} 1562 1563- (IBAction) saveDocumentAs: (id)sender 1564{ 1565 [self runModalSavePanelForSaveOperation: NSSaveAsOperation 1566 delegate: nil 1567 didSaveSelector: NULL 1568 contextInfo: NULL]; 1569} 1570 1571- (IBAction) saveDocumentTo: (id)sender 1572{ 1573 [self runModalSavePanelForSaveOperation: NSSaveToOperation 1574 delegate: nil 1575 didSaveSelector: NULL 1576 contextInfo: NULL]; 1577} 1578 1579- (void) saveDocumentWithDelegate: (id)delegate 1580 didSaveSelector: (SEL)didSaveSelector 1581 contextInfo: (void *)contextInfo 1582{ 1583 NSURL *fileURL = [self fileURL]; 1584 NSString *type = [self fileType]; 1585 1586 if ((fileURL != nil) && (type != nil)) 1587 { 1588 [self saveToURL: fileURL 1589 ofType: type 1590 forSaveOperation: NSSaveOperation 1591 delegate: delegate 1592 didSaveSelector: didSaveSelector 1593 contextInfo: contextInfo]; 1594 } 1595 else 1596 { 1597 [self runModalSavePanelForSaveOperation: NSSaveOperation 1598 delegate: delegate 1599 didSaveSelector: didSaveSelector 1600 contextInfo: contextInfo]; 1601 } 1602} 1603 1604- (void) saveToFile: (NSString *)fileName 1605 saveOperation: (NSSaveOperationType)saveOperation 1606 delegate: (id)delegate 1607 didSaveSelector: (SEL)didSaveSelector 1608 contextInfo: (void *)contextInfo 1609{ 1610 BOOL saved = NO; 1611 1612 if (fileName != nil) 1613 { 1614 saved = [self writeWithBackupToFile: fileName 1615 ofType: [self fileTypeFromLastRunSavePanel] 1616 saveOperation: saveOperation]; 1617 if (saved && 1618 (saveOperation == NSSaveOperation || 1619 saveOperation == NSSaveAsOperation)) 1620 { 1621 [[NSDocumentController sharedDocumentController] 1622 noteNewRecentDocument: self]; 1623 } 1624 } 1625 1626 if (delegate != nil && didSaveSelector != NULL) 1627 { 1628 void (*meth)(id, SEL, id, BOOL, void*); 1629 meth = (void (*)(id, SEL, id, BOOL, void*))[delegate methodForSelector: 1630 didSaveSelector]; 1631 if (meth) 1632 meth(delegate, didSaveSelector, self, saved, contextInfo); 1633 } 1634} 1635 1636- (BOOL) saveToURL: (NSURL *)url 1637 ofType: (NSString *)type 1638 forSaveOperation: (NSSaveOperationType)op 1639 error: (NSError **)error 1640{ 1641 BOOL saved = 1642 [self writeSafelyToURL: url 1643 ofType: type 1644 forSaveOperation: op 1645 error: error]; 1646 if (saved && (op == NSSaveOperation || op == NSSaveAsOperation)) 1647 { 1648 [[NSDocumentController sharedDocumentController] 1649 noteNewRecentDocument: self]; 1650 } 1651 return saved; 1652} 1653 1654- (void) saveToURL: (NSURL *)url 1655 ofType: (NSString *)type 1656 forSaveOperation: (NSSaveOperationType)op 1657 delegate: (id)delegate 1658 didSaveSelector: (SEL)didSaveSelector 1659 contextInfo: (void *)contextInfo 1660{ 1661 NSError *error; 1662 BOOL saved; 1663 1664 saved = [self saveToURL: url 1665 ofType: type 1666 forSaveOperation: op 1667 error: &error]; 1668 if (!saved) 1669 { 1670 [self presentError: error]; 1671 } 1672 else if (op == NSSaveOperation || op == NSSaveAsOperation) 1673 { 1674 [[NSDocumentController sharedDocumentController] 1675 noteNewRecentDocument: self]; 1676 } 1677 1678 if (delegate != nil && didSaveSelector != NULL) 1679 { 1680 void (*meth)(id, SEL, id, BOOL, void*); 1681 meth = (void (*)(id, SEL, id, BOOL, void*))[delegate methodForSelector: 1682 didSaveSelector]; 1683 if (meth) 1684 meth(delegate, didSaveSelector, self, saved, contextInfo); 1685 } 1686} 1687 1688- (IBAction) revertDocumentToSaved: (id)sender 1689{ 1690 int result; 1691 NSError *error; 1692 1693 result = NSRunAlertPanel 1694 (_(@"Revert"), 1695 _(@"%@ has been edited. Are you sure you want to undo changes?"), 1696 _(@"Revert"), _(@"Cancel"), nil, 1697 [self displayName]); 1698 1699 if (result == NSAlertDefaultReturn) 1700 { 1701 // FIXME: Revert registered editors 1702 if ([self revertToContentsOfURL: [self fileURL] 1703 ofType: [self fileType] 1704 error: &error]) 1705 { 1706 [self updateChangeCount: NSChangeCleared]; 1707 [[self undoManager] removeAllActions]; 1708 [self _removeAutosavedContentsFile]; 1709 } 1710 else 1711 { 1712 [self presentError: error]; 1713 } 1714 } 1715} 1716 1717/** Closes all the windows owned by the document, then removes itself 1718 from the list of documents known by the NSDocumentController. This 1719 method does not ask the user if they want to save the document before 1720 closing. It is closed without saving any information. 1721 */ 1722- (void) close 1723{ 1724 if (_doc_flags.in_close == NO) 1725 { 1726 int count = [_window_controllers count]; 1727 /* Closing a windowController will also send us a close, so make 1728 sure we don't go recursive */ 1729 _doc_flags.in_close = YES; 1730 1731 if (count > 0) 1732 { 1733 NSWindowController *array[count]; 1734 [_window_controllers getObjects: array]; 1735 while (count-- > 0) 1736 [array[count] close]; 1737 } 1738 [self _removeAutosavedContentsFile]; 1739 AUTORELEASE(RETAIN(self)); 1740 [[NSDocumentController sharedDocumentController] removeDocument: self]; 1741 } 1742} 1743 1744- (void) windowControllerWillLoadNib: (NSWindowController *)windowController {} 1745- (void) windowControllerDidLoadNib: (NSWindowController *)windowController {} 1746 1747- (NSUndoManager *) undoManager 1748{ 1749 if (_undo_manager == nil && [self hasUndoManager]) 1750 { 1751 [self setUndoManager: AUTORELEASE([[NSUndoManager alloc] init])]; 1752 } 1753 1754 return _undo_manager; 1755} 1756 1757- (void) setUndoManager: (NSUndoManager *)undoManager 1758{ 1759 if (undoManager != _undo_manager) 1760 { 1761 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 1762 1763 if (_undo_manager) 1764 { 1765 [center removeObserver: self 1766 name: NSUndoManagerWillCloseUndoGroupNotification 1767 object: _undo_manager]; 1768 [center removeObserver: self 1769 name: NSUndoManagerDidUndoChangeNotification 1770 object: _undo_manager]; 1771 [center removeObserver: self 1772 name: NSUndoManagerDidRedoChangeNotification 1773 object: _undo_manager]; 1774 } 1775 1776 ASSIGN(_undo_manager, undoManager); 1777 1778 if (_undo_manager == nil) 1779 { 1780 [self setHasUndoManager: NO]; 1781 } 1782 else 1783 { 1784 [center addObserver: self 1785 selector:@selector(_changeWasDone:) 1786 name: NSUndoManagerWillCloseUndoGroupNotification 1787 object: _undo_manager]; 1788 [center addObserver: self 1789 selector:@selector(_changeWasUndone:) 1790 name: NSUndoManagerDidUndoChangeNotification 1791 object: _undo_manager]; 1792 [center addObserver: self 1793 selector:@selector(_changeWasRedone:) 1794 name: NSUndoManagerDidRedoChangeNotification 1795 object: _undo_manager]; 1796 } 1797 } 1798} 1799 1800- (BOOL) hasUndoManager 1801{ 1802 return _doc_flags.has_undo_manager; 1803} 1804 1805- (void) setHasUndoManager: (BOOL)flag 1806{ 1807 if (_undo_manager && !flag) 1808 [self setUndoManager: nil]; 1809 1810 _doc_flags.has_undo_manager = flag; 1811} 1812 1813- (BOOL) presentError: (NSError *)error 1814{ 1815 error = [self willPresentError: error]; 1816 return [[NSDocumentController sharedDocumentController] presentError: error]; 1817} 1818 1819- (void) presentError: (NSError *)error 1820 modalForWindow: (NSWindow *)window 1821 delegate: (id)delegate 1822 didPresentSelector: (SEL)sel 1823 contextInfo: (void *)context 1824{ 1825 error = [self willPresentError: error]; 1826 [[NSDocumentController sharedDocumentController] presentError: error 1827 modalForWindow: window 1828 delegate: delegate 1829 didPresentSelector: sel 1830 contextInfo: context]; 1831} 1832 1833- (NSError *) willPresentError: (NSError *)error 1834{ 1835 return error; 1836} 1837 1838- (NSURL *) autosavedContentsFileURL 1839{ 1840 return _autosaved_file_url; 1841} 1842 1843- (void) setAutosavedContentsFileURL: (NSURL *)url 1844{ 1845 ASSIGN(_autosaved_file_url, url); 1846 [[NSDocumentController sharedDocumentController] 1847 _recordAutosavedDocument: self]; 1848} 1849 1850- (void) autosaveDocumentWithDelegate: (id)delegate 1851 didAutosaveSelector: (SEL)didAutosaveSelector 1852 contextInfo: (void *)context 1853{ 1854 NSURL *url = [self autosavedContentsFileURL]; 1855 NSString *type = [self autosavingFileType]; 1856 1857 if (url == nil) 1858 { 1859 static NSString *processName = nil; 1860 NSString *path; 1861 NSString *ext = [self fileNameExtensionForType: type 1862 saveOperation: NSAutosaveOperation]; 1863 1864 if (!processName) 1865 processName = [[[NSProcessInfo processInfo] processName] copy]; 1866 1867 path = [[NSDocumentController sharedDocumentController] 1868 _autosaveDirectory: YES]; 1869 path = [path stringByAppendingPathComponent: 1870 [NSString stringWithFormat: @"%@-%d", 1871 processName, _document_index]]; 1872 path = [path stringByAppendingPathExtension: ext]; 1873 url = [NSURL fileURLWithPath: path]; 1874 } 1875 1876 [self saveToURL: url 1877 ofType: type 1878 forSaveOperation: NSAutosaveOperation 1879 delegate: delegate 1880 didSaveSelector: didAutosaveSelector 1881 contextInfo: context]; 1882} 1883 1884- (NSString *) autosavingFileType 1885{ 1886 return [self fileType]; 1887} 1888 1889- (BOOL) hasUnautosavedChanges 1890{ 1891 return _autosave_change_count != 0 || _doc_flags.autosave_permanently_modified; 1892} 1893 1894@end 1895 1896@implementation NSDocument(Private) 1897 1898/* 1899 * This private method is used to transfer window ownership to the 1900 * NSWindowController in situations (such as the default) where the 1901 * document is set to the nib owner, and thus owns the window immediately 1902 * following the loading of the nib. 1903 */ 1904- (NSWindow *) _transferWindowOwnership 1905{ 1906 NSWindow *window = _window; 1907 _window = nil; 1908 return AUTORELEASE(window); 1909} 1910 1911- (void) _removeWindowController: (NSWindowController *)windowController 1912{ 1913 if ([_window_controllers containsObject: windowController]) 1914 { 1915 BOOL autoClose = [windowController shouldCloseDocument]; 1916 1917 [windowController setDocument: nil]; 1918 [_window_controllers removeObject: windowController]; 1919 1920 if (autoClose || [_window_controllers count] == 0) 1921 { 1922 [self close]; 1923 } 1924 } 1925} 1926 1927- (void) _removeAutosavedContentsFile 1928{ 1929 NSURL *url = [self autosavedContentsFileURL]; 1930 1931 if (url) 1932 { 1933 NSString *path = [[url path] retain]; 1934 1935 [self setAutosavedContentsFileURL: nil]; 1936 [[NSFileManager defaultManager] removeFileAtPath: path handler: nil]; 1937 [path release]; 1938 } 1939} 1940 1941- (void) _changeWasDone: (NSNotification *)notification 1942{ 1943 /* Prevent a document from appearing unmodified after saving the 1944 * document, undoing a number of changes, and then making an equal 1945 * number of changes. Ditto for autosaved changes. 1946 */ 1947 if (_change_count < 0) 1948 _doc_flags.permanently_modified = 1; 1949 if (_autosave_change_count < 0) 1950 _doc_flags.autosave_permanently_modified = 1; 1951 [self updateChangeCount: NSChangeDone]; 1952} 1953 1954- (void) _changeWasUndone: (NSNotification *)notification 1955{ 1956 [self updateChangeCount: NSChangeUndone]; 1957} 1958 1959- (void) _changeWasRedone: (NSNotification *)notification 1960{ 1961 /* FIXME 1962 * Mac OS X 10.5 uses a new constant NSChangeRedone here, but 1963 * old applications are not prepared to handle this constant 1964 * and expect NSChangeDone instead. 1965 */ 1966 [self updateChangeCount: NSChangeDone]; 1967} 1968 1969@end 1970