Annotation of Examples/AppKit/Draw/Group.m, revision 1.1.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.