API  0.9.6
 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 
27 
33 @implementation CPDocumentController : CPObject
34 {
35  CPArray _documents;
36  CPArray _documentTypes;
37 }
38 
44 + (id)sharedDocumentController
45 {
47  [[self alloc] init];
48 
50 }
51 
52 /*
53  @ignore
54 */
55 - (id)init
56 {
57  self = [super init];
58 
59  if (self)
60  {
61  _documents = [[CPArray alloc] init];
62 
65 
66  _documentTypes = [[[CPBundle mainBundle] infoDictionary] objectForKey:@"CPBundleDocumentTypes"];
67  }
68  return self;
69 }
70 
71 // Creating and Opening Documents
72 
80 - (CPDocument)documentForURL:(CPURL)aURL
81 {
82  var index = 0,
83  count = [_documents count];
84 
85  for (; index < count; ++index)
86  {
87  var theDocument = _documents[index];
88 
89  if ([[theDocument fileURL] isEqual:aURL])
90  return theDocument;
91  }
92 
93  return nil;
94 }
95 
101 - (void)openUntitledDocumentOfType:(CPString)aType display:(BOOL)shouldDisplay
102 {
103  var theDocument = [self makeUntitledDocumentOfType:aType error:nil];
104 
105  if (theDocument)
106  [self addDocument:theDocument];
107 
108  if (shouldDisplay)
109  {
110  [theDocument makeWindowControllers];
111  [theDocument showWindows];
112  }
113 
114  return theDocument;
115 }
116 
123 - (CPDocument)makeUntitledDocumentOfType:(CPString)aType error:({CPError})anError
124 {
125  return [[[self documentClassForType:aType] alloc] initWithType:aType error:anError];
126 }
127 
135 - (CPDocument)openDocumentWithContentsOfURL:(CPURL)anAbsoluteURL display:(BOOL)shouldDisplay error:(CPError)anError
136 {
137  var result = [self documentForURL:anAbsoluteURL];
138 
139  if (!result)
140  {
141  var type = [self typeForContentsOfURL:anAbsoluteURL error:anError];
142 
143  result = [self makeDocumentWithContentsOfURL:anAbsoluteURL ofType:type delegate:self didReadSelector:@selector(document:didRead:contextInfo:) contextInfo:[CPDictionary dictionaryWithObject:shouldDisplay forKey:@"shouldDisplay"]];
144 
145  [self addDocument:result];
146 
147  if (result)
148  [self noteNewRecentDocument:result];
149  }
150  else if (shouldDisplay)
151  [result showWindows];
152 
153  return result;
154 }
155 
164 - (CPDocument)reopenDocumentForURL:(CPURL)anAbsoluteURL withContentsOfURL:(CPURL)absoluteContentsURL error:(CPError)anError
165 {
166  return [self makeDocumentForURL:anAbsoluteURL withContentsOfURL:absoluteContentsURL ofType:[[_documentTypes objectAtIndex:0] objectForKey:@"CPBundleTypeName"] delegate:self didReadSelector:@selector(document:didRead:contextInfo:) contextInfo:nil];
167 }
168 
178 - (CPDocument)makeDocumentWithContentsOfURL:(CPURL)anAbsoluteURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aSelector contextInfo:(id)aContextInfo
179 {
180  return [[[self documentClassForType:aType] alloc] initWithContentsOfURL:anAbsoluteURL ofType:aType delegate:aDelegate didReadSelector:aSelector contextInfo:aContextInfo];
181 }
182 
194 - (CPDocument)makeDocumentForURL:(CPURL)anAbsoluteURL withContentsOfURL:(CPURL)absoluteContentsURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aSelector contextInfo:(id)aContextInfo
195 {
196  return [[[self documentClassForType:aType] alloc] initForURL:anAbsoluteURL withContentsOfURL:absoluteContentsURL ofType:aType delegate:aDelegate didReadSelector:aSelector contextInfo:aContextInfo];
197 }
198 
199 /*
200  Implemented delegate method
201  @ignore
202 */
203 - (void)document:(CPDocument)aDocument didRead:(BOOL)didRead contextInfo:(id)aContextInfo
204 {
205  if (!didRead)
206  return;
207 
208  [aDocument makeWindowControllers];
209 
210  if ([aContextInfo objectForKey:@"shouldDisplay"])
211  [aDocument showWindows];
212 }
213 
218 - (CFAction)newDocument:(id)aSender
219 {
220  [self openUntitledDocumentOfType:[[_documentTypes objectAtIndex:0] objectForKey:@"CPBundleTypeName"] display:YES];
221 }
222 
223 - (void)openDocument:(id)aSender
224 {
225  var openPanel = [CPOpenPanel openPanel];
226 
227  [openPanel runModal];
228 
229  var URLs = [openPanel URLs],
230  index = 0,
231  count = [URLs count];
232 
233  for (; index < count; ++index)
234  [self openDocumentWithContentsOfURL:[CPURL URLWithString:URLs[index]] display:YES error:nil];
235 }
236 
237 // Managing Documents
238 
243 - (CPArray)documents
244 {
245  return _documents;
246 }
247 
252 - (void)addDocument:(CPDocument)aDocument
253 {
254  [_documents addObject:aDocument];
255 }
256 
261 - (void)removeDocument:(CPDocument)aDocument
262 {
263  [_documents removeObjectIdenticalTo:aDocument];
264 }
265 
266 - (CPString)defaultType
267 {
268  return [_documentTypes[0] objectForKey:@"CPBundleTypeName"];
269 }
270 
271 - (CPString)typeForContentsOfURL:(CPURL)anAbsoluteURL error:(CPError)outError
272 {
273  var index = 0,
274  count = _documentTypes.length,
275 
276  extension = [[anAbsoluteURL pathExtension] lowercaseString],
277  starType = nil;
278 
279  for (; index < count; ++index)
280  {
281  var documentType = _documentTypes[index],
282  extensions = [documentType objectForKey:@"CFBundleTypeExtensions"],
283  extensionIndex = 0,
284  extensionCount = extensions.length;
285 
286  for (; extensionIndex < extensionCount; ++extensionIndex)
287  {
288  var thisExtension = [extensions[extensionIndex] lowercaseString];
289  if (thisExtension === extension)
290  return [documentType objectForKey:@"CPBundleTypeName"];
291 
292  if (thisExtension === "****")
293  starType = [documentType objectForKey:@"CPBundleTypeName"];
294  }
295  }
296 
297  return starType || [self defaultType];
298 }
299 
300 // Managing Document Types
301 
302 /* @ignore */
303 - (CPDictionary)_infoForType:(CPString)aType
304 {
305  var i = 0,
306  count = [_documentTypes count];
307 
308  for (;i < count; ++i)
309  {
310  var documentType = _documentTypes[i];
311 
312  if ([documentType objectForKey:@"CPBundleTypeName"] == aType)
313  return documentType;
314  }
315 
316  return nil;
317 }
318 
324 - (Class)documentClassForType:(CPString)aType
325 {
326  var className = [[self _infoForType:aType] objectForKey:@"CPDocumentClass"];
327 
328  return className ? CPClassFromString(className) : nil;
329 }
330 
331 @end
332 
334 
335 - (void)closeAllDocumentsWithDelegate:(id)aDelegate didCloseAllSelector:(SEL)didCloseSelector contextInfo:(Object)info
336 {
337  var context = {
338  delegate: aDelegate,
339  selector: didCloseSelector,
340  context: info
341  };
342 
343  [self _closeDocumentsStartingWith:nil shouldClose:YES context:context];
344 }
345 
346 // Recursive callback method. Start it by passing in a document of nil.
347 - (void)_closeDocumentsStartingWith:(CPDocument)aDocument shouldClose:(BOOL)shouldClose context:(Object)context
348 {
349  if (shouldClose)
350  {
351  [aDocument close];
352 
353  if ([[self documents] count] > 0)
354  {
355  [[[self documents] lastObject] canCloseDocumentWithDelegate:self
356  shouldCloseSelector:@selector(_closeDocumentsStartingWith:shouldClose:context:)
357  contextInfo:context];
358  return;
359  }
360  }
361 
362  if ([context.delegate respondsToSelector:context.selector])
363  objj_msgSend(context.delegate, context.selector, self, [[self documents] count] === 0, context.context);
364 }
365 
366 @end
367 
369 
370 - (CPArray)recentDocumentURLs
371 {
372  // FIXME move this to CP land
373  if (typeof window["cpRecentDocumentURLs"] === 'function')
374  return window.cpRecentDocumentURLs();
375 
376  return [];
377 }
378 
379 - (void)clearRecentDocuments:(id)sender
380 {
381  if (typeof window["cpClearRecentDocuments"] === 'function')
382  window.cpClearRecentDocuments();
383 
384  [self _updateRecentDocumentsMenu];
385 }
386 
387 - (void)noteNewRecentDocument:(CPDocument)aDocument
388 {
389  [self noteNewRecentDocumentURL:[aDocument fileURL]];
390 }
391 
392 - (void)noteNewRecentDocumentURL:(CPURL)aURL
393 {
394  var urlAsString = [aURL isKindOfClass:CPString] ? aURL : [aURL absoluteString];
395  if (typeof window["cpNoteNewRecentDocumentPath"] === 'function')
396  window.cpNoteNewRecentDocumentPath(urlAsString);
397 
398  [self _updateRecentDocumentsMenu];
399 }
400 
401 - (void)_removeAllRecentDocumentsFromMenu:(CPMenu)aMenu
402 {
403  var items = [aMenu itemArray],
404  count = [items count];
405 
406  while (count--)
407  {
408  var item = items[count];
409 
410  if ([item action] === @selector(_openRecentDocument:))
411  [aMenu removeItemAtIndex:count];
412  }
413 }
414 
415 - (void)_updateRecentDocumentsMenu
416 {
417  var menu = [[CPApp mainMenu] _menuWithName:@"_CPRecentDocumentsMenu"],
418  recentDocuments = [self recentDocumentURLs],
419  menuItems = [menu itemArray],
420  documentCount = [recentDocuments count],
421  menuItemCount = [menuItems count];
422 
423  [self _removeAllRecentDocumentsFromMenu:menu];
424 
425  if (menuItemCount)
426  {
427  if (!documentCount)
428  {
429  if ([menuItems[0] isSeparatorItem])
430  [menu removeItemAtIndex:0];
431  }
432  else
433  {
434  if (![menuItems[0] isSeparatorItem])
435  [menu insertItem:[CPMenuItem separatorItem] atIndex:0];
436  }
437  }
438 
439  while (documentCount--)
440  {
441  var path = recentDocuments[documentCount],
442  item = [[CPMenuItem alloc] initWithTitle:[path lastPathComponent] action:@selector(_openRecentDocument:) keyEquivalent:nil];
443 
444  [item setTag:path];
445  [menu insertItem:item atIndex:0];
446  }
447 }
448 
449 - (void)_openRecentDocument:(id)sender
450 {
451  [self openDocumentWithContentsOfURL:[sender tag] display:YES error:nil];
452 }
453 
454 @end