![]() |
API 0.9.5
|
00001 /* 00002 * CPDocumentController.j 00003 * AppKit 00004 * 00005 * Created by Francisco Tolmasky. 00006 * Copyright 2008, 280 North, Inc. 00007 * 00008 * This library is free software; you can redistribute it and/or 00009 * modify it under the terms of the GNU Lesser General Public 00010 * License as published by the Free Software Foundation; either 00011 * version 2.1 of the License, or (at your option) any later version. 00012 * 00013 * This library is distributed in the hope that it will be useful, 00014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00016 * Lesser General Public License for more details. 00017 * 00018 * You should have received a copy of the GNU Lesser General Public 00019 * License along with this library; if not, write to the Free Software 00020 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00021 */ 00022 00023 00024 00025 00026 var CPSharedDocumentController = nil; 00027 00033 @implementation CPDocumentController : CPObject 00034 { 00035 CPArray _documents; 00036 CPArray _documentTypes; 00037 } 00038 00044 + (id)sharedDocumentController 00045 { 00046 if (!CPSharedDocumentController) 00047 [[self alloc] init]; 00048 00049 return CPSharedDocumentController; 00050 } 00051 00052 /* 00053 @ignore 00054 */ 00055 - (id)init 00056 { 00057 self = [super init]; 00058 00059 if (self) 00060 { 00061 _documents = [[CPArray alloc] init]; 00062 00063 if (!CPSharedDocumentController) 00064 CPSharedDocumentController = self; 00065 00066 _documentTypes = [[[CPBundle mainBundle] infoDictionary] objectForKey:@"CPBundleDocumentTypes"]; 00067 } 00068 return self; 00069 } 00070 00071 // Creating and Opening Documents 00072 00080 - (CPDocument)documentForURL:(CPURL)aURL 00081 { 00082 var index = 0, 00083 count = [_documents count]; 00084 00085 for (; index < count; ++index) 00086 { 00087 var theDocument = _documents[index]; 00088 00089 if ([[theDocument fileURL] isEqual:aURL]) 00090 return theDocument; 00091 } 00092 00093 return nil; 00094 } 00095 00101 - (void)openUntitledDocumentOfType:(CPString)aType display:(BOOL)shouldDisplay 00102 { 00103 var theDocument = [self makeUntitledDocumentOfType:aType error:nil]; 00104 00105 if (theDocument) 00106 [self addDocument:theDocument]; 00107 00108 if (shouldDisplay) 00109 { 00110 [theDocument makeWindowControllers]; 00111 [theDocument showWindows]; 00112 } 00113 00114 return theDocument; 00115 } 00116 00123 - (CPDocument)makeUntitledDocumentOfType:(CPString)aType error:({CPError})anError 00124 { 00125 return [[[self documentClassForType:aType] alloc] initWithType:aType error:anError]; 00126 } 00127 00135 - (CPDocument)openDocumentWithContentsOfURL:(CPURL)anAbsoluteURL display:(BOOL)shouldDisplay error:(CPError)anError 00136 { 00137 var result = [self documentForURL:anAbsoluteURL]; 00138 00139 if (!result) 00140 { 00141 var type = [self typeForContentsOfURL:anAbsoluteURL error:anError]; 00142 00143 result = [self makeDocumentWithContentsOfURL:anAbsoluteURL ofType:type delegate:self didReadSelector:@selector(document:didRead:contextInfo:) contextInfo:[CPDictionary dictionaryWithObject:shouldDisplay forKey:@"shouldDisplay"]]; 00144 00145 [self addDocument:result]; 00146 00147 if (result) 00148 [self noteNewRecentDocument:result]; 00149 } 00150 else if (shouldDisplay) 00151 [result showWindows]; 00152 00153 return result; 00154 } 00155 00164 - (CPDocument)reopenDocumentForURL:(CPURL)anAbsoluteURL withContentsOfURL:(CPURL)absoluteContentsURL error:(CPError)anError 00165 { 00166 return [self makeDocumentForURL:anAbsoluteURL withContentsOfURL:absoluteContentsURL ofType:[[_documentTypes objectAtIndex:0] objectForKey:@"CPBundleTypeName"] delegate:self didReadSelector:@selector(document:didRead:contextInfo:) contextInfo:nil]; 00167 } 00168 00178 - (CPDocument)makeDocumentWithContentsOfURL:(CPURL)anAbsoluteURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aSelector contextInfo:(id)aContextInfo 00179 { 00180 return [[[self documentClassForType:aType] alloc] initWithContentsOfURL:anAbsoluteURL ofType:aType delegate:aDelegate didReadSelector:aSelector contextInfo:aContextInfo]; 00181 } 00182 00194 - (CPDocument)makeDocumentForURL:(CPURL)anAbsoluteURL withContentsOfURL:(CPURL)absoluteContentsURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aSelector contextInfo:(id)aContextInfo 00195 { 00196 return [[[self documentClassForType:aType] alloc] initForURL:anAbsoluteURL withContentsOfURL:absoluteContentsURL ofType:aType delegate:aDelegate didReadSelector:aSelector contextInfo:aContextInfo]; 00197 } 00198 00199 /* 00200 Implemented delegate method 00201 @ignore 00202 */ 00203 - (void)document:(CPDocument)aDocument didRead:(BOOL)didRead contextInfo:(id)aContextInfo 00204 { 00205 if (!didRead) 00206 return; 00207 00208 [aDocument makeWindowControllers]; 00209 00210 if ([aContextInfo objectForKey:@"shouldDisplay"]) 00211 [aDocument showWindows]; 00212 } 00213 00218 - (CFAction)newDocument:(id)aSender 00219 { 00220 [self openUntitledDocumentOfType:[[_documentTypes objectAtIndex:0] objectForKey:@"CPBundleTypeName"] display:YES]; 00221 } 00222 00223 - (void)openDocument:(id)aSender 00224 { 00225 var openPanel = [CPOpenPanel openPanel]; 00226 00227 [openPanel runModal]; 00228 00229 var URLs = [openPanel URLs], 00230 index = 0, 00231 count = [URLs count]; 00232 00233 for (; index < count; ++index) 00234 [self openDocumentWithContentsOfURL:[CPURL URLWithString:URLs[index]] display:YES error:nil]; 00235 } 00236 00237 // Managing Documents 00238 00243 - (CPArray)documents 00244 { 00245 return _documents; 00246 } 00247 00252 - (void)addDocument:(CPDocument)aDocument 00253 { 00254 [_documents addObject:aDocument]; 00255 } 00256 00261 - (void)removeDocument:(CPDocument)aDocument 00262 { 00263 [_documents removeObjectIdenticalTo:aDocument]; 00264 } 00265 00266 - (CPString)defaultType 00267 { 00268 return [_documentTypes[0] objectForKey:@"CPBundleTypeName"]; 00269 } 00270 00271 - (CPString)typeForContentsOfURL:(CPURL)anAbsoluteURL error:(CPError)outError 00272 { 00273 var index = 0, 00274 count = _documentTypes.length, 00275 00276 extension = [[anAbsoluteURL pathExtension] lowercaseString], 00277 starType = nil; 00278 00279 for (; index < count; ++index) 00280 { 00281 var documentType = _documentTypes[index], 00282 extensions = [documentType objectForKey:@"CFBundleTypeExtensions"], 00283 extensionIndex = 0, 00284 extensionCount = extensions.length; 00285 00286 for (; extensionIndex < extensionCount; ++extensionIndex) 00287 { 00288 var thisExtension = [extensions[extensionIndex] lowercaseString]; 00289 if (thisExtension === extension) 00290 return [documentType objectForKey:@"CPBundleTypeName"]; 00291 00292 if (thisExtension === "****") 00293 starType = [documentType objectForKey:@"CPBundleTypeName"]; 00294 } 00295 } 00296 00297 return starType || [self defaultType]; 00298 } 00299 00300 // Managing Document Types 00301 00302 /* @ignore */ 00303 - (CPDictionary)_infoForType:(CPString)aType 00304 { 00305 var i = 0, 00306 count = [_documentTypes count]; 00307 00308 for (;i < count; ++i) 00309 { 00310 var documentType = _documentTypes[i]; 00311 00312 if ([documentType objectForKey:@"CPBundleTypeName"] == aType) 00313 return documentType; 00314 } 00315 00316 return nil; 00317 } 00318 00324 - (Class)documentClassForType:(CPString)aType 00325 { 00326 var className = [[self _infoForType:aType] objectForKey:@"CPDocumentClass"]; 00327 00328 return className ? CPClassFromString(className) : nil; 00329 } 00330 00331 @end 00332 00333 @implementation CPDocumentController (Closing) 00334 00335 - (void)closeAllDocumentsWithDelegate:(id)aDelegate didCloseAllSelector:(SEL)didCloseSelector contextInfo:(Object)info 00336 { 00337 var context = { 00338 delegate: aDelegate, 00339 selector: didCloseSelector, 00340 context: info 00341 }; 00342 00343 [self _closeDocumentsStartingWith:nil shouldClose:YES context:context]; 00344 } 00345 00346 // Recursive callback method. Start it by passing in a document of nil. 00347 - (void)_closeDocumentsStartingWith:(CPDocument)aDocument shouldClose:(BOOL)shouldClose context:(Object)context 00348 { 00349 if (shouldClose) 00350 { 00351 [aDocument close]; 00352 00353 if ([[self documents] count] > 0) 00354 { 00355 [[[self documents] lastObject] canCloseDocumentWithDelegate:self 00356 shouldCloseSelector:@selector(_closeDocumentsStartingWith:shouldClose:context:) 00357 contextInfo:context]; 00358 return; 00359 } 00360 } 00361 00362 if ([context.delegate respondsToSelector:context.selector]) 00363 objj_msgSend(context.delegate, context.selector, self, [[self documents] count] === 0, context.context); 00364 } 00365 00366 @end 00367 00368 @implementation CPDocumentController (Recents) 00369 00370 - (CPArray)recentDocumentURLs 00371 { 00372 // FIXME move this to CP land 00373 if (typeof window["cpRecentDocumentURLs"] === 'function') 00374 return window.cpRecentDocumentURLs(); 00375 00376 return []; 00377 } 00378 00379 - (void)clearRecentDocuments:(id)sender 00380 { 00381 if (typeof window["cpClearRecentDocuments"] === 'function') 00382 window.cpClearRecentDocuments(); 00383 00384 [self _updateRecentDocumentsMenu]; 00385 } 00386 00387 - (void)noteNewRecentDocument:(CPDocument)aDocument 00388 { 00389 [self noteNewRecentDocumentURL:[aDocument fileURL]]; 00390 } 00391 00392 - (void)noteNewRecentDocumentURL:(CPURL)aURL 00393 { 00394 var urlAsString = [aURL isKindOfClass:CPString] ? aURL : [aURL absoluteString]; 00395 if (typeof window["cpNoteNewRecentDocumentPath"] === 'function') 00396 window.cpNoteNewRecentDocumentPath(urlAsString); 00397 00398 [self _updateRecentDocumentsMenu]; 00399 } 00400 00401 - (void)_removeAllRecentDocumentsFromMenu:(CPMenu)aMenu 00402 { 00403 var items = [aMenu itemArray], 00404 count = [items count]; 00405 00406 while (count--) 00407 { 00408 var item = items[count]; 00409 00410 if ([item action] === @selector(_openRecentDocument:)) 00411 [aMenu removeItemAtIndex:count]; 00412 } 00413 } 00414 00415 - (void)_updateRecentDocumentsMenu 00416 { 00417 var menu = [[CPApp mainMenu] _menuWithName:@"_CPRecentDocumentsMenu"], 00418 recentDocuments = [self recentDocumentURLs], 00419 menuItems = [menu itemArray], 00420 documentCount = [recentDocuments count], 00421 menuItemCount = [menuItems count]; 00422 00423 [self _removeAllRecentDocumentsFromMenu:menu]; 00424 00425 if (menuItemCount) 00426 { 00427 if (!documentCount) 00428 { 00429 if ([menuItems[0] isSeparatorItem]) 00430 [menu removeItemAtIndex:0]; 00431 } 00432 else 00433 { 00434 if (![menuItems[0] isSeparatorItem]) 00435 [menu insertItem:[CPMenuItem separatorItem] atIndex:0]; 00436 } 00437 } 00438 00439 while (documentCount--) 00440 { 00441 var path = recentDocuments[documentCount], 00442 item = [[CPMenuItem alloc] initWithTitle:[path lastPathComponent] action:@selector(_openRecentDocument:) keyEquivalent:nil]; 00443 00444 [item setTag:path]; 00445 [menu insertItem:item atIndex:0]; 00446 } 00447 } 00448 00449 - (void)_openRecentDocument:(id)sender 00450 { 00451 [self openDocumentWithContentsOfURL:[sender tag] display:YES error:nil]; 00452 } 00453 00454 @end