|
|
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:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.