home *** CD-ROM | disk | FTP | other *** search
- #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];
-
- gFlags.mightBeLinked = YES;
- i = [list count];
- graphic = [list objectAt:--i];
- [graphic getBounds:&bounds];
- gFlags.arrow = [graphic lineArrow];
- linewidth = [graphic lineWidth];
- bounds.size.width = MAX(1.0, bounds.size.width);
- bounds.size.height = MAX(1.0, bounds.size.height);
- while (i) {
- graphic = [list objectAt:--i];
- [graphic getBounds:&r];
- [graphic setCacheable:NO];
- r.size.width = MAX(1.0, r.size.width);
- r.size.height = MAX(1.0, r.size.height);
- 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:(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 = [graphic graphicIdentifiedBy:anIdentifier]) return graphic;
- }
- return nil;
- }
-
- /*
- * We pass this method onto all the things inside the group since
- * there might be linked things inside the group.
- */
-
- - readLinkFromPasteboard:(Pasteboard *)pboard usingManager:(NXDataLinkManager *)linkManager useNewIdentifier:(BOOL)useNewIdentifier
- {
- int i = [components count];
- while (i--) {
- Graphic *graphic = [components objectAt:i];
- if ([graphic mightBeLinked]) [graphic readLinkFromPasteboard:pboard usingManager:linkManager useNewIdentifier:useNewIdentifier];
- }
- 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
-
-