|
|
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.