Annotation of Examples/AppKit/Draw/Group.m, revision 1.1

1.1     ! root        1: #import "draw.h"
        !             2: 
        !             3: /* Optimally viewed in a wide window.  Make your window big enough so that this comment fits entirely on one line w/o wrapping. */
        !             4: 
        !             5: #define GROUP_CACHE_THRESHOLD 4
        !             6: 
        !             7: @implementation Group : Graphic
        !             8: /*
        !             9:  * This Graphic is used to create heirarchical groups of other Graphics.
        !            10:  * It simply keeps a list of all the Graphics in the group and resizes
        !            11:  * and translates them as the Group object itself is resized and moved.
        !            12:  * It also passes messages sent to the Group onto its members.
        !            13:  *
        !            14:  * For efficiency, we cache the group whenever it passes the caching
        !            15:  * threshold.  Thus, grouping becomes a tool to let the user have some
        !            16:  * control over the memory/speed tradeoff (which can be different
        !            17:  * depending on the kind of drawing the user is making).
        !            18:  */
        !            19: 
        !            20: /* Factory method */
        !            21: 
        !            22: + initialize
        !            23: /*
        !            24:  * This bumps the class version so that we can compatibly read
        !            25:  * old Graphic objects out of an archive.
        !            26:  */
        !            27: {
        !            28:     [Group setVersion:3];
        !            29:     return self;
        !            30: }
        !            31: 
        !            32: /* Initialization */
        !            33: 
        !            34: - initList:(List *)list
        !            35: /*
        !            36:  * Creates a new grouping with list containing the list of Graphics
        !            37:  * in the group.  Groups of Groups is perfectly allowable.  We have
        !            38:  * to keep track of the largest linewidth in the group as well as
        !            39:  * whether any of the elements of the group have arrows since both
        !            40:  * of those attributes affect the extended bounds of the Group.
        !            41:  * We set any objects which might be cacheing (notably subgroups of
        !            42:  * this group) to be not cacheable since it is no use for them to
        !            43:  * cache themselves when we are caching them as well.  We also have
        !            44:  * to check to see if there are any TextGraphic's in the group
        !            45:  * because we can't cache ourselves if there are (unfortunately).
        !            46:  */
        !            47: {
        !            48:     int i;
        !            49:     NXRect r;
        !            50:     Graphic *graphic;
        !            51: 
        !            52:     [super init];
        !            53: 
        !            54:     gFlags.mightBeLinked = YES;
        !            55:     i = [list count];
        !            56:     graphic = [list objectAt:--i];
        !            57:     [graphic getBounds:&bounds];
        !            58:     gFlags.arrow = [graphic lineArrow];
        !            59:     linewidth = [graphic lineWidth];
        !            60:     bounds.size.width = MAX(1.0, bounds.size.width);
        !            61:     bounds.size.height = MAX(1.0, bounds.size.height);
        !            62:     while (i) {
        !            63:        graphic = [list objectAt:--i];
        !            64:        [graphic getBounds:&r];
        !            65:        [graphic setCacheable:NO];
        !            66:        r.size.width = MAX(1.0, r.size.width);
        !            67:        r.size.height = MAX(1.0, r.size.height);
        !            68:        NXUnionRect(&r, &bounds);
        !            69:        if (!gFlags.arrow && [graphic lineArrow]) gFlags.arrow = [graphic lineArrow];
        !            70:        if ([graphic lineWidth] > linewidth) linewidth = [graphic lineWidth];
        !            71:        if ([graphic isKindOf:[TextGraphic class]] || ([graphic isKindOf:[Group class]] && [(Group *)graphic hasTextGraphic])) hasTextGraphic = YES;
        !            72:     }
        !            73: 
        !            74:     components = list;
        !            75:     lastRect = bounds;
        !            76: 
        !            77:     return self;
        !            78: }
        !            79: 
        !            80: - free
        !            81: {
        !            82:     [components freeObjects];
        !            83:     [components free];
        !            84:     [cache free];
        !            85:     return [super free];
        !            86: }
        !            87: 
        !            88: /* Public methods */
        !            89: 
        !            90: - transferSubGraphicsTo:(List *)list at:(int)position
        !            91: /*
        !            92:  * Called by Ungroup.  This just unloads the components into the
        !            93:  * passed list, modifying the bounds of each of the Graphics
        !            94:  * accordingly (remember that when a Graphic joins a Group, its
        !            95:  * bounds are still kept in GraphicView coordinates (not
        !            96:  * Group-relative coordinates), but they may be non-integral,
        !            97:  * we can't allow non-integral bounds outside a group because
        !            98:  * it conflicts with the compositing rules (and we use
        !            99:  * compositing to move graphics around).
        !           100:  */
        !           101: {
        !           102:     int i, count;
        !           103:     Graphic *graphic;
        !           104:     NXRect gbounds;
        !           105:     BOOL zeroWidth, zeroHeight;
        !           106: 
        !           107:     count = [components count];
        !           108:     for (i = (count - 1); i >= 0; i--) {
        !           109:        graphic = [components objectAt:i];
        !           110:        [graphic getBounds:&gbounds];
        !           111:        if (!gbounds.size.width) {
        !           112:            zeroWidth = YES;
        !           113:            gbounds.size.width = 1.0;
        !           114:        } else zeroWidth = NO;
        !           115:        if (!gbounds.size.height) {
        !           116:            zeroHeight = YES;
        !           117:            gbounds.size.height = 1.0;
        !           118:        } else zeroHeight = NO;
        !           119:        NXIntegralRect(&gbounds);
        !           120:        if (zeroWidth) gbounds.size.width = 0.0;
        !           121:        if (zeroHeight) gbounds.size.height = 0.0;
        !           122:        [graphic setBounds:&gbounds];
        !           123:        [graphic setCacheable:YES];
        !           124:        [list insertObject:graphic at:position];
        !           125:     }
        !           126: 
        !           127:     return self;
        !           128: }
        !           129: 
        !           130: - (List *)subGraphics
        !           131: {
        !           132:     return components;
        !           133: }
        !           134: 
        !           135: /* Group must override all the setting routines to forward to components */
        !           136: 
        !           137: - makeGraphicsPerform:(SEL)aSelector with:(const void *)anArgument
        !           138: {
        !           139:     [components makeObjectsPerform:aSelector with:(id)anArgument];
        !           140:     [cache free];
        !           141:     cache = nil;
        !           142:     return self;
        !           143: }
        !           144: 
        !           145: - changeFont:sender
        !           146: {
        !           147:     return [self makeGraphicsPerform:@selector(changeFont:) with:sender];
        !           148: }
        !           149: 
        !           150: - (Font *)font
        !           151: {
        !           152:     int i;
        !           153:     Font *gfont, *font = nil;
        !           154: 
        !           155:     i = [components count];
        !           156:     while (i--) {
        !           157:        gfont = [[components objectAt:i] font];
        !           158:        if (gfont) {
        !           159:            if (font && font != gfont) {
        !           160:                font = nil;
        !           161:                break;
        !           162:            } else {
        !           163:                font = gfont;
        !           164:            }
        !           165:        }
        !           166:     }
        !           167: 
        !           168:     return font;
        !           169: }
        !           170: 
        !           171: - setLineWidth:(const float *)value
        !           172: {
        !           173:     return [self makeGraphicsPerform:@selector(setLineWidth:) with:value];
        !           174: }
        !           175: 
        !           176: - setGray:(const float *)value
        !           177: {
        !           178:     return [self makeGraphicsPerform:@selector(setGray:) with:value];
        !           179: }
        !           180: 
        !           181: - setFillColor:(NXColor *)aColor
        !           182: {
        !           183:     return [self makeGraphicsPerform:@selector(setFillColor:) with:aColor];
        !           184: }
        !           185: 
        !           186: - setFill:(int)mode
        !           187: {
        !           188:     return [self makeGraphicsPerform:@selector(setFill:) with:(void *)mode];
        !           189: }
        !           190: 
        !           191: - setLineColor:(NXColor *)aColor
        !           192: {
        !           193:     return [self makeGraphicsPerform:@selector(setLineColor:) with:aColor];
        !           194: }
        !           195: 
        !           196: - setLineCap:(int)value
        !           197: {
        !           198:     return [self makeGraphicsPerform:@selector(setLineCap:) with:(void *)value];
        !           199: }
        !           200: 
        !           201: - setLineArrow:(int)value
        !           202: {
        !           203:     return [self makeGraphicsPerform:@selector(setLineArrow:) with:(void *)value];
        !           204: }
        !           205: 
        !           206: - setLineJoin:(int)value
        !           207: {
        !           208:     return [self makeGraphicsPerform:@selector(setLineJoin:) with:(void *)value];
        !           209: }
        !           210: 
        !           211: /* Link methods */
        !           212: 
        !           213: /*
        !           214:  * Called after unarchiving and after a linkManager has been created for
        !           215:  * the document this Graphic is in.  Graphic's implementation of this just
        !           216:  * adds the link to the linkManager.
        !           217:  */
        !           218: 
        !           219: - reviveLink:(NXDataLinkManager *)linkManager
        !           220: {
        !           221:     [components makeObjectsPerform:@selector(reviveLink:) with:linkManager];
        !           222:     return self;
        !           223: }
        !           224: 
        !           225: /*
        !           226:  * This returns self if there is more than one linked Graphic in the Group.
        !           227:  * If aLink is not nil, returns the Graphic which is linked by that link.
        !           228:  * If aLink is nil, then it returns the one and only linked Graphic in the
        !           229:  * group or nil otherwise.  Used when updating the link panel and when
        !           230:  * redrawing link outlines.
        !           231:  */
        !           232: 
        !           233: - (Graphic *)graphicLinkedBy:(NXDataLink *)aLink
        !           234: {
        !           235:     int i, linkCount = 0;
        !           236:     Graphic *graphic = nil;
        !           237: 
        !           238:     for (i = [components count]-1; i >= 0; i--) {
        !           239:        if (graphic = [[components objectAt:i] graphicLinkedBy:aLink]) {
        !           240:            if ([graphic isKindOf:[Group class]]) return graphic;
        !           241:            linkCount++;
        !           242:        }
        !           243:     }
        !           244: 
        !           245:     return (linkCount <= 1) ? graphic : self;
        !           246: }
        !           247: 
        !           248: /*
        !           249:  * When you copy/paste a Graphic, its identifier must be reset to something
        !           250:  * different since you don't want the pasted one to have the same identifier
        !           251:  * as the copied one!  See gvPasteboard.m.
        !           252:  */
        !           253: 
        !           254: - resetIdentifier
        !           255: {
        !           256:     [components makeObjectsPerform:@selector(resetIdentifier)];
        !           257:     return self;
        !           258: }
        !           259: 
        !           260: /*
        !           261:  * Used when creating an NXSelection representing all the Graphics
        !           262:  * in a selection.  Has to recurse through Groups because you still
        !           263:  * want the NXSelection to be valid even if the Graphics are ungrouped
        !           264:  * in the interim between the time the selection is determined to the
        !           265:  * time the links stuff asks questions about the selection later.
        !           266:  */
        !           267: 
        !           268: - writeIdentifierTo:(char *)buffer
        !           269: {
        !           270:     int i = [components count];
        !           271:     char *s = buffer;
        !           272: 
        !           273:     if (i) {
        !           274:        [[components objectAt:--i] writeIdentifierTo:s];
        !           275:        s += strlen(s);
        !           276:        while (i--) {
        !           277:            *s++ = ' ';
        !           278:            [[components objectAt:i] writeIdentifierTo:s];
        !           279:            s += strlen(s);
        !           280:        }
        !           281:     }
        !           282: 
        !           283:     return self;
        !           284: }
        !           285: 
        !           286: /*
        !           287:  * This is used by the links stuff to allocate a buffer big enough to
        !           288:  * put all the identifiers for all the graphics in this Group into.
        !           289:  */
        !           290: 
        !           291: - (int)graphicCount
        !           292: {
        !           293:     int count = 0, i = [components count];
        !           294:     while (i--) count += [[components objectAt:i] graphicCount];
        !           295:     return count;
        !           296: }
        !           297: 
        !           298: /*
        !           299:  * See the method findGraphicInSelection: in gvLinks.m to see how this
        !           300:  * method is used (it basically just lets you get back to a Graphic
        !           301:  * from its identifier whether its in a Group or not).
        !           302:  */
        !           303: 
        !           304: - (Graphic *)graphicIdentifiedBy:(int)anIdentifier
        !           305: {
        !           306:     int i = [components count];
        !           307:     while (i--) {
        !           308:        Graphic *graphic = [components objectAt:i];
        !           309:        if (graphic = [graphic graphicIdentifiedBy:anIdentifier]) return graphic;
        !           310:     }
        !           311:     return nil;
        !           312: }
        !           313: 
        !           314: /*
        !           315:  * We pass this method onto all the things inside the group since
        !           316:  * there might be linked things inside the group.
        !           317:  */
        !           318: 
        !           319: - readLinkFromPasteboard:(Pasteboard *)pboard usingManager:(NXDataLinkManager *)linkManager useNewIdentifier:(BOOL)useNewIdentifier
        !           320: {
        !           321:     int i = [components count];
        !           322:     while (i--) {
        !           323:        Graphic *graphic = [components objectAt:i];
        !           324:        if ([graphic mightBeLinked]) [graphic readLinkFromPasteboard:pboard usingManager:linkManager useNewIdentifier:useNewIdentifier];
        !           325:     }
        !           326:     return nil;
        !           327: }
        !           328: 
        !           329: /* Form Entry methods.  See TextGraphic.m for details. */
        !           330: 
        !           331: - (BOOL)hasFormEntries
        !           332: {
        !           333:     int i = [components count];
        !           334:     while (i--) if ([[components objectAt:i] hasFormEntries]) return YES;
        !           335:     return NO;
        !           336: }
        !           337: 
        !           338: - (BOOL)writeFormEntryToStream:(NXStream *)stream
        !           339: {
        !           340:     BOOL retval = NO;
        !           341:     int i = [components count];
        !           342:     while (i--) if ([[components objectAt:i] writeFormEntryToStream:stream]) retval = YES;
        !           343:     return retval;
        !           344: }
        !           345: 
        !           346: /* Notification methods */
        !           347: 
        !           348: - wasRemovedFrom:(GraphicView *)sender
        !           349: {
        !           350:     [components makeObjectsPerform:@selector(wasRemovedFrom:) with:sender];
        !           351:     [cache free];
        !           352:     cache = nil;
        !           353:     return self;
        !           354: }
        !           355: 
        !           356: - wasAddedTo:(GraphicView *)sender
        !           357: {
        !           358:     [components makeObjectsPerform:@selector(wasAddedTo:) with:sender];
        !           359:     return self;
        !           360: }
        !           361: 
        !           362: /* Color drag-and-drop support. */
        !           363: 
        !           364: - (Graphic *)colorAcceptorAt:(const NXPoint *)point
        !           365: {
        !           366:     int i, count;
        !           367:     Graphic *graphic;
        !           368: 
        !           369:     count = [components count];
        !           370:     for (i = 0; i < count; i++) {
        !           371:        if (graphic = [[components objectAt:i] colorAcceptorAt:point]) return graphic;
        !           372:     }
        !           373: 
        !           374:     return nil;
        !           375: }
        !           376: 
        !           377: /* We can't cache ourselves if we have a TextGraphic in the Group. */
        !           378: 
        !           379: - (BOOL)hasTextGraphic
        !           380: {
        !           381:     return hasTextGraphic;
        !           382: }
        !           383: 
        !           384: - setCacheable:(BOOL)flag
        !           385: /*
        !           386:  * Sets whether we do caching of this Group or not.
        !           387:  */
        !           388: {
        !           389:     dontCache = flag ? NO : YES;
        !           390:     if (dontCache) {
        !           391:        [cache free];
        !           392:        cache = nil;
        !           393:     }
        !           394:     return self;
        !           395: }
        !           396: 
        !           397: - (BOOL)isCacheable
        !           398: {
        !           399:     return !hasTextGraphic && !dontCache;
        !           400: }
        !           401: 
        !           402: - draw
        !           403: /*
        !           404:  * Individually scales and translates each Graphic in the group and draws
        !           405:  * them.  This is done this way so that ungrouping is trivial.  Note that
        !           406:  * if we are caching, we need to take the extra step of translating
        !           407:  * everything to the origin, drawing them in the cache, then translating
        !           408:  * them back.
        !           409:  */
        !           410: {
        !           411:     int i;
        !           412:     Graphic *g;
        !           413:     NXRect eb, b;
        !           414:     float sx = 1.0, sy = 1.0, tx, ty;
        !           415:     BOOL changed, changedSize, caching = NO;
        !           416: 
        !           417:     if (bounds.size.width < 1.0 || bounds.size.height < 1.0 || !components) return self;
        !           418: 
        !           419:     changedSize = lastRect.size.width != bounds.size.width || lastRect.size.height != bounds.size.height;
        !           420:     changed = changedSize || lastRect.origin.x != bounds.origin.x || lastRect.origin.y != bounds.origin.y;
        !           421: 
        !           422:     if ((changedSize || !cache) && NXDrawingStatus == NX_DRAWING) {
        !           423:        [cache free];
        !           424:        cache = nil;
        !           425:        if (DrawStatus != Resizing && [self isCacheable] && [components count] > GROUP_CACHE_THRESHOLD) {
        !           426:            caching = YES;
        !           427:            [self getExtendedBounds:&eb];
        !           428:            cache = [[NXImage allocFromZone:[self zone]] initSize:&eb.size];
        !           429:            [cache lockFocus];
        !           430:            [[[NXApp focusView] window] reenableDisplay];       /* workaround for AppKit bug? */
        !           431:            PStranslate(- eb.origin.x, - eb.origin.y);
        !           432:            PSsetalpha(0.0);
        !           433:            PSsetgray(NX_WHITE);
        !           434:            NXRectFill(&eb);
        !           435:            PSsetalpha(1.0);
        !           436:        }
        !           437:     }
        !           438: 
        !           439:     if (changedSize) {
        !           440:        sx = bounds.size.width / lastRect.size.width;
        !           441:        sy = bounds.size.height / lastRect.size.height;
        !           442:     }
        !           443: 
        !           444:     i = [components count];
        !           445:     while (i) {
        !           446:        g = [components objectAt:--i];
        !           447:        if (changed) {
        !           448:            [g getBounds:&b];
        !           449:            tx = (bounds.origin.x + ((b.origin.x - lastRect.origin.x) / lastRect.size.width * bounds.size.width)) - b.origin.x;
        !           450:            ty = (bounds.origin.y + ((b.origin.y - lastRect.origin.y) / lastRect.size.height * bounds.size.height)) - b.origin.y;
        !           451:            b.origin.x = b.origin.x + tx;
        !           452:            b.origin.y = b.origin.y + ty;
        !           453:            b.size.width = b.size.width * sx;
        !           454:            b.size.height = b.size.height * sy;
        !           455:            [g setBounds:&b];
        !           456:        }
        !           457:        if (NXDrawingStatus != NX_DRAWING || !cache || caching) {
        !           458:            [g setGraphicsState];       /* does a gsave ... */
        !           459:            [g draw];
        !           460:            PSgrestore();               /* ... so we need this grestore */
        !           461:        }
        !           462:     }
        !           463: 
        !           464:     if (cache && NXDrawingStatus == NX_DRAWING) {
        !           465:        if (caching) {
        !           466:            [cache unlockFocus];
        !           467:        } else {
        !           468:            [self getExtendedBounds:&eb];
        !           469:        }
        !           470:        [cache composite:NX_SOVER toPoint:&eb.origin];
        !           471:     }
        !           472: 
        !           473:     lastRect = bounds;
        !           474: 
        !           475:     return self;
        !           476: }
        !           477: 
        !           478: - (BOOL)hit:(const NXPoint *)point
        !           479: /*
        !           480:  * Gets a hit if any of the items in the group gets a hit.
        !           481:  */
        !           482: {
        !           483:     int i;
        !           484:     NXPoint p;
        !           485:     float px, py;
        !           486:     Graphic *graphic;
        !           487: 
        !           488:     if ([super hit:point]) {
        !           489:        if (components) {
        !           490:            p = *point;
        !           491:            px = (p.x - bounds.origin.x) / bounds.size.width;
        !           492:            p.x = px * lastRect.size.width + lastRect.origin.x;
        !           493:            py = (p.y - bounds.origin.y) / bounds.size.height;
        !           494:            p.y = py * lastRect.size.height + lastRect.origin.y;
        !           495:            i = [components count];
        !           496:            while (i) {
        !           497:                graphic = [components objectAt:--i];
        !           498:                if ([graphic hit:&p]) return YES;
        !           499:            }
        !           500:        } else {
        !           501:            return YES;
        !           502:        }
        !           503:     }
        !           504: 
        !           505:     return NO;
        !           506: }
        !           507: 
        !           508: /* Compatibility methods */
        !           509: 
        !           510: - replaceWithImage
        !           511: /*
        !           512:  * Since we got rid of Tiff and PSGraphic and replaced them
        !           513:  * with the unified Image graphic, we need to go through our
        !           514:  * list and replace all of them with an Image graphic.
        !           515:  */
        !           516: {
        !           517:     int i;
        !           518:     Graphic *graphic, *newGraphic;
        !           519: 
        !           520:     for (i = [components count]-1; i >= 0; i--) {
        !           521:        graphic = [components objectAt:i];
        !           522:        newGraphic = [graphic replaceWithImage];
        !           523:        if (graphic != newGraphic) {
        !           524:            if (graphic) {
        !           525:                [components replaceObjectAt:i with:newGraphic];
        !           526:            } else {
        !           527:                [components removeObjectAt:i];
        !           528:            }
        !           529:        }
        !           530:     }
        !           531: 
        !           532:     return self;
        !           533: }
        !           534: 
        !           535: /* Archiving methods */
        !           536: 
        !           537: - write:(NXTypedStream *)stream
        !           538: /*
        !           539:  * Just writes out the components.
        !           540:  */
        !           541: {
        !           542:     [super write:stream];
        !           543:     NXWriteTypes(stream, "@", &components);
        !           544:     NXWriteType(stream, "c", &dontCache);
        !           545:     NXWriteRect(stream, &lastRect);
        !           546:     NXWriteType(stream, "c", &hasTextGraphic);
        !           547:     return self;
        !           548: }
        !           549: 
        !           550: static BOOL checkForTextGraphic(List *list)
        !           551: {
        !           552:     int i;
        !           553:     Graphic *graphic;
        !           554: 
        !           555:     for (i = [list count]-1; i >= 0; i--) {
        !           556:        graphic = [list objectAt:i];
        !           557:        if ([graphic isKindOf:[TextGraphic class]] || ([graphic isKindOf:[Group class]] && [(Group *)graphic hasTextGraphic])) return YES;
        !           558:     }
        !           559: 
        !           560:     return NO;
        !           561: }
        !           562: 
        !           563: - read:(NXTypedStream *)stream
        !           564: {
        !           565:     [super read:stream];
        !           566:     NXReadTypes(stream, "@", &components);
        !           567:     lastRect = bounds;
        !           568:     if (NXTypedStreamClassVersion(stream, "Group") > 1) {
        !           569:        NXReadType(stream, "c", &dontCache);
        !           570:        NXReadRect(stream, &lastRect);
        !           571:     }
        !           572:     if (NXTypedStreamClassVersion(stream, "Group") > 2) {
        !           573:        NXReadType(stream, "c", &hasTextGraphic);
        !           574:     } else {
        !           575:        hasTextGraphic = checkForTextGraphic(components);
        !           576:     }
        !           577:     return self;
        !           578: }
        !           579: 
        !           580: @end
        !           581: 

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.