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