/* GNUstep ProjectCenter - http://www.gnustep.org Copyright (C) 2002-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 "PCDefines.h" #include "PCProjectEditor.h" #include "PCEditor.h" #include "PCEditorView.h" #include "PCLogController.h" @implementation PCEditor (UInterface) - (void)_createWindow { unsigned int style; NSRect rect; float windowWidth; // PCLogInfo(self, @"[_createWindow]"); style = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask; windowWidth = [[NSFont userFixedPitchFontOfSize:0.0] widthOfString:@"A"]; windowWidth *= 80; windowWidth += 35+80; rect = NSMakeRect(100,100,windowWidth,320); _window = [[NSWindow alloc] initWithContentRect:rect styleMask:style backing:NSBackingStoreBuffered defer:YES]; [_window setReleasedWhenClosed:YES]; [_window setMinSize:NSMakeSize(512,320)]; [_window setDelegate:self]; rect = [[_window contentView] frame]; // Scroll view _extScrollView = [[NSScrollView alloc] initWithFrame:rect]; [_extScrollView setHasHorizontalScroller:NO]; [_extScrollView setHasVerticalScroller:YES]; [_extScrollView setAutoresizingMask: (NSViewWidthSizable|NSViewHeightSizable)]; rect = [[_extScrollView contentView] frame]; // Editor view _extEditorView = [self _createEditorViewWithFrame:rect]; // Include editor view [_extScrollView setDocumentView:_extEditorView]; [_extEditorView setNeedsDisplay:YES]; RELEASE(_extEditorView); // Include scroll view [_window setContentView:_extScrollView]; [_window makeFirstResponder:_extEditorView]; RELEASE(_extScrollView); } - (void)_createInternalView { NSRect rect = NSMakeRect(0,0,512,320); // Scroll view _intScrollView = [[NSScrollView alloc] initWithFrame:rect]; [_intScrollView setHasHorizontalScroller:NO]; [_intScrollView setHasVerticalScroller:YES]; [_intScrollView setBorderType:NSBezelBorder]; [_intScrollView setAutoresizingMask:(NSViewWidthSizable|NSViewHeightSizable)]; rect = [[_intScrollView contentView] frame]; // Text view _intEditorView = [self _createEditorViewWithFrame:rect]; /* * Setting up ext view / scroll view / window */ [_intScrollView setDocumentView:_intEditorView]; [_intEditorView setNeedsDisplay:YES]; RELEASE(_intEditorView); } - (PCEditorView *)_createEditorViewWithFrame:(NSRect)fr { PCEditorView *ev; NSTextContainer *tc; NSLayoutManager *lm; /* * setting up the objects needed to manage the view but using the * shared textStorage. */ lm = [[NSLayoutManager alloc] init]; tc = [[NSTextContainer alloc] initWithContainerSize:fr.size]; [lm addTextContainer:tc]; RELEASE(tc); [_storage addLayoutManager:lm]; RELEASE(lm); ev = [[PCEditorView alloc] initWithFrame:fr textContainer:tc]; [ev setEditor:self]; [ev setMinSize:NSMakeSize( 0, 0)]; [ev setMaxSize:NSMakeSize(1e7, 1e7)]; [ev setRichText:YES]; [ev setEditable:YES]; [ev setVerticallyResizable:YES]; [ev setHorizontallyResizable:NO]; [ev setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; [ev setTextContainerInset:NSMakeSize( 5, 5)]; [[ev textContainer] setWidthTracksTextView:YES]; [[ev textContainer] setContainerSize:NSMakeSize(fr.size.width, 1e7)]; return ev; } @end @implementation PCEditor // =========================================================================== // ==== Initialization // =========================================================================== - (id)initWithPath:(NSString *)file categoryPath:(NSString *)categoryPath projectEditor:(PCProjectEditor *)aProjectEditor { if ((self = [super init])) { NSString *t; NSAttributedString *as; NSDictionary *at; NSFont *ft; projectEditor = aProjectEditor; _isEdited = NO; _isWindowed = NO; _window = nil; _path = [file copy]; _categoryPath = [categoryPath copy]; ft = [NSFont userFixedPitchFontOfSize:0.0]; at = [NSDictionary dictionaryWithObject:ft forKey:NSFontAttributeName]; t = [NSString stringWithContentsOfFile:file]; as = [[NSAttributedString alloc] initWithString:t attributes:at]; _storage = [[NSTextStorage alloc] init]; [_storage setAttributedString:as]; RELEASE(as); if (categoryPath) // category == nil if we're non project editor { NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; if (![[ud objectForKey:SeparateEditor] isEqualToString:@"YES"]) { [self _createInternalView]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidChange:) name:NSTextDidChangeNotification object:_intEditorView]; } } [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidChange:) name:NSTextDidChangeNotification object:_extEditorView]; // Inform about file opening [[NSNotificationCenter defaultCenter] postNotificationName:PCEditorDidOpenNotification object:self]; } return self; } - (id)initExternalEditor:(NSString *)editor withPath:(NSString *)file projectEditor:(PCProjectEditor *)aProjectEditor { NSTask *editorTask = nil; NSArray *ea = nil; NSMutableArray *args = nil; NSString *app = nil; if (!(self = [super init])) { return nil; } projectEditor = aProjectEditor; _extScrollView = nil; _extEditorView = nil; _intScrollView = nil; _intEditorView = nil; _storage = nil; _path = [file copy]; _categoryPath = nil; _window = nil; _isEdited = NO; _isWindowed = NO; _isExternal = YES; // Task ea = [editor componentsSeparatedByString:@" "]; args = [NSMutableArray arrayWithArray:ea]; app = [ea objectAtIndex:0]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector (externalEditorDidClose:) name:NSTaskDidTerminateNotification object:nil]; editorTask = [[NSTask alloc] init]; [editorTask setLaunchPath:app]; [args removeObjectAtIndex:0]; [args addObject:file]; [editorTask setArguments:args]; [editorTask launch]; // AUTORELEASE(editorTask); // Inform about file opening [[NSNotificationCenter defaultCenter] postNotificationName:PCEditorDidOpenNotification object:self]; return self; } - (void)externalEditorDidClose:(NSNotification *)aNotif { NSString *path = [[[aNotif object] arguments] lastObject]; if (![path isEqualToString:_path]) { PCLogError(self, @"external editor task terminated"); return; } PCLogStatus(self, @"Our Editor task terminated"); // Inform about closing [[NSNotificationCenter defaultCenter] postNotificationName:PCEditorDidCloseNotification object:self]; } - (void)dealloc { #ifdef DEVELOPMENT NSLog(@"PCEditor: %@ dealloc", _path); #endif [[NSNotificationCenter defaultCenter] removeObserver:self]; // _window is setReleasedWhenClosed:YES RELEASE(_path); RELEASE(_categoryPath); RELEASE(_intScrollView); [super dealloc]; } - (void)show { if (_isWindowed) { [_window makeKeyAndOrderFront:nil]; } } - (void)setWindowed:(BOOL)yn { if ( (yn && _isWindowed) || (!yn && !_isWindowed) ) { return; } if (yn && !_isWindowed) { [self _createWindow]; [_window setTitle:[NSString stringWithFormat: @"%@", [_path stringByAbbreviatingWithTildeInPath]]]; } else if (!yn && _isWindowed) { [_window close]; } _isWindowed = yn; } - (BOOL)isWindowed { return _isWindowed; } // =========================================================================== // ==== Accessor methods // =========================================================================== - (PCProjectEditor *)projectEditor { return projectEditor; } - (NSWindow *)editorWindow { return _window; } - (PCEditorView *)editorView { return _intEditorView; } - (NSView *)componentView { return _intScrollView; } - (NSString *)path { return _path; } - (void)setPath:(NSString *)path { NSMutableDictionary *notifDict = [[NSMutableDictionary dictionary] retain]; // Prepare notification object [notifDict setObject:self forKey:@"Editor"]; [notifDict setObject:_path forKey:@"OldFile"]; [notifDict setObject:path forKey:@"NewFile"]; // Set path [_path autorelease]; _path = [path copy]; // Post notification [[NSNotificationCenter defaultCenter] postNotificationName:PCEditorDidChangeFileNameNotification object:notifDict]; [notifDict autorelease]; } - (NSString *)categoryPath { return _categoryPath; } - (void)setCategoryPath:(NSString *)path { [_categoryPath autorelease]; _categoryPath = [path copy]; } - (BOOL)isEdited { return _isEdited; } - (void)setIsEdited:(BOOL)yn { if (_window) { [_window setDocumentEdited:yn]; } _isEdited = yn; } // =========================================================================== // ==== Object managment // =========================================================================== - (BOOL)saveFileIfNeeded { if ((_isEdited)) { return [self saveFile]; } return YES; } - (BOOL)saveFile { [self setIsEdited:NO]; // Operate on the text storage! return [[_storage string] writeToFile:_path atomically:YES]; } - (BOOL)saveFileTo:(NSString *)path { // Operate on the text storage! return [[_storage string] writeToFile:path atomically:YES]; } - (BOOL)revertFileToSaved { NSString *text = [NSString stringWithContentsOfFile:_path]; NSAttributedString *as = nil; NSDictionary *at = nil; NSFont *ft = nil; // This is temporary ft = [NSFont userFixedPitchFontOfSize:0.0]; at = [NSDictionary dictionaryWithObject:ft forKey:NSFontAttributeName]; as = [[NSAttributedString alloc] initWithString:text attributes:at]; [self setIsEdited:NO]; // Operate on the text storage! [_storage setAttributedString:as]; RELEASE(as); [_intEditorView setNeedsDisplay:YES]; [_extEditorView setNeedsDisplay:YES]; return YES; } - (BOOL)closeFile:(id)sender save:(BOOL)save { if ((save == NO) || [self editorShouldClose]) { // Close window first if visible if (_isWindowed && [_window isVisible] && (sender != _window)) { [_window close]; } // Inform about closing [[NSNotificationCenter defaultCenter] postNotificationName:PCEditorDidCloseNotification object:self]; return YES; } return NO; } - (BOOL)editorShouldClose { if (_isEdited) { BOOL ret; if (_isWindowed && [_window isVisible]) { [_window makeKeyAndOrderFront:self]; } ret = NSRunAlertPanel(@"Close File", @"File %@ has been modified", @"Save and Close", @"Don't save", @"Cancel", [_path lastPathComponent]); if (ret == YES) { if ([self saveFile] == NO) { NSRunAlertPanel(@"Alert", @"Error when saving file '%@'!", @"OK", nil, nil, [_path lastPathComponent]); return NO; } else { return YES; } } else if (ret == NO) // Close but don't save { return YES; } else // Cancel closing { return NO; } [self setIsEdited:NO]; } return YES; } // =========================================================================== // ==== Window delegate // =========================================================================== - (BOOL)windowShouldClose:(id)sender { if ([sender isEqual:_window]) { if (_intScrollView) { // Just close if this file also displayed in int view _isWindowed = NO; return YES; } else { return [self closeFile:_window save:YES]; } } return NO; } - (void)windowDidBecomeKey:(NSNotification *)aNotification { if ([[aNotification object] isEqual:_window] && [_window isVisible]) { [_window makeFirstResponder:_extEditorView]; } } - (void)windowDidResignKey:(NSNotification *)aNotification { if ([[aNotification object] isEqual:_window] && [_window isVisible]) { [_window makeFirstResponder:_window]; } } // =========================================================================== // ==== TextView (_intEditorView, _extEditorView) delegate // =========================================================================== - (void)textDidChange:(NSNotification *)aNotification { id object = [aNotification object]; if ([object isKindOfClass:[PCEditorView class]] && (object == _intEditorView || object == _extEditorView)) { [self setIsEdited:YES]; } } - (BOOL)becomeFirstResponder { [[NSNotificationCenter defaultCenter] postNotificationName:PCEditorDidBecomeActiveNotification object:self]; return YES; } - (BOOL)resignFirstResponder { [[NSNotificationCenter defaultCenter] postNotificationName:PCEditorDidResignActiveNotification object:self]; return YES; } // =========================================================================== // ==== Parser and scrolling // =========================================================================== - (NSString *)classNameFromString:(NSString *)string { NSString *className = nil; NSMutableArray *lineComps = nil; // @implementation ClassName (Category) // // @implementation ClassName(Category) // @implementation ClassName( Category) // @implementation ClassName(Category ) // // @implementation ClassName ( Category ) // @implementation ClassName (Category ) // @implementation ClassName ( Category) lineComps = [[string componentsSeparatedByString:@" "] mutableCopy]; if ([lineComps count] > 2) // && [[[lineComps objectAtIndex:2] substringWithRange:NSMakeRange(0,1)] isEqualToString:@"("]) { [lineComps removeObjectAtIndex:0]; className = [lineComps componentsJoinedByString:@""]; RELEASE(lineComps); return [NSString stringWithFormat:@"@%@", className]; } else { return [NSString stringWithFormat:@"@%@", [lineComps objectAtIndex:1]]; } return nil; } - (NSString *)methodNameFromString:(NSString *)string { NSString *methodName = nil; return methodName; } - (NSMutableArray *)linesWithKeyword:(NSString *)keyword atBOL:(BOOL)yn { NSMutableArray *lines = [[NSMutableArray alloc] init]; NSString *text = [_storage string]; NSRange range = {0, [text length]}; NSRange subRange = {0, 0}; NSRange lineRange = {0, 0}; NSString *tmpStr = nil; NSString *lineString = nil; while (range.location < [text length]) { subRange = [text rangeOfString:keyword options:NSLiteralSearch range:range]; NSLog(@"subRange: {%i, %i}", subRange.location, subRange.length); if (subRange.location == NSNotFound) { break; } // Set range for next search range.location = subRange.location + subRange.length; range.length = [text length] - range.location; NSLog(@"range: {%i, %i}", range.location, range.length); // If keyword is located not at the beginning of line then skip it. if (yn) { tmpStr = [text substringWithRange:NSMakeRange(subRange.location-1,1)]; if (![tmpStr isEqualToString:@"\n"]) { NSLog(@"CONTINUE %i %i", range.location, range.length); continue; } } // Get line range where @implementation is located lineRange = [text lineRangeForRange:subRange]; lineString = [text substringWithRange:lineRange]; NSLog(@"0. line range: {%i, %i}", lineRange.location, lineRange.length); [lines addObject:lineString]; } return AUTORELEASE(lines); } - (NSArray *)listOfClasses { NSMutableArray *classesArray = [[NSMutableArray alloc] init]; NSMutableArray *linesArray = nil; NSString *lineString = nil; int i; NSLog(@"Start searching for class implementations..."); // Get lines with keywords if ([[[_path lastPathComponent] pathExtension] isEqualToString:@"m"]) { linesArray = [self linesWithKeyword:@"@implementation" atBOL:YES]; } else if ([[[_path lastPathComponent] pathExtension] isEqualToString:@"h"]) { linesArray = [self linesWithKeyword:@"@interface" atBOL:YES]; } // Get class names for (i = 0; i < [linesArray count]; i++) { lineString = [linesArray objectAtIndex:i]; [classesArray addObject:[self classNameFromString:lineString]]; } return AUTORELEASE((NSArray*)classesArray); } - (NSArray *)listOfMethodsOfClass:(NSString *)className { NSMutableArray *methodsArray = [[NSMutableArray alloc] init]; return AUTORELEASE((NSArray*)methodsArray); } - (NSArray *)listOfDefines { NSMutableArray *definesArray = [[NSMutableArray alloc] init]; return AUTORELEASE((NSArray*)definesArray); } - (NSArray *)listOfVars { NSMutableArray *varsArray = [[NSMutableArray alloc] init]; return AUTORELEASE((NSArray*)varsArray); } //--- Scrolling - (void)scrollToClassName:(NSString *)className { } - (void)scrollToMethodName:(NSString *)className { } - (void)scrollToLineNumber:(int)line { } @end