home *** CD-ROM | disk | FTP | other *** search
/ OpenStep (Enterprise) / OpenStepENTCD.toast / OEDEV / DEV.Z / Group.m < prev    next >
Text File  |  1996-04-18  |  14KB  |  524 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. /* Initialization */
  23.  
  24. - initList:(NSMutableArray *)array
  25. /*
  26.  * Creates a new grouping with list containing the list of Graphics
  27.  * in the group.  Groups of Groups is perfectly allowable.  We have
  28.  * to keep track of the largest linewidth in the group as well as
  29.  * whether any of the elements of the group have arrows since both
  30.  * of those attributes affect the extended bounds of the Group.
  31.  * We set any objects which might be cacheing (notably subgroups of
  32.  * this group) to be not cacheable since it is no use for them to
  33.  * cache themselves when we are caching them as well.  We also have
  34.  * to check to see if there are any TextGraphic's in the group
  35.  * because we can't cache ourselves if there are (unfortunately).
  36.  */
  37. {
  38.     int i;
  39.     NSRect r;
  40.     Graphic *graphic;
  41.  
  42.     [super init];
  43.  
  44.     gFlags.mightBeLinked = YES;
  45.     i = [array count];
  46.     graphic = [array objectAtIndex:--i];
  47.     bounds = [graphic bounds];
  48.     gFlags.arrow = [graphic lineArrow];
  49.     linewidth = [graphic lineWidth];
  50.     bounds.size.width = MAX(1.0, bounds.size.width);
  51.     bounds.size.height = MAX(1.0, bounds.size.height);
  52.     while (i) {
  53.     graphic = [array objectAtIndex:--i];
  54.     r = [graphic bounds];
  55.     [graphic setCacheable:NO];
  56.     r.size.width = MAX(1.0, r.size.width);
  57.     r.size.height = MAX(1.0, r.size.height);
  58.     bounds = NSUnionRect(r, bounds);
  59.     if (!gFlags.arrow && [graphic lineArrow]) gFlags.arrow = [graphic lineArrow];
  60.     if ([graphic lineWidth] > linewidth) linewidth = [graphic lineWidth];
  61.     if ([graphic isKindOfClass:[TextGraphic class]] || ([graphic isKindOfClass:[Group class]] && [(Group *)graphic hasTextGraphic])) hasTextGraphic = YES;
  62.     }
  63.  
  64.     components = [[NSMutableArray alloc] initWithArray:array];
  65.     lastRect = bounds;
  66.  
  67.     return self;
  68. }
  69.  
  70. - (void)dealloc
  71. {
  72.     [components removeAllObjects];
  73.     [components release];
  74.     [cache release];
  75.     [super dealloc];
  76. }
  77.  
  78. /* Public methods */
  79.  
  80. - (void)transferSubGraphicsTo:(NSMutableArray *)array at:(int)position
  81. /*
  82.  * Called by Ungroup.  This just unloads the components into the
  83.  * passed list, modifying the bounds of each of the Graphics
  84.  * accordingly (remember that when a Graphic joins a Group, its
  85.  * bounds are still kept in GraphicView coordinates (not
  86.  * Group-relative coordinates), but they may be non-integral,
  87.  * we can't allow non-integral bounds outside a group because
  88.  * it conflicts with the compositing rules (and we use
  89.  * compositing to move graphics around).
  90.  */
  91. {
  92.     int i, count;
  93.     Graphic *graphic;
  94.     NSRect gbounds;
  95.     BOOL zeroWidth, zeroHeight;
  96.  
  97.     count = [components count];
  98.     for (i = (count - 1); i >= 0; i--) {
  99.     graphic = [components objectAtIndex:i];
  100.     gbounds = [graphic bounds];
  101.     if (!gbounds.size.width) {
  102.         zeroWidth = YES;
  103.         gbounds.size.width = 1.0;
  104.     } else zeroWidth = NO;
  105.     if (!gbounds.size.height) {
  106.         zeroHeight = YES;
  107.         gbounds.size.height = 1.0;
  108.     } else zeroHeight = NO;
  109.     gbounds = NSIntegralRect(gbounds);
  110.     if (zeroWidth) gbounds.size.width = 0.0;
  111.     if (zeroHeight) gbounds.size.height = 0.0;
  112.     [graphic setBounds:gbounds];
  113.     [graphic setCacheable:YES];
  114.     [array insertObject:graphic atIndex:position];
  115.     } 
  116. }
  117.  
  118. - (NSMutableArray *)subGraphics
  119. {
  120.     return components;
  121. }
  122.  
  123. /* Group must override all the setting routines to forward to components */
  124.  
  125. - (void)makeGraphicsPerform:(SEL)aSelector with:(const void *)anArgument
  126. {
  127.     [components makeObjectsPerform:aSelector withObject:(id)anArgument];
  128.     [cache release];
  129.     cache = nil; 
  130. }
  131.  
  132. - (void)changeFont:(id)sender
  133. {
  134.     [self makeGraphicsPerform:@selector(changeFont:) with:sender];
  135. }
  136.  
  137. - (NSFont *)font
  138. {
  139.     int i;
  140.     NSFont *gfont, *font = nil;
  141.  
  142.     i = [components count];
  143.     while (i--) {
  144.     gfont = [[components objectAtIndex:i] font];
  145.     if (gfont) {
  146.         if (font && font != gfont) {
  147.         font = nil;
  148.         break;
  149.         } else {
  150.         font = gfont;
  151.         }
  152.     }
  153.     }
  154.  
  155.     return font;
  156. }
  157.  
  158. - (void)setLineWidth:(const float *)value
  159. {
  160.     return [self makeGraphicsPerform:@selector(setLineWidth:) with:value];
  161. }
  162.  
  163. - (void)setGray:(const float *)value
  164. {
  165.     return [self makeGraphicsPerform:@selector(setGray:) with:value];
  166. }
  167.  
  168. - (void)setFillColor:(NSColor *)aColor
  169. {
  170.     return [self makeGraphicsPerform:@selector(setFillColor:) with:aColor];
  171. }
  172.  
  173. - (void)setFill:(int)mode
  174. {
  175.     return [self makeGraphicsPerform:@selector(setFill:) with:(void *)mode];
  176. }
  177.  
  178. - (void)setLineColor:(NSColor *)aColor
  179. {
  180.     return [self makeGraphicsPerform:@selector(setLineColor:) with:aColor];
  181. }
  182.  
  183. - (void)setLineCap:(int)value
  184. {
  185.     return [self makeGraphicsPerform:@selector(setLineCap:) with:(void *)value];
  186. }
  187.  
  188. - (void)setLineArrow:(int)value
  189. {
  190.     return [self makeGraphicsPerform:@selector(setLineArrow:) with:(void *)value];
  191. }
  192.  
  193. - (void)setLineJoin:(int)value
  194. {
  195.     return [self makeGraphicsPerform:@selector(setLineJoin:) with:(void *)value];
  196. }
  197.  
  198. /* Link methods */
  199.  
  200. /*
  201.  * Called after unarchiving and after a linkManager has been created for
  202.  * the document this Graphic is in.  Graphic's implementation of this just
  203.  * adds the link to the linkManager.
  204.  */
  205.  
  206. - (void)reviveLink:(NSDataLinkManager *)linkManager
  207. {
  208.     [components makeObjectsPerform:@selector(reviveLink:) withObject:linkManager]; 
  209. }
  210.  
  211. /*
  212.  * This returns self if there is more than one linked Graphic in the Group.
  213.  * If aLink is not nil, returns the Graphic which is linked by that link.
  214.  * If aLink is nil, then it returns the one and only linked Graphic in the
  215.  * group or nil otherwise.  Used when updating the link panel and when
  216.  * redrawing link outlines.
  217.  */
  218.  
  219. - (Graphic *)graphicLinkedBy:(NSDataLink *)aLink
  220. {
  221.     int i, linkCount = 0;
  222.     Graphic *graphic = nil;
  223.  
  224.     for (i = [components count]-1; i >= 0; i--) {
  225.     if ((graphic = [[components objectAtIndex:i] graphicLinkedBy:aLink])) {
  226.         if ([graphic isKindOfClass:[Group class]]) return graphic;
  227.         linkCount++;
  228.     }
  229.     }
  230.  
  231.     return (linkCount <= 1) ? graphic : self;
  232. }
  233.  
  234. /*
  235.  * When you copy/paste a Graphic, its identifier must be reset to something
  236.  * different since you don't want the pasted one to have the same identifier
  237.  * as the copied one!  See gvPasteboard.m.
  238.  */
  239.  
  240. - (void)resetIdentifier
  241. {
  242.     [components makeObjectsPerform:@selector(resetIdentifier)]; 
  243. }
  244.  
  245. /*
  246.  * Used when creating an NSSelection representing all the Graphics
  247.  * in a selection.  Has to recurse through Groups because you still
  248.  * want the NSSelection to be valid even if the Graphics are ungrouped
  249.  * in the interim between the time the selection is determined to the
  250.  * time the links stuff asks questions about the selection later.
  251.  */
  252.  
  253. - (NSString *)identifierString
  254. {
  255.     int i = [components count];
  256.     NSMutableString *retval = [NSMutableString stringWithFormat:@"%d", identifier];
  257.     while (i--) [retval appendFormat:@" %@", [[components objectAtIndex:i] identifierString]];
  258.     return retval;
  259. }
  260.  
  261. /*
  262.  * See the method findGraphicInSelection: in gvLinks.m to see how this
  263.  * method is used (it basically just lets you get back to a Graphic
  264.  * from its identifier whether its in a Group or not).
  265.  */
  266.  
  267. - (Graphic *)graphicIdentifiedBy:(int)anIdentifier
  268. {
  269.     int i;
  270.  
  271.     if (anIdentifier == identifier) return self;
  272.  
  273.     i = [components count];
  274.     while (i--) {
  275.     Graphic *graphic = [components objectAtIndex:i];
  276.     if ((graphic = [graphic graphicIdentifiedBy:anIdentifier])) return graphic;
  277.     }
  278.     return nil;
  279. }
  280.  
  281. /*
  282.  * We pass this method onto all the things inside the group since
  283.  * there might be linked things inside the group.
  284.  */
  285.  
  286. - (void)readLinkFromPasteboard:(NSPasteboard *)pboard usingManager:(NSDataLinkManager *)linkManager useNewIdentifier:(BOOL)useNewIdentifier
  287. {
  288.     int i = [components count];
  289.     while (i--) {
  290.     Graphic *graphic = [components objectAtIndex:i];
  291.     if ([graphic mightBeLinked]) [graphic readLinkFromPasteboard:pboard usingManager:linkManager useNewIdentifier:useNewIdentifier];
  292.     }
  293. }
  294.  
  295. /* Form Entry methods.  See TextGraphic.m for details. */
  296.  
  297. - (BOOL)hasFormEntries
  298. {
  299.     int i = [components count];
  300.     while (i--) if ([[components objectAtIndex:i] hasFormEntries]) return YES;
  301.     return NO;
  302. }
  303.  
  304. - (BOOL)writeFormEntryToMutableString:(NSMutableString *)string
  305. {
  306.     BOOL retval = NO;
  307.     int i = [components count];
  308.  
  309.     while (i--) {
  310.         if ([[components objectAtIndex:i] writeFormEntryToMutableString:string]) {
  311.             retval = YES;
  312.         }
  313.     }
  314.  
  315.     return retval;
  316. }
  317.  
  318. - (BOOL)writesFiles
  319. {
  320.     int i = [components count];
  321.     while (i--) if ([[components objectAtIndex:i] writesFiles]) return YES;
  322.     return NO;
  323. }
  324.  
  325. - (void)writeFilesToDirectory:(NSString *)directory
  326. {
  327.     int i = [components count];
  328.     while (i--) [[components objectAtIndex:i] writeFilesToDirectory:directory];
  329. }
  330.  
  331. /* Notification methods */
  332.  
  333. - (void)wasRemovedFrom:(GraphicView *)sender
  334. {
  335.     [components makeObjectsPerform:@selector(wasRemovedFrom:) withObject:sender];
  336.     [cache release];
  337.     cache = nil; 
  338. }
  339.  
  340. - (void)wasAddedTo:(GraphicView *)sender
  341. {
  342.     [components makeObjectsPerform:@selector(wasAddedTo:) withObject:sender]; 
  343. }
  344.  
  345. /* Color drag-and-drop support. */
  346.  
  347. - (Graphic *)colorAcceptorAt:(NSPoint)point
  348. {
  349.     int i, count;
  350.     Graphic *graphic;
  351.  
  352.     count = [components count];
  353.     for (i = 0; i < count; i++) {
  354.     if ((graphic = [[components objectAtIndex:i] colorAcceptorAt:point])) return graphic;
  355.     }
  356.  
  357.     return nil;
  358. }
  359.  
  360. /* We can't cache ourselves if we have a TextGraphic in the Group. */
  361.  
  362. - (BOOL)hasTextGraphic
  363. {
  364.     return hasTextGraphic;
  365. }
  366.  
  367. - (void)setCacheable:(BOOL)flag
  368. /*
  369.  * Sets whether we do caching of this Group or not.
  370.  */
  371. {
  372.     dontCache = flag ? NO : YES;
  373.     if (dontCache) {
  374.     [cache release];
  375.     cache = nil;
  376.     } 
  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.     NSRect eb;
  396.     NSRect b;
  397.     float sx = 1.0, sy = 1.0, tx, ty;
  398.     BOOL changed, changedSize, caching = NO;
  399.  
  400.     if (bounds.size.width < 1.0 || bounds.size.height < 1.0 || !components) return self;
  401.  
  402.     changedSize = lastRect.size.width != bounds.size.width || lastRect.size.height != bounds.size.height;
  403.     changed = changedSize || lastRect.origin.x != bounds.origin.x || lastRect.origin.y != bounds.origin.y;
  404.  
  405.     if ((changedSize || !cache) && [[NSDPSContext currentContext] isDrawingToScreen]) {
  406.     [cache release];
  407.     cache = nil;
  408.     if (DrawStatus != Resizing && [self isCacheable] && [components count] > GROUP_CACHE_THRESHOLD) {
  409.         caching = YES;
  410.         eb = [self extendedBounds];
  411.         cache = [[NSImage allocWithZone:(NSZone *)[self zone]] initWithSize:eb.size];
  412.         [cache lockFocus];
  413.         PStranslate(- eb.origin.x, - eb.origin.y);
  414.         PSsetalpha(0.0);
  415.         PSsetgray(NSWhite);
  416.         NSRectFill(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 objectAtIndex:--i];
  429.     if (changed) {
  430.         b = [g bounds];
  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.origin.x = 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 (![[NSDPSContext currentContext] isDrawingToScreen] || !cache || caching) {
  440.         [g setGraphicsState];    /* does a gsave ... */
  441.         [g draw];
  442.         PSgrestore();        /* ... so we need this grestore */
  443.     }
  444.     }
  445.  
  446.     if (cache && [[NSDPSContext currentContext] isDrawingToScreen]) {
  447.     if (caching) {
  448.         [cache unlockFocus];
  449.     } else {
  450.         eb = [self extendedBounds];
  451.     }
  452.     [cache compositeToPoint:eb.origin operation:NSCompositeSourceOver];
  453.     }
  454.  
  455.     lastRect = bounds;
  456.  
  457.     return self;
  458. }
  459.  
  460. - (BOOL)hit:(NSPoint)point
  461. /*
  462.  * Gets a hit if any of the items in the group gets a hit.
  463.  */
  464. {
  465.     int i;
  466.     NSPoint 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 objectAtIndex:--i];
  480.         if ([graphic hit:p]) return YES;
  481.         }
  482.     } else {
  483.         return YES;
  484.     }
  485.     }
  486.  
  487.     return NO;
  488. }
  489.  
  490. /* Archiving methods */
  491.  
  492. #define COMPONENTS_KEY @"Components"
  493.  
  494. - (void)convertSelf:(ConversionDirection)direction propertyList:(id)plist
  495. {
  496.     [super convertSelf:direction propertyList:plist];
  497.     PL_FLAG(plist, dontCache, @"DontCache", direction);
  498.     PL_FLAG(plist, hasTextGraphic, @"HasTextGraphic", direction);
  499.     PL_RECT(plist, lastRect, @"LastPosition", direction);
  500. }
  501.  
  502. - (id)propertyList
  503. {
  504.     NSMutableDictionary *plist = [super propertyList];
  505.     [plist setObject:propertyListFromArray(components) forKey:COMPONENTS_KEY];
  506.     return plist;
  507. }
  508.  
  509. - (NSString *)description
  510. {
  511.     NSMutableDictionary *plist = [super propertyList];
  512.     [plist setObject:components forKey:COMPONENTS_KEY]; // don't expand for description
  513.     return [plist description];
  514. }
  515.  
  516. - initFromPropertyList:(id)plist inDirectory:(NSString *)directory
  517. {
  518.     [super initFromPropertyList:plist inDirectory:directory];
  519.     components = arrayFromPropertyList([plist objectForKey:COMPONENTS_KEY], directory, [self zone]);
  520.     return self;
  521. }
  522.  
  523. @end
  524.