home *** CD-ROM | disk | FTP | other *** search
/ NeXTSTEP 3.0 / NeXTSTEP3.0.iso / NextDeveloper / Examples / AppKit / Draw / Group.m < prev    next >
Text File  |  1992-07-20  |  14KB  |  564 lines

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