API  0.9.6
 All Classes Files Functions Variables Macros Groups Pages
CPDocument.j
Go to the documentation of this file.
1 /*
2  * CPDocument.j
3  * AppKit
4  *
5  * Created by Francisco Tolmasky.
6  * Copyright 2008, 280 North, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 
25 
26 /*
27  @global
28  @group CPSaveOperationType
29 */
31 /*
32  @global
33  @group CPSaveOperationType
34 */
36 /*
37  @global
38  @group CPSaveOperationType
39 */
41 /*
42  @global
43  @group CPSaveOperationType
44 */
46 
47 /*
48  @global
49  @group CPDocumentChangeType
50 */
52 /*
53  @global
54  @group CPDocumentChangeType
55 */
57 /*
58  @global
59  @group CPDocumentChangeType
60 */
62 /*
63  @global
64  @group CPDocumentChangeType
65 */
67 /*
68  @global
69  @group CPDocumentChangeType
70 */
72 
73 CPDocumentWillSaveNotification = @"CPDocumentWillSaveNotification";
74 CPDocumentDidSaveNotification = @"CPDocumentDidSaveNotification";
75 CPDocumentDidFailToSaveNotification = @"CPDocumentDidFailToSaveNotification";
76 
78 
88 @implementation CPDocument : CPResponder
89 {
90  CPWindow _window; // For outlet purposes.
91  CPView _view; // For outlet purposes
92  CPDictionary _viewControllersForWindowControllers;
93 
94  CPURL _fileURL;
95  CPString _fileType;
96  CPArray _windowControllers;
97  unsigned _untitledDocumentIndex;
98 
99  BOOL _hasUndoManager;
100  CPUndoManager _undoManager;
101 
102  int _changeCount;
103 
104  CPURLConnection _readConnection;
105  CPURLRequest _writeRequest;
106 
107  CPAlert _canCloseAlert;
108 }
109 
114 - (id)init
115 {
116  self = [super init];
117 
118  if (self)
119  {
120  _windowControllers = [];
121  _viewControllersForWindowControllers = [CPDictionary dictionary];
122 
123  _hasUndoManager = YES;
124  _changeCount = 0;
125 
126  [self setNextResponder:CPApp];
127  }
128 
129  return self;
130 }
131 
138 - (id)initWithType:(CPString)aType error:({CPError})anError
139 {
140  self = [self init];
141 
142  if (self)
143  [self setFileType:aType];
144 
145  return self;
146 }
147 
159 - (id)initWithContentsOfURL:(CPURL)anAbsoluteURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aDidReadSelector contextInfo:(id)aContextInfo
160 {
161  self = [self init];
162 
163  if (self)
164  {
165  [self setFileURL:anAbsoluteURL];
166  [self setFileType:aType];
167 
168  [self readFromURL:anAbsoluteURL ofType:aType delegate:aDelegate didReadSelector:aDidReadSelector contextInfo:aContextInfo];
169  }
170 
171  return self;
172 }
173 
184 - (id)initForURL:(CPURL)anAbsoluteURL withContentsOfURL:(CPURL)absoluteContentsURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aDidReadSelector contextInfo:(id)aContextInfo
185 {
186  self = [self init];
187 
188  if (self)
189  {
190  [self setFileURL:anAbsoluteURL];
191  [self setFileType:aType];
192 
193  [self readFromURL:absoluteContentsURL ofType:aType delegate:aDelegate didReadSelector:aDidReadSelector contextInfo:aContextInfo];
194  }
195 
196  return self;
197 }
198 
207 - (CPData)dataOfType:(CPString)aType error:({CPError})anError
208 {
209  [CPException raise:CPUnsupportedMethodException
210  reason:"dataOfType:error: must be overridden by the document subclass."];
211 }
212 
222 - (void)readFromData:(CPData)aData ofType:(CPString)aType error:(CPError)anError
223 {
224  [CPException raise:CPUnsupportedMethodException
225  reason:"readFromData:ofType: must be overridden by the document subclass."];
226 }
227 
228 - (void)viewControllerWillLoadCib:(CPViewController)aViewController
229 {
230 }
231 
232 - (void)viewControllerDidLoadCib:(CPViewController)aViewController
233 {
234 }
235 
236 - (CPWindowController)firstEligibleExistingWindowController
237 {
238  return nil;
239 }
240 
241 // Creating and managing window controllers
245 - (void)makeWindowControllers
246 {
248 }
249 
250 - (void)makeViewAndWindowControllers
251 {
252  var viewCibName = [self viewCibName],
253  viewController = nil,
254  windowController = nil;
255 
256  // Create our view controller if we have a cib for it.
257  if ([viewCibName length])
258  viewController = [[CPViewController alloc] initWithCibName:viewCibName bundle:nil owner:self];
259 
260  // If we have a view controller, check if we have a free window for it.
261  if (viewController)
262  windowController = [self firstEligibleExistingWindowController];
263 
264  // If not, create one.
265  if (!windowController)
266  {
267  var windowCibName = [self windowCibName];
268 
269  // From a cib if we have one.
270  if ([windowCibName length])
271  windowController = [[CPWindowController alloc] initWithWindowCibName:windowCibName owner:self];
272 
273  // If not you get a standard window capable of displaying multiple documents and view
274  else if (viewController)
275  {
276  var view = [viewController view],
277  viewFrame = [view frame];
278 
279  viewFrame.origin = CGPointMake(50, 50);
280 
281  var theWindow = [[CPWindow alloc] initWithContentRect:viewFrame styleMask:CPTitledWindowMask | CPClosableWindowMask | CPMiniaturizableWindowMask | CPResizableWindowMask];
282 
283  windowController = [[CPWindowController alloc] initWithWindow:theWindow];
284  }
285  }
286 
287  if (windowController && viewController)
288  [windowController setSupportsMultipleDocuments:YES];
289 
290  if (windowController)
291  [self addWindowController:windowController];
292 
293  if (viewController)
294  [self addViewController:viewController forWindowController:windowController];
295 }
296 
300 - (CPArray)windowControllers
301 {
302  return _windowControllers;
303 }
304 
310 - (void)addWindowController:(CPWindowController)aWindowController
311 {
312  [_windowControllers addObject:aWindowController];
313 
314  if ([aWindowController document] !== self)
315  [aWindowController setDocument:self];
316 }
317 
323 - (void)removeWindowController:(CPWindowController)aWindowController
324 {
325  if (aWindowController)
326  [_windowControllers removeObject:aWindowController];
327 
328  if ([aWindowController document] === self)
329  [aWindowController setDocument:nil];
330 }
331 
332 - (CPView)view
333 {
334  return _view;
335 }
336 
337 - (CPArray)viewControllers
338 {
339  return [_viewControllersForWindowControllers allValues];
340 }
341 
342 - (void)addViewController:(CPViewController)aViewController forWindowController:(CPWindowController)aWindowController
343 {
344  // FIXME: exception if we don't own the window controller?
345  [_viewControllersForWindowControllers setObject:aViewController forKey:[aWindowController UID]];
346 
347  if ([aWindowController document] === self)
348  [aWindowController setViewController:aViewController];
349 }
350 
351 - (void)removeViewController:(CPViewController)aViewController
352 {
353  [_viewControllersForWindowControllers removeObject:aViewController];
354 }
355 
356 - (CPViewController)viewControllerForWindowController:(CPWindowController)aWindowController
357 {
358  return [_viewControllersForWindowControllers objectForKey:[aWindowController UID]];
359 }
360 
361 // Managing Document Windows
365 - (void)showWindows
366 {
367  [_windowControllers makeObjectsPerformSelector:@selector(setDocument:) withObject:self];
368  [_windowControllers makeObjectsPerformSelector:@selector(showWindow:) withObject:self];
369 }
370 
375 {
376  if (_fileURL)
377  return [_fileURL lastPathComponent];
378 
379  if (!_untitledDocumentIndex)
380  _untitledDocumentIndex = ++CPDocumentUntitledCount;
381 
382  if (_untitledDocumentIndex == 1)
383  return @"Untitled";
384 
385  return @"Untitled " + _untitledDocumentIndex;
386 }
387 
388 - (CPString)viewCibName
389 {
390  return nil;
391 }
392 
396 - (CPString)windowCibName
397 {
398  return nil;
399 }
400 
405 - (void)windowControllerDidLoadCib:(CPWindowController)aWindowController
406 {
407 }
408 
413 - (void)windowControllerWillLoadCib:(CPWindowController)aWindowController
414 {
415 }
416 
417 // Reading from and Writing to URLs
426 - (void)readFromURL:(CPURL)anAbsoluteURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aDidReadSelector contextInfo:(id)aContextInfo
427 {
428  [_readConnection cancel];
429 
430  // FIXME: Oh man is this every looking for trouble, we need to handle login at the Cappuccino level, with HTTP Errors.
431  _readConnection = [CPURLConnection connectionWithRequest:[CPURLRequest requestWithURL:anAbsoluteURL] delegate:self];
432 
433  _readConnection.session = _CPReadSessionMake(aType, aDelegate, aDidReadSelector, aContextInfo);
434 }
435 
439 - (CPURL)fileURL
440 {
441  return _fileURL;
442 }
443 
448 - (void)setFileURL:(CPURL)aFileURL
449 {
450  if (_fileURL === aFileURL)
451  return;
452 
453  _fileURL = aFileURL;
454 
455  [_windowControllers makeObjectsPerformSelector:@selector(synchronizeWindowTitleWithDocumentName)];
456 }
457 
468 - (void)saveToURL:(CPURL)anAbsoluteURL ofType:(CPString)aTypeName forSaveOperation:(CPSaveOperationType)aSaveOperation delegate:(id)aDelegate didSaveSelector:(SEL)aDidSaveSelector contextInfo:(id)aContextInfo
469 {
470  var data = [self dataOfType:[self fileType] error:nil],
471  oldChangeCount = _changeCount;
472 
473  _writeRequest = [CPURLRequest requestWithURL:anAbsoluteURL];
474 
475  // FIXME: THIS IS WRONG! We need a way to decide
476  if ([CPPlatform isBrowser])
477  [_writeRequest setHTTPMethod:@"POST"];
478  else
479  [_writeRequest setHTTPMethod:@"PUT"];
480 
481  [_writeRequest setHTTPBody:[data rawString]];
482 
483  [_writeRequest setValue:@"close" forHTTPHeaderField:@"Connection"];
484 
485  if (aSaveOperation === CPSaveOperation)
486  [_writeRequest setValue:@"true" forHTTPHeaderField:@"x-cappuccino-overwrite"];
487 
488  if (aSaveOperation !== CPSaveToOperation)
489  [self updateChangeCount:CPChangeCleared];
490 
491  // FIXME: Oh man is this every looking for trouble, we need to handle login at the Cappuccino level, with HTTP Errors.
492  var connection = [CPURLConnection connectionWithRequest:_writeRequest delegate:self];
493 
494  connection.session = _CPSaveSessionMake(anAbsoluteURL, aSaveOperation, oldChangeCount, aDelegate, aDidSaveSelector, aContextInfo, connection);
495 }
496 
497 /*
498  Implemented as a delegate method for CPURLConnection
499  @ignore
500 */
501 - (void)connection:(CPURLConnection)aConnection didReceiveResponse:(CPURLResponse)aResponse
502 {
503  // If we got this far and it wasn't an HTTP request, then everything is fine.
504  if (![aResponse isKindOfClass:[CPHTTPURLResponse class]])
505  return;
506 
507  var statusCode = [aResponse statusCode];
508 
509  // Nothing to do if everything is hunky dory.
510  if (statusCode === 200)
511  return;
512 
513  var session = aConnection.session;
514 
515  if (aConnection == _readConnection)
516  {
517  [aConnection cancel];
518 
519  alert("There was an error retrieving the document.");
520 
521  objj_msgSend(session.delegate, session.didReadSelector, self, NO, session.contextInfo);
522  }
523  else
524  {
525  // 409: Conflict, in Cappuccino, overwrite protection for documents.
526  if (statusCode == 409)
527  {
528  [aConnection cancel];
529 
530  if (confirm("There already exists a file with that name, would you like to overwrite it?"))
531  {
532  [_writeRequest setValue:@"true" forHTTPHeaderField:@"x-cappuccino-overwrite"];
533 
534  [aConnection start];
535  }
536  else
537  {
538  if (session.saveOperation != CPSaveToOperation)
539  {
540  _changeCount += session.changeCount;
541  [_windowControllers makeObjectsPerformSelector:@selector(setDocumentEdited:) withObject:[self isDocumentEdited]];
542  }
543 
544  _writeRequest = nil;
545 
546  objj_msgSend(session.delegate, session.didSaveSelector, self, NO, session.contextInfo);
547  [self _sendDocumentSavedNotification:NO];
548  }
549  }
550  }
551 }
552 
553 /*
554  Implemented as a delegate method for CPURLConnection
555  @ignore
556 */
557 - (void)connection:(CPURLConnection)aConnection didReceiveData:(CPString)aData
558 {
559  var session = aConnection.session;
560 
561  // READ
562  if (aConnection == _readConnection)
563  {
564  [self readFromData:[CPData dataWithRawString:aData] ofType:session.fileType error:nil];
565 
566  objj_msgSend(session.delegate, session.didReadSelector, self, YES, session.contextInfo);
567  }
568  else
569  {
570  if (session.saveOperation != CPSaveToOperation)
571  [self setFileURL:session.absoluteURL];
572 
573  _writeRequest = nil;
574 
575  objj_msgSend(session.delegate, session.didSaveSelector, self, YES, session.contextInfo);
576  [self _sendDocumentSavedNotification:YES];
577  }
578 }
579 
580 /*
581  Implemented as a delegate method for CPURLConnection
582  @ignore
583 */
584 - (void)connection:(CPURLConnection)aConnection didFailWithError:(CPError)anError
585 {
586  var session = aConnection.session;
587 
588  if (_readConnection == aConnection)
589  objj_msgSend(session.delegate, session.didReadSelector, self, NO, session.contextInfo);
590 
591  else
592  {
593  if (session.saveOperation != CPSaveToOperation)
594  {
595  _changeCount += session.changeCount;
596  [_windowControllers makeObjectsPerformSelector:@selector(setDocumentEdited:) withObject:[self isDocumentEdited]];
597  }
598 
599  _writeRequest = nil;
600 
601  alert("There was an error saving the document.");
602 
603  objj_msgSend(session.delegate, session.didSaveSelector, self, NO, session.contextInfo);
604  [self _sendDocumentSavedNotification:NO];
605  }
606 }
607 
608 /*
609  Implemented as a delegate method for CPURLConnection
610  @ignore
611 */
612 - (void)connectionDidFinishLoading:(CPURLConnection)aConnection
613 {
614  if (_readConnection == aConnection)
615  _readConnection = nil;
616 }
617 
618 // Managing Document Status
622 - (BOOL)isDocumentEdited
623 {
624  return _changeCount != 0;
625 }
626 
631 - (void)updateChangeCount:(CPDocumentChangeType)aChangeType
632 {
633  if (aChangeType == CPChangeDone)
634  ++_changeCount;
635  else if (aChangeType == CPChangeUndone)
636  --_changeCount;
637  else if (aChangeType == CPChangeCleared)
638  _changeCount = 0;
639  /*else if (aChangeType == CPCHangeReadOtherContents)
640 
641  else if (aChangeType == CPChangeAutosaved)*/
642 
643  [_windowControllers makeObjectsPerformSelector:@selector(setDocumentEdited:) withObject:[self isDocumentEdited]];
644 }
645 
646 // Managing File Types
651 - (void)setFileType:(CPString)aType
652 {
653  _fileType = aType;
654 }
655 
659 - (CPString)fileType
660 {
661  return _fileType;
662 }
663 
664 // Working with Undo Manager
669 - (BOOL)hasUndoManager
670 {
671  return _hasUndoManager;
672 }
673 
678 - (void)setHasUndoManager:(BOOL)aFlag
679 {
680  if (_hasUndoManager == aFlag)
681  return;
682 
683  _hasUndoManager = aFlag;
684 
685  if (!_hasUndoManager)
686  [self setUndoManager:nil];
687 }
688 
689 /* @ignore */
690 - (void)_undoManagerWillCloseGroup:(CPNotification)aNotification
691 {
692  var undoManager = [aNotification object];
693 
694  if ([undoManager isUndoing] || [undoManager isRedoing])
695  return;
696 
697  [self updateChangeCount:CPChangeDone];
698 }
699 
700 /* @ignore */
701 - (void)_undoManagerDidUndoChange:(CPNotification)aNotification
702 {
703  [self updateChangeCount:CPChangeUndone];
704 }
705 
706 /* @ignore */
707 - (void)_undoManagerDidRedoChange:(CPNotification)aNotification
708 {
709  [self updateChangeCount:CPChangeDone];
710 }
711 
712 /*
713  Sets the document's undo manager. This method will add the
714  undo manager as an observer to the notification center.
715  @param anUndoManager the new undo manager for the document
716 */
717 - (void)setUndoManager:(CPUndoManager)anUndoManager
718 {
719  var defaultCenter = [CPNotificationCenter defaultCenter];
720 
721  if (_undoManager)
722  {
723  [defaultCenter removeObserver:self
724  name:CPUndoManagerDidUndoChangeNotification
725  object:_undoManager];
726 
727  [defaultCenter removeObserver:self
728  name:CPUndoManagerDidRedoChangeNotification
729  object:_undoManager];
730 
731  [defaultCenter removeObserver:self
732  name:CPUndoManagerWillCloseUndoGroupNotification
733  object:_undoManager];
734  }
735 
736  _undoManager = anUndoManager;
737 
738  if (_undoManager)
739  {
740 
741  [defaultCenter addObserver:self
742  selector:@selector(_undoManagerDidUndoChange:)
743  name:CPUndoManagerDidUndoChangeNotification
744  object:_undoManager];
745 
746  [defaultCenter addObserver:self
747  selector:@selector(_undoManagerDidRedoChange:)
748  name:CPUndoManagerDidRedoChangeNotification
749  object:_undoManager];
750 
751  [defaultCenter addObserver:self
752  selector:@selector(_undoManagerWillCloseGroup:)
753  name:CPUndoManagerWillCloseUndoGroupNotification
754  object:_undoManager];
755  }
756 }
757 
764 - (CPUndoManager)undoManager
765 {
766  if (_hasUndoManager && !_undoManager)
767  [self setUndoManager:[[CPUndoManager alloc] init]];
768 
769  return _undoManager;
770 }
771 
772 /*
773  Implemented as a delegate of a CPWindow
774  @ignore
775 */
776 - (CPUndoManager)windowWillReturnUndoManager:(CPWindow)aWindow
777 {
778  return [self undoManager];
779 }
780 
781 // Handling User Actions
788 - (void)saveDocument:(id)aSender
789 {
791 }
792 
793 - (void)saveDocumentWithDelegate:(id)delegate didSaveSelector:(SEL)didSaveSelector contextInfo:(Object)contextInfo
794 {
795  if (_fileURL)
796  {
798  postNotificationName:CPDocumentWillSaveNotification
799  object:self];
800 
801  [self saveToURL:_fileURL ofType:[self fileType] forSaveOperation:CPSaveOperation delegate:delegate didSaveSelector:didSaveSelector contextInfo:contextInfo];
802  }
803  else
804  [self _saveDocumentAsWithDelegate:delegate didSaveSelector:didSaveSelector contextInfo:contextInfo];
805 }
806 
811 - (void)saveDocumentAs:(id)aSender
812 {
813  [self _saveDocumentAsWithDelegate:nil didSaveSelector:nil contextInfo:nil];
814 }
815 
816 - (void)_saveDocumentAsWithDelegate:(id)delegate didSaveSelector:(SEL)didSaveSelector contextInfo:(Object)contextInfo
817 {
818  var savePanel = [CPSavePanel savePanel],
819  response = [savePanel runModal];
820 
821  if (!response)
822  return;
823 
824  var saveURL = [savePanel URL];
825 
827  postNotificationName:CPDocumentWillSaveNotification
828  object:self];
829 
830  [self saveToURL:saveURL ofType:[self fileType] forSaveOperation:CPSaveAsOperation delegate:delegate didSaveSelector:didSaveSelector contextInfo:contextInfo];
831 }
832 
833 /*
834  @ignore
835 */
836 - (void)_sendDocumentSavedNotification:(BOOL)didSave
837 {
838  if (didSave)
840  postNotificationName:CPDocumentDidSaveNotification
841  object:self];
842  else
844  postNotificationName:CPDocumentDidFailToSaveNotification
845  object:self];
846 }
847 
848 @end
849 
851 
852 - (void)close
853 {
854  [_windowControllers makeObjectsPerformSelector:@selector(removeDocumentAndCloseIfNecessary:) withObject:self];
855  [[CPDocumentController sharedDocumentController] removeDocument:self];
856 }
857 
858 - (void)shouldCloseWindowController:(CPWindowController)controller delegate:(id)delegate shouldCloseSelector:(SEL)selector contextInfo:(Object)info
859 {
860  if ([controller shouldCloseDocument] || ([_windowControllers count] < 2 && [_windowControllers indexOfObject:controller] !== CPNotFound))
861  [self canCloseDocumentWithDelegate:self shouldCloseSelector:@selector(_document:shouldClose:context:) contextInfo:{delegate:delegate, selector:selector, context:info}];
862 
863  else if ([delegate respondsToSelector:selector])
864  objj_msgSend(delegate, selector, self, YES, info);
865 }
866 
867 - (void)_document:(CPDocument)aDocument shouldClose:(BOOL)shouldClose context:(Object)context
868 {
869  if (aDocument === self && shouldClose)
870  [self close];
871 
872  objj_msgSend(context.delegate, context.selector, aDocument, shouldClose, context.context);
873 }
874 
875 - (void)canCloseDocumentWithDelegate:(id)aDelegate shouldCloseSelector:(SEL)aSelector contextInfo:(Object)context
876 {
877  if (![self isDocumentEdited])
878  return [aDelegate respondsToSelector:aSelector] && objj_msgSend(aDelegate, aSelector, self, YES, context);
879 
880  _canCloseAlert = [[CPAlert alloc] init];
881 
882  [_canCloseAlert setDelegate:self];
883  [_canCloseAlert setAlertStyle:CPWarningAlertStyle];
884  [_canCloseAlert setTitle:@"Unsaved Document"];
885  [_canCloseAlert setMessageText:@"Do you want to save the changes you've made to the document \"" + ([self displayName] || [self fileName]) + "\"?"];
886 
887  [_canCloseAlert addButtonWithTitle:@"Save"];
888  [_canCloseAlert addButtonWithTitle:@"Cancel"];
889  [_canCloseAlert addButtonWithTitle:@"Don't Save"];
890 
891  _canCloseAlert._context = {delegate:aDelegate, selector:aSelector, context:context};
892 
893  [_canCloseAlert runModal];
894 }
895 
896 - (void)alertDidEnd:(CPAlert)alert returnCode:(int)returnCode
897 {
898  if (alert !== _canCloseAlert)
899  return;
900 
901  var delegate = alert._context.delegate,
902  selector = alert._context.selector,
903  context = alert._context.context;
904 
905  if (returnCode === 0)
906  [self saveDocumentWithDelegate:delegate didSaveSelector:selector contextInfo:context];
907  else
908  objj_msgSend(delegate, selector, self, returnCode === 2, context);
909 
910  _canCloseAlert = nil;
911 }
912 
913 @end
914 
915 var _CPReadSessionMake = function(aType, aDelegate, aDidReadSelector, aContextInfo)
916 {
917  return { fileType:aType, delegate:aDelegate, didReadSelector:aDidReadSelector, contextInfo:aContextInfo };
918 };
919 
920 var _CPSaveSessionMake = function(anAbsoluteURL, aSaveOperation, aChangeCount, aDelegate, aDidSaveSelector, aContextInfo, aConnection)
921 {
922  return { absoluteURL:anAbsoluteURL, saveOperation:aSaveOperation, changeCount:aChangeCount, delegate:aDelegate, didSaveSelector:aDidSaveSelector, contextInfo:aContextInfo, connection:aConnection };
923 };