/* 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