This is Group.m in view mode; [Download] [Up]
#import "draw.h" /* Optimally viewed in a wide window. Make your window big enough so that this comment fits entirely on one line w/o wrapping. */ #define GROUP_CACHE_THRESHOLD 4 @implementation Group : Graphic /* * This Graphic is used to create heirarchical groups of other Graphics. * It simply keeps a list of all the Graphics in the group and resizes * and translates them as the Group object itself is resized and moved. * It also passes messages sent to the Group onto its members. * * For efficiency, we cache the group whenever it passes the caching * threshold. Thus, grouping becomes a tool to let the user have some * control over the memory/speed tradeoff (which can be different * depending on the kind of drawing the user is making). */ /* Factory method */ + initialize /* * This bumps the class version so that we can compatibly read * old Graphic objects out of an archive. */ { [Group setVersion:3]; return self; } /* Initialization */ - initList:(List *)list /* * Creates a new grouping with list containing the list of Graphics * in the group. Groups of Groups is perfectly allowable. We have * to keep track of the largest linewidth in the group as well as * whether any of the elements of the group have arrows since both * of those attributes affect the extended bounds of the Group. * We set any objects which might be cacheing (notably subgroups of * this group) to be not cacheable since it is no use for them to * cache themselves when we are caching them as well. We also have * to check to see if there are any TextGraphic's in the group * because we can't cache ourselves if there are (unfortunately). */ { int i; NXRect r; Graphic *graphic; [super init]; i = [list count]; graphic = [list objectAt:--i]; [graphic getBounds:&bounds]; gFlags.arrow = [graphic lineArrow]; linewidth = [graphic lineWidth]; while (i) { graphic = [list objectAt:--i]; [graphic getBounds:&r]; [graphic setCacheable:NO]; if (!r.size.width) r.size.width = 1.0; if (!r.size.height) r.size.height = 1.0; NXUnionRect(&r, &bounds); if (!gFlags.arrow && [graphic lineArrow]) gFlags.arrow = [graphic lineArrow]; if ([graphic lineWidth] > linewidth) linewidth = [graphic lineWidth]; if ([graphic isKindOf:[TextGraphic class]] || ([graphic isKindOf:[Group class]] && [(Group *)graphic hasTextGraphic])) hasTextGraphic = YES; } components = list; lastRect = bounds; return self; } - free { [components freeObjects]; [components free]; [cache free]; return [super free]; } /* Public methods */ - transferSubGraphicsTo:(List *)list at:(int)position /* * Called by Ungroup. This just unloads the components into the * passed list, modifying the bounds of each of the Graphics * accordingly (remember that when a Graphic joins a Group, its * bounds are still kept in GraphicView coordinates (not * Group-relative coordinates), but they may be non-integral, * we can't allow non-integral bounds outside a group because * it conflicts with the compositing rules (and we use * compositing to move graphics around). */ { int i, count; Graphic *graphic; NXRect gbounds; BOOL zeroWidth, zeroHeight; count = [components count]; for (i = (count - 1); i >= 0; i--) { graphic = [components objectAt:i]; [graphic getBounds:&gbounds]; if (!gbounds.size.width) { zeroWidth = YES; gbounds.size.width = 1.0; } else zeroWidth = NO; if (!gbounds.size.height) { zeroHeight = YES; gbounds.size.height = 1.0; } else zeroHeight = NO; NXIntegralRect(&gbounds); if (zeroWidth) gbounds.size.width = 0.0; if (zeroHeight) gbounds.size.height = 0.0; [graphic setBounds:&gbounds]; [graphic setCacheable:YES]; [list insertObject:graphic at:position]; } return self; } - (List *)subGraphics { return components; } /* Group must override all the setting routines to forward to components */ - makeGraphicsPerform:(SEL)aSelector with:(const void *)anArgument { [components makeObjectsPerform:aSelector with:(id)anArgument]; [cache free]; cache = nil; return self; } - changeFont:sender { return [self makeGraphicsPerform:@selector(changeFont:) with:sender]; } - (Font *)font { int i; Font *gfont, *font = nil; i = [components count]; while (i--) { gfont = [[components objectAt:i] font]; if (gfont) { if (font && font != gfont) { font = nil; break; } else { font = gfont; } } } return font; } - setLineWidth:(const float *)value { return [self makeGraphicsPerform:@selector(setLineWidth:) with:value]; } - setGray:(const float *)value { return [self makeGraphicsPerform:@selector(setGray:) with:value]; } - setFillColor:(const NXColor *)aColor { return [self makeGraphicsPerform:@selector(setFillColor:) with:aColor]; } - setFill:(int)mode { return [self makeGraphicsPerform:@selector(setFill:) with:(void *)mode]; } - setLineColor:(NXColor *)aColor { return [self makeGraphicsPerform:@selector(setLineColor:) with:aColor]; } - setLineCap:(int)value { return [self makeGraphicsPerform:@selector(setLineCap:) with:(void *)value]; } - setLineArrow:(int)value { return [self makeGraphicsPerform:@selector(setLineArrow:) with:(void *)value]; } - setLineJoin:(int)value { return [self makeGraphicsPerform:@selector(setLineJoin:) with:(void *)value]; } /* Link methods */ /* * Called after unarchiving and after a linkManager has been created for * the document this Graphic is in. Graphic's implementation of this just * adds the link to the linkManager. */ - reviveLink:(NXDataLinkManager *)linkManager { [components makeObjectsPerform:@selector(reviveLink:) with:linkManager]; return self; } /* * This returns self if there is more than one linked Graphic in the Group. * If aLink is not nil, returns the Graphic which is linked by that link. * If aLink is nil, then it returns the one and only linked Graphic in the * group or nil otherwise. Used when updating the link panel and when * redrawing link outlines. */ - (Graphic *)graphicLinkedBy:(NXDataLink *)aLink { int i, linkCount = 0; Graphic *graphic = nil; for (i = [components count]-1; i >= 0; i--) { if (graphic = [[components objectAt:i] graphicLinkedBy:aLink]) { if ([graphic isKindOf:[Group class]]) return graphic; linkCount++; } } return (linkCount <= 1) ? graphic : self; } /* * When you copy/paste a Graphic, its identifier must be reset to something * different since you don't want the pasted one to have the same identifier * as the copied one! See gvPasteboard.m. */ - resetIdentifier { [components makeObjectsPerform:@selector(resetIdentifier)]; return self; } /* * Used when creating an NXSelection representing all the Graphics * in a selection. Has to recurse through Groups because you still * want the NXSelection to be valid even if the Graphics are ungrouped * in the interim between the time the selection is determined to the * time the links stuff asks questions about the selection later. */ - writeIdentifierTo:(char *)buffer { int i = [components count]; char *s = buffer; if (i) { [[components objectAt:--i] writeIdentifierTo:s]; s += strlen(s); while (i--) { *s++ = ' '; [[components objectAt:i] writeIdentifierTo:s]; s += strlen(s); } } return self; } /* * This is used by the links stuff to allocate a buffer big enough to * put all the identifiers for all the graphics in this Group into. */ - (int)graphicCount { int count = 0, i = [components count]; while (i--) count += [[components objectAt:i] graphicCount]; return count; } /* * See the method findGraphicInSelection: in gvLinks.m to see how this * method is used (it basically just lets you get back to a Graphic * from its identifier whether its in a Group or not). */ - (Graphic *)graphicIdentifiedBy:(int)anIdentifier { int i = [components count]; while (i--) { Graphic *graphic = [components objectAt:i]; if ([graphic graphicIdentifiedBy:anIdentifier]) return self; } return nil; } /* Form Entry methods. See TextGraphic.m for details. */ - (BOOL)hasFormEntries { int i = [components count]; while (i--) if ([[components objectAt:i] hasFormEntries]) return YES; return NO; } - (BOOL)writeFormEntryToStream:(NXStream *)stream { BOOL retval = NO; int i = [components count]; while (i--) if ([[components objectAt:i] writeFormEntryToStream:stream]) retval = YES; return retval; } /* Notification methods */ - wasRemovedFrom:(GraphicView *)sender { [components makeObjectsPerform:@selector(wasRemovedFrom:) with:sender]; [cache free]; cache = nil; return self; } - wasAddedTo:(GraphicView *)sender { [components makeObjectsPerform:@selector(wasAddedTo:) with:sender]; return self; } /* Color drag-and-drop support. */ - (Graphic *)colorAcceptorAt:(const NXPoint *)point { int i, count; Graphic *graphic; count = [components count]; for (i = 0; i < count; i++) { if (graphic = [[components objectAt:i] colorAcceptorAt:point]) return graphic; } return nil; } /* We can't cache ourselves if we have a TextGraphic in the Group. */ - (BOOL)hasTextGraphic { return hasTextGraphic; } - setCacheable:(BOOL)flag /* * Sets whether we do caching of this Group or not. */ { dontCache = flag ? NO : YES; if (dontCache) { [cache free]; cache = nil; } return self; } - (BOOL)isCacheable { return !hasTextGraphic && !dontCache; } - draw /* * Individually scales and translates each Graphic in the group and draws * them. This is done this way so that ungrouping is trivial. Note that * if we are caching, we need to take the extra step of translating * everything to the origin, drawing them in the cache, then translating * them back. */ { int i; Graphic *g; NXRect eb, b; float sx = 1.0, sy = 1.0, tx, ty; BOOL changed, changedSize, caching = NO; if (bounds.size.width < 1.0 || bounds.size.height < 1.0 || !components) return self; changedSize = lastRect.size.width != bounds.size.width || lastRect.size.height != bounds.size.height; changed = changedSize || lastRect.origin.x != bounds.origin.x || lastRect.origin.y != bounds.origin.y; if ((changedSize || !cache) && NXDrawingStatus == NX_DRAWING) { [cache free]; cache = nil; if (DrawStatus != Resizing && [self isCacheable] && [components count] > GROUP_CACHE_THRESHOLD) { caching = YES; [self getExtendedBounds:&eb]; cache = [[NXImage allocFromZone:[self zone]] initSize:&eb.size]; [cache lockFocus]; [[[NXApp focusView] window] reenableDisplay]; /* workaround for AppKit bug? */ PStranslate(- eb.origin.x, - eb.origin.y); PSsetalpha(0.0); PSsetgray(NX_WHITE); NXRectFill(&eb); PSsetalpha(1.0); } } if (changedSize) { sx = bounds.size.width / lastRect.size.width; sy = bounds.size.height / lastRect.size.height; } i = [components count]; while (i) { g = [components objectAt:--i]; if (changed) { [g getBounds:&b]; tx = (bounds.origin.x + ((b.origin.x - lastRect.origin.x) / lastRect.size.width * bounds.size.width)) - b.origin.x; ty = (bounds.origin.y + ((b.origin.y - lastRect.origin.y) / lastRect.size.height * bounds.size.height)) - b.origin.y; b.origin.x = b.origin.x + tx; b.origin.y = b.origin.y + ty; b.size.width = b.size.width * sx; b.size.height = b.size.height * sy; [g setBounds:&b]; } if (NXDrawingStatus != NX_DRAWING || !cache || caching) { [g setGraphicsState]; /* does a gsave ... */ [g draw]; PSgrestore(); /* ... so we need this grestore */ } } if (cache && NXDrawingStatus == NX_DRAWING) { if (caching) { [cache unlockFocus]; } else { [self getExtendedBounds:&eb]; } [cache composite:NX_SOVER toPoint:&eb.origin]; } lastRect = bounds; return self; } - (BOOL)hit:(const NXPoint *)point /* * Gets a hit if any of the items in the group gets a hit. */ { int i; NXPoint p; float px, py; Graphic *graphic; if ([super hit:point]) { if (components) { p = *point; px = (p.x - bounds.origin.x) / bounds.size.width; p.x = px * lastRect.size.width + lastRect.origin.x; py = (p.y - bounds.origin.y) / bounds.size.height; p.y = py * lastRect.size.height + lastRect.origin.y; i = [components count]; while (i) { graphic = [components objectAt:--i]; if ([graphic hit:&p]) return YES; } } else { return YES; } } return NO; } /* Compatibility methods */ - replaceWithImage /* * Since we got rid of Tiff and PSGraphic and replaced them * with the unified Image graphic, we need to go through our * list and replace all of them with an Image graphic. */ { int i; Graphic *graphic, *newGraphic; for (i = [components count]-1; i >= 0; i--) { graphic = [components objectAt:i]; newGraphic = [graphic replaceWithImage]; if (graphic != newGraphic) { if (graphic) { [components replaceObjectAt:i with:newGraphic]; } else { [components removeObjectAt:i]; } } } return self; } /* Archiving methods */ - write:(NXTypedStream *)stream /* * Just writes out the components. */ { [super write:stream]; NXWriteTypes(stream, "@", &components); NXWriteType(stream, "c", &dontCache); NXWriteRect(stream, &lastRect); NXWriteType(stream, "c", &hasTextGraphic); return self; } static BOOL checkForTextGraphic(List *list) { int i; Graphic *graphic; for (i = [list count]-1; i >= 0; i--) { graphic = [list objectAt:i]; if ([graphic isKindOf:[TextGraphic class]] || ([graphic isKindOf:[Group class]] && [(Group *)graphic hasTextGraphic])) return YES; } return NO; } - read:(NXTypedStream *)stream { [super read:stream]; NXReadTypes(stream, "@", &components); lastRect = bounds; if (NXTypedStreamClassVersion(stream, "Group") > 1) { NXReadType(stream, "c", &dontCache); NXReadRect(stream, &lastRect); } if (NXTypedStreamClassVersion(stream, "Group") > 2) { NXReadType(stream, "c", &hasTextGraphic); } else { hasTextGraphic = checkForTextGraphic(components); } return self; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.