diff --git a/ChangeLog b/ChangeLog index fb54bb22a..6206e0a20 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,22 @@ +Thu May 21 09:38:14 1998 Adam Fedor + + * src/NSUser.m (NSUserName): Use getpwuid for BSD machines (patch + provided by Stefanos Kiakas ). + +Wed May 20 15:26:50 1998 Richard Frith-Macdonald + + * src/GNUmakefile: Added NSUndoManager.[hm] + + * src/Invocation.m: ([-_initArgframeFrom:withType:retainArgs:]) fixed + memory leak due to failing to set 'args_retained' flag. + + * src/NSObject.m: ([-forward::]) modified to call (forwardInvocation:) + method so we can conform to OpenStep spec. + + * src/NSUndoManager.m: Implementation of new class. + + * src/include/NSUndoManager.h: Interface for new class. + Wed May 13 13:18:35 1998 Adam Fedor * src/NSArray.m ([NSArray -initWithContentsOfFile:]): Move diff --git a/Headers/gnustep/base/NSUndoManager.h b/Headers/gnustep/base/NSUndoManager.h new file mode 100644 index 000000000..caed68788 --- /dev/null +++ b/Headers/gnustep/base/NSUndoManager.h @@ -0,0 +1,95 @@ +/* Interface for for GNUStep + Copyright (C) 1998 Free Software Foundation, Inc. + + Written by: Richard Frith-Macdonald + + This file is part of the GNUstep Base Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef __NSUndoManager_h_OBJECTS_INCLUDE +#define __NSUndoManager_h_OBJECTS_INCLUDE + +#include +#include + +@class NSArray; +@class NSMutableArray; +@class NSInvocation; + +/* Public notification */ +extern NSString *NSUndoManagerCheckpointNotification; +extern NSString *NSUndoManagerDidOpenUndoGroupNotification; +extern NSString *NSUndoManagerDidRedoChangeNotification; +extern NSString *NSUndoManagerDidUndoChangeNotification; +extern NSString *NSUndoManagerWillCloseUndoGroupNotification; +extern NSString *NSUndoManagerWillRedoChangeNotification; +extern NSString *NSUndoManagerWillUndoChangeNotification; + +@interface NSUndoManager: NSObject +{ +@private + NSMutableArray *redoStack; + NSMutableArray *undoStack; + NSString *actionName; + id group; + id nextTarget; + NSArray *modes; + BOOL isRedoing; + BOOL isUndoing; + BOOL groupsByEvent; + BOOL registeredUndo; + unsigned disableCount; + unsigned levelsOfUndo; +} + +- (void) beginUndoGrouping; +- (BOOL) canRedo; +- (BOOL) canUndo; +- (void) disableUndoRegistration; +- (void) enableUndoRegistration; +- (void) endUndoGrouping; +- (void) forwardInvocation: (NSInvocation*)anInvocation; +- (int) groupingLevel; +- (BOOL) groupsByEvent; +- (BOOL) isRedoing; +- (BOOL) isUndoing; +- (BOOL) isUndoRegistrationEnabled; +- (unsigned int) levelsOfUndo; +- (id) prepareWithInvocationTarget: (id)target; +- (void) redo; +- (NSString*) redoActionName; +- (NSString*) redoMenuItemTitle; +- (NSString*) redoMenuTitleForUndoActionName: (NSString*)actionName; +- (void) registerUndoWithTarget: (id)target + selector: (SEL)aSelector + object: (id)anObject; +- (void) removeAllActions; +- (void) removeAllActionsWithTarget: (id)target; +- (NSArray*) runLoopModes; +- (void) setActionName: (NSString*)actionName; +- (void) setGroupsByEvent: (BOOL)flag; +- (void) setLevelsOfUndo: (unsigned)num; +- (void) setRunLoopModes: (NSArray*)modes; +- (void) undo; +- (NSString*) undoActionName; +- (NSString*) undoMenuItemTitle; +- (NSString*) undoMenuTitleForUndoActionName: (NSString*)name; +- (void) undoNestedGroup; + +@end + +#endif /* __NSUndoManager_h_OBJECTS_INCLUDE */ diff --git a/Source/GNUmakefile b/Source/GNUmakefile index d2f3bd959..db20adee9 100644 --- a/Source/GNUmakefile +++ b/Source/GNUmakefile @@ -360,6 +360,7 @@ NSTask.m \ NSThread.m \ NSTimer.m \ NSTimeZone.m \ +NSUndoManager.m \ NSUser.m \ NSUserDefaults.m \ NSValue.m \ @@ -448,6 +449,7 @@ Foundation/NSString.h \ Foundation/NSTask.h \ Foundation/NSThread.h \ Foundation/NSTimer.h \ +Foundation/NSUndoManager.h \ Foundation/NSUserDefaults.h \ Foundation/NSUtilities.h \ Foundation/NSValue.h \ diff --git a/Source/Invocation.m b/Source/Invocation.m index c624c9933..e2d819953 100644 --- a/Source/Invocation.m +++ b/Source/Invocation.m @@ -343,7 +343,10 @@ my_method_get_next_argument (arglist_t argframe, reg_argsize); memcpy(argframe->arg_ptr, frame->arg_ptr, stack_argsize); if (f) - [self _retainArguments]; + { + [self _retainArguments]; + args_retained = YES; + } } } diff --git a/Source/NSObject.m b/Source/NSObject.m index c7b6ea1f9..c3d17e41f 100644 --- a/Source/NSObject.m +++ b/Source/NSObject.m @@ -246,7 +246,6 @@ extraRefCount (id anObject) (object_is_instance(self) ? class_get_instance_method(self->isa, aSelector) : class_get_class_method(self->isa, aSelector)); - return mth ? [NSMethodSignature signatureWithObjCTypes:mth->method_types] : nil; } @@ -275,21 +274,24 @@ extraRefCount (id anObject) - (retval_t) forward:(SEL)aSel :(arglist_t)argFrame { -#if 1 - [self doesNotRecognizeSelector:aSel]; - return NULL; -#else void *retFrame; - NSMethodSignature *ms = [self methodSignatureForSelector:aSel]; - NSInvocation *inv = [NSInvocation invocationWithMethodSignature:ms - frame:argFrame]; + NSMethodSignature *sig; + NSInvocation *inv; + int retLength; + + inv = [[[NSInvocation alloc] initWithArgframe: argFrame + selector: aSel] autorelease]; /* is this right? */ - retFrame = (void*) alloca([ms methodReturnLength]); + sig = [inv methodSignature]; + retLength = [sig methodReturnLength]; + /* Make sure we have something even if we are returnign void */ + if (retLength == 0) + retLength = sizeof(void*); + + retFrame = (void*) alloca(retLength); [self forwardInvocation:inv]; - [inv getReturnValue:retFrame]; - /* where do ms and inv get free'd? */ + [inv getReturnValue: retFrame]; return retFrame; -#endif } - (void) forwardInvocation: (NSInvocation*)anInvocation diff --git a/Source/NSUndoManager.m b/Source/NSUndoManager.m new file mode 100644 index 000000000..5ad413a77 --- /dev/null +++ b/Source/NSUndoManager.m @@ -0,0 +1,646 @@ +/* Implementatikon for for GNUStep + Copyright (C) 1998 Free Software Foundation, Inc. + + Written by: Richard Frith-Macdonald + + This file is part of the GNUstep Base Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Public notifications */ +NSString *NSUndoManagerCheckpointNotification = + @"NSUndoManagerCheckpointNotification"; +NSString *NSUndoManagerDidOpenUndoGroupNotification = + @"NSUndoManagerDidOpenUndoGroupNotification"; +NSString *NSUndoManagerDidRedoChangeNotification = + @"NSUndoManagerDidRedoChangeNotification"; +NSString *NSUndoManagerDidUndoChangeNotification = + @"NSUndoManagerDidUndoChangeNotification"; +NSString *NSUndoManagerWillCloseUndoGroupNotification = + @"NSUndoManagerWillCloseUndoGroupNotification"; +NSString *NSUndoManagerWillRedoChangeNotification = + @"NSUndoManagerWillRedoChangeNotification"; +NSString *NSUndoManagerWillUndoChangeNotification = + @"NSUndoManagerWillUndoChangeNotification"; + + +/* + * Private class for grouping undo/redo actions. + */ +@interface PrivateUndoGroup : NSObject +{ + PrivateUndoGroup *parent; + NSMutableArray *actions; +} +- (NSMutableArray*) actions; +- (void) addInvocation: (NSInvocation*)inv; +- (id) initWithParent: (PrivateUndoGroup*)parent; +- (void) orphan; +- (PrivateUndoGroup*) parent; +- (void) redo; +- (BOOL) removeActionsForTarget: (id)target; +- (void) undo; +@end + +@implementation PrivateUndoGroup + +- (NSMutableArray*) actions +{ + return actions; +} + +- (void) addInvocation: (NSInvocation*)inv +{ + if (actions == nil) { + actions = [[NSMutableArray alloc] initWithCapacity: 2]; + } + [actions addObject: inv]; +} + +- (void) dealloc +{ + [actions release]; + [parent release]; + [super dealloc]; +} + +- (id) initWithParent: (PrivateUndoGroup*)p +{ + self = [super init]; + if (self) { + parent = [p retain]; + actions = nil; + } + return self; +} + +- (void) orphan +{ + id p = parent; + parent = nil; + [p release]; +} + +- (PrivateUndoGroup*) parent +{ + return parent; +} + +- (void) redo +{ + if (actions != nil) { + int i; + + for (i = 0; i < [actions count]; i++) { + [[actions objectAtIndex: i] invoke]; + } + } +} + +- (BOOL) removeActionsForTarget: (id)target +{ + if (actions != nil) { + int i; + + for (i = [actions count]; i > 0; i--) { + NSInvocation *inv = [actions objectAtIndex: i-1]; + + if ([inv target] == target) { + [actions removeObjectAtIndex: i-1]; + } + } + if ([actions count] > 0) { + return YES; + } + } + return NO; +} + +- (void) undo +{ + if (actions != nil) { + int i; + + for (i = [actions count]; i > 0; i--) { + [[actions objectAtIndex: i-1] invoke]; + } + } +} + +@end + + + +/* + * Private catagory for the method used to handle default grouping + */ +@interface NSUndoManager (Private) +- (void) _loop: (id)arg; +@end + +@implementation NSUndoManager (Private) +- (void) _loop: (id)arg +{ + if (groupsByEvent) { + if (group != nil) { + [self endUndoGrouping]; + } + [self beginUndoGrouping]; + } +} +@end + + + +/* + * The main part for the NSUndoManager implementation. + */ +@implementation NSUndoManager + +- (void) beginUndoGrouping +{ + PrivateUndoGroup *parent; + + if (isUndoing == NO) { + [[NSNotificationCenter defaultCenter] + postNotificationName: NSUndoManagerCheckpointNotification + object: self]; + } + parent = (PrivateUndoGroup*)group; + group = [[PrivateUndoGroup alloc] initWithParent: parent]; + if (group == nil) { + group = parent; + [NSException raise: NSInternalInconsistencyException + format: @"beginUndoGrouping failed to greate group"]; + } + else { + [parent release]; + + [[NSNotificationCenter defaultCenter] + postNotificationName: NSUndoManagerDidOpenUndoGroupNotification + object: self]; + } +} + +- (BOOL) canRedo +{ + [[NSNotificationCenter defaultCenter] + postNotificationName: NSUndoManagerCheckpointNotification + object: self]; + if ([redoStack count] > 0) { + return YES; + } + else { + return NO; + } +} + +- (BOOL) canUndo +{ + if ([undoStack count] > 0) { + return YES; + } + if (group != nil && [[group actions] count] > 0) { + return YES; + } + return NO; +} + +- (void) dealloc +{ + [[NSRunLoop currentRunLoop] cancelPerformSelector: @selector(_loop:) + target: self + argument: nil]; + [redoStack release]; + [undoStack release]; + [actionName release]; + [group release]; + [modes release]; + [super dealloc]; +} + +- (void) disableUndoRegistration +{ + disableCount++; +} + +- (void) enableUndoRegistration +{ + if (disableCount > 0) { + disableCount--; + registeredUndo = NO; /* No operations since registration enabled. */ + } + else { + [NSException raise: NSInternalInconsistencyException + format: @"enableUndoRegistration without disable"]; + } +} + +- (void) endUndoGrouping +{ + PrivateUndoGroup *g; + PrivateUndoGroup *p; + + if (group == nil) { + [NSException raise: NSInternalInconsistencyException + format: @"endUndoGrouping without beginUndoGrouping"]; + } + [[NSNotificationCenter defaultCenter] + postNotificationName: NSUndoManagerCheckpointNotification + object: self]; + g = (PrivateUndoGroup*)group; + p = [[g parent] retain]; + group = p; + [g orphan]; + [[NSNotificationCenter defaultCenter] + postNotificationName: NSUndoManagerWillCloseUndoGroupNotification + object: self]; + if (p == nil) { + if (isUndoing) { + if (levelsOfUndo > 0 && [redoStack count] == levelsOfUndo) { + [redoStack removeObjectAtIndex: 0]; + } + [redoStack addObject: g]; + } + else { + if (levelsOfUndo > 0 && [undoStack count] == levelsOfUndo) { + [undoStack removeObjectAtIndex: 0]; + } + [undoStack addObject: g]; + } + } + else if ([g actions] != nil) { + NSArray *a = [g actions]; + int i; + + for (i = 0; i < [a count]; i++) { + [p addInvocation: [a objectAtIndex: i]]; + } + } + [g release]; +} + +- (void) forwardInvocation: (NSInvocation*)anInvocation +{ + if (disableCount == 0) { + if (nextTarget == nil) { + [NSException raise: NSInternalInconsistencyException + format: @"forwardInvocation without perparation"]; + } + if (group == nil) { + [NSException raise: NSInternalInconsistencyException + format: @"forwardInvocation without beginUndoGrouping"]; + } + [anInvocation setTarget: nextTarget]; + nextTarget = nil; + [group addInvocation: anInvocation]; + [redoStack removeAllObjects]; + registeredUndo = YES; + } +} + +- (int) groupingLevel +{ + PrivateUndoGroup *g = (PrivateUndoGroup*)group; + int level = 0; + + while (g != nil) { + level++; + g = [g parent]; + } + return level; +} + +- (BOOL) groupsByEvent +{ + return groupsByEvent; +} + +- (id) init +{ + self = [super init]; + if (self) { + actionName = @""; + redoStack = [[NSMutableArray alloc] initWithCapacity: 16]; + undoStack = [[NSMutableArray alloc] initWithCapacity: 16]; + [self setRunLoopModes: + [NSArray arrayWithObjects: NSDefaultRunLoopMode, nil]]; + } + return self; +} + +- (BOOL) isRedoing +{ + return isRedoing; +} + +- (BOOL) isUndoing +{ + return isUndoing; +} + +- (BOOL) isUndoRegistrationEnabled +{ + if (disableCount == 0) { + return YES; + } + else { + return NO; + } +} + +- (unsigned int) levelsOfUndo +{ + return levelsOfUndo; +} + +- (id) prepareWithInvocationTarget: (id)target +{ + nextTarget = target; + return self; +} + +- (void) redo +{ + if (isUndoing || isRedoing) { + [NSException raise: NSInternalInconsistencyException + format: @"redo while undoing or redoing"]; + } + [[NSNotificationCenter defaultCenter] + postNotificationName: NSUndoManagerCheckpointNotification + object: self]; + if ([redoStack count] > 0) { + PrivateUndoGroup *oldGroup; + PrivateUndoGroup *groupToRedo; + + [[NSNotificationCenter defaultCenter] + postNotificationName: NSUndoManagerWillRedoChangeNotification + object: self]; + groupToRedo = [redoStack objectAtIndex: [redoStack count] - 1]; + [groupToRedo retain]; + [redoStack removeObjectAtIndex: [redoStack count] - 1]; + oldGroup = group; + group = nil; + isRedoing = YES; + [self disableUndoRegistration]; + [groupToRedo redo]; + [undoStack addObject: groupToRedo]; + [groupToRedo release]; + [self enableUndoRegistration]; + isRedoing = NO; + group = oldGroup; + [[NSNotificationCenter defaultCenter] + postNotificationName: NSUndoManagerDidRedoChangeNotification + object: self]; + } +} + +- (NSString*) redoActionName +{ + if ([self canRedo] == NO) { + return nil; + } + return actionName; +} + +- (NSString*) redoMenuItemTitle +{ + return [self redoMenuTitleForUndoActionName: [self redoActionName]]; +} + +- (NSString*) redoMenuTitleForUndoActionName: (NSString*)name +{ + if (name) { + if ([name isEqual: @""]) { + return @"Redo"; + } + else { + return [NSString stringWithFormat: @"Redo %@", name]; + } + } + return name; +} + +- (void) registerUndoWithTarget: (id)target + selector: (SEL)aSelector + object: (id)anObject +{ + if (disableCount == 0) { + NSMethodSignature *sig; + NSInvocation *inv; + PrivateUndoGroup *g; + + if (group == nil) { + [NSException raise: NSInternalInconsistencyException + format: @"registerUndo without beginUndoGrouping"]; + } + g = group; + sig = [target methodSignatureForSelector: aSelector]; + inv = [NSInvocation invocationWithMethodSignature: sig]; + [inv setTarget: target]; + [inv setSelector: aSelector]; + [inv setArgument: &anObject atIndex: 2]; + [g addInvocation: inv]; + [redoStack removeAllObjects]; + registeredUndo = YES; + } +} + +- (void) removeAllActions +{ + [redoStack removeAllObjects]; + [undoStack removeAllObjects]; + isRedoing = NO; + isUndoing = NO; + disableCount = 0; +} + +- (void) removeAllActionsWithTarget: (id)target +{ + int i; + + for (i = [redoStack count]; i > 0; i--) { + PrivateUndoGroup *g; + + g = [redoStack objectAtIndex: i-1]; + if ([g removeActionsForTarget: target] == NO) { + [redoStack removeObjectAtIndex: i-1]; + } + } + for (i = [undoStack count]; i > 0; i--) { + PrivateUndoGroup *g; + + g = [undoStack objectAtIndex: i-1]; + if ([g removeActionsForTarget: target] == NO) { + [undoStack removeObjectAtIndex: i-1]; + } + } +} + +- (NSArray*) runLoopModes +{ + return modes; +} + +- (void) setActionName: (NSString*)name +{ + if (name != nil && actionName != name) { + [actionName release]; + actionName = [name copy]; + } +} + +- (void) setGroupsByEvent: (BOOL)flag +{ + if (groupsByEvent != flag) { + groupsByEvent = flag; + } +} + +- (void) setLevelsOfUndo: (unsigned)num +{ + levelsOfUndo = num; + if (num > 0) { + while ([undoStack count] > num) { + [undoStack removeObjectAtIndex: 0]; + } + while ([redoStack count] > num) { + [redoStack removeObjectAtIndex: 0]; + } + } +} + +- (void) setRunLoopModes: (NSArray*)newModes +{ + if (modes != newModes) { + [modes release]; + modes = [newModes retain]; + [[NSRunLoop currentRunLoop] cancelPerformSelector: @selector(_loop:) + target: self + argument: nil]; + [[NSRunLoop currentRunLoop] performSelector: @selector(_loop:) + target: self + argument: nil + order: 0 + modes: modes]; + } +} + +- (void) undo +{ + if ([self groupingLevel] == 1) { + [self endUndoGrouping]; + } + if (group != nil) { + [NSException raise: NSInternalInconsistencyException + format: @"undo with nested groups"]; + } + [self undoNestedGroup]; +} + +- (NSString*) undoActionName +{ + if ([self canUndo] == NO) { + return nil; + } + return actionName; +} + +- (NSString*) undoMenuItemTitle +{ + return [self undoMenuTitleForUndoActionName: [self undoActionName]]; +} + +- (NSString*) undoMenuTitleForUndoActionName: (NSString*)name +{ + if (name) { + if ([name isEqual: @""]) { + return @"Undo"; + } + else { + return [NSString stringWithFormat: @"Undo %@", name]; + } + } + return name; +} + +- (void) undoNestedGroup +{ + PrivateUndoGroup *oldGroup; + PrivateUndoGroup *groupToUndo; + + [[NSNotificationCenter defaultCenter] + postNotificationName: NSUndoManagerCheckpointNotification + object: self]; +#if 0 +/* + * The documentation says we should raise an exception - but I can't + * make sense of it - raising an exception seems to break everything. + * It would make more sense to raise an exception if NO undo operations + * had been registered. + */ + if (registeredUndo) { + [NSException raise: NSInternalInconsistencyException + format: @"undoNestedGroup with registered undo ops"]; + } +#endif + if (isUndoing || isRedoing) { + [NSException raise: NSInternalInconsistencyException + format: @"undoNestedGroup while undoing or redoing"]; + } + if (group != nil && [undoStack count] == 0) { + return; + } + [[NSNotificationCenter defaultCenter] + postNotificationName: NSUndoManagerWillUndoChangeNotification + object: self]; + oldGroup = group; + group = nil; + isUndoing = YES; + if (oldGroup) { + groupToUndo = oldGroup; + oldGroup = [[oldGroup parent] retain]; + [groupToUndo orphan]; + [redoStack addObject: groupToUndo]; + } + else { + groupToUndo = [undoStack objectAtIndex: [undoStack count] - 1]; + [groupToUndo retain]; + [undoStack removeObjectAtIndex: [undoStack count] - 1]; + } + [self disableUndoRegistration]; + [groupToUndo undo]; + [redoStack addObject: groupToUndo]; + [groupToUndo release]; + [self enableUndoRegistration]; + isUndoing = NO; + group = oldGroup; + [[NSNotificationCenter defaultCenter] + postNotificationName: NSUndoManagerDidUndoChangeNotification + object: self]; +} + +@end + diff --git a/Source/NSUser.m b/Source/NSUser.m index be929f09d..e1acf50a5 100644 --- a/Source/NSUser.m +++ b/Source/NSUser.m @@ -46,7 +46,7 @@ NSUserName () return [NSString stringWithCString: buf]; else return [NSString stringWithCString: ""]; -#elif __SOLARIS__ +#elif __SOLARIS__ || defined(BSD) int uid = geteuid(); // get the effective user id struct passwd *pwent = getpwuid (uid); NSString* name = [NSString stringWithCString: pwent->pw_name];