/**
NSDocument
The abstract document class
Copyright (C) 1999 Free Software Foundation, Inc.
Author: Carl Lindberg
Date: 1999
Modifications: Fred Kiefer
Date: June 2000, Dec 2006
This file is part of the GNUstep GUI Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; see the file COPYING.LIB.
If not, see or write to the
Free Software Foundation, 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include
#include "AppKit/NSBox.h"
#include "AppKit/NSDocument.h"
#include "AppKit/NSFileWrapper.h"
#include "AppKit/NSSavePanel.h"
#include "AppKit/NSPageLayout.h"
#include "AppKit/NSPrintInfo.h"
#include "AppKit/NSPrintOperation.h"
#include "AppKit/NSPopUpButton.h"
#include "AppKit/NSView.h"
#include "NSDocumentFrameworkPrivate.h"
#include "GSGuiPrivate.h"
@implementation NSDocument
+ (NSArray *)readableTypes
{
return [[NSDocumentController sharedDocumentController]
_editorAndViewerTypesForClass: self];
}
+ (NSArray *)writableTypes
{
return [[NSDocumentController sharedDocumentController]
_editorTypesForClass: self];
}
+ (BOOL)isNativeType: (NSString *)type
{
return ([[self readableTypes] containsObject: type] &&
[[self writableTypes] containsObject: type]);
}
- (id) init
{
static int untitledCount = 1;
NSArray *fileTypes;
self = [super init];
if (self != nil)
{
_document_index = untitledCount++;
_window_controllers = [[NSMutableArray alloc] init];
fileTypes = [[self class] readableTypes];
_doc_flags.has_undo_manager = YES;
/* Set our default type */
if ([fileTypes count])
{
[self setFileType: [fileTypes objectAtIndex: 0]];
ASSIGN(_save_type, [fileTypes objectAtIndex: 0]);
}
}
return self;
}
/**
* Initialises the receiver with the contents of the document at fileName
* assuming that the type of data is as specified by fileType.
* Destroys the receiver and returns nil on failure.
*/
- (id) initWithContentsOfFile: (NSString*)fileName ofType: (NSString*)fileType
{
self = [self init];
if (self != nil)
{
[self setFileType: fileType];
[self setFileName: fileName];
if (![self readFromFile: fileName ofType: fileType])
{
NSRunAlertPanel (_(@"Load failed"),
_(@"Could not load file %@."),
nil, nil, nil, fileName);
DESTROY(self);
}
}
return self;
}
/**
* Initialises the receiver with the contents of the document at url
* assuming that the type of data is as specified by fileType.
* Destroys the receiver and returns nil on failure.
*/
- (id) initWithContentsOfURL: (NSURL*)url ofType: (NSString*)fileType
{
self = [self init];
if (self != nil)
{
[self setFileType: fileType];
[self setFileName: [url path]];
if (![self readFromURL: url ofType: fileType])
{
NSRunAlertPanel(_(@"Load failed"),
_(@"Could not load URL %@."),
nil, nil, nil, [url absoluteString]);
DESTROY(self);
}
}
return self;
}
- (id)initForURL: (NSURL *)forUrl
withContentsOfURL: (NSURL *)url
ofType: (NSString *)type
error: (NSError **)error
{
self = [self initWithType: type error: error];
if (self != nil)
{
if ([self readFromURL: url
ofType: type
error: error])
{
if (forUrl != nil)
{
[self setFileURL: forUrl];
}
}
else
{
DESTROY(self);
}
}
return self;
}
- (id)initWithContentsOfURL: (NSURL *)url
ofType: (NSString *)type
error: (NSError **)error
{
return [self initForURL: url
withContentsOfURL: url
ofType: type
error: error];
}
- (id)initWithType:(NSString *)type
error:(NSError **)error
{
self = [self init];
if (self != nil)
{
[self setFileType: type];
}
return self;
}
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
RELEASE(_undo_manager);
RELEASE(_file_name);
RELEASE(_file_url);
RELEASE(_file_type);
RELEASE(_last_component_file_name);
RELEASE(_autosaved_file_url);
RELEASE(_file_modification_date);
RELEASE(_window_controllers);
RELEASE(_window);
RELEASE(_print_info);
RELEASE(_printOp_delegate);
RELEASE(_save_panel_accessory);
RELEASE(_spa_button);
RELEASE(_save_type);
[super dealloc];
}
/*
* Private helper method to check, if the method given via the selector sel
* has been overridden in the current subclass.
*/
- (BOOL)_hasOverridden: (SEL)sel
{
// The actual signature is not important as we wont call the methods.
IMP meth1;
IMP meth2;
meth1 = [self methodForSelector: sel];
meth2 = [[NSDocument class] instanceMethodForSelector: sel];
return (meth1 != meth2);
}
#define OVERRIDDEN(sel) [self _hasOverridden: @selector(sel)]
- (NSString *)fileName
{
return _file_name;
}
- (void)setFileName: (NSString *)fileName
{
NSURL *fileUrl = fileName ? [NSURL fileURLWithPath: fileName] : nil;
// This check is to prevent super calls from recursing.
if (!OVERRIDDEN(setFileName:))
{
[self setFileURL: fileUrl];
}
else
{
ASSIGN(_file_name, fileName);
ASSIGN(_file_url, fileUrl);
[self setLastComponentOfFileName: [_file_name lastPathComponent]];
}
}
- (NSString *)fileType
{
return _file_type;
}
- (void)setFileType: (NSString *)type
{
ASSIGN(_file_type, type);
}
- (NSURL *)fileURL
{
if (OVERRIDDEN(fileName))
{
NSString *fileName = [self fileName];
return fileName ? [NSURL fileURLWithPath: fileName] : nil;
}
else
{
return _file_url;
}
}
- (void)setFileURL: (NSURL *)url
{
if (OVERRIDDEN(setFileName:) &&
((url == nil) || [url isFileURL]))
{
[self setFileName: [url path]];
}
else
{
ASSIGN(_file_url, url);
ASSIGN(_file_name, (url && [url isFileURL]) ? [url path] : (NSString*)nil);
[self setLastComponentOfFileName: [[_file_url path] lastPathComponent]];
}
}
- (NSDate *)fileModificationDate
{
return _file_modification_date;
}
- (void)setFileModificationDate: (NSDate *)date
{
ASSIGN(_file_modification_date, date);
}
- (NSString *)lastComponentOfFileName
{
return _last_component_file_name;
}
- (void)setLastComponentOfFileName: (NSString *)str
{
ASSIGN(_last_component_file_name, str);
[[self windowControllers] makeObjectsPerformSelector:
@selector(synchronizeWindowTitleWithDocumentName)];
}
- (NSArray *)windowControllers
{
return _window_controllers;
}
- (void)addWindowController: (NSWindowController *)windowController
{
[_window_controllers addObject: windowController];
if ([windowController document] != self)
{
[windowController setDocument: self];
}
}
- (void)removeWindowController: (NSWindowController *)windowController
{
if ([_window_controllers containsObject: windowController])
{
[windowController setDocument: nil];
[_window_controllers removeObject: windowController];
}
}
- (NSString *)windowNibName
{
return nil;
}
// private; called during nib load.
// we do not retain the window, since it should
// already have a retain from the nib.
- (void)setWindow: (NSWindow *)aWindow
{
_window = aWindow;
}
- (NSWindow *)windowForSheet
{
NSWindow *win;
if (([_window_controllers count] > 0) &&
((win = [[_window_controllers objectAtIndex: 0] window]) != nil))
{
return win;
}
return nil;
}
/**
* Creates the window controllers for the current document. Calls
* addWindowController: on the receiver to add them to the controller
* array.
*/
- (void) makeWindowControllers
{
NSString *name = [self windowNibName];
if (name != nil && [name length] > 0)
{
NSWindowController *controller;
controller = [[NSWindowController alloc] initWithWindowNibName: name
owner: self];
[self addWindowController: controller];
RELEASE(controller);
}
else
{
[NSException raise: NSInternalInconsistencyException
format: @"%@ must override either -windowNibName "
@"or -makeWindowControllers", NSStringFromClass([self class])];
}
}
/**
* Makes all the documents windows visible by ordering them to the
* front and making them main or key.
* If the document has no windows, this method has no effect.
*/
- (void) showWindows
{
[_window_controllers makeObjectsPerformSelector: @selector(showWindow:)
withObject: self];
}
- (BOOL) isDocumentEdited
{
return _change_count != 0;
}
- (void)updateChangeCount: (NSDocumentChangeType)change
{
int i, count = [_window_controllers count];
BOOL isEdited;
switch (change)
{
case NSChangeDone: _change_count++;
_autosave_change_count++;
break;
case NSChangeUndone: _change_count--;
_autosave_change_count--;
break;
case NSChangeReadOtherContents:
case NSChangeCleared: _change_count = 0;
_autosave_change_count = 0;
break;
case NSChangeAutosaved: _autosave_change_count = 0;
break;
}
/*
* NOTE: Apple's implementation seems to not call -isDocumentEdited
* here but directly checks to see if _changeCount == 0. It seems it
* would be better to call the method in case it's overridden by a
* subclass, but we may want to keep Apple's behavior.
*/
isEdited = [self isDocumentEdited];
for (i=0; i 0)
{
NSString *extension = [extensions objectAtIndex: 0];
NSString *newFileName = [[fileName stringByDeletingPathExtension]
stringByAppendingPathExtension: extension];
if ([fileManager fileExistsAtPath: newFileName])
{
backupFilename = [self _backupFileNameFor: newFileName];
if (![self _writeBackupForFile: newFileName
toFile: backupFilename])
{
return NO;
}
}
if ([self writeToFile: fileName
ofType: fileType
originalFile: backupFilename
saveOperation: saveOp])
{
// FIXME: Should set the file attributes
if (saveOp != NSSaveToOperation)
{
[self setFileName: newFileName];
[self setFileType: fileType];
[self updateChangeCount: NSChangeCleared];
}
if (backupFilename && ![self keepBackupFile])
{
[fileManager removeFileAtPath: backupFilename handler: nil];
}
return YES;
}
}
}
return NO;
}
- (BOOL)writeSafelyToURL: (NSURL *)url
ofType: (NSString *)type
forSaveOperation: (NSSaveOperationType)saveOp
error: (NSError **)error
{
NSDictionary *attrs;
NSURL *original = [self fileURL];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *backupFilename = nil;
BOOL isNativeType = [[self class] isNativeType: type];
if (OVERRIDDEN(writeWithBackupToFile:ofType:saveOperation:))
{
if (saveOp == NSAutosaveOperation)
{
saveOp = NSSaveToOperation;
}
*error = nil;
return [self writeWithBackupToFile: [url path]
ofType: type
saveOperation: saveOp];
}
if (!isNativeType || (url == nil))
{
// FIXME: Set error
*error = nil;
return NO;
}
if (saveOp == NSSaveOperation)
{
if ([url isFileURL])
{
NSString *newFileName;
newFileName = [url path];
if ([fileManager fileExistsAtPath: newFileName])
{
backupFilename = [self _backupFileNameFor: newFileName];
if (![self _writeBackupForFile: newFileName
toFile: backupFilename])
{
// FIXME: Set error.
*error = nil;
return NO;
}
}
}
}
if (![self writeToURL: url
ofType: type
forSaveOperation: saveOp
originalContentsURL: original
error: error])
{
return NO;
}
attrs = [self fileAttributesToWriteToURL: url
ofType: type
forSaveOperation: saveOp
originalContentsURL: original
error: error];
// FIXME: Should set the file attributes
if (saveOp != NSSaveToOperation)
{
[self setFileURL: url];
[self setFileType: type];
[self updateChangeCount: NSChangeCleared];
}
if (backupFilename && ![self keepBackupFile])
{
[fileManager removeFileAtPath: backupFilename handler: nil];
}
return YES;
}
- (BOOL)writeToURL: (NSURL *)url
ofType: (NSString *)type
error: (NSError **)error
{
NSFileWrapper *wrapper;
if (OVERRIDDEN(writeToFile:ofType:))
{
*error = nil;
return [self writeToFile: [url path] ofType: type];
}
wrapper = [self fileWrapperOfType: type
error: error];
if (wrapper == nil)
{
// FIXME: Set error
*error = nil;
return NO;
}
*error = nil;
return [wrapper writeToFile: [url path] atomically: YES updateFilenames: YES];
}
- (BOOL)writeToURL: (NSURL *)url
ofType: (NSString *)type
forSaveOperation: (NSSaveOperationType)saveOp
originalContentsURL: (NSURL *)orig
error: (NSError **)error
{
if (OVERRIDDEN(writeToFile:ofType:originalFile:saveOperation:))
{
if (saveOp == NSAutosaveOperation)
{
saveOp = NSSaveToOperation;
}
*error = nil;
return [self writeToFile: [url path]
ofType: type
originalFile: [orig path]
saveOperation: saveOp];
}
return [self writeToURL: url
ofType: type
error: error];
}
- (IBAction)changeSaveType: (id)sender
{
NSDocumentController *controller =
[NSDocumentController sharedDocumentController];
NSArray *extensions = nil;
ASSIGN(_save_type, [controller _nameForHumanReadableType:
[sender titleOfSelectedItem]]);
extensions = [controller fileExtensionsFromType: _save_type];
if ([extensions count] > 0)
{
[(NSSavePanel *)[sender window] setRequiredFileType: [extensions objectAtIndex:0]];
}
}
- (int)runModalSavePanel: (NSSavePanel *)savePanel
withAccessoryView: (NSView *)accessoryView
{
if (accessoryView)
{
[savePanel setAccessoryView: accessoryView];
}
return [savePanel runModal];
}
- (void)runModalSavePanelForSaveOperation: (NSSaveOperationType)saveOperation
delegate: (id)delegate
didSaveSelector: (SEL)didSaveSelector
contextInfo: (void *)contextInfo
{
NSString *fileName;
// FIXME: Setting of the delegate of the save panel is missing
fileName = [self fileNameFromRunningSavePanelForSaveOperation: saveOperation];
[self saveToFile: fileName
saveOperation: saveOperation
delegate: delegate
didSaveSelector: didSaveSelector
contextInfo: contextInfo];
}
- (BOOL)prepareSavePanel: (NSSavePanel *)savePanel
{
return YES;
}
- (BOOL)shouldRunSavePanelWithAccessoryView
{
return YES;
}
- (void) _createPanelAccessory
{
if (_save_panel_accessory == nil)
{
NSRect accessoryFrame = NSMakeRect(0,0,380,70);
NSRect spaFrame = NSMakeRect(115,14,150,22);
_save_panel_accessory = [[NSBox alloc] initWithFrame: accessoryFrame];
[(NSBox *)_save_panel_accessory setTitle: @"File Type"];
[_save_panel_accessory setAutoresizingMask:
NSViewWidthSizable | NSViewHeightSizable];
_spa_button = [[NSPopUpButton alloc] initWithFrame: spaFrame];
[_spa_button setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable | NSViewMinYMargin |
NSViewMaxYMargin | NSViewMinXMargin | NSViewMaxXMargin];
[_spa_button setTarget: self];
[_spa_button setAction: @selector(changeSaveType:)];
[_save_panel_accessory addSubview: _spa_button];
}
}
- (void) _addItemsToSpaButtonFromArray: (NSArray *)types
{
NSEnumerator *en = [types objectEnumerator];
NSString *title = nil;
int i = 0;
while ((title = [en nextObject]) != nil)
{
[_spa_button addItemWithTitle: title];
i++;
}
// if it's more than one, then
[_spa_button setEnabled: (i > 0)];
// if we have some items, select the current filetype.
if (i > 0)
{
NSString *title = [[NSDocumentController sharedDocumentController]
displayNameForType: [self fileType]];
if ([_spa_button itemWithTitle: title] != nil)
{
[_spa_button selectItemWithTitle: title];
}
else
{
[_spa_button selectItemAtIndex: 0];
}
}
}
- (NSString *)fileNameFromRunningSavePanelForSaveOperation: (NSSaveOperationType)saveOperation
{
NSView *accessory = nil;
NSString *title;
NSString *directory;
NSArray *displayNames;
NSDocumentController *controller;
NSSavePanel *savePanel = [NSSavePanel savePanel];
ASSIGN(_save_type, [self fileType]);
controller = [NSDocumentController sharedDocumentController];
displayNames = [controller _displayNamesForClass: [self class]];
if ([self shouldRunSavePanelWithAccessoryView])
{
if (_save_panel_accessory == nil)
[self _createPanelAccessory];
[self _addItemsToSpaButtonFromArray: displayNames];
accessory = _save_panel_accessory;
}
if ([displayNames count] > 0)
{
NSArray *extensions = [[NSDocumentController sharedDocumentController]
fileExtensionsFromType: [self fileType]];
if ([extensions count] > 0)
{
[savePanel setRequiredFileType:[extensions objectAtIndex:0]];
}
}
switch (saveOperation)
{
case NSSaveAsOperation: title = _(@"Save As"); break;
case NSSaveToOperation: title = _(@"Save To"); break;
case NSSaveOperation:
default:
title = _(@"Save");
break;
}
[savePanel setTitle: title];
if ([self fileName])
directory = [[self fileName] stringByDeletingLastPathComponent];
else
directory = [controller currentDirectory];
[savePanel setDirectory: directory];
if (![self prepareSavePanel: savePanel])
{
return nil;
}
if ([self runModalSavePanel: savePanel withAccessoryView: accessory])
{
return [savePanel filename];
}
return nil;
}
- (NSArray *)writableTypesForSaveOperation: (NSSaveOperationType)op
{
NSArray *types = [isa writableTypes];
NSMutableArray *muTypes;
int i, len;
if (op == NSSaveToOperation)
{
return types;
}
len = [types count];
muTypes = [NSMutableArray arrayWithCapacity: len];
for (i = 0; i < len; i++)
{
NSString *type;
type = [types objectAtIndex: i];
if ([[self class] isNativeType: type])
{
[muTypes addObject: type];
}
}
return muTypes;
}
- (BOOL)fileNameExtensionWasHiddenInLastRunSavePanel
{
// FIXME
return NO;
}
- (BOOL)shouldChangePrintInfo: (NSPrintInfo *)newPrintInfo
{
return YES;
}
- (NSPrintInfo *)printInfo
{
return _print_info? _print_info : [NSPrintInfo sharedPrintInfo];
}
- (void)setPrintInfo: (NSPrintInfo *)printInfo
{
ASSIGN(_print_info, printInfo);
[self updateChangeCount: NSChangeDone];
}
// Page layout panel (Page Setup)
- (BOOL)preparePageLayout:(NSPageLayout *)pageLayout
{
return YES;
}
- (int)runModalPageLayoutWithPrintInfo: (NSPrintInfo *)printInfo
{
NSPageLayout *pageLayout;
pageLayout = [NSPageLayout pageLayout];
if ([self preparePageLayout: pageLayout])
{
return [pageLayout runModalWithPrintInfo: printInfo];
}
else
{
return NSCancelButton;
}
}
- (IBAction)runPageLayout: (id)sender
{
NSPrintInfo *printInfo = [self printInfo];
if ([self runModalPageLayoutWithPrintInfo: printInfo]
&& [self shouldChangePrintInfo: printInfo])
{
[self setPrintInfo: printInfo];
}
}
- (void)runModalPageLayoutWithPrintInfo: (NSPrintInfo *)printInfo
delegate: (id)delegate
didRunSelector: (SEL)sel
contextInfo: (void *)context
{
NSPageLayout *pageLayout;
pageLayout = [NSPageLayout pageLayout];
if ([self preparePageLayout: pageLayout])
{
NSWindow *win;
win = [self windowForSheet];
[pageLayout beginSheetWithPrintInfo: printInfo
modalForWindow: win
delegate: delegate
didEndSelector: sel
contextInfo: context];
}
}
/* This is overridden by subclassers; the default implementation does nothing. */
- (void)printShowingPrintPanel: (BOOL)flag
{
}
- (IBAction)printDocument: (id)sender
{
[self printShowingPrintPanel: YES];
}
- (void)printDocumentWithSettings: (NSDictionary *)settings
showPrintPanel: (BOOL)flag
delegate: (id)delegate
didPrintSelector: (SEL)sel
contextInfo: (void *)context
{
NSPrintOperation *printOp;
NSError *error;
if (OVERRIDDEN(printShowingPrintPanel:))
{
// FIXME: More communication with the panel is needed.
return [self printShowingPrintPanel: flag];
}
printOp = [self printOperationWithSettings: settings
error: &error];
if (printOp != nil)
{
[printOp setShowsPrintPanel: flag];
[self runModalPrintOperation: printOp
delegate: delegate
didRunSelector: sel
contextInfo: context];
}
else
{
[self presentError: error];
if (delegate != nil && sel != NULL)
{
void (*meth)(id, SEL, id, BOOL, void*);
meth = (void (*)(id, SEL, id, BOOL, void*))[delegate methodForSelector: sel];
if (meth)
meth(delegate, sel, self, NO, context);
}
}
}
- (NSPrintOperation *)printOperationWithSettings: (NSDictionary *)settings
error: (NSError **)error
{
*error = nil;
return nil;
}
- (void)runModalPrintOperation: (NSPrintOperation *)op
delegate: (id)delegate
didRunSelector: (SEL)sel
contextInfo: (void *)context
{
ASSIGN(_printOp_delegate, delegate);
_printOp_didRunSelector = sel;
[op runOperationModalForWindow: [self windowForSheet]
delegate: self
didRunSelector: @selector(_runModalPrintOperationDidSucceed:contextInfo:)
contextInfo: context];
}
- (void)_runModalPrintOperationDidSucceed: (BOOL)success
contextInfo: (void *)context
{
id delegate = _printOp_delegate;
SEL didRunSelector = _printOp_didRunSelector;
void (*didRun)(id, SEL, NSDocument *, BOOL, id);
if (delegate && [delegate respondsToSelector:didRunSelector])
{
didRun = (void (*)(id, SEL, NSDocument *, BOOL, id))
[delegate methodForSelector:didRunSelector];
didRun(delegate, didRunSelector, self, success, context);
}
DESTROY(_printOp_delegate);
}
- (BOOL)validateMenuItem: (NSMenuItem *)anItem
{
BOOL result = YES;
SEL action = [anItem action];
// FIXME should validate spa popup items; return YES if it's a native type.
if (sel_eq(action, @selector(revertDocumentToSaved:)))
{
result = ([self fileName] != nil && [self isDocumentEdited]);
}
else if (sel_eq(action, @selector(undo:)))
{
if (_undo_manager == nil)
{
result = NO;
}
else
{
if ([_undo_manager canUndo])
{
[anItem setTitle: [_undo_manager undoMenuItemTitle]];
result = YES;
}
else
{
[anItem setTitle: [_undo_manager undoMenuTitleForUndoActionName: @""]];
result = NO;
}
}
}
else if (sel_eq(action, @selector(redo:)))
{
if (_undo_manager == nil)
{
result = NO;
}
else
{
if ([_undo_manager canRedo])
{
[anItem setTitle: [_undo_manager redoMenuItemTitle]];
result = YES;
}
else
{
[anItem setTitle: [_undo_manager redoMenuTitleForUndoActionName: @""]];
result = NO;
}
}
}
return result;
}
- (BOOL)validateUserInterfaceItem: (id )anItem
{
if ([anItem action] == @selector(revertDocumentToSaved:))
return ([self fileName] != nil);
return YES;
}
- (NSString *)fileTypeFromLastRunSavePanel
{
return _save_type;
}
- (NSDictionary *)fileAttributesToWriteToFile: (NSString *)fullDocumentPath
ofType: (NSString *)docType
saveOperation: (NSSaveOperationType)saveOperationType
{
// FIXME: Implement. Should set NSFileExtensionHidden
return [NSDictionary dictionary];
}
- (NSDictionary *)fileAttributesToWriteToURL:(NSURL *)url
ofType:(NSString *)type
forSaveOperation:(NSSaveOperationType)op
originalContentsURL:(NSURL *)original
error:(NSError **)error
{
// FIXME: Implement. Should set NSFileExtensionHidden
*error = nil;
return [NSDictionary dictionary];
}
- (IBAction)saveDocument: (id)sender
{
NSString *filename = [self fileName];
if (filename == nil)
{
[self saveDocumentAs: sender];
return;
}
[self writeWithBackupToFile: filename
ofType: [self fileType]
saveOperation: NSSaveOperation];
}
- (IBAction)saveDocumentAs: (id)sender
{
[self runModalSavePanelForSaveOperation: NSSaveAsOperation
delegate: nil
didSaveSelector: NULL
contextInfo: NULL];
}
- (IBAction)saveDocumentTo: (id)sender
{
[self runModalSavePanelForSaveOperation: NSSaveToOperation
delegate: nil
didSaveSelector: NULL
contextInfo: NULL];
}
- (void)saveDocumentWithDelegate: (id)delegate
didSaveSelector: (SEL)didSaveSelector
contextInfo: (void *)contextInfo
{
NSURL *fileURL = [self fileURL];
NSString *type = [self fileType];
if ((fileURL != nil) && (type != nil))
{
[self saveToURL: fileURL
ofType: type
forSaveOperation: NSSaveOperation
delegate: delegate
didSaveSelector: didSaveSelector
contextInfo: contextInfo];
}
else
{
[self runModalSavePanelForSaveOperation: NSSaveOperation
delegate: delegate
didSaveSelector: didSaveSelector
contextInfo: contextInfo];
}
}
- (void)saveToFile: (NSString *)fileName
saveOperation: (NSSaveOperationType)saveOperation
delegate: (id)delegate
didSaveSelector: (SEL)didSaveSelector
contextInfo: (void *)contextInfo
{
BOOL saved = NO;
if (fileName != nil)
{
saved = [self writeWithBackupToFile: fileName
ofType: [self fileTypeFromLastRunSavePanel]
saveOperation: saveOperation];
}
if (delegate != nil && didSaveSelector != NULL)
{
void (*meth)(id, SEL, id, BOOL, void*);
meth = (void (*)(id, SEL, id, BOOL, void*))[delegate methodForSelector:
didSaveSelector];
if (meth)
meth(delegate, didSaveSelector, self, saved, contextInfo);
}
}
- (BOOL)saveToURL: (NSURL *)url
ofType: (NSString *)type
forSaveOperation: (NSSaveOperationType)op
error: (NSError **)error
{
return [self writeSafelyToURL: url
ofType: type
forSaveOperation: op
error: error];
}
- (BOOL)saveToURL: (NSURL *)url
ofType: (NSString *)type
forSaveOperation: (NSSaveOperationType)op
delegate: (id)delegate
didSaveSelector: (SEL)didSaveSelector
contextInfo:(void *)contextInfo
{
NSError *error;
BOOL saved;
saved = [self saveToURL: url
ofType: type
forSaveOperation: op
error: &error];
if (!saved)
{
[self presentError: error];
}
if (delegate != nil && didSaveSelector != NULL)
{
void (*meth)(id, SEL, id, BOOL, void*);
meth = (void (*)(id, SEL, id, BOOL, void*))[delegate methodForSelector:
didSaveSelector];
if (meth)
meth(delegate, didSaveSelector, self, saved, contextInfo);
}
return saved;
}
- (IBAction)revertDocumentToSaved: (id)sender
{
int result;
NSError *error;
result = NSRunAlertPanel
(_(@"Revert"),
_(@"%@ has been edited. Are you sure you want to undo changes?"),
_(@"Revert"), _(@"Cancel"), nil,
[self displayName]);
if (result == NSAlertDefaultReturn)
{
if ([self revertToContentsOfURL: [self fileURL]
ofType: [self fileType]
error: &error])
{
[self updateChangeCount: NSChangeCleared];
}
else
{
[self presentError: error];
}
}
}
/** Closes all the windows owned by the document, then removes itself
from the list of documents known by the NSDocumentController. This
method does not ask the user if they want to save the document before
closing. It is closed without saving any information.
*/
- (void)close
{
if (_doc_flags.in_close == NO)
{
int count = [_window_controllers count];
/* Closing a windowController will also send us a close, so make
sure we don't go recursive */
_doc_flags.in_close = YES;
if (count > 0)
{
NSWindowController *array[count];
[_window_controllers getObjects: array];
while (count-- > 0)
[array[count] close];
}
[[NSDocumentController sharedDocumentController] removeDocument: self];
}
}
- (void)windowControllerWillLoadNib: (NSWindowController *)windowController {}
- (void)windowControllerDidLoadNib: (NSWindowController *)windowController {}
- (NSUndoManager *)undoManager
{
if (_undo_manager == nil && [self hasUndoManager])
{
[self setUndoManager: AUTORELEASE([[NSUndoManager alloc] init])];
}
return _undo_manager;
}
- (void)setUndoManager: (NSUndoManager *)undoManager
{
if (undoManager != _undo_manager)
{
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
if (_undo_manager)
{
[center removeObserver: self
name: NSUndoManagerWillCloseUndoGroupNotification
object:_undo_manager];
[center removeObserver: self
name: NSUndoManagerDidUndoChangeNotification
object:_undo_manager];
[center removeObserver: self
name: NSUndoManagerDidRedoChangeNotification
object:_undo_manager];
}
ASSIGN(_undo_manager, undoManager);
if (_undo_manager == nil)
{
[self setHasUndoManager: NO];
}
else
{
[center addObserver: self
selector:@selector(_changeWasDone:)
name: NSUndoManagerWillCloseUndoGroupNotification
object:_undo_manager];
[center addObserver: self
selector:@selector(_changeWasUndone:)
name: NSUndoManagerDidUndoChangeNotification
object:_undo_manager];
[center addObserver: self
selector:@selector(_changeWasRedone:)
name: NSUndoManagerDidRedoChangeNotification
object:_undo_manager];
}
}
}
- (BOOL)hasUndoManager
{
return _doc_flags.has_undo_manager;
}
- (void)setHasUndoManager: (BOOL)flag
{
if (_undo_manager && !flag)
[self setUndoManager: nil];
_doc_flags.has_undo_manager = flag;
}
- (BOOL)presentError: (NSError *)error
{
error = [self willPresentError: error];
return [[NSDocumentController sharedDocumentController] presentError: error];
}
- (void)presentError: (NSError *)error
modalForWindow: (NSWindow *)window
delegate: (id)delegate
didPresentSelector: (SEL)sel
contextInfo: (void *)context
{
error = [self willPresentError: error];
[[NSDocumentController sharedDocumentController] presentError: error
modalForWindow: window
delegate: delegate
didPresentSelector: sel
contextInfo: context];
}
- (NSError *)willPresentError:(NSError *)error
{
return error;
}
- (NSURL *)autosavedContentsFileURL
{
return _autosaved_file_url;
}
- (void)setAutosavedContentsFileURL: (NSURL *)url
{
ASSIGN(_autosaved_file_url, url);
}
- (void)autosaveDocumentWithDelegate: (id)delegate
didAutosaveSelector: (SEL)didAutosaveSelector
contextInfo: (void *)context
{
[self saveToURL: [self autosavedContentsFileURL]
ofType: [self autosavingFileType]
forSaveOperation: NSAutosaveOperation
delegate: delegate
didSaveSelector: didAutosaveSelector
contextInfo: context];
}
- (NSString *)autosavingFileType
{
return [self fileType];
}
- (BOOL)hasUnautosavedChanges
{
return _autosave_change_count != 0;
}
@end
@implementation NSDocument(Private)
/*
* This private method is used to transfer window ownership to the
* NSWindowController in situations (such as the default) where the
* document is set to the nib owner, and thus owns the window immediately
* following the loading of the nib.
*/
- (NSWindow *) _transferWindowOwnership
{
NSWindow *window = _window;
_window = nil;
return AUTORELEASE(window);
}
- (void) _removeWindowController: (NSWindowController *)windowController
{
if ([_window_controllers containsObject: windowController])
{
BOOL autoClose = [windowController shouldCloseDocument];
[windowController setDocument: nil];
[_window_controllers removeObject: windowController];
if (autoClose || [_window_controllers count] == 0)
{
[self close];
}
}
}
- (void) _changeWasDone: (NSNotification *)notification
{
[self updateChangeCount: NSChangeDone];
}
- (void) _changeWasUndone: (NSNotification *)notification
{
[self updateChangeCount: NSChangeUndone];
}
- (void) _changeWasRedone: (NSNotification *)notification
{
[self updateChangeCount: NSChangeDone];
}
- (void) undo: (id)sender
{
[[self undoManager] undo];
}
- (void) redo: (id)sender
{
[[self undoManager] redo];
}
@end