00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023 @import <Foundation/CPString.j>
00024 @import <Foundation/CPArray.j>
00025
00026 @import "CPResponder.j"
00027 @import "CPSavePanel.j"
00028 @import "CPViewController.j"
00029 @import "CPWindowController.j"
00030
00031
00032
00033
00034
00035
00036 CPSaveOperation = 0;
00037
00038
00039
00040
00041 CPSaveAsOperation = 1;
00042
00043
00044
00045
00046 CPSaveToOperation = 2;
00047
00048
00049
00050
00051 CPAutosaveOperation = 3;
00052
00053
00054
00055
00056
00057 CPChangeDone = 0;
00058
00059
00060
00061
00062 CPChangeUndone = 1;
00063
00064
00065
00066
00067 CPChangeCleared = 2;
00068
00069
00070
00071
00072 CPChangeReadOtherContents = 3;
00073
00074
00075
00076
00077 CPChangeAutosaved = 4;
00078
00079 CPDocumentWillSaveNotification = @"CPDocumentWillSaveNotification";
00080 CPDocumentDidSaveNotification = @"CPDocumentDidSaveNotification";
00081 CPDocumentDidFailToSaveNotification = @"CPDocumentDidFailToSaveNotification";
00082
00083 var CPDocumentUntitledCount = 0;
00084
00094 @implementation CPDocument : CPResponder
00095 {
00096 CPWindow _window;
00097 CPView _view;
00098 CPDictionary _viewControllersForWindowControllers;
00099
00100 CPURL _fileURL;
00101 CPString _fileType;
00102 CPArray _windowControllers;
00103 unsigned _untitledDocumentIndex;
00104
00105 BOOL _hasUndoManager;
00106 CPUndoManager _undoManager;
00107
00108 int _changeCount;
00109
00110 CPURLConnection _readConnection;
00111 CPURLRequest _writeRequest;
00112
00113 CPAlert _canCloseAlert;
00114 }
00115
00120 - (id)init
00121 {
00122 self = [super init];
00123
00124 if (self)
00125 {
00126 _windowControllers = [];
00127 _viewControllersForWindowControllers = [CPDictionary dictionary];
00128
00129 _hasUndoManager = YES;
00130 _changeCount = 0;
00131
00132 [self setNextResponder:CPApp];
00133 }
00134
00135 return self;
00136 }
00137
00144 - (id)initWithType:(CPString)aType error:({CPError})anError
00145 {
00146 self = [self init];
00147
00148 if (self)
00149 [self setFileType:aType];
00150
00151 return self;
00152 }
00153
00165 - (id)initWithContentsOfURL:(CPURL)anAbsoluteURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aDidReadSelector contextInfo:(id)aContextInfo
00166 {
00167 self = [self init];
00168
00169 if (self)
00170 {
00171 [self setFileURL:anAbsoluteURL];
00172 [self setFileType:aType];
00173
00174 [self readFromURL:anAbsoluteURL ofType:aType delegate:aDelegate didReadSelector:aDidReadSelector contextInfo:aContextInfo];
00175 }
00176
00177 return self;
00178 }
00179
00190 - (id)initForURL:(CPURL)anAbsoluteURL withContentsOfURL:(CPURL)absoluteContentsURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aDidReadSelector contextInfo:(id)aContextInfo
00191 {
00192 self = [self init];
00193
00194 if (self)
00195 {
00196 [self setFileURL:anAbsoluteURL];
00197 [self setFileType:aType];
00198
00199 [self readFromURL:absoluteContentsURL ofType:aType delegate:aDelegate didReadSelector:aDidReadSelector contextInfo:aContextInfo];
00200 }
00201
00202 return self;
00203 }
00204
00213 - (CPData)dataOfType:(CPString)aType error:({CPError})anError
00214 {
00215 [CPException raise:CPUnsupportedMethodException
00216 reason:"dataOfType:error: must be overridden by the document subclass."];
00217 }
00218
00228 - (void)readFromData:(CPData)aData ofType:(CPString)aType error:(CPError)anError
00229 {
00230 [CPException raise:CPUnsupportedMethodException
00231 reason:"readFromData:ofType: must be overridden by the document subclass."];
00232 }
00233
00234 - (void)viewControllerWillLoadCib:(CPViewController)aViewController
00235 {
00236 }
00237
00238 - (void)viewControllerDidLoadCib:(CPViewController)aViewController
00239 {
00240 }
00241
00242 - (CPWindowController)firstEligibleExistingWindowController
00243 {
00244 return nil;
00245 }
00246
00247
00251 - (void)makeWindowControllers
00252 {
00253 [self makeViewAndWindowControllers];
00254 }
00255
00256 - (void)makeViewAndWindowControllers
00257 {
00258 var viewCibName = [self viewCibName],
00259 viewController = nil,
00260 windowController = nil;
00261
00262
00263 if ([viewCibName length])
00264 viewController = [[CPViewController alloc] initWithCibName:viewCibName bundle:nil owner:self];
00265
00266
00267 if (viewController)
00268 windowController = [self firstEligibleExistingWindowController];
00269
00270
00271 if (!windowController)
00272 {
00273 var windowCibName = [self windowCibName];
00274
00275
00276 if ([windowCibName length])
00277 windowController = [[CPWindowController alloc] initWithWindowCibName:windowCibName owner:self];
00278
00279
00280 else if (viewController)
00281 {
00282 var view = [viewController view],
00283 viewFrame = [view frame];
00284
00285 viewFrame.origin = CGPointMake(50, 50);
00286
00287 var theWindow = [[CPWindow alloc] initWithContentRect:viewFrame styleMask:CPTitledWindowMask | CPClosableWindowMask | CPMiniaturizableWindowMask | CPResizableWindowMask];
00288
00289 windowController = [[CPWindowController alloc] initWithWindow:theWindow];
00290 }
00291 }
00292
00293 if (windowController && viewController)
00294 [windowController setSupportsMultipleDocuments:YES];
00295
00296 if (windowController)
00297 [self addWindowController:windowController];
00298
00299 if (viewController)
00300 [self addViewController:viewController forWindowController:windowController];
00301 }
00302
00306 - (CPArray)windowControllers
00307 {
00308 return _windowControllers;
00309 }
00310
00316 - (void)addWindowController:(CPWindowController)aWindowController
00317 {
00318 [_windowControllers addObject:aWindowController];
00319
00320 if ([aWindowController document] !== self)
00321 [aWindowController setDocument:self];
00322 }
00323
00329 - (void)removeWindowController:(CPWindowController)aWindowController
00330 {
00331 if (aWindowController)
00332 [_windowControllers removeObject:aWindowController];
00333
00334 if ([aWindowController document] === self)
00335 [aWindowController setDocument:nil];
00336 }
00337
00338 - (CPView)view
00339 {
00340 return _view;
00341 }
00342
00343 - (CPArray)viewControllers
00344 {
00345 return [_viewControllersForWindowControllers allValues];
00346 }
00347
00348 - (void)addViewController:(CPViewController)aViewController forWindowController:(CPWindowController)aWindowController
00349 {
00350
00351 [_viewControllersForWindowControllers setObject:aViewController forKey:[aWindowController UID]];
00352
00353 if ([aWindowController document] === self)
00354 [aWindowController setViewController:aViewController];
00355 }
00356
00357 - (void)removeViewController:(CPViewController)aViewController
00358 {
00359 [_viewControllersForWindowControllers removeObject:aViewController];
00360 }
00361
00362 - (CPViewController)viewControllerForWindowController:(CPWindowController)aWindowController
00363 {
00364 return [_viewControllersForWindowControllers objectForKey:[aWindowController UID]];
00365 }
00366
00367
00371 - (void)showWindows
00372 {
00373 [_windowControllers makeObjectsPerformSelector:@selector(setDocument:) withObject:self];
00374 [_windowControllers makeObjectsPerformSelector:@selector(showWindow:) withObject:self];
00375 }
00376
00380 - (CPString)displayName
00381 {
00382 if (_fileURL)
00383 return [_fileURL lastPathComponent];
00384
00385 if (!_untitledDocumentIndex)
00386 _untitledDocumentIndex = ++CPDocumentUntitledCount;
00387
00388 if (_untitledDocumentIndex == 1)
00389 return @"Untitled";
00390
00391 return @"Untitled " + _untitledDocumentIndex;
00392 }
00393
00394 - (CPString)viewCibName
00395 {
00396 return nil;
00397 }
00398
00402 - (CPString)windowCibName
00403 {
00404 return nil;
00405 }
00406
00411 - (void)windowControllerDidLoadCib:(CPWindowController)aWindowController
00412 {
00413 }
00414
00419 - (void)windowControllerWillLoadCib:(CPWindowController)aWindowController
00420 {
00421 }
00422
00423
00432 - (void)readFromURL:(CPURL)anAbsoluteURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aDidReadSelector contextInfo:(id)aContextInfo
00433 {
00434 [_readConnection cancel];
00435
00436
00437 _readConnection = [CPURLConnection connectionWithRequest:[CPURLRequest requestWithURL:anAbsoluteURL] delegate:self];
00438
00439 _readConnection.session = _CPReadSessionMake(aType, aDelegate, aDidReadSelector, aContextInfo);
00440 }
00441
00445 - (CPURL)fileURL
00446 {
00447 return _fileURL;
00448 }
00449
00454 - (void)setFileURL:(CPURL)aFileURL
00455 {
00456 if (_fileURL === aFileURL)
00457 return;
00458
00459 _fileURL = aFileURL;
00460
00461 [_windowControllers makeObjectsPerformSelector:@selector(synchronizeWindowTitleWithDocumentName)];
00462 }
00463
00474 - (void)saveToURL:(CPURL)anAbsoluteURL ofType:(CPString)aTypeName forSaveOperation:(CPSaveOperationType)aSaveOperation delegate:(id)aDelegate didSaveSelector:(SEL)aDidSaveSelector contextInfo:(id)aContextInfo
00475 {
00476 var data = [self dataOfType:[self fileType] error:nil],
00477 oldChangeCount = _changeCount;
00478
00479 _writeRequest = [CPURLRequest requestWithURL:anAbsoluteURL];
00480
00481
00482 if ([CPPlatform isBrowser])
00483 [_writeRequest setHTTPMethod:@"POST"];
00484 else
00485 [_writeRequest setHTTPMethod:@"PUT"];
00486
00487 [_writeRequest setHTTPBody:[data rawString]];
00488
00489 [_writeRequest setValue:@"close" forHTTPHeaderField:@"Connection"];
00490
00491 if (aSaveOperation == CPSaveOperation)
00492 [_writeRequest setValue:@"true" forHTTPHeaderField:@"x-cappuccino-overwrite"];
00493
00494 if (aSaveOperation != CPSaveToOperation)
00495 [self updateChangeCount:CPChangeCleared];
00496
00497
00498 var connection = [CPURLConnection connectionWithRequest:_writeRequest delegate:self];
00499
00500 connection.session = _CPSaveSessionMake(anAbsoluteURL, aSaveOperation, oldChangeCount, aDelegate, aDidSaveSelector, aContextInfo, connection);
00501 }
00502
00503
00504
00505
00506
00507 - (void)connection:(CPURLConnection)aConnection didReceiveResponse:(CPURLResponse)aResponse
00508 {
00509
00510 if (![aResponse isKindOfClass:[CPHTTPURLResponse class]])
00511 return;
00512
00513 var statusCode = [aResponse statusCode];
00514
00515
00516 if (statusCode === 200)
00517 return;
00518
00519 var session = aConnection.session;
00520
00521 if (aConnection == _readConnection)
00522 {
00523 [aConnection cancel];
00524
00525 alert("There was an error retrieving the document.");
00526
00527 objj_msgSend(session.delegate, session.didReadSelector, self, NO, session.contextInfo);
00528 }
00529 else
00530 {
00531
00532 if (statusCode == 409)
00533 {
00534 [aConnection cancel];
00535
00536 if (confirm("There already exists a file with that name, would you like to overwrite it?"))
00537 {
00538 [_writeRequest setValue:@"true" forHTTPHeaderField:@"x-cappuccino-overwrite"];
00539
00540 [aConnection start];
00541 }
00542 else
00543 {
00544 if (session.saveOperation != CPSaveToOperation)
00545 {
00546 _changeCount += session.changeCount;
00547 [_windowControllers makeObjectsPerformSelector:@selector(setDocumentEdited:) withObject:[self isDocumentEdited]];
00548 }
00549
00550 _writeRequest = nil;
00551
00552 objj_msgSend(session.delegate, session.didSaveSelector, self, NO, session.contextInfo);
00553 [self _sendDocumentSavedNotification:NO];
00554 }
00555 }
00556 }
00557 }
00558
00559
00560
00561
00562
00563 - (void)connection:(CPURLConnection)aConnection didReceiveData:(CPString)aData
00564 {
00565 var session = aConnection.session;
00566
00567
00568 if (aConnection == _readConnection)
00569 {
00570 [self readFromData:[CPData dataWithRawString:aData] ofType:session.fileType error:nil];
00571
00572 objj_msgSend(session.delegate, session.didReadSelector, self, YES, session.contextInfo);
00573 }
00574 else
00575 {
00576 if (session.saveOperation != CPSaveToOperation)
00577 [self setFileURL:session.absoluteURL];
00578
00579 _writeRequest = nil;
00580
00581 objj_msgSend(session.delegate, session.didSaveSelector, self, YES, session.contextInfo);
00582 [self _sendDocumentSavedNotification:YES];
00583 }
00584 }
00585
00586
00587
00588
00589
00590 - (void)connection:(CPURLConnection)aConnection didFailWithError:(CPError)anError
00591 {
00592 var session = aConnection.session;
00593
00594 if (_readConnection == aConnection)
00595 objj_msgSend(session.delegate, session.didReadSelector, self, NO, session.contextInfo);
00596
00597 else
00598 {
00599 if (session.saveOperation != CPSaveToOperation)
00600 {
00601 _changeCount += session.changeCount;
00602 [_windowControllers makeObjectsPerformSelector:@selector(setDocumentEdited:) withObject:[self isDocumentEdited]];
00603 }
00604
00605 _writeRequest = nil;
00606
00607 alert("There was an error saving the document.");
00608
00609 objj_msgSend(session.delegate, session.didSaveSelector, self, NO, session.contextInfo);
00610 [self _sendDocumentSavedNotification:NO];
00611 }
00612 }
00613
00614
00615
00616
00617
00618 - (void)connectionDidFinishLoading:(CPURLConnection)aConnection
00619 {
00620 if (_readConnection == aConnection)
00621 _readConnection = nil;
00622 }
00623
00624
00628 - (BOOL)isDocumentEdited
00629 {
00630 return _changeCount != 0;
00631 }
00632
00637 - (void)updateChangeCount:(CPDocumentChangeType)aChangeType
00638 {
00639 if (aChangeType == CPChangeDone)
00640 ++_changeCount;
00641 else if (aChangeType == CPChangeUndone)
00642 --_changeCount;
00643 else if (aChangeType == CPChangeCleared)
00644 _changeCount = 0;
00645
00646
00647
00648
00649 [_windowControllers makeObjectsPerformSelector:@selector(setDocumentEdited:) withObject:[self isDocumentEdited]];
00650 }
00651
00652
00657 - (void)setFileType:(CPString)aType
00658 {
00659 _fileType = aType;
00660 }
00661
00665 - (CPString)fileType
00666 {
00667 return _fileType;
00668 }
00669
00670
00675 - (BOOL)hasUndoManager
00676 {
00677 return _hasUndoManager;
00678 }
00679
00684 - (void)setHasUndoManager:(BOOL)aFlag
00685 {
00686 if (_hasUndoManager == aFlag)
00687 return;
00688
00689 _hasUndoManager = aFlag;
00690
00691 if (!_hasUndoManager)
00692 [self setUndoManager:nil];
00693 }
00694
00695
00696 - (void)_undoManagerWillCloseGroup:(CPNotification)aNotification
00697 {
00698 var undoManager = [aNotification object];
00699
00700 if ([undoManager isUndoing] || [undoManager isRedoing])
00701 return;
00702
00703 [self updateChangeCount:CPChangeDone];
00704 }
00705
00706
00707 - (void)_undoManagerDidUndoChange:(CPNotification)aNotification
00708 {
00709 [self updateChangeCount:CPChangeUndone];
00710 }
00711
00712
00713 - (void)_undoManagerDidRedoChange:(CPNotification)aNotification
00714 {
00715 [self updateChangeCount:CPChangeDone];
00716 }
00717
00718
00719
00720
00721
00722
00723 - (void)setUndoManager:(CPUndoManager)anUndoManager
00724 {
00725 var defaultCenter = [CPNotificationCenter defaultCenter];
00726
00727 if (_undoManager)
00728 {
00729 [defaultCenter removeObserver:self
00730 name:CPUndoManagerDidUndoChangeNotification
00731 object:_undoManager];
00732
00733 [defaultCenter removeObserver:self
00734 name:CPUndoManagerDidRedoChangeNotification
00735 object:_undoManager];
00736
00737 [defaultCenter removeObserver:self
00738 name:CPUndoManagerWillCloseUndoGroupNotification
00739 object:_undoManager];
00740 }
00741
00742 _undoManager = anUndoManager;
00743
00744 if (_undoManager)
00745 {
00746
00747 [defaultCenter addObserver:self
00748 selector:@selector(_undoManagerDidUndoChange:)
00749 name:CPUndoManagerDidUndoChangeNotification
00750 object:_undoManager];
00751
00752 [defaultCenter addObserver:self
00753 selector:@selector(_undoManagerDidRedoChange:)
00754 name:CPUndoManagerDidRedoChangeNotification
00755 object:_undoManager];
00756
00757 [defaultCenter addObserver:self
00758 selector:@selector(_undoManagerWillCloseGroup:)
00759 name:CPUndoManagerWillCloseUndoGroupNotification
00760 object:_undoManager];
00761 }
00762 }
00763
00770 - (CPUndoManager)undoManager
00771 {
00772 if (_hasUndoManager && !_undoManager)
00773 [self setUndoManager:[[CPUndoManager alloc] init]];
00774
00775 return _undoManager;
00776 }
00777
00778
00779
00780
00781
00782 - (CPUndoManager)windowWillReturnUndoManager:(CPWindow)aWindow
00783 {
00784 return [self undoManager];
00785 }
00786
00787
00794 - (void)saveDocument:(id)aSender
00795 {
00796 [self saveDocumentWithDelegate:nil didSaveSelector:nil contextInfo:nil];
00797 }
00798
00799 - (void)saveDocumentWithDelegate:(id)delegate didSaveSelector:(SEL)didSaveSelector contextInfo:(Object)contextInfo
00800 {
00801 if (_fileURL)
00802 {
00803 [[CPNotificationCenter defaultCenter]
00804 postNotificationName:CPDocumentWillSaveNotification
00805 object:self];
00806
00807 [self saveToURL:_fileURL ofType:[self fileType] forSaveOperation:CPSaveOperation delegate:delegate didSaveSelector:didSaveSelector contextInfo:contextInfo];
00808 }
00809 else
00810 [self _saveDocumentAsWithDelegate:delegate didSaveSelector:didSaveSelector contextInfo:contextInfo];
00811 }
00812
00817 - (void)saveDocumentAs:(id)aSender
00818 {
00819 [self _saveDocumentAsWithDelegate:nil didSaveSelector:nil contextInfo:nil];
00820 }
00821
00822 - (void)_saveDocumentAsWithDelegate:(id)delegate didSaveSelector:(SEL)didSaveSelector contextInfo:(Object)contextInfo
00823 {
00824 var savePanel = [CPSavePanel savePanel],
00825 response = [savePanel runModal];
00826
00827 if (!response)
00828 return;
00829
00830 var saveURL = [savePanel URL];
00831
00832 [[CPNotificationCenter defaultCenter]
00833 postNotificationName:CPDocumentWillSaveNotification
00834 object:self];
00835
00836 [self saveToURL:saveURL ofType:[self fileType] forSaveOperation:CPSaveAsOperation delegate:delegate didSaveSelector:didSaveSelector contextInfo:contextInfo];
00837 }
00838
00839
00840
00841
00842 - (void)_sendDocumentSavedNotification:(BOOL)didSave
00843 {
00844 if (didSave)
00845 [[CPNotificationCenter defaultCenter]
00846 postNotificationName:CPDocumentDidSaveNotification
00847 object:self];
00848 else
00849 [[CPNotificationCenter defaultCenter]
00850 postNotificationName:CPDocumentDidFailToSaveNotification
00851 object:self];
00852 }
00853
00854 @end
00855
00856 @implementation CPDocument (ClosingDocuments)
00857
00858 - (void)close
00859 {
00860 [_windowControllers makeObjectsPerformSelector:@selector(removeDocumentAndCloseIfNecessary:) withObject:self];
00861 [[CPDocumentController sharedDocumentController] removeDocument:self];
00862 }
00863
00864 - (void)shouldCloseWindowController:(CPWindowController)controller delegate:(id)delegate shouldCloseSelector:(SEL)selector contextInfo:(Object)info
00865 {
00866 if ([controller shouldCloseDocument] || ([_windowControllers count] < 2 && [_windowControllers indexOfObject:controller] !== CPNotFound))
00867 [self canCloseDocumentWithDelegate:self shouldCloseSelector:@selector(_document:shouldClose:context:) contextInfo:{delegate:delegate, selector:selector, context:info}];
00868
00869 else if ([delegate respondsToSelector:selector])
00870 objj_msgSend(delegate, selector, self, YES, info);
00871 }
00872
00873 - (void)_document:(CPDocument)aDocument shouldClose:(BOOL)shouldClose context:(Object)context
00874 {
00875 if (aDocument === self && shouldClose)
00876 [self close];
00877
00878 objj_msgSend(context.delegate, context.selector, aDocument, shouldClose, context.context);
00879 }
00880
00881 - (void)canCloseDocumentWithDelegate:(id)aDelegate shouldCloseSelector:(SEL)aSelector contextInfo:(Object)context
00882 {
00883 if (![self isDocumentEdited])
00884 return [aDelegate respondsToSelector:aSelector] && objj_msgSend(aDelegate, aSelector, self, YES, context);
00885
00886 _canCloseAlert = [[CPAlert alloc] init];
00887
00888 [_canCloseAlert setDelegate:self];
00889 [_canCloseAlert setAlertStyle:CPWarningAlertStyle];
00890 [_canCloseAlert setTitle:@"Unsaved Document"];
00891 [_canCloseAlert setMessageText:@"Do you want to save the changes you've made to the document \"" + ([self displayName] || [self fileName]) + "\"?"];
00892
00893 [_canCloseAlert addButtonWithTitle:@"Save"];
00894 [_canCloseAlert addButtonWithTitle:@"Cancel"];
00895 [_canCloseAlert addButtonWithTitle:@"Don't Save"];
00896
00897 _canCloseAlert._context = {delegate:aDelegate, selector:aSelector, context:context};
00898
00899 [_canCloseAlert runModal];
00900 }
00901
00902 - (void)alertDidEnd:(CPAlert)alert returnCode:(int)returnCode
00903 {
00904 if (alert !== _canCloseAlert)
00905 return;
00906
00907 var delegate = alert._context.delegate,
00908 selector = alert._context.selector,
00909 context = alert._context.context;
00910
00911 if (returnCode === 0)
00912 [self saveDocumentWithDelegate:delegate didSaveSelector:selector contextInfo:context];
00913 else
00914 objj_msgSend(delegate, selector, self, returnCode === 2, context);
00915
00916 _canCloseAlert = nil;
00917 }
00918
00919 @end
00920
00921 var _CPReadSessionMake = function(aType, aDelegate, aDidReadSelector, aContextInfo)
00922 {
00923 return { fileType:aType, delegate:aDelegate, didReadSelector:aDidReadSelector, contextInfo:aContextInfo };
00924 }
00925
00926 var _CPSaveSessionMake = function(anAbsoluteURL, aSaveOperation, aChangeCount, aDelegate, aDidSaveSelector, aContextInfo, aConnection)
00927 {
00928 return { absoluteURL:anAbsoluteURL, saveOperation:aSaveOperation, changeCount:aChangeCount, delegate:aDelegate, didSaveSelector:aDidSaveSelector, contextInfo:aContextInfo, connection:aConnection };
00929 }