From a6d96cd14a69f3d0b752db5a9722ca7885874360 Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Sat, 7 Dec 2024 13:14:53 -0500 Subject: [PATCH] Add new class for outline, update existing classes --- GormCore/GormDocument.m | 48 ++- GormCore/GormGenericEditor.h | 2 +- GormCore/GormObjectEditor.h | 12 + GormCore/GormObjectEditor.m | 6 +- GormCore/GormObjectOutline.h | 43 +++ GormCore/GormObjectOutline.m | 587 +++++++++++++++++++++++++++++++++++ 6 files changed, 691 insertions(+), 7 deletions(-) create mode 100644 GormCore/GormObjectOutline.h create mode 100644 GormCore/GormObjectOutline.m diff --git a/GormCore/GormDocument.m b/GormCore/GormDocument.m index c1dc81a3..36b1b22c 100644 --- a/GormCore/GormDocument.m +++ b/GormCore/GormDocument.m @@ -3914,26 +3914,70 @@ willBeInsertedIntoToolbar: (BOOL)flag child: (NSInteger)index ofItem: (id)item { + if (item == nil) + { + return [[topLevelObjects allObjects] objectAtIndex: index]; + } + else if ([item isKindOfClass: [NSWindow class]]) + { + return [item contentView]; + } + else if ([item isKindOfClass: [NSView class]]) + { + return [[item subviews] objectAtIndex: index]; + } + return nil; } - (BOOL) outlineView: (NSOutlineView *)ov isItemExpandable: (id)item { + if (item == nil) + { + return [topLevelObjects count] > 0; + } + else if ([item isKindOfClass: [NSWindow class]]) + { + return [item contentView] != nil; + } + else if ([item isKindOfClass: [NSView class]]) + { + return [[item subviews] count] > 0; + } + return NO; } - (NSInteger) outlineView: (NSOutlineView *)ov numberOfChildrenOfItem: (id)item { + if (item == nil) + { + return [topLevelObjects count]; + } + else if ([item isKindOfClass: [NSWindow class]]) + { + return 1; // [[[item contentView] subviews] count]; + } + else if ([item isKindOfClass: [NSView class]]) + { + return [[item subviews] count]; + } + return 0; } - (id) outlineView: (NSOutlineView *)ov objectValueForTableColumn: (NSTableColumn *)tableColumn - byItem: (id)tem + byItem: (id)item { - return nil; + if (item == nil) + { + return @"Objects"; + } + + return [self nameForObject: item]; } // Other methods... diff --git a/GormCore/GormGenericEditor.h b/GormCore/GormGenericEditor.h index 8e7a5bfa..4b3b9d45 100644 --- a/GormCore/GormGenericEditor.h +++ b/GormCore/GormGenericEditor.h @@ -31,7 +31,7 @@ #include -@interface GormGenericEditor : NSMatrix +@interface GormGenericEditor : NSMatrix { NSMutableArray *objects; id document; diff --git a/GormCore/GormObjectEditor.h b/GormCore/GormObjectEditor.h index 80358a94..ad48e080 100644 --- a/GormCore/GormObjectEditor.h +++ b/GormCore/GormObjectEditor.h @@ -28,6 +28,18 @@ #include "GormGenericEditor.h" +@interface NSObject (GormObjectAdditions) + +- (NSImage*) imageForViewer; +- (NSString*) inspectorClassName; +- (NSString*) connectInspectorClassName; +- (NSString*) sizeInspectorClassName; +- (NSString*) helpInspectorClassName; +- (NSString*) classInspectorClassName; +- (NSString*) editorClassName; + +@end + @interface GormObjectEditor : GormGenericEditor { } diff --git a/GormCore/GormObjectEditor.m b/GormCore/GormObjectEditor.m index 9e087fb2..e447b8c4 100644 --- a/GormCore/GormObjectEditor.m +++ b/GormCore/GormObjectEditor.m @@ -34,14 +34,11 @@ #import "GormClassManager.h" #import "GormAbstractDelegate.h" +@implementation NSObject (GormObjectAdditions) /* * Method to return the image that should be used to display objects within * the matrix containing the objects in a document. */ -@interface NSObject (GormObjectAdditions) -@end - -@implementation NSObject (GormObjectAdditions) - (NSImage*) imageForViewer { static NSImage *image = nil; @@ -86,6 +83,7 @@ { return @"GormObjectEditor"; } + @end @implementation NSView (GormObjectAdditions) diff --git a/GormCore/GormObjectOutline.h b/GormCore/GormObjectOutline.h new file mode 100644 index 00000000..b9e380fb --- /dev/null +++ b/GormCore/GormObjectOutline.h @@ -0,0 +1,43 @@ +/* GormObjectOutline.h + * + * Copyright (C) 2024 Free Software Foundation, Inc. + * + * Author: Gregory John Casamento + * Date: 2024 + * + * This file is part of GNUstep. + * + * This program 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 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111 USA. + */ + +#ifndef INCLUDED_GormObjectEditor_h +#define INCLUDED_GormObjectEditor_h + +#include "GormObjectEditor.h" + +extern static NSMapTable *docMap; + +@interface GormObjectOutline : GormObjectEditor +{ +} ++ (void) setEditor: (id)editor forDocument: (id)aDocument; +- (void) draggedImage: (NSImage*)i endedAt: (NSPoint)p deposited: (BOOL)f; +- (NSDragOperation) draggingSourceOperationMaskForLocal: (BOOL)flag; +- (BOOL) acceptsTypeFromArray: (NSArray*)types; +- (void) makeSelectionVisible: (BOOL)flag; +- (void) resetObject: (id)anObject; +@end + +#endif diff --git a/GormCore/GormObjectOutline.m b/GormCore/GormObjectOutline.m new file mode 100644 index 00000000..99fdbc31 --- /dev/null +++ b/GormCore/GormObjectOutline.m @@ -0,0 +1,587 @@ +/* GormObjectOutline.m + * + * Copyright (C) 2024 Free Software Foundation, Inc. + * + * Author: Gregory John Casamento + * Date: 2024 + * + * This file is part of GNUstep. + * + * This program 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 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111 USA. + */ + +#import +#import + +#import "GormPrivate.h" +#import "GormObjectEditor.h" +#import "GormFunctions.h" +#import "GormDocument.h" +#import "GormClassManager.h" +#import "GormAbstractDelegate.h" + +/* + * Method to return the image that should be used to display objects within + * the matrix containing the objects in a document. + */ +@implementation NSObject (GormObjectAdditions) +- (NSImage*) imageForViewer +{ + static NSImage *image = nil; + GormAbstractDelegate *delegate = (GormAbstractDelegate *)[NSApp delegate]; + + if (image == nil && [delegate isInTool] == NO) + { + NSBundle *bundle = [NSBundle bundleForClass: [self class]]; + NSString *path = [bundle pathForImageResource: @"GormUnknown"]; + image = [[NSImage alloc] initWithContentsOfFile: path]; + } + + return image; +} + +- (NSString*) inspectorClassName +{ + return @"GormObjectInspector"; +} + +- (NSString*) connectInspectorClassName +{ + return @"GormConnectionInspector"; +} + +- (NSString*) sizeInspectorClassName +{ + return @"GormNotApplicableInspector"; +} + +- (NSString*) helpInspectorClassName +{ + return @"GormNotApplicableInspector"; +} + +- (NSString*) classInspectorClassName +{ + return @"GormCustomClassInspector"; +} + +- (NSString*) editorClassName +{ + return @"GormObjectEditor"; +} +@end + +@implementation NSView (GormObjectAdditions) +- (NSString*) helpInspectorClassName +{ + return @"GormHelpInspector"; +} +@end + +// Map top level document editors to their documents... +static NSMapTable *docMap = 0; + +@implementation GormObjectOutline + ++ (void) initialize +{ + if (self == [GormObjectEditor class]) + { + docMap = NSCreateMapTable(NSNonRetainedObjectMapKeyCallBacks, + NSNonRetainedObjectMapValueCallBacks, + 2); + } +} + ++ (id) editorForDocument: (id)aDocument +{ + id editor = NSMapGet(docMap, (void*)aDocument); + + if (editor == nil) + { + editor = [[self alloc] initWithObject: nil inDocument: aDocument]; + AUTORELEASE(editor); + } + return editor; +} + ++ (void) setEditor: (id)editor + forDocument: (id)aDocument +{ + NSMapInsert(docMap, (void*)aDocument, (void*)editor); +} + + +- (BOOL) acceptsTypeFromArray: (NSArray*)types +{ + return ([[(GormDocument *)document allManagedPboardTypes] firstObjectCommonWithArray: types] != nil); +} + +- (void) pasteInSelection +{ + NSPasteboard *pb = [NSPasteboard generalPasteboard]; + NSString *type = [[(GormDocument *)document allManagedPboardTypes] firstObjectCommonWithArray: [pb types]]; + + if(type != nil) + { + // paste the object in. + [document pasteType: type + fromPasteboard: pb + parent: nil]; + } +} + +- (void) copySelection +{ + NSArray *sel = [self selection]; + if([sel count] > 0) + { + NSString *type = nil; + id obj = [sel objectAtIndex: 0]; + + if([obj isKindOfClass: [NSWindow class]]) + { + type = IBWindowPboardType; + } + else if([obj isKindOfClass: [NSView class]]) + { + type = IBViewPboardType; + } + else + { + type = IBObjectPboardType; + } + + [document copyObjects: sel + type: type + toPasteboard: [NSPasteboard generalPasteboard]]; + } +} + +- (void) deleteSelection +{ + if (selected != nil + && [[document nameForObject: selected] isEqualToString: @"NSOwner"] == NO + && [[document nameForObject: selected] isEqualToString: @"NSFirst"] == NO) + { + if ([selected isKindOfClass: [NSMenu class]] && + [[document nameForObject: selected] isEqual: @"NSMenu"] == YES) + { + NSString *title = _(@"Removing Main Menu"); + NSString *msg = _(@"Are you sure you want to do this?"); + NSInteger retval = NSRunAlertPanel(title, msg,_(@"OK"),_(@"Cancel"), nil, nil); + + // if the user *really* wants to delete the menu, do it. + if(retval != NSAlertDefaultReturn) + return; + } + + [document detachObject: selected]; + if ([selected isKindOfClass: [NSWindow class]] == YES) + { + NSArray *subviews = allSubviews([(NSWindow *)selected contentView]); + [document detachObjects: subviews]; + [selected close]; + } + + if ([selected isKindOfClass: [NSMenu class]] == YES) + { + NSArray *items = findAll( selected ); + NSEnumerator *en = [items objectEnumerator]; + id obj = nil; + + while((obj = [en nextObject]) != nil) + { + [document detachObject: obj]; + } + } + + [objects removeObjectIdenticalTo: selected]; + [self selectObjects: [NSArray array]]; + [self refreshCells]; + } +} + +/* + * Dragging source protocol implementation + */ +- (void) draggedImage: (NSImage*)i endedAt: (NSPoint)p deposited: (BOOL)f +{ +} + +- (NSDragOperation) draggingEntered: (id)sender +{ + NSArray *pbTypes = nil; + + // Get the resource manager first, if nil don't bother calling the rest... + dragPb = [sender draggingPasteboard]; + pbTypes = [dragPb types]; + + if ([pbTypes containsObject: GormLinkPboardType] == YES) + { + dragType = GormLinkPboardType; + } + else + { + dragType = nil; + } + + return [self draggingUpdated: sender]; +} + +- (NSDragOperation) draggingUpdated: (id)sender +{ + if (dragType == GormLinkPboardType) + { + NSPoint loc = [sender draggingLocation]; + NSInteger r, c; + int pos; + id obj = nil; + id delegate = [NSApp delegate]; + + loc = [self convertPoint: loc fromView: nil]; + [self getRow: &r column: &c forPoint: loc]; + pos = r * [self numberOfColumns] + c; + if (pos >= 0 && pos < [objects count]) + { + obj = [objects objectAtIndex: pos]; + } + if (obj == [delegate connectSource]) + { + return NSDragOperationNone; /* Can't drag an object onto itsself */ + } + + [delegate displayConnectionBetween: [delegate connectSource] and: obj]; + if (obj != nil) + { + return NSDragOperationLink; + } + + return NSDragOperationNone; + } + + return NSDragOperationNone; +} + + +/** + * Used for autoscrolling when you connect IBActions. + * FIXME: Maybye there is a better way to do it. +*/ +- (void)draggingExited:(id < NSDraggingInfo >)sender +{ + if (dragType == GormLinkPboardType) + { + NSRect documentVisibleRect; + NSRect documentRect; + NSPoint loc = [sender draggingLocation]; + + loc = [self convertPoint:loc fromView:nil]; + documentVisibleRect = [(NSClipView *)[self superview] documentVisibleRect]; + documentRect = [(NSClipView *)[self superview] documentRect]; + + /* Down */ + if ( (loc.y >= documentVisibleRect.size.height) + && ( ! NSEqualRects(documentVisibleRect,documentRect) ) ) + { + loc.x = 0; + loc.y = documentRect.origin.y + [self cellSize].height; + [(NSClipView*) [self superview] scrollToPoint:loc]; + } + /* up */ + else if ( (loc.y + 10 >= documentVisibleRect.origin.y ) + && ( ! NSEqualRects(documentVisibleRect,documentRect) ) ) + { + loc.x = 0; + loc.y = documentRect.origin.y - [self cellSize].height; + [(NSClipView*) [self superview] scrollToPoint:loc]; + } + + } +} + +- (NSDragOperation) draggingSourceOperationMaskForLocal: (BOOL)flag +{ + return NSDragOperationLink; +} + +- (void) drawSelection +{ +} + +- (void) handleNotification: (NSNotification*)aNotification +{ + NSString *name = [aNotification name]; + + if([name isEqual: GormResizeCellNotification]) + { + NSDebugLog(@"Received notification"); + [self setCellSize: defaultCellSize()]; + } +} + +/* + * Initialisation - register to receive DnD with our own types. + */ +- (id) initWithObject: (id)anObject inDocument: (id)aDocument +{ + id old = NSMapGet(docMap, (void*)aDocument); + + if (old != nil) + { + RELEASE(self); + self = RETAIN(old); + [self addObject: anObject]; + return self; + } + + self = [super initWithObject: anObject inDocument: aDocument]; + if (self != nil) + { + NSButtonCell *proto; + NSColor *color = [NSColor colorWithCalibratedRed: 0.850980 + green: 0.737255 + blue: 0.576471 + alpha: 0.0 ]; + + document = aDocument; + + [self registerForDraggedTypes:[NSArray arrayWithObject:GormLinkPboardType]]; + [self setAutosizesCells: NO]; + [self setCellSize: defaultCellSize()]; + [self setIntercellSpacing: NSMakeSize(8,8)]; + [self setAutoresizingMask: NSViewMinYMargin|NSViewWidthSizable]; + [self setMode: NSRadioModeMatrix]; + /* + * Send mouse click actions to self, so we can handle selection. + */ + [self setAction: @selector(changeSelection:)]; + [self setDoubleAction: @selector(raiseSelection:)]; + [self setTarget: self]; + + // set the background color. + [self setBackgroundColor: color]; + + objects = [[NSMutableArray alloc] init]; + proto = [[NSButtonCell alloc] init]; + [proto setBordered: NO]; + [proto setAlignment: NSCenterTextAlignment]; + [proto setImagePosition: NSImageAbove]; + [proto setSelectable: NO]; + [proto setEditable: NO]; + [self setPrototype: proto]; + RELEASE(proto); + [self setEditor: self + forDocument: aDocument]; + [self addObject: anObject]; + + // set up the notification... + [[NSNotificationCenter defaultCenter] + addObserver: self + selector: @selector(handleNotification:) + name: GormResizeCellNotification + object: nil]; + + [[NSNotificationCenter defaultCenter] + addObserver: self + selector: @selector(handleNotification:) + name: IBResourceManagerRegistryDidChangeNotification + object: nil]; + } + return self; +} + +- (void) willCloseDocument: (NSNotification *)aNotification +{ + NSMapRemove(docMap,document); + [super willCloseDocument: aNotification]; +} + +- (void) close +{ + [super close]; + [[NSNotificationCenter defaultCenter] removeObserver: self]; + NSMapRemove(docMap,document); +} + +- (void) makeSelectionVisible: (BOOL)flag +{ + if (flag == YES && selected != nil) + { + unsigned pos = [objects indexOfObjectIdenticalTo: selected]; + int r = pos / [self numberOfColumns]; + int c = pos % [self numberOfColumns]; + + [self selectCellAtRow: r column: c]; + } + else + { + [self deselectAllCells]; + } + [self displayIfNeeded]; + [[self window] flushWindow]; +} + +- (void) mouseDown: (NSEvent*)theEvent +{ + if ([theEvent modifierFlags] & NSControlKeyMask) + { + NSPoint loc = [theEvent locationInWindow]; + NSString *name; + NSInteger r = 0, c = 0; + int pos = 0; + id obj = nil; + + loc = [self convertPoint: loc fromView: nil]; + [self getRow: &r column: &c forPoint: loc]; + pos = r * [self numberOfColumns] + c; + if (pos >= 0 && pos < [objects count]) + { + obj = [objects objectAtIndex: pos]; + } + if (obj != nil && obj != selected) + { + [self selectObjects: [NSArray arrayWithObject: obj]]; + [self makeSelectionVisible: YES]; + } + name = [document nameForObject: obj]; + if ([name isEqualToString: @"NSFirst"] == NO && name != nil) + { + NSPasteboard *pb; + + pb = [NSPasteboard pasteboardWithName: NSDragPboard]; + [pb declareTypes: [NSArray arrayWithObject: GormLinkPboardType] + owner: self]; + [pb setString: name forType: GormLinkPboardType]; + [[NSApp delegate] displayConnectionBetween: obj and: nil]; + [[NSApp delegate] startConnecting]; + + [self dragImage: [[NSApp delegate] linkImage] + at: loc + offset: NSZeroSize + event: theEvent + pasteboard: pb + source: self + slideBack: YES]; + [self makeSelectionVisible: YES]; + return; + } + } + + [super mouseDown: theEvent]; +} + +- (BOOL) performDragOperation: (id)sender +{ + if (dragType == GormLinkPboardType) + { + NSPoint loc = [sender draggingLocation]; + NSInteger r, c; + int pos; + id obj = nil; + + loc = [self convertPoint: loc fromView: nil]; + [self getRow: &r column: &c forPoint: loc]; + pos = r * [self numberOfColumns] + c; + if (pos >= 0 && pos < [objects count]) + { + obj = [objects objectAtIndex: pos]; + } + if (obj == nil) + { + return NO; + } + else + { + [[NSApp delegate] displayConnectionBetween: [NSApp connectSource] and: obj]; + [[NSApp delegate] startConnecting]; + return YES; + } + } + else + { + NSLog(@"Drop with unrecognized type!"); + return NO; + } +} + +- (BOOL) prepareForDragOperation: (id)sender +{ + /* + * Tell the source that we will accept the drop if we can. + */ + if (dragType == GormLinkPboardType) + { + NSPoint loc = [sender draggingLocation]; + NSInteger r, c; + int pos; + id obj = nil; + + loc = [self convertPoint: loc fromView: nil]; + [self getRow: &r column: &c forPoint: loc]; + pos = r * [self numberOfColumns] + c; + if (pos >= 0 && pos < [objects count]) + { + obj = [objects objectAtIndex: pos]; + } + if (obj != nil) + { + return YES; + } + } + return NO; +} + +- (id) raiseSelection: (id)sender +{ + id obj = [self changeSelection: sender]; + id e; + + if(obj != nil) + { + e = [document editorForObject: obj create: YES]; + [e orderFront]; + [e resetObject: obj]; + } + + return self; +} + +- (void) resetObject: (id)anObject +{ + NSString *name = [document nameForObject: anObject]; + GormInspectorsManager *mgr = [(id)[NSApp delegate] inspectorsManager]; + + if ([name isEqual: @"NSOwner"] == YES) + { + [mgr setClassInspector]; + } + if ([name isEqual: @"NSFirst"] == YES) + { + [mgr setClassInspector]; + } +} + +- (void) addObject:(id)anObject +{ + [super addObject:anObject]; + /* we need to do this for palettes which can drop top level objects */ + [(GormDocument *)document changeToViewWithTag:0]; +} + + +@end + +