/* GNUstep ProjectCenter - http://www.gnustep.org Copyright (C) 2000-2004 Free Software Foundation Authors: Philippe C.D. Robert Serg Stoyan This file is part of GNUstep. This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This application is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "PCFileManager.h" #include "PCProject.h" #include "PCDefines.h" #include "PCProjectWindow.h" #include "PCProjectBrowser.h" #include "PCProjectLoadedFiles.h" #include "PCProjectInspector.h" #include "PCProjectBuilder.h" #include "PCProjectEditor.h" #include "PCProjectLauncher.h" #include "PCEditor.h" #include "PCLogController.h" NSString *PCProjectDictDidChangeNotification = @"PCProjectDictDidChangeNotification"; NSString *PCProjectDictDidSaveNotification = @"PCProjectDictDidSaveNotification"; @implementation PCProject // ============================================================================ // ==== Init and free // ============================================================================ - (id)init { if ((self = [super init])) { buildOptions = [[NSMutableDictionary alloc] init]; projectBuilder = nil; projectLauncher = nil; loadedSubprojects = [[NSMutableArray alloc] init]; isSubproject = NO; activeSubproject = nil; } return self; } - (id)initWithProjectDictionary:(NSDictionary *)dict path:(NSString *)path; { NSAssert(dict,@"No valid project dictionary!"); if ((self = [self init])) { if ([[path lastPathComponent] isEqualToString:@"PC.project"]) { projectPath = [[path stringByDeletingLastPathComponent] copy]; } else { projectPath = [path copy]; } PCLogStatus(self, @"initWithProjectDictionary"); if (![self assignProjectDict:dict]) { PCLogError(self, @"could not load the project..."); [self autorelease]; return nil; } [self save]; } return self; } - (void)setProjectManager:(PCProjectManager *)aManager { projectManager = aManager; if (isSubproject) { return; } if (!projectBrowser && !isSubproject) { projectBrowser = [[PCProjectBrowser alloc] initWithProject:self]; } if (!projectLoadedFiles && !isSubproject) { projectLoadedFiles = [[PCProjectLoadedFiles alloc] initWithProject:self]; } if (!projectEditor && !isSubproject) { projectEditor = [[PCProjectEditor alloc] initWithProject:self]; } if (!projectWindow && !isSubproject) { projectWindow = [[PCProjectWindow alloc] initWithProject:self]; } } - (BOOL)close:(id)sender { // PCLogInfo(self, @"Closing %@ project", projectName); // Save visible windows and panels positions to project dictionary if (isSubproject == NO) { [self saveProjectWindowsAndPanels]; [projectBrowser setPath:@"/"]; [projectManager setActiveProject:self]; } // Project files (GNUmakefile, PC.project etc.) if (isSubproject == NO && [self isProjectChanged] == YES) { int ret; ret = NSRunAlertPanel(@"Alert", @"Project or subprojects are modified", @"Save and Close",@"Don't save",@"Cancel"); switch (ret) { case NSAlertDefaultReturn: if ([self save] == NO) { return NO; } break; case NSAlertAlternateReturn: break; case NSAlertOtherReturn: return NO; break; } } // Close subprojects while ([loadedSubprojects count]) { [(PCProject *)[loadedSubprojects objectAtIndex:0] close:self]; // We should release subproject here, because it retains us // and we never reach -dealloc in other case. [loadedSubprojects removeObjectAtIndex:0]; } if (isSubproject == YES) { return YES; } // Editors // "Cancel" button on "Save Edited Files" panel selected if ([projectEditor closeAllEditors] == NO) { return NO; } // Project window if (sender != projectWindow) { [projectWindow close]; } // Remove self from loaded projects [projectManager closeProject:self]; return YES; } - (BOOL)saveProjectWindowsAndPanels { NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; NSMutableDictionary *windows = [NSMutableDictionary dictionary]; NSString *projectFile = nil; NSMutableDictionary *projectFileDict = nil; projectFile = [projectPath stringByAppendingPathComponent:@"PC.project"]; projectFileDict = [NSMutableDictionary dictionaryWithContentsOfFile:projectFile]; // Project Window [windows setObject:[projectWindow stringWithSavedFrame] forKey:@"ProjectWindow"]; if ([projectWindow isToolbarVisible] == YES) { [windows setObject:[NSString stringWithString:@"YES"] forKey:@"ShowToolbar"]; } else { [windows setObject:[NSString stringWithString:@"NO"] forKey:@"ShowToolbar"]; } // Write to file and exit if prefernces wasn't set to save panels if (![[ud objectForKey:RememberWindows] isEqualToString:@"YES"]) { [projectFileDict setObject:windows forKey:@"PC_WINDOWS"]; [projectFileDict writeToFile:projectFile atomically:YES]; return YES; } // Project Build if (projectBuilder && [[projectManager buildPanel] isVisible]) { [windows setObject:[[projectManager buildPanel] stringWithSavedFrame] forKey:@"ProjectBuild"]; } else { [windows removeObjectForKey:@"ProjectBuild"]; } // Project Launch if (projectLauncher && [[projectManager launchPanel] isVisible]) { [windows setObject:[[projectManager launchPanel] stringWithSavedFrame] forKey:@"ProjectLaunch"]; } else { [windows removeObjectForKey:@"ProjectLaunch"]; } // Project Inspector /* if ([[projectManager inspectorPanel] isVisible]) { [windows setObject:[[projectManager inspectorPanel] stringWithSavedFrame] forKey:@"ProjectInspector"]; } else { [windows removeObjectForKey:@"ProjectInspector"]; }*/ // Loaded Files if (projectLoadedFiles && [[projectManager loadedFilesPanel] isVisible]) { [windows setObject:[[projectManager loadedFilesPanel] stringWithSavedFrame] forKey:@"LoadedFiles"]; } else { [windows removeObjectForKey:@"LoadedFiles"]; } // Set to project dict for case if project changed // Don't notify about projectDict changes [projectDict setObject:windows forKey:@"PC_WINDOWS"]; // Now save it directly to PC.project file [projectFileDict setObject:windows forKey:@"PC_WINDOWS"]; [projectFileDict writeToFile:projectFile atomically:YES]; // PCLogInfo(self, @"Windows and geometries saved"); return YES; } - (void)dealloc { #ifdef DEVELOPMENT NSLog (@"PCProject %@: dealloc", projectName); #endif [[NSNotificationCenter defaultCenter] removeObserver:self]; RELEASE(projectName); RELEASE(projectPath); RELEASE(projectDict); RELEASE(loadedSubprojects); RELEASE(buildOptions); // Initialized in -setProjectManager: of project and // in setSuperProject: of subproject RELEASE(projectWindow); RELEASE(projectBrowser); RELEASE(projectLoadedFiles); RELEASE(projectEditor); if (projectBuilder) RELEASE(projectBuilder); if (projectLauncher) RELEASE(projectLauncher); if (isSubproject == YES) { RELEASE(rootProject); RELEASE(superProject); } [super dealloc]; } // ============================================================================ // ==== Accessory methods // ============================================================================ - (PCProjectManager *)projectManager { return projectManager; } - (PCProjectWindow *)projectWindow { return projectWindow; } - (PCProjectBrowser *)projectBrowser { return projectBrowser; } - (PCProjectLoadedFiles *)projectLoadedFiles { if (!projectLoadedFiles && !isSubproject) { projectLoadedFiles = [[PCProjectLoadedFiles alloc] initWithProject:self]; } return projectLoadedFiles; } - (PCProjectBuilder *)projectBuilder { if (!projectBuilder && !isSubproject) { projectBuilder = [[PCProjectBuilder alloc] initWithProject:self]; } return projectBuilder; } - (PCProjectLauncher *)projectLauncher { if (!projectLauncher && !isSubproject) { projectLauncher = [[PCProjectLauncher alloc] initWithProject:self]; } return projectLauncher; } - (PCProjectEditor *)projectEditor { return projectEditor; } - (void)setProjectDictObject:(id)object forKey:(NSString *)key notify:(BOOL)yn { id currentObject = [projectDict objectForKey:key]; NSMutableDictionary *notifObject = [NSMutableDictionary dictionary]; if ([object isKindOfClass:[NSString class]] && [currentObject isEqualToString:object]) { return; } [projectDict setObject:object forKey:key]; // Send in notification project itself and project dictionary object key // that was changed [notifObject setObject:self forKey:@"Project"]; [notifObject setObject:key forKey:@"Attribute"]; if (yn == YES) { [[NSNotificationCenter defaultCenter] postNotificationName:PCProjectDictDidChangeNotification object:notifObject]; } } - (void)setProjectName:(NSString *)aName { AUTORELEASE(projectName); projectName = [aName copy]; [projectWindow setFileIconTitle:projectName]; } - (NSString *)projectName { return projectName; } - (BOOL)isProjectChanged { return [projectWindow isDocumentEdited]; } // ============================================================================ // ==== Can be overriden // ============================================================================ - (NSView *)projectAttributesView { return nil; } - (Class)builderClass { return nil; } - (NSString *)projectDescription { return @"Abstract PCProject class!"; } - (BOOL)isExecutable { return NO; } - (NSString *)execToolName { return nil; } - (BOOL)canHavePublicHeaders { return NO; } - (NSArray *)publicHeaders { return nil; } - (void)setHeaderFile:(NSString *)file public:(BOOL)yn { } - (void)setLocalizableFile:(NSString *)file public:(BOOL)yn { } - (NSArray *)buildTargets { return nil; } - (NSArray *)sourceFileKeys { return nil; } - (NSArray *)resourceFileKeys { return nil; } - (NSArray *)otherKeys { return nil; } - (NSArray *)allowableSubprojectTypes { return nil; } - (NSArray *)localizableKeys { return nil; } - (BOOL)isEditableCategory:(NSString *)category { NSString *key = [self keyForCategory:category]; if ([key isEqualToString:PCClasses] || [key isEqualToString:PCHeaders] || [key isEqualToString:PCSupportingFiles] || [key isEqualToString:PCDocuFiles] || [key isEqualToString:PCOtherSources] || [key isEqualToString:PCOtherResources] || [key isEqualToString:PCNonProject]) { return YES; } return NO; } - (BOOL)isEditableFile:(NSString *)filePath { NSString *key = [self keyForCategory:[projectBrowser nameOfSelectedCategory]]; NSString *extension = [filePath pathExtension]; if ([key isEqualToString:PCSupportingFiles] || [key isEqualToString:PCDocuFiles]) { return YES; } if ([extension isEqualToString:@"m"] || [extension isEqualToString:@"h"] || [extension isEqualToString:@"c"] || [extension isEqualToString:@"plist"]) { return YES; } return NO; } - (NSArray *)fileTypesForCategoryKey:(NSString *)key { if ([key isEqualToString:PCClasses]) { return [NSArray arrayWithObjects:@"m",nil]; } else if ([key isEqualToString:PCHeaders]) { return [NSArray arrayWithObjects:@"h",nil]; } else if ([key isEqualToString:PCOtherSources]) { return [NSArray arrayWithObjects:@"c",@"C",@"m",nil]; } else if ([key isEqualToString:PCInterfaces]) { return [NSArray arrayWithObjects:@"gmodel",@"gorm",nil]; } else if ([key isEqualToString:PCImages]) { return [NSImage imageFileTypes]; } else if ([key isEqualToString:PCSubprojects]) { return [NSArray arrayWithObjects:@"subproj",nil]; } else if ([key isEqualToString:PCLibraries]) { return [NSArray arrayWithObjects:@"so",@"a",@"lib",nil]; } return nil; } - (NSString *)categoryKeyForFileType:(NSString *)type { NSEnumerator *keysEnum = [rootKeys objectEnumerator]; NSString *key = nil; while ((key = [keysEnum nextObject])) { if ([[self fileTypesForCategoryKey:key] containsObject:type]) { return key; } } return nil; } - (NSString *)dirForCategoryKey:(NSString *)key { if ([key isEqualToString:PCInterfaces] || [key isEqualToString:PCImages] || [key isEqualToString:PCOtherResources] || [key isEqualToString:PCDocuFiles]) { return [projectPath stringByAppendingPathComponent:@"Resources"]; } return projectPath; } - (NSString *)complementaryTypeForType:(NSString *)type { if ([type isEqualToString:@"m"] || [type isEqualToString:@"c"]) { return [NSString stringWithString:@"h"]; } else if ([type isEqualToString:@"h"]) { return [NSString stringWithString:@"m"]; } return nil; } // Saves backup file - (BOOL)writeMakefile { NSString *mf = [projectPath stringByAppendingPathComponent:@"GNUmakefile"]; NSString *bu = [projectPath stringByAppendingPathComponent:@"GNUmakefile~"]; NSFileManager *fm = [NSFileManager defaultManager]; if ([fm isReadableFileAtPath:mf]) { if ([fm isWritableFileAtPath:bu]) { [fm removeFileAtPath:bu handler:nil]; } if (![fm copyPath:mf toPath:bu handler:nil]) { NSRunAlertPanel(@"Attention!", @"Could not keep a backup of the GNUMakefile!", @"OK",nil,nil); } } return YES; } // ============================================================================ // ==== File Handling // ============================================================================ - (NSString *)projectFileFromFile:(NSString *)file forKey:(NSString *)type { NSString *projectFile = nil; NSString *_path = nil; NSMutableArray *_pathComponents = nil; NSString *_file = nil; NSArray *subprojects = [projectDict objectForKey:PCSubprojects]; NSRange pathRange; NSString *spDir = nil; _path = [file stringByDeletingLastPathComponent]; _pathComponents = [[_path pathComponents] mutableCopy]; _file = [file lastPathComponent]; // Remove "lib" prefix from library name if ([type isEqualToString:PCLibraries]) { _file = [_file stringByDeletingPathExtension]; _file = [_file substringFromIndex:3]; } pathRange = [_path rangeOfString:projectPath]; // File is located in project's directory tree if (pathRange.length && ![type isEqualToString:PCLibraries]) { int i; for (i = 0; i < [subprojects count]; i++) { spDir = [[subprojects objectAtIndex:i] stringByAppendingPathExtension:@"subproj"]; if ([_pathComponents containsObject:spDir]) { break; } spDir = nil; } } if (spDir != nil) { while (![[_pathComponents objectAtIndex:0] isEqualToString:spDir]) { [_pathComponents removeObjectAtIndex:0]; } } else { [_pathComponents removeAllObjects]; } // Construct project file name if ([_pathComponents count]) { projectFile = [NSString pathWithComponents:_pathComponents]; projectFile = [projectFile stringByAppendingPathComponent:_file]; } else { projectFile = [NSString stringWithString:_file]; } RELEASE(_pathComponents); return projectFile; } - (BOOL)doesAcceptFile:(NSString *)file forKey:(NSString *)type { NSString *pFile = [self projectFileFromFile:file forKey:type]; NSArray *sourceKeys = [self sourceFileKeys]; NSArray *resourceKeys = [self resourceFileKeys]; NSEnumerator *keyEnum = nil; NSString *key = nil; NSArray *projectFiles = nil; if ([sourceKeys containsObject:type]) { keyEnum = [sourceKeys objectEnumerator]; } else if ([resourceKeys containsObject:type]) { keyEnum = [resourceKeys objectEnumerator]; } else { return YES; } while ((key = [keyEnum nextObject])) { projectFiles = [projectDict objectForKey:key]; if ([projectFiles containsObject:pFile]) { return NO; } } return YES; } - (BOOL)addAndCopyFiles:(NSArray *)files forKey:(NSString *)key { NSEnumerator *fileEnum = [files objectEnumerator]; NSString *file = nil; NSMutableArray *fileList = [[files mutableCopy] autorelease]; NSString *complementaryType = nil; NSString *complementaryKey = nil; NSString *complementaryDir = nil; NSMutableArray *complementaryFiles = [NSMutableArray array]; PCFileManager *fileManager = [projectManager fileManager]; NSString *directory = [self dirForCategoryKey:key]; complementaryType = [self complementaryTypeForType:[[files objectAtIndex:0] pathExtension]]; if (complementaryType) { complementaryKey = [self categoryKeyForFileType:complementaryType]; complementaryDir = [self dirForCategoryKey:complementaryKey]; } // PCLogInfo(self, @"{%@} {addAndCopyFiles} %@", projectName, fileList); // Validate files while ((file = [fileEnum nextObject])) { if (![self doesAcceptFile:file forKey:key]) { [fileList removeObject:file]; } else if (complementaryType != nil) { NSString *compFile = nil; compFile = [[file stringByDeletingPathExtension] stringByAppendingPathExtension:complementaryType]; if ([[NSFileManager defaultManager] fileExistsAtPath:compFile] && [self doesAcceptFile:compFile forKey:complementaryKey]) { [complementaryFiles addObject:compFile]; } } } // PCLogInfo(self, @"{addAndCopyFiles} %@", fileList); // Copy files if (![key isEqualToString:PCLibraries]) // Don't copy libraries { if (![fileManager copyFiles:fileList intoDirectory:directory]) { NSRunAlertPanel(@"Alert", @"Error adding files to project %@!", @"OK", nil, nil, projectName); return NO; } // PCLogInfo(self, @"Complementary files: %@", complementaryFiles); // Complementaries if (![fileManager copyFiles:complementaryFiles intoDirectory:complementaryDir]) { NSRunAlertPanel(@"Alert", @"Error adding complementary files to project %@!", @"OK", nil, nil, projectName); return NO; } } if ([complementaryFiles count] > 0) { [self addFiles:complementaryFiles forKey:complementaryKey notify:NO]; } // Add files to project [self addFiles:fileList forKey:key notify:YES]; return YES; } - (void)addFiles:(NSArray *)files forKey:(NSString *)type notify:(BOOL)yn { NSEnumerator *enumerator = nil; NSString *file = nil; NSString *pFile = nil; NSArray *types = [projectDict objectForKey:type]; NSMutableArray *projectFiles = [NSMutableArray arrayWithArray:types]; if ([type isEqualToString:PCLibraries]) { NSMutableArray *searchLibs = [NSMutableArray arrayWithCapacity:1]; NSString *path = nil; path = [[files objectAtIndex:0] stringByDeletingLastPathComponent]; [searchLibs setArray:[projectDict objectForKey:PCSearchLibs]]; [searchLibs addObject:path]; [self setProjectDictObject:searchLibs forKey:PCSearchLibs notify:yn]; } enumerator = [files objectEnumerator]; while ((file = [enumerator nextObject])) { pFile = [self projectFileFromFile:file forKey:type]; [projectFiles addObject:pFile]; } [self setProjectDictObject:projectFiles forKey:type notify:yn]; } - (BOOL)removeFiles:(NSArray *)files forKey:(NSString *)key notify:(BOOL)yn { NSEnumerator *enumerator = nil; NSString *filePath = nil; NSString *file = nil; NSMutableArray *projectFiles = nil; // Remove files from project projectFiles = [NSMutableArray arrayWithArray:[projectDict objectForKey:key]]; enumerator = [files objectEnumerator]; while ((file = [enumerator nextObject])) { if ([key isEqualToString:PCSubprojects]) { [self removeSubprojectWithName:file]; } [projectFiles removeObject:file]; // Close editor filePath = [projectPath stringByAppendingPathComponent:file]; [projectEditor closeEditorForFile:filePath]; } [self setProjectDictObject:projectFiles forKey:key notify:yn]; return YES; } - (BOOL)renameFile:(NSString *)fromFile toFile:(NSString *)toFile { NSFileManager *fm = [NSFileManager defaultManager]; NSString *selectedCategory = nil; NSString *selectedCategoryKey = nil; NSString *fromPath = nil; NSString *toPath = nil; NSMutableDictionary *_pDict = nil; NSString *_file = nil; NSMutableArray *_array = nil; BOOL saveToFile = NO; int index = 0; PCEditor *_editor = nil; NSString *_editorPath = nil; NSString *_editorCategory = nil; selectedCategory = [projectBrowser nameOfSelectedCategory]; selectedCategoryKey = [self keyForCategory:selectedCategory]; fromPath = [[self dirForCategoryKey:selectedCategoryKey] stringByAppendingPathComponent:fromFile]; toPath = [[self dirForCategoryKey:selectedCategoryKey] stringByAppendingPathComponent:toFile]; if ([fm fileExistsAtPath:toPath]) { switch (NSRunAlertPanel(@"Rename file", @"File \"%@\" already exist", @"Overwrite file",@"Cancel",nil, toFile)) { case NSAlertDefaultReturn: // Overwrite if ([fm removeFileAtPath:toPath handler:nil] == NO) { return NO; } break; case NSAlertAlternateReturn: // Stop rename return NO; break; } } /* PCLogInfo(self, @"{%@} move %@ to %@ category: %@", projectName, fromPath, toPath, selectedCategory);*/ if ([fm movePath:fromPath toPath:toPath handler:nil] == YES) { if ([self isProjectChanged]) { // Project already has changes saveToFile = YES; } // Make changes to projectDict _array = [projectDict objectForKey:selectedCategoryKey]; index = [_array indexOfObject:fromFile]; [_array replaceObjectAtIndex:index withObject:toFile]; // Put only this change to project file, leaving // other changes in memory(projectDict) if (saveToFile) { _file = [projectPath stringByAppendingPathComponent:@"PC.project"]; _pDict = [NSMutableDictionary dictionaryWithContentsOfFile:_file]; _array = [_pDict objectForKey:selectedCategoryKey]; [_array removeObject:fromFile]; [_array addObject:toFile]; [_pDict setObject:_array forKey:selectedCategoryKey]; [_pDict writeToFile:_file atomically:YES]; } else { [self save]; } // Set browser path to new file name [projectBrowser reloadLastColumnAndSelectFile:toFile]; // Handle editor(if any) information // if ([[[_editor path] lastPathComponent] isEqualToString:fromFile]) _editor = [projectEditor activeEditor]; if (_editor) { _editorPath = [_editor path]; _editorPath = [_editorPath stringByDeletingLastPathComponent]; _editorPath = [_editorPath stringByAppendingPathComponent:toFile]; [_editor setPath:_editorPath]; _editorCategory = [_editor categoryPath]; _editorCategory = [_editorCategory stringByDeletingLastPathComponent]; _editorCategory = [_editorCategory stringByAppendingPathComponent:toFile]; [_editor setCategoryPath:_editorCategory]; } } return YES; } // ============================================================================ // ==== Project handling // ============================================================================ - (BOOL)assignProjectDict:(NSDictionary *)aDict { NSAssert(aDict,@"No valid project dictionary!"); [projectDict release]; projectDict = [[NSMutableDictionary alloc] initWithDictionary:aDict]; PCLogStatus(self, @"assignProjectDict"); [self setProjectName:[projectDict objectForKey:PCProjectName]]; [self writeMakefile]; // Also notify on dictionary changes. Update the interface and so on. [projectDict setObject:[NSUserDefaults userLanguages] forKey:PCUserLanguages]; return YES; } - (NSDictionary *)projectDict { return (NSDictionary *)projectDict; } - (void)setProjectPath:(NSString *)aPath { [projectPath autorelease]; projectPath = [aPath copy]; } - (NSString *)projectPath { return projectPath; } - (NSArray *)rootKeys { // e.g. CLASS_FILES return rootKeys; } - (NSArray *)rootCategories { // e.g. Classes return rootCategories; } - (NSDictionary *)rootEntries { return rootEntries; } // Category - the name we see in project browser, e.g. "Classes" // Key - the uppercase names located in PC.roject, e.g. "CLASS_FILES" - (NSString *)keyForCategory:(NSString *)category { int index = -1; if (![rootCategories containsObject:category]) { return nil; } index = [rootCategories indexOfObject:category]; return [rootKeys objectAtIndex:index]; } - (NSString *)categoryForKey:(NSString *)key { return [rootEntries objectForKey:key]; } - (BOOL)save { NSString *file = [projectPath stringByAppendingPathComponent:@"PC.project"]; NSString *backup = [file stringByAppendingPathExtension:@"backup"]; NSFileManager *fm = [NSFileManager defaultManager]; NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; NSString *keepBackup = [defs objectForKey:KeepBackup]; BOOL shouldKeep = [keepBackup isEqualToString:@"YES"]; int spCount = [loadedSubprojects count]; int i; for (i = 0; i < spCount; i++) { [[loadedSubprojects objectAtIndex:i] save]; } // Remove backup file if exists if ([fm fileExistsAtPath:backup] && ![fm removeFileAtPath:backup handler:nil]) { NSRunAlertPanel(@"Save project", @"Error removing the old project backup!", @"OK",nil,nil); return NO; } // Save backup if (shouldKeep == YES && [fm isReadableFileAtPath:file]) { if ([fm copyPath:file toPath:backup handler:nil] == NO) { NSRunAlertPanel(@"Save project", @"Error when saving project backup file!", @"OK",nil,nil); return NO; } } // Save project file [projectDict setObject:[[NSCalendarDate date] description] forKey:PCLastEditing]; if ([projectDict writeToFile:file atomically:YES] == NO) { return NO; } [[NSNotificationCenter defaultCenter] postNotificationName:PCProjectDictDidSaveNotification object:self]; // Save GNUmakefile if ([self writeMakefile] == NO) { NSRunAlertPanel(@"Save project", @"Error when writing makefile for project %@", @"OK",nil,nil,projectName); return NO; } return YES; } - (BOOL)isValidDictionary:(NSDictionary *)aDict { NSString *_file; NSString *key; Class projClass = [self builderClass]; NSDictionary *origin; NSArray *keys; NSEnumerator *enumerator; _file = [[NSBundle bundleForClass:projClass] pathForResource:@"PC" ofType:@"project"]; origin = [NSMutableDictionary dictionaryWithContentsOfFile:_file]; keys = [origin allKeys]; enumerator = [keys objectEnumerator]; while ((key = [enumerator nextObject])) { if ([aDict objectForKey:key] == nil) { return NO; } } return YES; } - (void)updateProjectDict { Class projClass = [self builderClass]; NSString *_file = nil; NSString *key = nil; NSDictionary *origin = nil; NSArray *keys = nil; NSEnumerator *enumerator = nil; _file = [[NSBundle bundleForClass:projClass] pathForResource:@"PC" ofType:@"project"]; origin = [NSMutableDictionary dictionaryWithContentsOfFile:_file]; keys = [origin allKeys]; enumerator = [keys objectEnumerator]; while ((key = [enumerator nextObject])) { if ([projectDict objectForKey:key] == nil) { // Doesn't call setProjectDictObject:forKey for opimizations [projectDict setObject:[origin objectForKey:key] forKey:key]; } } [self save]; } - (void)validateProjectDict { if ([self isValidDictionary:projectDict] == NO) { [self updateProjectDict]; NSRunAlertPanel(@"Project updated!", @"The project file was converted from previous version!\nPlease make sure that every project attribute contain valid values!", @"OK",nil,nil); } } // ============================================================================ // ==== Subprojects // ============================================================================ - (NSArray *)loadedSubprojects { return loadedSubprojects; } - (PCProject *)activeSubproject { return activeSubproject; } - (BOOL)isSubproject { return isSubproject; } - (void)setIsSubproject:(BOOL)yn { isSubproject = yn; } - (PCProject *)superProject { return superProject; } - (void)setSuperProject:(PCProject *)project { if (superProject != nil) { return; } ASSIGN(superProject, project); // Assigning releases left part ASSIGN(projectBrowser,[project projectBrowser]); ASSIGN(projectLoadedFiles,[project projectLoadedFiles]); ASSIGN(projectEditor,[project projectEditor]); ASSIGN(projectWindow,[project projectWindow]); } - (PCProject *)subprojectWithName:(NSString *)name { int count = [loadedSubprojects count]; int i; PCProject *sp = nil; NSString *spName = nil; NSString *spFile = nil; // Subproject in project but not loaded if ([[projectDict objectForKey:PCSubprojects] containsObject:name]) { /* PCLogInfo(self, @"{%@}Searching for loaded subproject: %@", projectName, name);*/ // Search for subproject with name among loaded subprojects for (i = 0; i < count; i++) { sp = [loadedSubprojects objectAtIndex:i]; spName = [sp projectName]; if ([spName isEqualToString:name]) { break; } sp = nil; } // Subproject not found in array, load it if (sp == nil) { spFile = [projectPath stringByAppendingPathComponent:name]; spFile = [spFile stringByAppendingPathExtension:@"subproj"]; spFile = [spFile stringByAppendingPathComponent:@"PC.project"]; /* PCLogInfo(self, @"Not found! Load subproject: %@ at path: %@", name, spFile);*/ sp = [projectManager loadProjectAt:spFile]; if (sp) { [sp setIsSubproject:YES]; [sp setSuperProject:self]; [sp setProjectManager:projectManager]; [loadedSubprojects addObject:sp]; } } } return sp; } - (void)addSubproject:(PCProject *)aSubproject { NSMutableArray *_subprojects; if (!aSubproject) { return; } _subprojects = [NSMutableArray arrayWithArray:[projectDict objectForKey:PCSubprojects]]; [_subprojects addObject:[aSubproject projectName]]; [loadedSubprojects addObject:aSubproject]; [self setProjectDictObject:_subprojects forKey:PCSubprojects notify:YES]; } - (void)addSubprojectWithName:(NSString *)name { NSMutableArray *_subprojects = nil; if (!name) { return; } _subprojects = [NSMutableArray arrayWithArray:[projectDict objectForKey:PCSubprojects]]; [_subprojects addObject:name]; [self setProjectDictObject:_subprojects forKey:PCSubprojects notify:YES]; } - (BOOL)removeSubprojectWithName:(NSString *)subprojectName { NSString *extension = [subprojectName pathExtension]; NSString *sName = subprojectName; if (extension && [extension isEqualToString:@"subproj"]) { sName = [subprojectName stringByDeletingPathExtension]; } return [self removeSubproject:[self subprojectWithName:sName]]; } - (BOOL)removeSubproject:(PCProject *)aSubproject { if ([loadedSubprojects containsObject:aSubproject]) { [aSubproject close:self]; [loadedSubprojects removeObject:aSubproject]; } return YES; } @end @implementation PCProject (CategoryPaths) // TODO: Think about moving all category related methods into PCProjectBrowser - (NSArray *)contentAtCategoryPath:(NSString *)categoryPath { NSString *key = [self keyForRootCategoryInCategoryPath:categoryPath]; NSArray *pathArray = nil; pathArray = [categoryPath componentsSeparatedByString:@"/"]; /* PCLogInfo(self, @"{%@}{contentAtCategoryPath:} %@", projectName, categoryPath);*/ // Click on /Category if ([pathArray count] == 2) { if ([projectManager activeProject] != self) { [projectManager setActiveProject:self]; } activeSubproject = nil; } if ([categoryPath isEqualToString:@""] || [categoryPath isEqualToString:@"/"]) { if ([projectManager activeProject] != self) { [projectManager setActiveProject:self]; } return rootCategories; } else if ([key isEqualToString:PCSubprojects] && [pathArray count] > 2) { // Click on "/Subprojects/Name+" PCProject *_subproject = nil; NSString *spCategoryPath = nil; NSMutableArray *mCategoryPath = [NSMutableArray arrayWithArray:pathArray]; _subproject = [self subprojectWithName:[pathArray objectAtIndex:2]]; activeSubproject = _subproject; [mCategoryPath removeObjectAtIndex:1]; [mCategoryPath removeObjectAtIndex:1]; spCategoryPath = [mCategoryPath componentsJoinedByString:@"/"]; return [_subproject contentAtCategoryPath:spCategoryPath]; } else if ([[[categoryPath lastPathComponent] pathExtension] isEqualToString:@"m"] || [[[categoryPath lastPathComponent] pathExtension] isEqualToString:@"h"]) { // ".m" file return [[projectEditor activeEditor] listOfClasses]; } return [projectDict objectForKey:key]; } - (BOOL)hasChildrenAtCategoryPath:(NSString *)categoryPath { NSString *listEntry = nil; PCProject *activeProject = [projectManager activeProject]; if (self != activeProject) { return [activeProject hasChildrenAtCategoryPath:categoryPath]; } listEntry = [[categoryPath componentsSeparatedByString:@"/"] lastObject]; // Categories if ([rootCategories containsObject:listEntry]) { return YES; } // Subprojects if ([[projectDict objectForKey:PCSubprojects] containsObject:listEntry] && [[projectBrowser nameOfSelectedCategory] isEqualToString:@"Subprojects"]) { return YES; } // Class and header files /* if ([[listEntry pathExtension] isEqualToString:@"m"] || [[listEntry pathExtension] isEqualToString:@"h"]) { return YES; }*/ // TODO: Libraries // if ([[projectBrowser nameOfSelectedCategory] isEqualToString:@"Libraries"]) // { // } return NO; } - (NSString *)rootCategoryForCategoryPath:(NSString *)categoryPath { NSArray *pathComponents = nil; if ([categoryPath isEqualToString:@"/"] || [categoryPath isEqualToString:@""]) { return nil; } pathComponents = [categoryPath componentsSeparatedByString:@"/"]; return [pathComponents objectAtIndex:1]; } /*- (NSString *)categoryForCategoryPath:(NSString *)categoryPath { NSString *category = nil; NSString *key = nil; NSArray *pathComponents = nil; int i = 0; category = [self rootCategoryForCategoryPath:categoryPath]; if (category == nil) { return nil; } key = [self keyForCategory:category]; pathComponents = [categoryPath componentsSeparatedByString:@"/"]; if ([key isEqualToString:PCSubprojects]) { // /Subprojects/Name/Classes/Class.m, should return Classes // 0 1 2 3 4 // ("",Subprojects,Name,Classes,Class.m) // 0 1 2 3 4 // ("",Subprojects,Name,Subprojects,Name) if ([pathComponents count] > 4 && activeSubproject) { i = [pathComponents count] - 1; for (; i >= 0; i--) { category = [pathComponents objectAtIndex:i]; if ([[activeSubproject rootCategories] containsObject:category]) { return category; } } } } return category; }*/ /*- (NSString *)keyForCategoryPath:(NSString *)categoryPath { return [self keyForCategory:[self categoryForCategoryPath:categoryPath]]; }*/ - (NSString *)keyForRootCategoryInCategoryPath:(NSString *)categoryPath { NSString *category = nil; NSString *key = nil; if (categoryPath == nil || [categoryPath isEqualToString:@""] || [categoryPath isEqualToString:@"/"]) { return nil; } category = [self rootCategoryForCategoryPath:categoryPath]; key = [self keyForCategory:category]; /* PCLogInfo(self, @"{%@}(keyForRootCategoryInCategoryPath): %@ key:%@", projectName, categoryPath, key);*/ return key; } @end