apps-projectcenter/Library/PCProject.m
Sergii Stoian a66f7753e3 Fix bug in releasing PCButton
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/apps/projectcenter/trunk@20529 72102866-910b-0410-8b05-ffd578937521
2005-01-08 22:17:23 +00:00

1511 lines
37 KiB
Objective-C

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