mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 04:57:03 +00:00
In some places the objj_msgSend function is called directly. Most of the times the old slower version is called. This commit will use the never faster version instead.
930 lines
27 KiB
Plaintext
930 lines
27 KiB
Plaintext
/*
|
|
* CPDocument.j
|
|
* AppKit
|
|
*
|
|
* Created by Francisco Tolmasky.
|
|
* Copyright 2008, 280 North, Inc.
|
|
*
|
|
* 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.1 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; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
@import <Foundation/CPString.j>
|
|
@import <Foundation/CPArray.j>
|
|
|
|
@import "CPAlert.j"
|
|
@import "CPResponder.j"
|
|
@import "CPSavePanel.j"
|
|
@import "CPViewController.j"
|
|
@import "CPWindowController.j"
|
|
|
|
@class CPDocumentController
|
|
|
|
@global CPApp
|
|
|
|
|
|
/*
|
|
@global
|
|
@group CPSaveOperationType
|
|
*/
|
|
CPSaveOperation = 0;
|
|
/*
|
|
@global
|
|
@group CPSaveOperationType
|
|
*/
|
|
CPSaveAsOperation = 1;
|
|
/*
|
|
@global
|
|
@group CPSaveOperationType
|
|
*/
|
|
CPSaveToOperation = 2;
|
|
/*
|
|
@global
|
|
@group CPSaveOperationType
|
|
*/
|
|
CPAutosaveOperation = 3;
|
|
|
|
/*
|
|
@global
|
|
@group CPDocumentChangeType
|
|
*/
|
|
CPChangeDone = 0;
|
|
/*
|
|
@global
|
|
@group CPDocumentChangeType
|
|
*/
|
|
CPChangeUndone = 1;
|
|
/*
|
|
@global
|
|
@group CPDocumentChangeType
|
|
*/
|
|
CPChangeCleared = 2;
|
|
/*
|
|
@global
|
|
@group CPDocumentChangeType
|
|
*/
|
|
CPChangeReadOtherContents = 3;
|
|
/*
|
|
@global
|
|
@group CPDocumentChangeType
|
|
*/
|
|
CPChangeAutosaved = 4;
|
|
|
|
CPDocumentWillSaveNotification = @"CPDocumentWillSaveNotification";
|
|
CPDocumentDidSaveNotification = @"CPDocumentDidSaveNotification";
|
|
CPDocumentDidFailToSaveNotification = @"CPDocumentDidFailToSaveNotification";
|
|
|
|
var CPDocumentUntitledCount = 0;
|
|
|
|
/*!
|
|
@ingroup appkit
|
|
@class CPDocument
|
|
|
|
CPDocument is used to represent a document/file in a Cappuccino application.
|
|
In a document-based application, generally multiple documents are open simultaneously
|
|
(multiple text documents, slide presentations, spreadsheets, etc.), and multiple
|
|
CPDocuments should be used to represent this.
|
|
*/
|
|
@implementation CPDocument : CPResponder
|
|
{
|
|
CPWindow _window; // For outlet purposes.
|
|
CPView _view; // For outlet purposes
|
|
CPDictionary _viewControllersForWindowControllers;
|
|
|
|
CPURL _fileURL;
|
|
CPString _fileType;
|
|
CPArray _windowControllers;
|
|
unsigned _untitledDocumentIndex;
|
|
|
|
BOOL _hasUndoManager;
|
|
CPUndoManager _undoManager;
|
|
|
|
int _changeCount;
|
|
|
|
CPURLConnection _readConnection;
|
|
CPURLRequest _writeRequest;
|
|
|
|
CPAlert _canCloseAlert;
|
|
}
|
|
|
|
/*!
|
|
Initializes an empty document.
|
|
@return the initialized document
|
|
*/
|
|
- (id)init
|
|
{
|
|
self = [super init];
|
|
|
|
if (self)
|
|
{
|
|
_windowControllers = [];
|
|
_viewControllersForWindowControllers = @{};
|
|
|
|
_hasUndoManager = YES;
|
|
_changeCount = 0;
|
|
|
|
[self setNextResponder:CPApp];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
/*!
|
|
Initializes the document with a specific data type.
|
|
@param aType the type of document to initialize
|
|
@param anError not used
|
|
@return the initialized document
|
|
*/
|
|
- (id)initWithType:(CPString)aType error:(/*{*/CPError/*}*/)anError
|
|
{
|
|
self = [self init];
|
|
|
|
if (self)
|
|
[self setFileType:aType];
|
|
|
|
return self;
|
|
}
|
|
|
|
/*!
|
|
Initializes a document of a specific type located at a URL. Notifies
|
|
the provided delegate after initialization.
|
|
@param anAbsoluteURL the url of the document content
|
|
@param aType the type of document located at the URL
|
|
@param aDelegate the delegate to notify
|
|
@param aDidReadSelector the selector used to notify the delegate
|
|
@param aContextInfo context information passed to the delegate
|
|
after initialization
|
|
@return the initialized document
|
|
*/
|
|
- (id)initWithContentsOfURL:(CPURL)anAbsoluteURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aDidReadSelector contextInfo:(id)aContextInfo
|
|
{
|
|
self = [self init];
|
|
|
|
if (self)
|
|
{
|
|
[self setFileURL:anAbsoluteURL];
|
|
[self setFileType:aType];
|
|
|
|
[self readFromURL:anAbsoluteURL ofType:aType delegate:aDelegate didReadSelector:aDidReadSelector contextInfo:aContextInfo];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
/*!
|
|
Initializes the document from a URL.
|
|
@param anAbsoluteURL the document location
|
|
@param absoluteContentsURL the location of the document's contents
|
|
@param aType the type of the contents
|
|
@param aDelegate this object will receive a callback after the document's contents are loaded
|
|
@param aDidReadSelector the message selector that will be sent to \c aDelegate
|
|
@param aContextInfo passed as the argument to the message sent to the \c aDelegate
|
|
@return the initialized document
|
|
*/
|
|
- (id)initForURL:(CPURL)anAbsoluteURL withContentsOfURL:(CPURL)absoluteContentsURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aDidReadSelector contextInfo:(id)aContextInfo
|
|
{
|
|
self = [self init];
|
|
|
|
if (self)
|
|
{
|
|
[self setFileURL:anAbsoluteURL];
|
|
[self setFileType:aType];
|
|
|
|
[self readFromURL:absoluteContentsURL ofType:aType delegate:aDelegate didReadSelector:aDidReadSelector contextInfo:aContextInfo];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
/*!
|
|
Returns the receiver's data in a specified type. The default implementation just
|
|
throws an exception.
|
|
@param aType the format of the data
|
|
@param anError not used
|
|
@throws CPUnsupportedMethodException if this method hasn't been overridden by the subclass
|
|
@return the document data
|
|
*/
|
|
- (CPData)dataOfType:(CPString)aType error:(/*{*/CPError/*}*/)anError
|
|
{
|
|
[CPException raise:CPUnsupportedMethodException
|
|
reason:"dataOfType:error: must be overridden by the document subclass."];
|
|
}
|
|
|
|
/*!
|
|
Sets the content of the document by reading the provided
|
|
data. The default implementation just throws an exception.
|
|
@param aData the document's data
|
|
@param aType the document type
|
|
@param anError not used
|
|
@throws CPUnsupportedMethodException if this method hasn't been
|
|
overridden by the subclass
|
|
*/
|
|
- (void)readFromData:(CPData)aData ofType:(CPString)aType error:(CPError)anError
|
|
{
|
|
[CPException raise:CPUnsupportedMethodException
|
|
reason:"readFromData:ofType: must be overridden by the document subclass."];
|
|
}
|
|
|
|
- (void)viewControllerWillLoadCib:(CPViewController)aViewController
|
|
{
|
|
}
|
|
|
|
- (void)viewControllerDidLoadCib:(CPViewController)aViewController
|
|
{
|
|
}
|
|
|
|
// Creating and managing window controllers
|
|
/*!
|
|
Creates the window controller for this document.
|
|
*/
|
|
- (void)makeWindowControllers
|
|
{
|
|
[self makeViewAndWindowControllers];
|
|
}
|
|
|
|
- (void)makeViewAndWindowControllers
|
|
{
|
|
var viewCibName = [self viewCibName],
|
|
windowCibName = [self windowCibName],
|
|
viewController = nil,
|
|
windowController = nil;
|
|
|
|
// Create our view controller if we have a cib for it.
|
|
if ([viewCibName length])
|
|
viewController = [[CPViewController alloc] initWithCibName:viewCibName bundle:nil owner:self];
|
|
|
|
// From a cib if we have one.
|
|
if ([windowCibName length])
|
|
windowController = [[CPWindowController alloc] initWithWindowCibName:windowCibName owner:self];
|
|
|
|
// If not you get a standard window capable of displaying multiple documents and view
|
|
else if (viewController)
|
|
{
|
|
var view = [viewController view],
|
|
viewFrame = [view frame];
|
|
|
|
viewFrame.origin = CGPointMake(50, 50);
|
|
|
|
var theWindow = [[CPWindow alloc] initWithContentRect:viewFrame styleMask:CPTitledWindowMask | CPClosableWindowMask | CPMiniaturizableWindowMask | CPResizableWindowMask];
|
|
|
|
windowController = [[CPWindowController alloc] initWithWindow:theWindow];
|
|
}
|
|
|
|
if (windowController && viewController)
|
|
[windowController setSupportsMultipleDocuments:YES];
|
|
|
|
if (windowController)
|
|
[self addWindowController:windowController];
|
|
|
|
if (viewController)
|
|
[self addViewController:viewController forWindowController:windowController];
|
|
}
|
|
|
|
/*!
|
|
Returns the document's window controllers
|
|
*/
|
|
- (CPArray)windowControllers
|
|
{
|
|
return _windowControllers;
|
|
}
|
|
|
|
/*!
|
|
Add a controller to the document's list of controllers. This should
|
|
be called after making a new window controller.
|
|
@param aWindowController the controller to add
|
|
*/
|
|
- (void)addWindowController:(CPWindowController)aWindowController
|
|
{
|
|
[_windowControllers addObject:aWindowController];
|
|
|
|
if ([aWindowController document] !== self)
|
|
[aWindowController setDocument:self];
|
|
}
|
|
|
|
/*!
|
|
Remove a controller to the document's list of controllers. This should
|
|
be called after closing the controller's window.
|
|
@param aWindowController the controller to remove
|
|
*/
|
|
- (void)removeWindowController:(CPWindowController)aWindowController
|
|
{
|
|
if (aWindowController)
|
|
[_windowControllers removeObject:aWindowController];
|
|
|
|
if ([aWindowController document] === self)
|
|
[aWindowController setDocument:nil];
|
|
}
|
|
|
|
- (CPView)view
|
|
{
|
|
return _view;
|
|
}
|
|
|
|
- (CPArray)viewControllers
|
|
{
|
|
return [_viewControllersForWindowControllers allValues];
|
|
}
|
|
|
|
- (void)addViewController:(CPViewController)aViewController forWindowController:(CPWindowController)aWindowController
|
|
{
|
|
// FIXME: exception if we don't own the window controller?
|
|
[_viewControllersForWindowControllers setObject:aViewController forKey:[aWindowController UID]];
|
|
|
|
if ([aWindowController document] === self)
|
|
[aWindowController setViewController:aViewController];
|
|
}
|
|
|
|
- (void)removeViewController:(CPViewController)aViewController
|
|
{
|
|
[_viewControllersForWindowControllers removeObject:aViewController];
|
|
}
|
|
|
|
- (CPViewController)viewControllerForWindowController:(CPWindowController)aWindowController
|
|
{
|
|
return [_viewControllersForWindowControllers objectForKey:[aWindowController UID]];
|
|
}
|
|
|
|
// Managing Document Windows
|
|
/*!
|
|
Shows all the document's windows.
|
|
*/
|
|
- (void)showWindows
|
|
{
|
|
[_windowControllers makeObjectsPerformSelector:@selector(setDocument:) withObject:self];
|
|
[_windowControllers makeObjectsPerformSelector:@selector(showWindow:) withObject:self];
|
|
}
|
|
|
|
/*!
|
|
Returns the name of the document as displayed in the title bar.
|
|
*/
|
|
- (CPString)displayName
|
|
{
|
|
if (_fileURL)
|
|
return [_fileURL lastPathComponent];
|
|
|
|
if (!_untitledDocumentIndex)
|
|
_untitledDocumentIndex = ++CPDocumentUntitledCount;
|
|
|
|
if (_untitledDocumentIndex == 1)
|
|
return @"Untitled";
|
|
|
|
return @"Untitled " + _untitledDocumentIndex;
|
|
}
|
|
|
|
- (CPString)viewCibName
|
|
{
|
|
return nil;
|
|
}
|
|
|
|
/*!
|
|
Returns the document's Cib name
|
|
*/
|
|
- (CPString)windowCibName
|
|
{
|
|
return nil;
|
|
}
|
|
|
|
/*!
|
|
Called after \c aWindowController loads the document's Nib file.
|
|
@param aWindowController the controller that loaded the Nib file
|
|
*/
|
|
- (void)windowControllerDidLoadCib:(CPWindowController)aWindowController
|
|
{
|
|
}
|
|
|
|
/*!
|
|
Called before \c aWindowController will load the document's Nib file.
|
|
@param aWindowController the controller that will load the Nib file
|
|
*/
|
|
- (void)windowControllerWillLoadCib:(CPWindowController)aWindowController
|
|
{
|
|
}
|
|
|
|
// Reading from and Writing to URLs
|
|
/*!
|
|
Set the document's data from a URL. Notifies the provided delegate afterwards.
|
|
@param anAbsoluteURL the URL to the document's content
|
|
@param aType the document type
|
|
@param aDelegate delegate to notify after reading the data
|
|
@param aDidReadSelector message that will be sent to the delegate
|
|
@param aContextInfo context information that gets sent to the delegate
|
|
*/
|
|
- (void)readFromURL:(CPURL)anAbsoluteURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aDidReadSelector contextInfo:(id)aContextInfo
|
|
{
|
|
[_readConnection cancel];
|
|
|
|
// FIXME: Oh man is this every looking for trouble, we need to handle login at the Cappuccino level, with HTTP Errors.
|
|
_readConnection = [CPURLConnection connectionWithRequest:[CPURLRequest requestWithURL:anAbsoluteURL] delegate:self];
|
|
|
|
_readConnection.session = _CPReadSessionMake(aType, aDelegate, aDidReadSelector, aContextInfo);
|
|
}
|
|
|
|
/*!
|
|
Returns the path to the document's file.
|
|
*/
|
|
- (CPURL)fileURL
|
|
{
|
|
return _fileURL;
|
|
}
|
|
|
|
/*!
|
|
Sets the path to the document's file.
|
|
@param aFileURL the path to the document's file
|
|
*/
|
|
- (void)setFileURL:(CPURL)aFileURL
|
|
{
|
|
if (_fileURL === aFileURL)
|
|
return;
|
|
|
|
_fileURL = aFileURL;
|
|
|
|
[_windowControllers makeObjectsPerformSelector:@selector(synchronizeWindowTitleWithDocumentName)];
|
|
}
|
|
|
|
/*!
|
|
Saves the document to the specified URL. Notifies the provided delegate
|
|
with the provided selector and context info afterwards.
|
|
@param anAbsoluteURL the url to write the document data to
|
|
@param aTypeName the document type
|
|
@param aSaveOperation the type of save operation
|
|
@param aDelegate the delegate to notify after saving
|
|
@param aDidSaveSelector the selector to send the delegate
|
|
@param aContextInfo context info that gets passed to the delegate
|
|
*/
|
|
- (void)saveToURL:(CPURL)anAbsoluteURL ofType:(CPString)aTypeName forSaveOperation:(CPSaveOperationType)aSaveOperation delegate:(id)aDelegate didSaveSelector:(SEL)aDidSaveSelector contextInfo:(id)aContextInfo
|
|
{
|
|
var data = [self dataOfType:[self fileType] error:nil],
|
|
oldChangeCount = _changeCount;
|
|
|
|
_writeRequest = [CPURLRequest requestWithURL:anAbsoluteURL];
|
|
|
|
// FIXME: THIS IS WRONG! We need a way to decide
|
|
if ([CPPlatform isBrowser])
|
|
[_writeRequest setHTTPMethod:@"POST"];
|
|
else
|
|
[_writeRequest setHTTPMethod:@"PUT"];
|
|
|
|
[_writeRequest setHTTPBody:[data rawString]];
|
|
|
|
[_writeRequest setValue:@"close" forHTTPHeaderField:@"Connection"];
|
|
|
|
if (aSaveOperation === CPSaveOperation)
|
|
[_writeRequest setValue:@"true" forHTTPHeaderField:@"x-cappuccino-overwrite"];
|
|
|
|
if (aSaveOperation !== CPSaveToOperation)
|
|
[self updateChangeCount:CPChangeCleared];
|
|
|
|
// FIXME: Oh man is this every looking for trouble, we need to handle login at the Cappuccino level, with HTTP Errors.
|
|
var connection = [CPURLConnection connectionWithRequest:_writeRequest delegate:self];
|
|
|
|
connection.session = _CPSaveSessionMake(anAbsoluteURL, aSaveOperation, oldChangeCount, aDelegate, aDidSaveSelector, aContextInfo, connection);
|
|
}
|
|
|
|
/*
|
|
Implemented as a delegate method for CPURLConnection
|
|
@ignore
|
|
*/
|
|
- (void)connection:(CPURLConnection)aConnection didReceiveResponse:(CPURLResponse)aResponse
|
|
{
|
|
// If we got this far and it wasn't an HTTP request, then everything is fine.
|
|
if (![aResponse isKindOfClass:[CPHTTPURLResponse class]])
|
|
return;
|
|
|
|
var statusCode = [aResponse statusCode];
|
|
|
|
// Nothing to do if everything is hunky dory.
|
|
if (statusCode === 200)
|
|
return;
|
|
|
|
var session = aConnection.session;
|
|
|
|
if (aConnection == _readConnection)
|
|
{
|
|
[aConnection cancel];
|
|
|
|
alert("There was an error retrieving the document.");
|
|
|
|
var theDelegate = session.delegate;
|
|
|
|
theDelegate.isa.objj_msgSend3(theDelegate, session.didReadSelector, self, NO, session.contextInfo);
|
|
}
|
|
else
|
|
{
|
|
// 409: Conflict, in Cappuccino, overwrite protection for documents.
|
|
if (statusCode == 409)
|
|
{
|
|
[aConnection cancel];
|
|
|
|
if (confirm("There already exists a file with that name, would you like to overwrite it?"))
|
|
{
|
|
[_writeRequest setValue:@"true" forHTTPHeaderField:@"x-cappuccino-overwrite"];
|
|
|
|
[aConnection start];
|
|
}
|
|
else
|
|
{
|
|
if (session.saveOperation != CPSaveToOperation)
|
|
{
|
|
_changeCount += session.changeCount;
|
|
[_windowControllers makeObjectsPerformSelector:@selector(setDocumentEdited:) withObject:[self isDocumentEdited]];
|
|
}
|
|
|
|
_writeRequest = nil;
|
|
|
|
var theDelegate = session.delegate;
|
|
|
|
theDelegate.isa.objj_msgSend3(theDelegate, session.didSaveSelector, self, NO, session.contextInfo);
|
|
[self _sendDocumentSavedNotification:NO];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
Implemented as a delegate method for CPURLConnection
|
|
@ignore
|
|
*/
|
|
- (void)connection:(CPURLConnection)aConnection didReceiveData:(CPString)aData
|
|
{
|
|
var session = aConnection.session,
|
|
theDelegate = session.delegate;
|
|
|
|
// READ
|
|
if (aConnection == _readConnection)
|
|
{
|
|
[self readFromData:[CPData dataWithRawString:aData] ofType:session.fileType error:nil];
|
|
|
|
theDelegate.isa.objj_msgSend3(theDelegate, session.didReadSelector, self, YES, session.contextInfo);
|
|
}
|
|
else
|
|
{
|
|
if (session.saveOperation != CPSaveToOperation)
|
|
[self setFileURL:session.absoluteURL];
|
|
|
|
_writeRequest = nil;
|
|
|
|
theDelegate.isa.objj_msgSend3(theDelegate, session.didSaveSelector, self, YES, session.contextInfo);
|
|
[self _sendDocumentSavedNotification:YES];
|
|
}
|
|
}
|
|
|
|
/*
|
|
Implemented as a delegate method for CPURLConnection
|
|
@ignore
|
|
*/
|
|
- (void)connection:(CPURLConnection)aConnection didFailWithError:(CPError)anError
|
|
{
|
|
var session = aConnection.session,
|
|
theDelegate = session.delegate;
|
|
|
|
if (_readConnection == aConnection)
|
|
theDelegate.isa.objj_msgSend3(theDelegate, session.didReadSelector, self, NO, session.contextInfo);
|
|
|
|
else
|
|
{
|
|
if (session.saveOperation != CPSaveToOperation)
|
|
{
|
|
_changeCount += session.changeCount;
|
|
[_windowControllers makeObjectsPerformSelector:@selector(setDocumentEdited:) withObject:[self isDocumentEdited]];
|
|
}
|
|
|
|
_writeRequest = nil;
|
|
|
|
alert("There was an error saving the document.");
|
|
|
|
theDelegate.isa.objj_msgSend3(theDelegate, session.didSaveSelector, self, NO, session.contextInfo);
|
|
[self _sendDocumentSavedNotification:NO];
|
|
}
|
|
}
|
|
|
|
/*
|
|
Implemented as a delegate method for CPURLConnection
|
|
@ignore
|
|
*/
|
|
- (void)connectionDidFinishLoading:(CPURLConnection)aConnection
|
|
{
|
|
if (_readConnection == aConnection)
|
|
_readConnection = nil;
|
|
}
|
|
|
|
// Managing Document Status
|
|
/*!
|
|
Returns \c YES if there are any unsaved changes.
|
|
*/
|
|
- (BOOL)isDocumentEdited
|
|
{
|
|
return _changeCount != 0;
|
|
}
|
|
|
|
/*!
|
|
Updates the number of unsaved changes to the document.
|
|
@param aChangeType a new document change to apply
|
|
*/
|
|
- (void)updateChangeCount:(CPDocumentChangeType)aChangeType
|
|
{
|
|
if (aChangeType == CPChangeDone)
|
|
++_changeCount;
|
|
else if (aChangeType == CPChangeUndone)
|
|
--_changeCount;
|
|
else if (aChangeType == CPChangeCleared)
|
|
_changeCount = 0;
|
|
/*else if (aChangeType == CPCHangeReadOtherContents)
|
|
|
|
else if (aChangeType == CPChangeAutosaved)*/
|
|
|
|
[_windowControllers makeObjectsPerformSelector:@selector(setDocumentEdited:) withObject:[self isDocumentEdited]];
|
|
}
|
|
|
|
// Managing File Types
|
|
/*!
|
|
Sets the document's file type
|
|
@param aType the document's type
|
|
*/
|
|
- (void)setFileType:(CPString)aType
|
|
{
|
|
_fileType = aType;
|
|
}
|
|
|
|
/*!
|
|
Returns the document's file type
|
|
*/
|
|
- (CPString)fileType
|
|
{
|
|
return _fileType;
|
|
}
|
|
|
|
// Working with Undo Manager
|
|
/*!
|
|
Returns \c YES if the document has a
|
|
CPUndoManager.
|
|
*/
|
|
- (BOOL)hasUndoManager
|
|
{
|
|
return _hasUndoManager;
|
|
}
|
|
|
|
/*!
|
|
Sets whether the document should have a CPUndoManager.
|
|
@param aFlag \c YES makes the document have an undo manager
|
|
*/
|
|
- (void)setHasUndoManager:(BOOL)aFlag
|
|
{
|
|
if (_hasUndoManager == aFlag)
|
|
return;
|
|
|
|
_hasUndoManager = aFlag;
|
|
|
|
if (!_hasUndoManager)
|
|
[self setUndoManager:nil];
|
|
}
|
|
|
|
/* @ignore */
|
|
- (void)_undoManagerWillCloseGroup:(CPNotification)aNotification
|
|
{
|
|
var undoManager = [aNotification object];
|
|
|
|
if ([undoManager isUndoing] || [undoManager isRedoing])
|
|
return;
|
|
|
|
[self updateChangeCount:CPChangeDone];
|
|
}
|
|
|
|
/* @ignore */
|
|
- (void)_undoManagerDidUndoChange:(CPNotification)aNotification
|
|
{
|
|
[self updateChangeCount:CPChangeUndone];
|
|
}
|
|
|
|
/* @ignore */
|
|
- (void)_undoManagerDidRedoChange:(CPNotification)aNotification
|
|
{
|
|
[self updateChangeCount:CPChangeDone];
|
|
}
|
|
|
|
/*
|
|
Sets the document's undo manager. This method will add the
|
|
undo manager as an observer to the notification center.
|
|
@param anUndoManager the new undo manager for the document
|
|
*/
|
|
- (void)setUndoManager:(CPUndoManager)anUndoManager
|
|
{
|
|
var defaultCenter = [CPNotificationCenter defaultCenter];
|
|
|
|
if (_undoManager)
|
|
{
|
|
[defaultCenter removeObserver:self
|
|
name:CPUndoManagerDidUndoChangeNotification
|
|
object:_undoManager];
|
|
|
|
[defaultCenter removeObserver:self
|
|
name:CPUndoManagerDidRedoChangeNotification
|
|
object:_undoManager];
|
|
|
|
[defaultCenter removeObserver:self
|
|
name:CPUndoManagerWillCloseUndoGroupNotification
|
|
object:_undoManager];
|
|
}
|
|
|
|
_undoManager = anUndoManager;
|
|
|
|
if (_undoManager)
|
|
{
|
|
|
|
[defaultCenter addObserver:self
|
|
selector:@selector(_undoManagerDidUndoChange:)
|
|
name:CPUndoManagerDidUndoChangeNotification
|
|
object:_undoManager];
|
|
|
|
[defaultCenter addObserver:self
|
|
selector:@selector(_undoManagerDidRedoChange:)
|
|
name:CPUndoManagerDidRedoChangeNotification
|
|
object:_undoManager];
|
|
|
|
[defaultCenter addObserver:self
|
|
selector:@selector(_undoManagerWillCloseGroup:)
|
|
name:CPUndoManagerWillCloseUndoGroupNotification
|
|
object:_undoManager];
|
|
}
|
|
}
|
|
|
|
/*!
|
|
Returns the document's undo manager. If the document
|
|
should have one, but the manager is \c nil, it
|
|
will be created and then returned.
|
|
@return the document's undo manager
|
|
*/
|
|
- (CPUndoManager)undoManager
|
|
{
|
|
if (_hasUndoManager && !_undoManager)
|
|
[self setUndoManager:[[CPUndoManager alloc] init]];
|
|
|
|
return _undoManager;
|
|
}
|
|
|
|
/*
|
|
Implemented as a delegate of a CPWindow
|
|
@ignore
|
|
*/
|
|
- (CPUndoManager)windowWillReturnUndoManager:(CPWindow)aWindow
|
|
{
|
|
return [self undoManager];
|
|
}
|
|
|
|
// Handling User Actions
|
|
/*!
|
|
Saves the document. If the document does not
|
|
have a file path to save to (\c fileURL)
|
|
then \c -saveDocumentAs: will be called.
|
|
@param aSender the object requesting the save
|
|
*/
|
|
- (void)saveDocument:(id)aSender
|
|
{
|
|
[self saveDocumentWithDelegate:nil didSaveSelector:nil contextInfo:nil];
|
|
}
|
|
|
|
- (void)saveDocumentWithDelegate:(id)delegate didSaveSelector:(SEL)didSaveSelector contextInfo:(Object)contextInfo
|
|
{
|
|
if (_fileURL)
|
|
{
|
|
[[CPNotificationCenter defaultCenter]
|
|
postNotificationName:CPDocumentWillSaveNotification
|
|
object:self];
|
|
|
|
[self saveToURL:_fileURL ofType:[self fileType] forSaveOperation:CPSaveOperation delegate:delegate didSaveSelector:didSaveSelector contextInfo:contextInfo];
|
|
}
|
|
else
|
|
[self _saveDocumentAsWithDelegate:delegate didSaveSelector:didSaveSelector contextInfo:contextInfo];
|
|
}
|
|
|
|
/*!
|
|
Saves the document to a user specified path.
|
|
@param aSender the object requesting the operation
|
|
*/
|
|
- (void)saveDocumentAs:(id)aSender
|
|
{
|
|
[self _saveDocumentAsWithDelegate:nil didSaveSelector:nil contextInfo:nil];
|
|
}
|
|
|
|
- (void)_saveDocumentAsWithDelegate:(id)delegate didSaveSelector:(SEL)didSaveSelector contextInfo:(Object)contextInfo
|
|
{
|
|
var savePanel = [CPSavePanel savePanel],
|
|
response = [savePanel runModal];
|
|
|
|
if (!response)
|
|
return;
|
|
|
|
var saveURL = [savePanel URL];
|
|
|
|
[[CPNotificationCenter defaultCenter]
|
|
postNotificationName:CPDocumentWillSaveNotification
|
|
object:self];
|
|
|
|
[self saveToURL:saveURL ofType:[self fileType] forSaveOperation:CPSaveAsOperation delegate:delegate didSaveSelector:didSaveSelector contextInfo:contextInfo];
|
|
}
|
|
|
|
/*
|
|
@ignore
|
|
*/
|
|
- (void)_sendDocumentSavedNotification:(BOOL)didSave
|
|
{
|
|
if (didSave)
|
|
[[CPNotificationCenter defaultCenter]
|
|
postNotificationName:CPDocumentDidSaveNotification
|
|
object:self];
|
|
else
|
|
[[CPNotificationCenter defaultCenter]
|
|
postNotificationName:CPDocumentDidFailToSaveNotification
|
|
object:self];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation CPDocument (ClosingDocuments)
|
|
|
|
- (void)close
|
|
{
|
|
[_windowControllers makeObjectsPerformSelector:@selector(removeDocumentAndCloseIfNecessary:) withObject:self];
|
|
[[CPDocumentController sharedDocumentController] removeDocument:self];
|
|
}
|
|
|
|
- (void)shouldCloseWindowController:(CPWindowController)controller delegate:(id)delegate shouldCloseSelector:(SEL)selector contextInfo:(Object)info
|
|
{
|
|
if ([controller shouldCloseDocument] || ([_windowControllers count] < 2 && [_windowControllers indexOfObject:controller] !== CPNotFound))
|
|
[self canCloseDocumentWithDelegate:self shouldCloseSelector:@selector(_document:shouldClose:context:) contextInfo:{delegate:delegate, selector:selector, context:info}];
|
|
|
|
else if ([delegate respondsToSelector:selector])
|
|
delegate.isa.objj_msgSend3(delegate, selector, self, YES, info);
|
|
}
|
|
|
|
- (void)_document:(CPDocument)aDocument shouldClose:(BOOL)shouldClose context:(Object)context
|
|
{
|
|
var theDelegate = context.delegate;
|
|
|
|
if (aDocument === self && shouldClose)
|
|
[self close];
|
|
|
|
if (theDelegate != null)
|
|
theDelegate.isa.objj_msgSend3(theDelegate, context.selector, aDocument, shouldClose, context.context);
|
|
}
|
|
|
|
- (void)canCloseDocumentWithDelegate:(id)aDelegate shouldCloseSelector:(SEL)aSelector contextInfo:(Object)context
|
|
{
|
|
if (![self isDocumentEdited])
|
|
return [aDelegate respondsToSelector:aSelector] && aDelegate.isa.objj_msgSend3(aDelegate, aSelector, self, YES, context);
|
|
|
|
_canCloseAlert = [[CPAlert alloc] init];
|
|
|
|
[_canCloseAlert setDelegate:self];
|
|
[_canCloseAlert setAlertStyle:CPWarningAlertStyle];
|
|
[_canCloseAlert setTitle:@"Unsaved Document"];
|
|
[_canCloseAlert setMessageText:@"Do you want to save the changes you've made to the document \"" + ([self displayName] || [self fileName]) + "\"?"];
|
|
|
|
[_canCloseAlert addButtonWithTitle:@"Save"];
|
|
[_canCloseAlert addButtonWithTitle:@"Cancel"];
|
|
[_canCloseAlert addButtonWithTitle:@"Don't Save"];
|
|
|
|
_canCloseAlert._context = {delegate:aDelegate, selector:aSelector, context:context};
|
|
|
|
[_canCloseAlert runModal];
|
|
}
|
|
|
|
- (void)alertDidEnd:(CPAlert)alert returnCode:(int)returnCode
|
|
{
|
|
if (alert !== _canCloseAlert)
|
|
return;
|
|
|
|
var delegate = alert._context.delegate,
|
|
selector = alert._context.selector,
|
|
context = alert._context.context;
|
|
|
|
if (returnCode === 0)
|
|
[self saveDocumentWithDelegate:delegate didSaveSelector:selector contextInfo:context];
|
|
else if (delegate != null)
|
|
delegate.isa.objj_msgSend3(delegate, selector, self, returnCode === 2, context);
|
|
|
|
_canCloseAlert = nil;
|
|
}
|
|
|
|
@end
|
|
|
|
var _CPReadSessionMake = function(aType, aDelegate, aDidReadSelector, aContextInfo)
|
|
{
|
|
return { fileType:aType, delegate:aDelegate, didReadSelector:aDidReadSelector, contextInfo:aContextInfo };
|
|
};
|
|
|
|
var _CPSaveSessionMake = function(anAbsoluteURL, aSaveOperation, aChangeCount, aDelegate, aDidSaveSelector, aContextInfo, aConnection)
|
|
{
|
|
return { absoluteURL:anAbsoluteURL, saveOperation:aSaveOperation, changeCount:aChangeCount, delegate:aDelegate, didSaveSelector:aDidSaveSelector, contextInfo:aContextInfo, connection:aConnection };
|
|
};
|