API  0.9.10
CPDocumentController.j
Go to the documentation of this file.
1 /*
2  * CPDocumentController.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 @global CPApp
26 
27 
29 
34 @implementation CPDocumentController : CPObject
35 {
36  CPArray _documents;
37  CPArray _documentTypes;
38 }
39 
46 {
48  [[self alloc] init];
49 
51 }
52 
53 /*
54  @ignore
55 */
56 - (id)init
57 {
58  self = [super init];
59 
60  if (self)
61  {
62  _documents = [[CPArray alloc] init];
63 
66 
67  _documentTypes = [[[CPBundle mainBundle] infoDictionary] objectForKey:@"CPBundleDocumentTypes"];
68  }
69  return self;
70 }
71 
72 // Creating and Opening Documents
73 
81 - (CPDocument)documentForURL:(CPURL)aURL
82 {
83  var index = 0,
84  count = [_documents count];
85 
86  for (; index < count; ++index)
87  {
88  var theDocument = _documents[index];
89 
90  if ([[theDocument fileURL] isEqual:aURL])
91  return theDocument;
92  }
93 
94  return nil;
95 }
96 
102 - (void)openUntitledDocumentOfType:(CPString)aType display:(BOOL)shouldDisplay
103 {
104  var theDocument = [self makeUntitledDocumentOfType:aType error:nil];
105 
106  if (theDocument)
107  [self addDocument:theDocument];
108 
109  if (shouldDisplay)
110  {
111  [theDocument makeWindowControllers];
112  [theDocument showWindows];
113  }
114 
115  return theDocument;
116 }
117 
124 - (CPDocument)makeUntitledDocumentOfType:(CPString)aType error:(/*{*/CPError/*}*/)anError
125 {
126  return [[[self documentClassForType:aType] alloc] initWithType:aType error:anError];
127 }
128 
136 - (CPDocument)openDocumentWithContentsOfURL:(CPURL)anAbsoluteURL display:(BOOL)shouldDisplay error:(CPError)anError
137 {
138  var result = [self documentForURL:anAbsoluteURL];
139 
140  if (!result)
141  {
142  var type = [self typeForContentsOfURL:anAbsoluteURL error:anError];
143 
144  result = [self makeDocumentWithContentsOfURL:anAbsoluteURL ofType:type delegate:self didReadSelector:@selector(document:didRead:contextInfo:) contextInfo:@{ @"shouldDisplay": shouldDisplay }];
145 
146  [self addDocument:result];
147 
148  if (result)
149  [self noteNewRecentDocument:result];
150  }
151  else if (shouldDisplay)
152  [result showWindows];
153 
154  return result;
155 }
156 
165 - (CPDocument)reopenDocumentForURL:(CPURL)anAbsoluteURL withContentsOfURL:(CPURL)absoluteContentsURL error:(CPError)anError
166 {
167  return [self makeDocumentForURL:anAbsoluteURL withContentsOfURL:absoluteContentsURL ofType:[[_documentTypes objectAtIndex:0] objectForKey:@"CPBundleTypeName"] delegate:self didReadSelector:@selector(document:didRead:contextInfo:) contextInfo:nil];
168 }
169 
179 - (CPDocument)makeDocumentWithContentsOfURL:(CPURL)anAbsoluteURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aSelector contextInfo:(id)aContextInfo
180 {
181  return [[[self documentClassForType:aType] alloc] initWithContentsOfURL:anAbsoluteURL ofType:aType delegate:aDelegate didReadSelector:aSelector contextInfo:aContextInfo];
182 }
183 
195 - (CPDocument)makeDocumentForURL:(CPURL)anAbsoluteURL withContentsOfURL:(CPURL)absoluteContentsURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aSelector contextInfo:(id)aContextInfo
196 {
197  return [[[self documentClassForType:aType] alloc] initForURL:anAbsoluteURL withContentsOfURL:absoluteContentsURL ofType:aType delegate:aDelegate didReadSelector:aSelector contextInfo:aContextInfo];
198 }
199 
200 /*
201  Implemented delegate method
202  @ignore
203 */
204 - (void)document:(CPDocument)aDocument didRead:(BOOL)didRead contextInfo:(id)aContextInfo
205 {
206  if (!didRead)
207  return;
208 
209  [aDocument makeWindowControllers];
210 
211  if ([aContextInfo objectForKey:@"shouldDisplay"])
212  [aDocument showWindows];
213 }
214 
219 - (CFAction)newDocument:(id)aSender
220 {
221  [self openUntitledDocumentOfType:[[_documentTypes objectAtIndex:0] objectForKey:@"CPBundleTypeName"] display:YES];
222 }
223 
224 - (void)openDocument:(id)aSender
225 {
226  var openPanel = [CPOpenPanel openPanel];
227 
228  [openPanel runModal];
229 
230  var URLs = [openPanel URLs],
231  index = 0,
232  count = [URLs count];
233 
234  for (; index < count; ++index)
235  [self openDocumentWithContentsOfURL:[CPURL URLWithString:URLs[index]] display:YES error:nil];
236 }
237 
238 // Managing Documents
239 
244 - (CPArray)documents
245 {
246  return _documents;
247 }
248 
253 {
254  return [[[CPApp mainWindow] windowController] document];
255 }
256 
261 - (void)addDocument:(CPDocument)aDocument
262 {
263  [_documents addObject:aDocument];
264 }
265 
270 - (void)removeDocument:(CPDocument)aDocument
271 {
272  [_documents removeObjectIdenticalTo:aDocument];
273 }
274 
279 - (CPDocument)documentForWindow:(CPWindow)aWindow
280 {
281  return [[aWindow windowController] document];
282 }
283 
289 {
290  var iter = [_documents objectEnumerator],
291  obj;
292 
293  while ((obj = [iter nextObject]) !== nil)
294  {
295  if ([obj isDocumentEdited])
296  return YES;
297  }
298 
299  return NO;
300 }
301 
303 {
304  return [_documentTypes[0] objectForKey:@"CPBundleTypeName"];
305 }
306 
307 - (CPString)typeForContentsOfURL:(CPURL)anAbsoluteURL error:(CPError)outError
308 {
309  var index = 0,
310  count = _documentTypes.length,
311 
312  extension = [[anAbsoluteURL pathExtension] lowercaseString],
313  starType = nil;
314 
315  for (; index < count; ++index)
316  {
317  var documentType = _documentTypes[index],
318  extensions = [documentType objectForKey:@"CFBundleTypeExtensions"],
319  extensionIndex = 0,
320  extensionCount = extensions.length;
321 
322  for (; extensionIndex < extensionCount; ++extensionIndex)
323  {
324  var thisExtension = [extensions[extensionIndex] lowercaseString];
325  if (thisExtension === extension)
326  return [documentType objectForKey:@"CPBundleTypeName"];
327 
328  if (thisExtension === "****")
329  starType = [documentType objectForKey:@"CPBundleTypeName"];
330  }
331  }
332 
333  return starType || [self defaultType];
334 }
335 
336 // Managing Document Types
337 
338 /* @ignore */
339 - (CPDictionary)_infoForType:(CPString)aType
340 {
341  var i = 0,
342  count = [_documentTypes count];
343 
344  for (;i < count; ++i)
345  {
346  var documentType = _documentTypes[i];
347 
348  if ([documentType objectForKey:@"CPBundleTypeName"] == aType)
349  return documentType;
350  }
351 
352  return nil;
353 }
354 
360 - (Class)documentClassForType:(CPString)aType
361 {
362  var className = [[self _infoForType:aType] objectForKey:@"CPDocumentClass"];
363 
364  return className ? CPClassFromString(className) : nil;
365 }
366 
367 @end
368 
370 
371 - (void)closeAllDocumentsWithDelegate:(id)aDelegate didCloseAllSelector:(SEL)didCloseSelector contextInfo:(Object)info
372 {
373  var context = {
374  delegate: aDelegate,
375  selector: didCloseSelector,
376  context: info
377  };
378 
379  [self _closeDocumentsStartingWith:nil shouldClose:YES context:context];
380 }
381 
382 // Recursive callback method. Start it by passing in a document of nil.
383 - (void)_closeDocumentsStartingWith:(CPDocument)aDocument shouldClose:(BOOL)shouldClose context:(Object)context
384 {
385  if (shouldClose)
386  {
387  [aDocument close];
388 
389  if ([[self documents] count] > 0)
390  {
391  [[[self documents] lastObject] canCloseDocumentWithDelegate:self
392  shouldCloseSelector:@selector(_closeDocumentsStartingWith:shouldClose:context:)
393  contextInfo:context];
394  return;
395  }
396  }
397 
398  var theDelegate = context.delegate;
399 
400  if ([theDelegate respondsToSelector:context.selector])
401  theDelegate.isa.objj_msgSend3(theDelegate, context.selector, self, [[self documents] count] === 0, context.context);
402 }
403 
404 @end
405 
407 
409 {
410  // FIXME move this to CP land
411  if (typeof window["cpRecentDocumentURLs"] === 'function')
412  return window.cpRecentDocumentURLs();
413 
414  return [];
415 }
416 
417 - (void)clearRecentDocuments:(id)sender
418 {
419  if (typeof window["cpClearRecentDocuments"] === 'function')
420  window.cpClearRecentDocuments();
421 
422  [self _updateRecentDocumentsMenu];
423 }
424 
425 - (void)noteNewRecentDocument:(CPDocument)aDocument
426 {
427  [self noteNewRecentDocumentURL:[aDocument fileURL]];
428 }
429 
430 - (void)noteNewRecentDocumentURL:(CPURL)aURL
431 {
432  var urlAsString = [aURL isKindOfClass:CPString] ? aURL : [aURL absoluteString];
433  if (typeof window["cpNoteNewRecentDocumentPath"] === 'function')
434  window.cpNoteNewRecentDocumentPath(urlAsString);
435 
436  [self _updateRecentDocumentsMenu];
437 }
438 
439 - (void)_removeAllRecentDocumentsFromMenu:(CPMenu)aMenu
440 {
441  var items = [aMenu itemArray],
442  count = [items count];
443 
444  while (count--)
445  {
446  var item = items[count];
447 
448  if ([item action] === @selector(_openRecentDocument:))
449  [aMenu removeItemAtIndex:count];
450  }
451 }
452 
453 - (void)_updateRecentDocumentsMenu
454 {
455  var menu = [[CPApp mainMenu] _menuWithName:@"_CPRecentDocumentsMenu"],
456  recentDocuments = [self recentDocumentURLs],
457  menuItems = [menu itemArray],
458  documentCount = [recentDocuments count],
459  menuItemCount = [menuItems count];
460 
461  [self _removeAllRecentDocumentsFromMenu:menu];
462 
463  if (menuItemCount)
464  {
465  if (!documentCount)
466  {
467  if ([menuItems[0] isSeparatorItem])
468  [menu removeItemAtIndex:0];
469  }
470  else
471  {
472  if (![menuItems[0] isSeparatorItem])
473  [menu insertItem:[CPMenuItem separatorItem] atIndex:0];
474  }
475  }
476 
477  while (documentCount--)
478  {
479  var path = recentDocuments[documentCount],
480  item = [[CPMenuItem alloc] initWithTitle:[path lastPathComponent] action:@selector(_openRecentDocument:) keyEquivalent:nil];
481 
482  [item setTag:path];
483  [menu insertItem:item atIndex:0];
484  }
485 }
486 
487 - (void)_openRecentDocument:(id)sender
488 {
489  [self openDocumentWithContentsOfURL:[sender tag] display:YES error:nil];
490 }
491 
492 @end
Definition: CPMenu.h:2
CPString pathExtension()
Definition: CPURL.j:198
var isEqual
CPWindowController windowController()
Definition: CPWindow.j:1554
Class documentClassForType:(CPString aType)
CPString className()
Definition: CPObject.j:527
void openUntitledDocumentOfType:display:(CPString aType, [display] BOOL shouldDisplay)
CPMenuItem separatorItem()
Definition: CPMenuItem.j:536
id delegate()
Definition: CALayer.j:965
CPDocument makeDocumentWithContentsOfURL:ofType:delegate:didReadSelector:contextInfo:(CPURL anAbsoluteURL, [ofType] CPString aType, [delegate] id aDelegate, [didReadSelector] SEL aSelector, [contextInfo] id aContextInfo)
void makeWindowControllers()
Definition: CPDocument.j:242
A mutable key-value pair collection.
Definition: CPDictionary.h:2
void display()
Definition: CALayer.j:488
CPDocument makeDocumentForURL:withContentsOfURL:ofType:delegate:didReadSelector:contextInfo:(CPURL anAbsoluteURL, [withContentsOfURL] CPURL absoluteContentsURL, [ofType] CPString aType, [delegate] id aDelegate, [didReadSelector] SEL aSelector, [contextInfo] id aContextInfo)
An immutable string (collection of characters).
Definition: CPString.h:2
CPString absoluteString()
Definition: CPURL.j:105
CPBundle mainBundle()
Definition: CPBundle.j:82
CPString lowercaseString()
Definition: CPString.j:665
id objectForKey:(id aKey)
Definition: CPDictionary.j:515
CPDocument openDocumentWithContentsOfURL:display:error:(CPURL anAbsoluteURL, [display] BOOL shouldDisplay, [error] CPError anError)
void close()
Definition: CPDocument.j:846
BOOL isKindOfClass:(Class aClass)
Definition: CPObject.j:219
Used for encapsulating, presenting, and recovery from errors.
Definition: CPError.h:2
void noteNewRecentDocumentURL:(CPURL aURL)
id init()
Definition: CPObject.j:145
CPURL fileURL()
Definition: CPDocument.j:427
CPDocument documentForURL:(CPURL aURL)
id initWithTitle:action:keyEquivalent:(CPString aTitle, [action] SEL anAction, [keyEquivalent] CPString aKeyEquivalent)
Definition: CPMenuItem.j:121
void noteNewRecentDocument:(CPDocument aDocument)
global CPApp var CPSharedDocumentController
CPString typeForContentsOfURL:error:(CPURL anAbsoluteURL, [error] CPError outError)
CPDocument makeUntitledDocumentOfType:error:(CPString aType, [error]/*{ */CPError/*} */anError)
void addDocument:(CPDocument aDocument)
CPDictionary infoDictionary()
Definition: CPBundle.j:178
Definition: CPURL.h:2
void showWindows()
Definition: CPDocument.j:353
function CPClassFromString(aClassName)
Definition: CPObjJRuntime.j:33
CPMenu menu
id alloc()
Definition: CPObject.j:130