/AppKit/CPDocument.j

http://github.com/cacaodev/cappuccino · Unknown · 920 lines · 757 code · 163 blank · 0 comment · 0 complexity · 9c94d25031367a70c39f529770468472 MD5 · raw 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. @import <Foundation/CPString.j>
  23. @import <Foundation/CPArray.j>
  24. @import "CPAlert.j"
  25. @import "CPResponder.j"
  26. @import "CPSavePanel.j"
  27. @import "CPViewController.j"
  28. @import "CPWindowController.j"
  29. @class CPDocumentController
  30. @global CPApp
  31. /*
  32. @global
  33. @group CPSaveOperationType
  34. */
  35. CPSaveOperation = 0;
  36. /*
  37. @global
  38. @group CPSaveOperationType
  39. */
  40. CPSaveAsOperation = 1;
  41. /*
  42. @global
  43. @group CPSaveOperationType
  44. */
  45. CPSaveToOperation = 2;
  46. /*
  47. @global
  48. @group CPSaveOperationType
  49. */
  50. CPAutosaveOperation = 3;
  51. /*
  52. @global
  53. @group CPDocumentChangeType
  54. */
  55. CPChangeDone = 0;
  56. /*
  57. @global
  58. @group CPDocumentChangeType
  59. */
  60. CPChangeUndone = 1;
  61. /*
  62. @global
  63. @group CPDocumentChangeType
  64. */
  65. CPChangeCleared = 2;
  66. /*
  67. @global
  68. @group CPDocumentChangeType
  69. */
  70. CPChangeReadOtherContents = 3;
  71. /*
  72. @global
  73. @group CPDocumentChangeType
  74. */
  75. CPChangeAutosaved = 4;
  76. CPDocumentWillSaveNotification = @"CPDocumentWillSaveNotification";
  77. CPDocumentDidSaveNotification = @"CPDocumentDidSaveNotification";
  78. CPDocumentDidFailToSaveNotification = @"CPDocumentDidFailToSaveNotification";
  79. var CPDocumentUntitledCount = 0;
  80. /*!
  81. @ingroup appkit
  82. @class CPDocument
  83. CPDocument is used to represent a document/file in a Cappuccino application.
  84. In a document-based application, generally multiple documents are open simultaneously
  85. (multiple text documents, slide presentations, spreadsheets, etc.), and multiple
  86. CPDocuments should be used to represent this.
  87. */
  88. @implementation CPDocument : CPResponder
  89. {
  90. CPWindow _window; // For outlet purposes.
  91. CPView _view; // For outlet purposes
  92. CPDictionary _viewControllersForWindowControllers;
  93. CPURL _fileURL;
  94. CPString _fileType;
  95. CPArray _windowControllers;
  96. unsigned _untitledDocumentIndex;
  97. BOOL _hasUndoManager;
  98. CPUndoManager _undoManager;
  99. int _changeCount;
  100. CPURLConnection _readConnection;
  101. CPURLRequest _writeRequest;
  102. CPAlert _canCloseAlert;
  103. }
  104. /*!
  105. Initializes an empty document.
  106. @return the initialized document
  107. */
  108. - (id)init
  109. {
  110. self = [super init];
  111. if (self)
  112. {
  113. _windowControllers = [];
  114. _viewControllersForWindowControllers = @{};
  115. _hasUndoManager = YES;
  116. _changeCount = 0;
  117. [self setNextResponder:CPApp];
  118. }
  119. return self;
  120. }
  121. /*!
  122. Initializes the document with a specific data type.
  123. @param aType the type of document to initialize
  124. @param anError not used
  125. @return the initialized document
  126. */
  127. - (id)initWithType:(CPString)aType error:(/*{*/CPError/*}*/)anError
  128. {
  129. self = [self init];
  130. if (self)
  131. [self setFileType:aType];
  132. return self;
  133. }
  134. /*!
  135. Initializes a document of a specific type located at a URL. Notifies
  136. the provided delegate after initialization.
  137. @param anAbsoluteURL the url of the document content
  138. @param aType the type of document located at the URL
  139. @param aDelegate the delegate to notify
  140. @param aDidReadSelector the selector used to notify the delegate
  141. @param aContextInfo context information passed to the delegate
  142. after initialization
  143. @return the initialized document
  144. */
  145. - (id)initWithContentsOfURL:(CPURL)anAbsoluteURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aDidReadSelector contextInfo:(id)aContextInfo
  146. {
  147. self = [self init];
  148. if (self)
  149. {
  150. [self setFileURL:anAbsoluteURL];
  151. [self setFileType:aType];
  152. [self readFromURL:anAbsoluteURL ofType:aType delegate:aDelegate didReadSelector:aDidReadSelector contextInfo:aContextInfo];
  153. }
  154. return self;
  155. }
  156. /*!
  157. Initializes the document from a URL.
  158. @param anAbsoluteURL the document location
  159. @param absoluteContentsURL the location of the document's contents
  160. @param aType the type of the contents
  161. @param aDelegate this object will receive a callback after the document's contents are loaded
  162. @param aDidReadSelector the message selector that will be sent to \c aDelegate
  163. @param aContextInfo passed as the argument to the message sent to the \c aDelegate
  164. @return the initialized document
  165. */
  166. - (id)initForURL:(CPURL)anAbsoluteURL withContentsOfURL:(CPURL)absoluteContentsURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aDidReadSelector contextInfo:(id)aContextInfo
  167. {
  168. self = [self init];
  169. if (self)
  170. {
  171. [self setFileURL:anAbsoluteURL];
  172. [self setFileType:aType];
  173. [self readFromURL:absoluteContentsURL ofType:aType delegate:aDelegate didReadSelector:aDidReadSelector contextInfo:aContextInfo];
  174. }
  175. return self;
  176. }
  177. /*!
  178. Returns the receiver's data in a specified type. The default implementation just
  179. throws an exception.
  180. @param aType the format of the data
  181. @param anError not used
  182. @throws CPUnsupportedMethodException if this method hasn't been overridden by the subclass
  183. @return the document data
  184. */
  185. - (CPData)dataOfType:(CPString)aType error:(/*{*/CPError/*}*/)anError
  186. {
  187. [CPException raise:CPUnsupportedMethodException
  188. reason:"dataOfType:error: must be overridden by the document subclass."];
  189. }
  190. /*!
  191. Sets the content of the document by reading the provided
  192. data. The default implementation just throws an exception.
  193. @param aData the document's data
  194. @param aType the document type
  195. @param anError not used
  196. @throws CPUnsupportedMethodException if this method hasn't been
  197. overridden by the subclass
  198. */
  199. - (void)readFromData:(CPData)aData ofType:(CPString)aType error:(CPError)anError
  200. {
  201. [CPException raise:CPUnsupportedMethodException
  202. reason:"readFromData:ofType: must be overridden by the document subclass."];
  203. }
  204. - (void)viewControllerWillLoadCib:(CPViewController)aViewController
  205. {
  206. }
  207. - (void)viewControllerDidLoadCib:(CPViewController)aViewController
  208. {
  209. }
  210. // Creating and managing window controllers
  211. /*!
  212. Creates the window controller for this document.
  213. */
  214. - (void)makeWindowControllers
  215. {
  216. [self makeViewAndWindowControllers];
  217. }
  218. - (void)makeViewAndWindowControllers
  219. {
  220. var viewCibName = [self viewCibName],
  221. windowCibName = [self windowCibName],
  222. viewController = nil,
  223. windowController = nil;
  224. // Create our view controller if we have a cib for it.
  225. if ([viewCibName length])
  226. viewController = [[CPViewController alloc] initWithCibName:viewCibName bundle:nil owner:self];
  227. // From a cib if we have one.
  228. if ([windowCibName length])
  229. windowController = [[CPWindowController alloc] initWithWindowCibName:windowCibName owner:self];
  230. // If not you get a standard window capable of displaying multiple documents and view
  231. else if (viewController)
  232. {
  233. var view = [viewController view],
  234. viewFrame = [view frame];
  235. viewFrame.origin = CGPointMake(50, 50);
  236. var theWindow = [[CPWindow alloc] initWithContentRect:viewFrame styleMask:CPTitledWindowMask | CPClosableWindowMask | CPMiniaturizableWindowMask | CPResizableWindowMask];
  237. windowController = [[CPWindowController alloc] initWithWindow:theWindow];
  238. }
  239. if (windowController && viewController)
  240. [windowController setSupportsMultipleDocuments:YES];
  241. if (windowController)
  242. [self addWindowController:windowController];
  243. if (viewController)
  244. [self addViewController:viewController forWindowController:windowController];
  245. }
  246. /*!
  247. Returns the document's window controllers
  248. */
  249. - (CPArray)windowControllers
  250. {
  251. return _windowControllers;
  252. }
  253. /*!
  254. Add a controller to the document's list of controllers. This should
  255. be called after making a new window controller.
  256. @param aWindowController the controller to add
  257. */
  258. - (void)addWindowController:(CPWindowController)aWindowController
  259. {
  260. [_windowControllers addObject:aWindowController];
  261. if ([aWindowController document] !== self)
  262. [aWindowController setDocument:self];
  263. }
  264. /*!
  265. Remove a controller to the document's list of controllers. This should
  266. be called after closing the controller's window.
  267. @param aWindowController the controller to remove
  268. */
  269. - (void)removeWindowController:(CPWindowController)aWindowController
  270. {
  271. if (aWindowController)
  272. [_windowControllers removeObject:aWindowController];
  273. if ([aWindowController document] === self)
  274. [aWindowController setDocument:nil];
  275. }
  276. - (CPView)view
  277. {
  278. return _view;
  279. }
  280. - (CPArray)viewControllers
  281. {
  282. return [_viewControllersForWindowControllers allValues];
  283. }
  284. - (void)addViewController:(CPViewController)aViewController forWindowController:(CPWindowController)aWindowController
  285. {
  286. // FIXME: exception if we don't own the window controller?
  287. [_viewControllersForWindowControllers setObject:aViewController forKey:[aWindowController UID]];
  288. if ([aWindowController document] === self)
  289. [aWindowController setViewController:aViewController];
  290. }
  291. - (void)removeViewController:(CPViewController)aViewController
  292. {
  293. [_viewControllersForWindowControllers removeObject:aViewController];
  294. }
  295. - (CPViewController)viewControllerForWindowController:(CPWindowController)aWindowController
  296. {
  297. return [_viewControllersForWindowControllers objectForKey:[aWindowController UID]];
  298. }
  299. // Managing Document Windows
  300. /*!
  301. Shows all the document's windows.
  302. */
  303. - (void)showWindows
  304. {
  305. [_windowControllers makeObjectsPerformSelector:@selector(setDocument:) withObject:self];
  306. [_windowControllers makeObjectsPerformSelector:@selector(showWindow:) withObject:self];
  307. }
  308. /*!
  309. Returns the name of the document as displayed in the title bar.
  310. */
  311. - (CPString)displayName
  312. {
  313. if (_fileURL)
  314. return [_fileURL lastPathComponent];
  315. if (!_untitledDocumentIndex)
  316. _untitledDocumentIndex = ++CPDocumentUntitledCount;
  317. if (_untitledDocumentIndex == 1)
  318. return @"Untitled";
  319. return @"Untitled " + _untitledDocumentIndex;
  320. }
  321. - (CPString)viewCibName
  322. {
  323. return nil;
  324. }
  325. /*!
  326. Returns the document's Cib name
  327. */
  328. - (CPString)windowCibName
  329. {
  330. return nil;
  331. }
  332. /*!
  333. Called after \c aWindowController loads the document's Nib file.
  334. @param aWindowController the controller that loaded the Nib file
  335. */
  336. - (void)windowControllerDidLoadCib:(CPWindowController)aWindowController
  337. {
  338. }
  339. /*!
  340. Called before \c aWindowController will load the document's Nib file.
  341. @param aWindowController the controller that will load the Nib file
  342. */
  343. - (void)windowControllerWillLoadCib:(CPWindowController)aWindowController
  344. {
  345. }
  346. // Reading from and Writing to URLs
  347. /*!
  348. Set the document's data from a URL. Notifies the provided delegate afterwards.
  349. @param anAbsoluteURL the URL to the document's content
  350. @param aType the document type
  351. @param aDelegate delegate to notify after reading the data
  352. @param aDidReadSelector message that will be sent to the delegate
  353. @param aContextInfo context information that gets sent to the delegate
  354. */
  355. - (void)readFromURL:(CPURL)anAbsoluteURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aDidReadSelector contextInfo:(id)aContextInfo
  356. {
  357. [_readConnection cancel];
  358. // FIXME: Oh man is this every looking for trouble, we need to handle login at the Cappuccino level, with HTTP Errors.
  359. _readConnection = [CPURLConnection connectionWithRequest:[CPURLRequest requestWithURL:anAbsoluteURL] delegate:self];
  360. _readConnection.session = _CPReadSessionMake(aType, aDelegate, aDidReadSelector, aContextInfo);
  361. }
  362. /*!
  363. Returns the path to the document's file.
  364. */
  365. - (CPURL)fileURL
  366. {
  367. return _fileURL;
  368. }
  369. /*!
  370. Sets the path to the document's file.
  371. @param aFileURL the path to the document's file
  372. */
  373. - (void)setFileURL:(CPURL)aFileURL
  374. {
  375. if (_fileURL === aFileURL)
  376. return;
  377. _fileURL = aFileURL;
  378. [_windowControllers makeObjectsPerformSelector:@selector(synchronizeWindowTitleWithDocumentName)];
  379. }
  380. /*!
  381. Saves the document to the specified URL. Notifies the provided delegate
  382. with the provided selector and context info afterwards.
  383. @param anAbsoluteURL the url to write the document data to
  384. @param aTypeName the document type
  385. @param aSaveOperation the type of save operation
  386. @param aDelegate the delegate to notify after saving
  387. @param aDidSaveSelector the selector to send the delegate
  388. @param aContextInfo context info that gets passed to the delegate
  389. */
  390. - (void)saveToURL:(CPURL)anAbsoluteURL ofType:(CPString)aTypeName forSaveOperation:(CPSaveOperationType)aSaveOperation delegate:(id)aDelegate didSaveSelector:(SEL)aDidSaveSelector contextInfo:(id)aContextInfo
  391. {
  392. var data = [self dataOfType:[self fileType] error:nil],
  393. oldChangeCount = _changeCount;
  394. _writeRequest = [CPURLRequest requestWithURL:anAbsoluteURL];
  395. // FIXME: THIS IS WRONG! We need a way to decide
  396. if ([CPPlatform isBrowser])
  397. [_writeRequest setHTTPMethod:@"POST"];
  398. else
  399. [_writeRequest setHTTPMethod:@"PUT"];
  400. [_writeRequest setHTTPBody:[data rawString]];
  401. [_writeRequest setValue:@"close" forHTTPHeaderField:@"Connection"];
  402. if (aSaveOperation === CPSaveOperation)
  403. [_writeRequest setValue:@"true" forHTTPHeaderField:@"x-cappuccino-overwrite"];
  404. if (aSaveOperation !== CPSaveToOperation)
  405. [self updateChangeCount:CPChangeCleared];
  406. // FIXME: Oh man is this every looking for trouble, we need to handle login at the Cappuccino level, with HTTP Errors.
  407. var connection = [CPURLConnection connectionWithRequest:_writeRequest delegate:self];
  408. connection.session = _CPSaveSessionMake(anAbsoluteURL, aSaveOperation, oldChangeCount, aDelegate, aDidSaveSelector, aContextInfo, connection);
  409. }
  410. /*
  411. Implemented as a delegate method for CPURLConnection
  412. @ignore
  413. */
  414. - (void)connection:(CPURLConnection)aConnection didReceiveResponse:(CPURLResponse)aResponse
  415. {
  416. // If we got this far and it wasn't an HTTP request, then everything is fine.
  417. if (![aResponse isKindOfClass:[CPHTTPURLResponse class]])
  418. return;
  419. var statusCode = [aResponse statusCode];
  420. // Nothing to do if everything is hunky dory.
  421. if (statusCode === 200)
  422. return;
  423. var session = aConnection.session;
  424. if (aConnection == _readConnection)
  425. {
  426. [aConnection cancel];
  427. alert("There was an error retrieving the document.");
  428. objj_msgSend(session.delegate, session.didReadSelector, self, NO, session.contextInfo);
  429. }
  430. else
  431. {
  432. // 409: Conflict, in Cappuccino, overwrite protection for documents.
  433. if (statusCode == 409)
  434. {
  435. [aConnection cancel];
  436. if (confirm("There already exists a file with that name, would you like to overwrite it?"))
  437. {
  438. [_writeRequest setValue:@"true" forHTTPHeaderField:@"x-cappuccino-overwrite"];
  439. [aConnection start];
  440. }
  441. else
  442. {
  443. if (session.saveOperation != CPSaveToOperation)
  444. {
  445. _changeCount += session.changeCount;
  446. [_windowControllers makeObjectsPerformSelector:@selector(setDocumentEdited:) withObject:[self isDocumentEdited]];
  447. }
  448. _writeRequest = nil;
  449. objj_msgSend(session.delegate, session.didSaveSelector, self, NO, session.contextInfo);
  450. [self _sendDocumentSavedNotification:NO];
  451. }
  452. }
  453. }
  454. }
  455. /*
  456. Implemented as a delegate method for CPURLConnection
  457. @ignore
  458. */
  459. - (void)connection:(CPURLConnection)aConnection didReceiveData:(CPString)aData
  460. {
  461. var session = aConnection.session;
  462. // READ
  463. if (aConnection == _readConnection)
  464. {
  465. [self readFromData:[CPData dataWithRawString:aData] ofType:session.fileType error:nil];
  466. objj_msgSend(session.delegate, session.didReadSelector, self, YES, session.contextInfo);
  467. }
  468. else
  469. {
  470. if (session.saveOperation != CPSaveToOperation)
  471. [self setFileURL:session.absoluteURL];
  472. _writeRequest = nil;
  473. objj_msgSend(session.delegate, session.didSaveSelector, self, YES, session.contextInfo);
  474. [self _sendDocumentSavedNotification:YES];
  475. }
  476. }
  477. /*
  478. Implemented as a delegate method for CPURLConnection
  479. @ignore
  480. */
  481. - (void)connection:(CPURLConnection)aConnection didFailWithError:(CPError)anError
  482. {
  483. var session = aConnection.session;
  484. if (_readConnection == aConnection)
  485. objj_msgSend(session.delegate, session.didReadSelector, self, NO, session.contextInfo);
  486. else
  487. {
  488. if (session.saveOperation != CPSaveToOperation)
  489. {
  490. _changeCount += session.changeCount;
  491. [_windowControllers makeObjectsPerformSelector:@selector(setDocumentEdited:) withObject:[self isDocumentEdited]];
  492. }
  493. _writeRequest = nil;
  494. alert("There was an error saving the document.");
  495. objj_msgSend(session.delegate, session.didSaveSelector, self, NO, session.contextInfo);
  496. [self _sendDocumentSavedNotification:NO];
  497. }
  498. }
  499. /*
  500. Implemented as a delegate method for CPURLConnection
  501. @ignore
  502. */
  503. - (void)connectionDidFinishLoading:(CPURLConnection)aConnection
  504. {
  505. if (_readConnection == aConnection)
  506. _readConnection = nil;
  507. }
  508. // Managing Document Status
  509. /*!
  510. Returns \c YES if there are any unsaved changes.
  511. */
  512. - (BOOL)isDocumentEdited
  513. {
  514. return _changeCount != 0;
  515. }
  516. /*!
  517. Updates the number of unsaved changes to the document.
  518. @param aChangeType a new document change to apply
  519. */
  520. - (void)updateChangeCount:(CPDocumentChangeType)aChangeType
  521. {
  522. if (aChangeType == CPChangeDone)
  523. ++_changeCount;
  524. else if (aChangeType == CPChangeUndone)
  525. --_changeCount;
  526. else if (aChangeType == CPChangeCleared)
  527. _changeCount = 0;
  528. /*else if (aChangeType == CPCHangeReadOtherContents)
  529. else if (aChangeType == CPChangeAutosaved)*/
  530. [_windowControllers makeObjectsPerformSelector:@selector(setDocumentEdited:) withObject:[self isDocumentEdited]];
  531. }
  532. // Managing File Types
  533. /*!
  534. Sets the document's file type
  535. @param aType the document's type
  536. */
  537. - (void)setFileType:(CPString)aType
  538. {
  539. _fileType = aType;
  540. }
  541. /*!
  542. Returns the document's file type
  543. */
  544. - (CPString)fileType
  545. {
  546. return _fileType;
  547. }
  548. // Working with Undo Manager
  549. /*!
  550. Returns \c YES if the document has a
  551. CPUndoManager.
  552. */
  553. - (BOOL)hasUndoManager
  554. {
  555. return _hasUndoManager;
  556. }
  557. /*!
  558. Sets whether the document should have a CPUndoManager.
  559. @param aFlag \c YES makes the document have an undo manager
  560. */
  561. - (void)setHasUndoManager:(BOOL)aFlag
  562. {
  563. if (_hasUndoManager == aFlag)
  564. return;
  565. _hasUndoManager = aFlag;
  566. if (!_hasUndoManager)
  567. [self setUndoManager:nil];
  568. }
  569. /* @ignore */
  570. - (void)_undoManagerWillCloseGroup:(CPNotification)aNotification
  571. {
  572. var undoManager = [aNotification object];
  573. if ([undoManager isUndoing] || [undoManager isRedoing])
  574. return;
  575. [self updateChangeCount:CPChangeDone];
  576. }
  577. /* @ignore */
  578. - (void)_undoManagerDidUndoChange:(CPNotification)aNotification
  579. {
  580. [self updateChangeCount:CPChangeUndone];
  581. }
  582. /* @ignore */
  583. - (void)_undoManagerDidRedoChange:(CPNotification)aNotification
  584. {
  585. [self updateChangeCount:CPChangeDone];
  586. }
  587. /*
  588. Sets the document's undo manager. This method will add the
  589. undo manager as an observer to the notification center.
  590. @param anUndoManager the new undo manager for the document
  591. */
  592. - (void)setUndoManager:(CPUndoManager)anUndoManager
  593. {
  594. var defaultCenter = [CPNotificationCenter defaultCenter];
  595. if (_undoManager)
  596. {
  597. [defaultCenter removeObserver:self
  598. name:CPUndoManagerDidUndoChangeNotification
  599. object:_undoManager];
  600. [defaultCenter removeObserver:self
  601. name:CPUndoManagerDidRedoChangeNotification
  602. object:_undoManager];
  603. [defaultCenter removeObserver:self
  604. name:CPUndoManagerWillCloseUndoGroupNotification
  605. object:_undoManager];
  606. }
  607. _undoManager = anUndoManager;
  608. if (_undoManager)
  609. {
  610. [defaultCenter addObserver:self
  611. selector:@selector(_undoManagerDidUndoChange:)
  612. name:CPUndoManagerDidUndoChangeNotification
  613. object:_undoManager];
  614. [defaultCenter addObserver:self
  615. selector:@selector(_undoManagerDidRedoChange:)
  616. name:CPUndoManagerDidRedoChangeNotification
  617. object:_undoManager];
  618. [defaultCenter addObserver:self
  619. selector:@selector(_undoManagerWillCloseGroup:)
  620. name:CPUndoManagerWillCloseUndoGroupNotification
  621. object:_undoManager];
  622. }
  623. }
  624. /*!
  625. Returns the document's undo manager. If the document
  626. should have one, but the manager is \c nil, it
  627. will be created and then returned.
  628. @return the document's undo manager
  629. */
  630. - (CPUndoManager)undoManager
  631. {
  632. if (_hasUndoManager && !_undoManager)
  633. [self setUndoManager:[[CPUndoManager alloc] init]];
  634. return _undoManager;
  635. }
  636. /*
  637. Implemented as a delegate of a CPWindow
  638. @ignore
  639. */
  640. - (CPUndoManager)windowWillReturnUndoManager:(CPWindow)aWindow
  641. {
  642. return [self undoManager];
  643. }
  644. // Handling User Actions
  645. /*!
  646. Saves the document. If the document does not
  647. have a file path to save to (\c fileURL)
  648. then \c -saveDocumentAs: will be called.
  649. @param aSender the object requesting the save
  650. */
  651. - (void)saveDocument:(id)aSender
  652. {
  653. [self saveDocumentWithDelegate:nil didSaveSelector:nil contextInfo:nil];
  654. }
  655. - (void)saveDocumentWithDelegate:(id)delegate didSaveSelector:(SEL)didSaveSelector contextInfo:(Object)contextInfo
  656. {
  657. if (_fileURL)
  658. {
  659. [[CPNotificationCenter defaultCenter]
  660. postNotificationName:CPDocumentWillSaveNotification
  661. object:self];
  662. [self saveToURL:_fileURL ofType:[self fileType] forSaveOperation:CPSaveOperation delegate:delegate didSaveSelector:didSaveSelector contextInfo:contextInfo];
  663. }
  664. else
  665. [self _saveDocumentAsWithDelegate:delegate didSaveSelector:didSaveSelector contextInfo:contextInfo];
  666. }
  667. /*!
  668. Saves the document to a user specified path.
  669. @param aSender the object requesting the operation
  670. */
  671. - (void)saveDocumentAs:(id)aSender
  672. {
  673. [self _saveDocumentAsWithDelegate:nil didSaveSelector:nil contextInfo:nil];
  674. }
  675. - (void)_saveDocumentAsWithDelegate:(id)delegate didSaveSelector:(SEL)didSaveSelector contextInfo:(Object)contextInfo
  676. {
  677. var savePanel = [CPSavePanel savePanel],
  678. response = [savePanel runModal];
  679. if (!response)
  680. return;
  681. var saveURL = [savePanel URL];
  682. [[CPNotificationCenter defaultCenter]
  683. postNotificationName:CPDocumentWillSaveNotification
  684. object:self];
  685. [self saveToURL:saveURL ofType:[self fileType] forSaveOperation:CPSaveAsOperation delegate:delegate didSaveSelector:didSaveSelector contextInfo:contextInfo];
  686. }
  687. /*
  688. @ignore
  689. */
  690. - (void)_sendDocumentSavedNotification:(BOOL)didSave
  691. {
  692. if (didSave)
  693. [[CPNotificationCenter defaultCenter]
  694. postNotificationName:CPDocumentDidSaveNotification
  695. object:self];
  696. else
  697. [[CPNotificationCenter defaultCenter]
  698. postNotificationName:CPDocumentDidFailToSaveNotification
  699. object:self];
  700. }
  701. @end
  702. @implementation CPDocument (ClosingDocuments)
  703. - (void)close
  704. {
  705. [_windowControllers makeObjectsPerformSelector:@selector(removeDocumentAndCloseIfNecessary:) withObject:self];
  706. [[CPDocumentController sharedDocumentController] removeDocument:self];
  707. }
  708. - (void)shouldCloseWindowController:(CPWindowController)controller delegate:(id)delegate shouldCloseSelector:(SEL)selector contextInfo:(Object)info
  709. {
  710. if ([controller shouldCloseDocument] || ([_windowControllers count] < 2 && [_windowControllers indexOfObject:controller] !== CPNotFound))
  711. [self canCloseDocumentWithDelegate:self shouldCloseSelector:@selector(_document:shouldClose:context:) contextInfo:{delegate:delegate, selector:selector, context:info}];
  712. else if ([delegate respondsToSelector:selector])
  713. objj_msgSend(delegate, selector, self, YES, info);
  714. }
  715. - (void)_document:(CPDocument)aDocument shouldClose:(BOOL)shouldClose context:(Object)context
  716. {
  717. if (aDocument === self && shouldClose)
  718. [self close];
  719. objj_msgSend(context.delegate, context.selector, aDocument, shouldClose, context.context);
  720. }
  721. - (void)canCloseDocumentWithDelegate:(id)aDelegate shouldCloseSelector:(SEL)aSelector contextInfo:(Object)context
  722. {
  723. if (![self isDocumentEdited])
  724. return [aDelegate respondsToSelector:aSelector] && objj_msgSend(aDelegate, aSelector, self, YES, context);
  725. _canCloseAlert = [[CPAlert alloc] init];
  726. [_canCloseAlert setDelegate:self];
  727. [_canCloseAlert setAlertStyle:CPWarningAlertStyle];
  728. [_canCloseAlert setTitle:@"Unsaved Document"];
  729. [_canCloseAlert setMessageText:@"Do you want to save the changes you've made to the document \"" + ([self displayName] || [self fileName]) + "\"?"];
  730. [_canCloseAlert addButtonWithTitle:@"Save"];
  731. [_canCloseAlert addButtonWithTitle:@"Cancel"];
  732. [_canCloseAlert addButtonWithTitle:@"Don't Save"];
  733. _canCloseAlert._context = {delegate:aDelegate, selector:aSelector, context:context};
  734. [_canCloseAlert runModal];
  735. }
  736. - (void)alertDidEnd:(CPAlert)alert returnCode:(int)returnCode
  737. {
  738. if (alert !== _canCloseAlert)
  739. return;
  740. var delegate = alert._context.delegate,
  741. selector = alert._context.selector,
  742. context = alert._context.context;
  743. if (returnCode === 0)
  744. [self saveDocumentWithDelegate:delegate didSaveSelector:selector contextInfo:context];
  745. else
  746. objj_msgSend(delegate, selector, self, returnCode === 2, context);
  747. _canCloseAlert = nil;
  748. }
  749. @end
  750. var _CPReadSessionMake = function(aType, aDelegate, aDidReadSelector, aContextInfo)
  751. {
  752. return { fileType:aType, delegate:aDelegate, didReadSelector:aDidReadSelector, contextInfo:aContextInfo };
  753. };
  754. var _CPSaveSessionMake = function(anAbsoluteURL, aSaveOperation, aChangeCount, aDelegate, aDidSaveSelector, aContextInfo, aConnection)
  755. {
  756. return { absoluteURL:anAbsoluteURL, saveOperation:aSaveOperation, changeCount:aChangeCount, delegate:aDelegate, didSaveSelector:aDidSaveSelector, contextInfo:aContextInfo, connection:aConnection };
  757. };