|
|
1.1 root 1: #import "draw.h"
2:
3: @implementation Graphic : Object
4:
5: static int KNOB_WIDTH = 0.0;
6: static int KNOB_HEIGHT = 0.0;
7:
8: #define MINSIZE 5.0 /* minimum size of a Graphic */
9:
10: NXCursor *CrossCursor = nil; /* global since subclassers may need it */
11:
12: /* Optimization method. */
13:
14: /*
15: * The fastKnobFill optimization just keeps a list of black and dark gray
16: * rectangles (the knobbies are made out of black and dark gray rectangles)
17: * and emits them in a single NXRectFillList() which is much faster than
18: * doing individual rectfills (we also save the repeated setgrays).
19: */
20:
21: static NXRect *blackRectList = NULL;
22: static int blackRectSize = 0;
23: static int blackRectCount = 0;
24: static NXRect *dkgrayRectList = NULL;
25: static int dkgrayRectSize = 0;
26: static int dkgrayRectCount = 0;
27:
28: + fastKnobFill:(const NXRect *)aRect isBlack:(BOOL)isBlack
29: {
30: if (!aRect) return self;
31:
32: if (isBlack) {
33: if (!blackRectList) {
34: blackRectSize = 16;
35: NX_ZONEMALLOC([NXApp zone], blackRectList, NXRect, blackRectSize);
36: } else {
37: while (blackRectCount >= blackRectSize) blackRectSize <<= 1;
38: NX_ZONEREALLOC([NXApp zone], blackRectList, NXRect, blackRectSize);
39: }
40: blackRectList[blackRectCount++] = *aRect;
41: } else {
42: if (!dkgrayRectList) {
43: dkgrayRectSize = 16;
44: NX_ZONEMALLOC([NXApp zone], dkgrayRectList, NXRect, dkgrayRectSize);
45: } else {
46: while (dkgrayRectCount >= dkgrayRectSize) dkgrayRectSize <<= 1;
47: NX_ZONEREALLOC([NXApp zone], dkgrayRectList, NXRect, dkgrayRectSize);
48: }
49: dkgrayRectList[dkgrayRectCount++] = *aRect;
50: }
51:
52: return self;
53: }
54:
55: + showFastKnobFills
56: {
57: PSsetgray(NX_BLACK);
58: NXRectFillList(blackRectList, blackRectCount);
59: PSsetgray(NX_DKGRAY);
60: NXRectFillList(dkgrayRectList, dkgrayRectCount);
61: blackRectCount = 0;
62: dkgrayRectCount = 0;
63: return self;
64: }
65:
66: /* Factory methods. */
67:
68: + initialize
69: /*
70: * This sets the class version so that we can compatibly read
71: * old Graphic objects out of an archive.
72: */
73: {
74: [Graphic setVersion:3];
75: return self;
76: }
77:
78: + (BOOL)isEditable
79: /*
80: * Any Graphic which can be edited should return YES from this
81: * and its instances should do something in the response to the
82: * edit:in: method.
83: */
84: {
85: return NO;
86: }
87:
88: + cursor
89: /*
90: * Any Graphic that doesn't have a special cursor gets the default cross.
91: */
92: {
93: NXPoint spot;
94:
95: if (!CrossCursor) {
96: CrossCursor = [NXCursor newFromImage:[NXImage newFromSection:"Cross.tiff"]];
97: spot.x = 7.0; spot.y = 7.0;
98: [CrossCursor setHotSpot:&spot];
99: }
100:
101: return CrossCursor;
102: }
103:
104: static void initClassVars()
105: {
106: const char *value;
107: NXCoord w = 2.0, h = 2.0;
108:
109: if (!KNOB_WIDTH) {
110: value = NXGetDefaultValue([NXApp appName], "KnobWidth");
111: if (value) w = floor(atof(value) / 2.0);
112: value = NXGetDefaultValue([NXApp appName], "KnobHeight");
113: if (value) h = floor(atof(value) / 2.0);
114: w = MAX(w, 1.0); h = MAX(h, 1.0);
115: KNOB_WIDTH = w * 2.0 + 1.0; /* size must be odd */
116: KNOB_HEIGHT = h * 2.0 + 1.0;
117: }
118: }
119:
120: /*
121: * The currentGraphicIdentifier is a number that is kept unique for a given
122: * Draw document by being monotonically increasing and is bumped each time a
123: * new Graphic is created. The method of the same name is used during the
124: * archiving of a Draw document to write out what the number is at save-time.
125: * updateCurrentGraphicIdentifer: is used at document load time to reset
126: * the number to that level (if it's already higher, then we don't need to
127: * bump it).
128: */
129:
130: static int currentGraphicIdentifier = 1;
131:
132: + (int)currentGraphicIdentifier
133: {
134: return currentGraphicIdentifier;
135: }
136:
137: + updateCurrentGraphicIdentifier:(int)newMaxIdentifier
138: {
139: if (newMaxIdentifier > currentGraphicIdentifier) currentGraphicIdentifier = newMaxIdentifier;
140: return self;
141: }
142:
143: - init
144: {
145: [super init];
146: gFlags.active = YES;
147: gFlags.selected = YES;
148: initClassVars();
149: identifier = currentGraphicIdentifier++;
150: return self;
151: }
152:
153: - awake
154: {
155: initClassVars();
156: return [super awake];
157: }
158:
159: /* Private C functions and macros used to implement methods in this class. */
160:
161: static void drawKnobs(const NXRect *rect, int cornerMask, BOOL black)
162: /*
163: * Draws either the knobs or their shadows (not both).
164: */
165: {
166: NXRect knob;
167: NXCoord dx, dy;
168: BOOL oddx, oddy;
169:
170: knob = *rect;
171: dx = knob.size.width / 2.0;
172: dy = knob.size.height / 2.0;
173: oddx = (floor(dx) != dx);
174: oddy = (floor(dy) != dy);
175: knob.size.width = KNOB_WIDTH;
176: knob.size.height = KNOB_HEIGHT;
177: knob.origin.x -= ((KNOB_WIDTH - 1.0) / 2.0);
178: knob.origin.y -= ((KNOB_HEIGHT - 1.0) / 2.0);
179:
180: if (cornerMask & LOWER_LEFT_MASK) [Graphic fastKnobFill:&knob isBlack:black];
181: knob.origin.y += dy;
182: if (oddy) knob.origin.y -= 0.5;
183: if (cornerMask & LEFT_SIDE_MASK) [Graphic fastKnobFill:&knob isBlack:black];
184: knob.origin.y += dy;
185: if (oddy) knob.origin.y += 0.5;
186: if (cornerMask & UPPER_LEFT_MASK) [Graphic fastKnobFill:&knob isBlack:black];
187: knob.origin.x += dx;
188: if (oddx) knob.origin.x -= 0.5;
189: if (cornerMask & TOP_SIDE_MASK) [Graphic fastKnobFill:&knob isBlack:black];
190: knob.origin.x += dx;
191: if (oddx) knob.origin.x += 0.5;
192: if (cornerMask & UPPER_RIGHT_MASK) [Graphic fastKnobFill:&knob isBlack:black];
193: knob.origin.y -= dy;
194: if (oddy) knob.origin.y -= 0.5;
195: if (cornerMask & RIGHT_SIDE_MASK) [Graphic fastKnobFill:&knob isBlack:black];
196: knob.origin.y -= dy;
197: if (oddy) knob.origin.y += 0.5;
198: if (cornerMask & LOWER_RIGHT_MASK) [Graphic fastKnobFill:&knob isBlack:black];
199: knob.origin.x -= dx;
200: if (oddx) knob.origin.x += 0.5;
201: if (cornerMask & BOTTOM_SIDE_MASK) [Graphic fastKnobFill:&knob isBlack:black];
202: }
203:
204: /* Private methods sometimes overridden by subclassers */
205:
206: - setGraphicsState
207: /*
208: * Emits a gsave, must be balanced by grestore.
209: */
210: {
211: PSSetParameters(gFlags.linecap, gFlags.linejoin, linewidth);
212: return self;
213: }
214:
215: - setLineColor
216: {
217: if (lineColor) {
218: NXSetColor(*lineColor);
219: } else {
220: NXSetColor(NX_COLORBLACK);
221: }
222: return self;
223: }
224:
225: - setFillColor
226: {
227: if (fillColor) NXSetColor(*fillColor);
228: return self;
229: }
230:
231: - (int)cornerMask
232: /*
233: * Returns a mask of the corners which should have a knobby in them.
234: */
235: {
236: return ALL_CORNERS;
237: }
238:
239: /* Data link methods -- see Links.rtf and gvLinks.m for more info */
240:
241: /*
242: * Most Graphics aren't linked (i.e. their visual display is
243: * not determined by some other document). See Image and
244: * TextGraphic for examples of Graphics that sometimes do.
245: */
246:
247: - setLink:(NXDataLink *)aLink
248: {
249: return nil;
250: }
251:
252: - (NXDataLink *)link
253: {
254: return nil;
255: }
256:
257: - (Graphic *)graphicLinkedBy:(NXDataLink *)aLink
258: /*
259: * The reason we implement this method (instead of just relying on
260: * saying if ([graphic link] == aLink)) is for the sake of Group
261: * objects which may have a linked Graphic embedded in them.
262: */
263: {
264: NXDataLink *link = [self link];
265:
266: if (link) {
267: if (!aLink) { /* !aLink means any link */
268: return ([link disposition] != NX_LinkBroken) ? self : nil;
269: } else {
270: return (aLink == link) ? self : nil;
271: }
272: }
273:
274: return nil;
275: }
276:
277: - reviveLink:(NXDataLinkManager *)linkManager
278: /*
279: * We never archive link information (but, of course, the unique identifer
280: * is always archived with a Graphic). Thus, when our document is reloaded,
281: * we just asked the NXDataLinkManager which NXDataLink object is associated
282: * with the NXSelection which represents this Graphic.
283: */
284: {
285: if (![self link]) [self setLink:[linkManager findDestinationLinkWithSelection:[self selection]]];
286: return self;
287: }
288:
289: - (NXSelection *)selection
290: /*
291: * Just creates an NXSelection "bag o' bits" with our unique identifier in it.
292: */
293: {
294: char buffer[200];
295: sprintf(buffer, "%d %d", ByGraphic, [self identifier]);
296: return [[NXSelection allocFromZone:[self zone]] initWithDescription:buffer length:strlen(buffer)+1];
297: }
298:
299: - (BOOL)mightBeLinked
300: /*
301: * This is set whenever our Graphic has a link set in it.
302: * It is never cleared.
303: * We use it during copy/paste to determine whether we have
304: * to check with the data link manager to possibly reestablish
305: * a link to this object.
306: */
307: {
308: return gFlags.mightBeLinked;
309: }
310:
311: - readLinkFromPasteboard:(Pasteboard *)pboard usingManager:(NXDataLinkManager *)linkManager useNewIdentifier:(BOOL)useNewIdentifier
312: /*
313: * This is called by pasteFromPasteboard: when we paste a Graphic (i.e. copied/pasted from
314: * another Draw document) in case that Graphic was linked to something when it was copied.
315: * Since we called writeLinksToPasteboard: when we put the Graphic into the pasteboard (see
316: * writeLinkToPasteboard:types: above) we can simply retrieve all the link information for
317: * that graphic by using the linkManager method addLinkPreviouslyAt:fromPasteboard:at:.
318: */
319: {
320: NXDataLink *link;
321: NXSelection *oldSelection, *newSelection;
322:
323: oldSelection = [self selection];
324: if (linkManager && oldSelection) {
325: if (useNewIdentifier) [self resetIdentifier];
326: newSelection = [self selection];
327: link = [linkManager addLinkPreviouslyAt:oldSelection
328: fromPasteboard:pboard
329: at:newSelection];
330: if (!link) [newSelection free];
331: [self setLink:link];
332: }
333: if (useNewIdentifier) [oldSelection free];
334:
335: return self;
336: }
337:
338: /* Notification messages */
339:
340: /*
341: * These methods are sent when a Graphic is added to or removed
342: * from a GraphicView (respectively). Currently we only use them
343: * to break and reestablish links if any.
344: */
345:
346: - wasAddedTo:(GraphicView *)sender
347: {
348: NXDataLink *link;
349: NXDataLinkManager *linkManager;
350:
351: if ((linkManager = [sender linkManager]) && (link = [self link])) {
352: if ([link disposition] == NX_LinkBroken) {
353: [linkManager addLink:link at:[self selection]];
354: }
355: }
356:
357: return self;
358: }
359:
360: - wasRemovedFrom:(GraphicView *)sender
361: {
362: [[self link] break];
363: return self;
364: }
365:
366: /* Methods for uniquely identifying a Graphic. */
367:
368: - resetIdentifier
369: {
370: identifier = currentGraphicIdentifier++;
371: return self;
372: }
373:
374: - writeIdentifierTo:(char *)buffer
375: /*
376: * This method is necessary to support a Group which never writes out
377: * its own identifier, but, instead has its components each write out
378: * their own identifier.
379: */
380: {
381: sprintf(buffer, "%d", identifier);
382: return self;
383: }
384:
385: - (int)identifier
386: {
387: return identifier;
388: }
389:
390: - (Graphic *)graphicIdentifiedBy:(int)anIdentifier
391: {
392: return (identifier == anIdentifier) ? self : nil;
393: }
394:
395: /* Event handling */
396:
397: - (BOOL)handleEvent:(NXEvent *)event at:(const NXPoint *)p inView:(View *)view
398: /*
399: * Currently the only Graphic's that handle events are Image Graphic's that
400: * are linked to something else (they follow the link on double-click and
401: * the track the mouse for link buttons, for example). This method should
402: * return YES only if it tracked the mouse until it went up.
403: */
404: {
405: return NO;
406: }
407:
408: /* Number of Graphics this Graphic represents (always 1 for non-Group). */
409:
410: - (int)graphicCount
411: /*
412: * This is really only here to support Groups. It is used by the
413: * Object Link stuff purely to know how much space will be needed to
414: * create an NXSelection with the unique identifiers of all the objects
415: * in the selection.
416: */
417: {
418: return 1;
419: }
420:
421: /* Public routines mostly called by GraphicView's. */
422:
423: - (const char *)title
424: {
425: return NXLoadLocalStringFromTableInBundle(NULL, nil, [self name], NULL);
426: }
427:
428: - (BOOL)isSelected
429: {
430: return gFlags.selected;
431: }
432:
433: - (BOOL)isActive
434: {
435: return gFlags.active;
436: }
437:
438: - (BOOL)isLocked
439: {
440: return gFlags.locked;
441: }
442:
443: - select
444: {
445: gFlags.selected = YES;
446: return self;
447: }
448:
449: - deselect
450: {
451: gFlags.selected = NO;
452: return self;
453: }
454:
455: - activate
456: /*
457: * Activation is used to *temporarily* take a Graphic out of the GraphicView.
458: */
459: {
460: gFlags.active = YES;
461: return self;
462: }
463:
464: - deactivate
465: {
466: gFlags.active = NO;
467: return self;
468: }
469:
470: - lock
471: /*
472: * A locked graphic cannot be selected, resized or moved.
473: */
474: {
475: gFlags.locked = YES;
476: return self;
477: }
478:
479: - unlock
480: {
481: gFlags.locked = NO;
482: return self;
483: }
484:
485: /* See TextGraphic for more info about form entries. */
486:
487: - (BOOL)isFormEntry
488: {
489: return NO;
490: }
491:
492: - setFormEntry:(int)flag
493: {
494: return self;
495: }
496:
497: - (BOOL)hasFormEntries
498: {
499: return NO;
500: }
501:
502: - (BOOL)writeFormEntryToStream:(NXStream *)stream
503: {
504: return NO;
505: }
506:
507: /* See Group and Image for more info about cacheability. */
508:
509: - setCacheable:(BOOL)flag
510: {
511: return self;
512: }
513:
514: - (BOOL)isCacheable
515: {
516: return YES;
517: }
518:
519: /* Getting and setting the bounds. */
520:
521: - getBounds:(NXRect *)theRect
522: {
523: *theRect = bounds;
524: return self;
525: }
526:
527: - setBounds:(const NXRect *)aRect
528: {
529: bounds = *aRect;
530: return self;
531: }
532:
533: - (NXRect *)getExtendedBounds:(NXRect *)theRect
534: /*
535: * Returns, by reference, the rectangle which encloses the Graphic
536: * AND ITS KNOBBIES and its increased line width (if appropriate).
537: */
538: {
539: if (bounds.size.width < 0.0) {
540: theRect->origin.x = bounds.origin.x + bounds.size.width;
541: theRect->size.width = - bounds.size.width;
542: } else {
543: theRect->origin.x = bounds.origin.x;
544: theRect->size.width = bounds.size.width;
545: }
546: if (bounds.size.height < 0.0) {
547: theRect->origin.y = bounds.origin.y + bounds.size.height;
548: theRect->size.height = - bounds.size.height;
549: } else {
550: theRect->origin.y = bounds.origin.y;
551: theRect->size.height = bounds.size.height;
552: }
553:
554: theRect->size.width = MAX(1.0, theRect->size.width);
555: theRect->size.height = MAX(1.0, theRect->size.height);
556:
557: NXInsetRect(theRect, - ((KNOB_WIDTH - 1.0) + linewidth + 1.0),
558: - ((KNOB_HEIGHT - 1.0) + linewidth + 1.0));
559:
560: if (gFlags.arrow) {
561: if (linewidth) {
562: NXInsetRect(theRect, - linewidth * 2.5, - linewidth * 2.5);
563: } else {
564: NXInsetRect(theRect, - 13.0, - 13.0);
565: }
566: }
567:
568: NXIntegralRect(theRect);
569:
570: return theRect;
571: }
572:
573: - (int)knobHit:(const NXPoint *)p
574: /*
575: * Returns 0 if point is in bounds, and Graphic isOpaque, and no knobHit.
576: * Returns -1 if outside bounds or not opaque or not active.
577: * Returns corner number if there is a hit on a corner.
578: * We have to be careful when the bounds are off an odd size since the
579: * knobs on the sides are one pixel larger.
580: */
581: {
582: NXRect eb;
583: NXRect knob;
584: NXCoord dx, dy;
585: BOOL oddx, oddy;
586: int cornerMask = [self cornerMask];
587:
588: [self getExtendedBounds:&eb];
589:
590: if (!gFlags.active) {
591: return -1;
592: } else if (!gFlags.selected) {
593: return (NXMouseInRect(p, &bounds, NO) && [self isOpaque]) ? 0 : -1;
594: } else {
595: if (!NXMouseInRect(p, &eb, NO)) return -1;
596: }
597:
598: knob = bounds;
599: dx = knob.size.width / 2.0;
600: dy = knob.size.height / 2.0;
601: oddx = (floor(dx) != dx);
602: oddy = (floor(dy) != dy);
603: knob.size.width = KNOB_WIDTH;
604: knob.size.height = KNOB_HEIGHT;
605: knob.origin.x -= ((KNOB_WIDTH - 1.0) / 2.0);
606: knob.origin.y -= ((KNOB_HEIGHT - 1.0) / 2.0);
607:
608: if ((cornerMask & LOWER_LEFT_MASK) && NXMouseInRect(p, &knob, NO))
609: return(LOWER_LEFT);
610: knob.origin.y += dy;
611: if (oddy) knob.origin.y -= 0.5;
612: if ((cornerMask & LEFT_SIDE_MASK) && NXMouseInRect(p, &knob, NO))
613: return(LEFT_SIDE);
614: knob.origin.y += dy;
615: if (oddy) knob.origin.y += 0.5;
616: if ((cornerMask & UPPER_LEFT_MASK) && NXMouseInRect(p, &knob, NO))
617: return(UPPER_LEFT);
618: knob.origin.x += dx;
619: if (oddx) knob.origin.x -= 0.5;
620: if ((cornerMask & TOP_SIDE_MASK) && NXMouseInRect(p, &knob, NO))
621: return(TOP_SIDE);
622: knob.origin.x += dx;
623: if (oddx) knob.origin.x += 0.5;
624: if ((cornerMask & UPPER_RIGHT_MASK) && NXMouseInRect(p, &knob, NO))
625: return(UPPER_RIGHT);
626: knob.origin.y -= dy;
627: if (oddy) knob.origin.y -= 0.5;
628: if ((cornerMask & RIGHT_SIDE_MASK) && NXMouseInRect(p, &knob, NO))
629: return(RIGHT_SIDE);
630: knob.origin.y -= dy;
631: if (oddy) knob.origin.y += 0.5;
632: if ((cornerMask & LOWER_RIGHT_MASK) && NXMouseInRect(p, &knob, NO))
633: return(LOWER_RIGHT);
634: knob.origin.x -= dx;
635: if (oddx) knob.origin.x += 0.5;
636: if ((cornerMask & BOTTOM_SIDE_MASK) && NXMouseInRect(p, &knob, NO))
637: return(BOTTOM_SIDE);
638:
639: return NXMouseInRect(p, &bounds, NO) ? ([self isOpaque] ? 0 : -1) : -1;
640: }
641:
642: /* This method is analogous to display (not drawSelf::) in View. */
643:
644: - draw:(const NXRect *)rect
645: /*
646: * Draws the graphic inside rect. If rect is NULL, then it draws the
647: * entire Graphic. If the Graphic is not intersected by rect, then it
648: * is not drawn at all. If the Graphic is selected, it is drawn with
649: * its knobbies. This method is not intended to be overridden. It
650: * calls the overrideable method "draw" which doesn't have to worry
651: * about drawing the knobbies.
652: *
653: * Note the showFastKnobFills optimization here. If this Graphic is
654: * opaque then there is a possibility that it might obscure knobbies
655: * of Graphics underneath it, so we must emit the cached rectfills
656: * before drawing this Graphic.
657: */
658: {
659: NXRect r;
660:
661: [self getExtendedBounds:&r];
662: if (gFlags.active && (!rect || NXIntersectsRect(rect, &r))) {
663: if ([self isOpaque]) [Graphic showFastKnobFills];
664: [self setGraphicsState]; /* does a gsave */
665: [self draw];
666: PSgrestore(); /* so we need a grestore here */
667: if (NXDrawingStatus == NX_DRAWING) {
668: if (gFlags.selected) {
669: r.origin.x = floor(bounds.origin.x);
670: r.origin.y = floor(bounds.origin.y);
671: r.size.width = floor(bounds.origin.x + bounds.size.width + 0.99) - r.origin.x;
672: r.size.height = floor(bounds.origin.y + bounds.size.height + 0.99) - r.origin.y;
673: r.origin.x += 1.0;
674: r.origin.y -= 1.0;
675: drawKnobs(&r, [self cornerMask], YES); /* shadows */
676: r.origin.x = floor(bounds.origin.x);
677: r.origin.y = floor(bounds.origin.y);
678: r.size.width = floor(bounds.origin.x + bounds.size.width + 0.99) - r.origin.x;
679: r.size.height = floor(bounds.origin.y + bounds.size.height + 0.99) - r.origin.y;
680: drawKnobs(&r, [self cornerMask], NO); /* knobs */
681: }
682: }
683: return self;
684: }
685:
686: return nil;
687: }
688:
689: /*
690: * Returns whether this Graphic can emit, all by itself, fully
691: * encapsulated PostScript (or fully conforming TIFF) representing
692: * itself. This is an optimization for copy/paste.
693: */
694:
695: - (BOOL)canEmitEPS
696: {
697: return NO;
698: }
699:
700: - (BOOL)canEmitTIFF
701: {
702: return NO;
703: }
704:
705: /* Sizing, aligning and moving. */
706:
707: - moveLeftEdgeTo:(const NXCoord *)x
708: {
709: bounds.origin.x = *x;
710: return self;
711: }
712:
713: - moveRightEdgeTo:(const NXCoord *)x
714: {
715: bounds.origin.x = *x - bounds.size.width;
716: return self;
717: }
718:
719: - moveTopEdgeTo:(const NXCoord *)y
720: {
721: bounds.origin.y = *y - bounds.size.height;
722: return self;
723: }
724:
725: - moveBottomEdgeTo:(const NXCoord *)y
726: {
727: bounds.origin.y = *y;
728: return self;
729: }
730:
731: - moveHorizontalCenterTo:(const NXCoord *)x
732: {
733: bounds.origin.x = *x - floor(bounds.size.width / 2.0);
734: return self;
735: }
736:
737: - moveVerticalCenterTo:(const NXCoord *)y
738: {
739: bounds.origin.y = *y - floor(bounds.size.height / 2.0);
740: return self;
741: }
742:
743: - (NXCoord)baseline
744: {
745: return 0.0;
746: }
747:
748: - moveBaselineTo:(const NXCoord *)y
749: {
750: return self;
751: }
752:
753: - moveBy:(const NXPoint *)offset
754: {
755: bounds.origin.x += floor(offset->x);
756: bounds.origin.y += floor(offset->y);
757: return self;
758: }
759:
760: - moveTo:(const NXPoint *)p
761: {
762: bounds.origin.x = floor(p->x);
763: bounds.origin.y = floor(p->y);
764: return self;
765: }
766:
767: - centerAt:(const NXPoint *)p
768: {
769: bounds.origin.x = floor(p->x - bounds.size.width / 2.0);
770: bounds.origin.y = floor(p->y - bounds.size.height / 2.0);
771: return self;
772: }
773:
774: - sizeTo:(const NXSize *)size
775: {
776: bounds.size.width = floor(size->width);
777: bounds.size.height = floor(size->height);
778: return self;
779: }
780:
781: - sizeToNaturalAspectRatio
782: {
783: return [self constrainCorner:UPPER_RIGHT toAspectRatio:[self naturalAspectRatio]];
784: }
785:
786: - sizeToGrid:(GraphicView *)graphicView
787: {
788: NXPoint p;
789:
790: [graphicView grid:&bounds.origin];
791: p.x = bounds.origin.x + bounds.size.width;
792: p.y = bounds.origin.y + bounds.size.height;
793: [graphicView grid:&p];
794: bounds.size.width = p.x - bounds.origin.x;
795: bounds.size.height = p.y - bounds.origin.y;
796:
797: return self;
798: }
799:
800: - alignToGrid:(GraphicView *)graphicView
801: {
802: [graphicView grid:&bounds.origin];
803: return self;
804: }
805:
806: /* Compatibility method for old PSGraphic and Tiff classes. */
807:
808: - replaceWithImage
809: {
810: return self;
811: }
812:
813: /* Public routines. */
814:
815: - setLineWidth:(const float *)value
816: /*
817: * This is called with value indirected so that it can be called via
818: * a perform:with: method. Kind of screwy, but ...
819: */
820: {
821: if (value) linewidth = *value;
822: return self;
823: }
824:
825: - (float)lineWidth
826: {
827: return linewidth;
828: }
829:
830: - setLineColor:(const NXColor *)color
831: {
832: if (color) {
833: if (NXEqualColor(*color, NX_COLORBLACK)) {
834: NX_FREE(lineColor);
835: lineColor = NULL;
836: gFlags.nooutline = NO;
837: } else {
838: if (!lineColor) NX_ZONEMALLOC([self zone], lineColor, NXColor, 1);
839: *lineColor = *color;
840: gFlags.nooutline = NO;
841: }
842: }
843: return self;
844: }
845:
846: - (NXColor)lineColor
847: {
848: return lineColor ? *lineColor : NX_COLORBLACK;
849: }
850:
851: - setFillColor:(const NXColor *)color
852: {
853: if (color) {
854: if (!fillColor) NX_ZONEMALLOC([self zone], fillColor, NXColor, 1);
855: *fillColor = *color;
856: if (![self fill]) [self setFill:FILL_NZWR];
857: }
858: return self;
859: }
860:
861: - (NXColor)fillColor
862: {
863: return fillColor ? *fillColor : NX_COLORWHITE;
864: }
865:
866: - (Graphic *)colorAcceptorAt:(const NXPoint *)point
867: /*
868: * This method supports dragging and dropping colors on Graphics.
869: * Whatever object is returned from this may well be sent
870: * setFillColor: if the color actually gets dropped on it.
871: * See gvDrag.m's acceptsColor:atPoint: method.
872: */
873: {
874: return nil;
875: }
876:
877: - changeFont:sender
878: {
879: return self;
880: }
881:
882: - font
883: {
884: return nil;
885: }
886:
887: - setGray:(const float *)value
888: /*
889: * This is called with value indirected so that it can be called via
890: * a perform:with: method. Kind of screwy, but ...
891: * Now that we have converted to using NXColor's, we'll interpret this
892: * method as a request to set the lineColor.
893: */
894: {
895: NXColor color;
896:
897: if (value) {
898: color = NXConvertGrayToColor(*value);
899: [self setLineColor:&color];
900: }
901:
902: return self;
903: }
904:
905: - (float)gray
906: {
907: float retval;
908:
909: if (lineColor) {
910: NXConvertColorToGray(*lineColor, &retval);
911: } else {
912: retval = NX_BLACK;
913: }
914:
915: return retval;
916: }
917:
918: - setFill:(int)mode
919: {
920: switch (mode) {
921: case FILL_NONE: gFlags.eofill = gFlags.fill = NO; break;
922: case FILL_EO: gFlags.eofill = YES; gFlags.fill = NO; break;
923: case FILL_NZWR: gFlags.eofill = NO; gFlags.fill = YES; break;
924: }
925: return self;
926: }
927:
928: - (int)fill
929: {
930: if (gFlags.eofill) {
931: return FILL_EO;
932: } else if (gFlags.fill) {
933: return FILL_NZWR;
934: } else {
935: return FILL_NONE;
936: }
937: }
938:
939: - setOutlined:(BOOL)outlinedFlag
940: {
941: gFlags.nooutline = outlinedFlag ? NO : YES;
942: return self;
943: }
944:
945: - (BOOL)isOutlined
946: {
947: return gFlags.nooutline ? NO : YES;
948: }
949:
950: - setLineCap:(int)capValue
951: {
952: if (capValue >= 0 && capValue <= 2) {
953: gFlags.linecap = capValue;
954: }
955: return self;
956: }
957:
958: - (int)lineCap
959: {
960: return gFlags.linecap;
961: }
962:
963: - setLineArrow:(int)arrowValue
964: {
965: if (arrowValue >= 0 && arrowValue <= 3) {
966: gFlags.arrow = arrowValue;
967: }
968: return self;
969: }
970:
971: - (int)lineArrow
972: {
973: return gFlags.arrow;
974: }
975:
976: - setLineJoin:(int)joinValue
977: {
978: if (joinValue >= 0 && joinValue <= 2) {
979: gFlags.linejoin = joinValue;
980: }
981: return self;
982: }
983:
984: - (int)lineJoin
985: {
986: return gFlags.linejoin;
987: }
988:
989: /* Archiver-related methods. */
990:
991: - write:(NXTypedStream *)stream
992: /*
993: * Since a typical document has many Graphics, we want to try and make
994: * the archived document small, so we don't write out the linewidth and
995: * gray values if they are the most common 0 and NX_BLACK. To accomplish
996: * this, we note that we haven't written them out by setting the
997: * bits in gFlags.
998: */
999: {
1000: [super write:stream];
1001: gFlags.linewidthSet = (linewidth != 0.0);
1002: gFlags.lineColorSet = lineColor ? YES : NO;
1003: gFlags.fillColorSet = fillColor ? YES : NO;
1004: NXWriteTypes(stream, "ffffii", &bounds.origin.x, &bounds.origin.y,
1005: &bounds.size.width, &bounds.size.height, &gFlags, &identifier);
1006: if (gFlags.linewidthSet) NXWriteTypes(stream, "f", &linewidth);
1007: if (gFlags.lineColorSet) NXWriteColor(stream, *lineColor);
1008: if (gFlags.fillColorSet) NXWriteColor(stream, *fillColor);
1009: return self;
1010: }
1011:
1012: - read:(NXTypedStream *)stream
1013: {
1014: int version;
1015: float gray = NX_BLACK;
1016:
1017: [super read:stream];
1018: version = NXTypedStreamClassVersion(stream, "Graphic");
1019: if (version > 2) {
1020: NXReadTypes(stream, "ffffii", &bounds.origin.x, &bounds.origin.y,
1021: &bounds.size.width, &bounds.size.height, &gFlags, &identifier);
1022: } else if (version > 1) {
1023: NXReadTypes(stream, "ffffsi", &bounds.origin.x, &bounds.origin.y,
1024: &bounds.size.width, &bounds.size.height, &gFlags, &identifier);
1025: #ifdef __LITTLE_ENDIAN__
1026: *(unsigned int *)&gFlags = *(unsigned int *)&gFlags <<= 16;
1027: #endif
1028: } else {
1029: NXReadTypes(stream, "ffffs", &bounds.origin.x, &bounds.origin.y,
1030: &bounds.size.width, &bounds.size.height, &gFlags);
1031: #ifdef __LITTLE_ENDIAN__
1032: *(unsigned int *)&gFlags = *(unsigned int *)&gFlags <<= 16;
1033: #endif
1034: identifier = currentGraphicIdentifier++;
1035: }
1036: if (version > 1 && identifier >= currentGraphicIdentifier) currentGraphicIdentifier = identifier+1;
1037: if (gFlags.linewidthSet) NXReadTypes(stream, "f", &linewidth);
1038: if (version < 1) {
1039: if (gFlags.lineColorSet) NXReadTypes(stream, "f", &gray);
1040: if (gFlags.fillColorSet && (gFlags.eofill | gFlags.fill)) {
1041: NX_ZONEMALLOC([self zone], lineColor, NXColor, 1);
1042: *lineColor = NXConvertGrayToColor(NX_BLACK);
1043: NX_ZONEMALLOC([self zone], fillColor, NXColor, 1);
1044: *fillColor = NXConvertGrayToColor(gray);
1045: } else if (gFlags.eofill | gFlags.fill) {
1046: NX_ZONEMALLOC([self zone], fillColor, NXColor, 1);
1047: *fillColor = NXConvertGrayToColor(gray);
1048: [self setOutlined:NO];
1049: } else {
1050: NX_ZONEMALLOC([self zone], lineColor, NXColor, 1);
1051: *lineColor = NXConvertGrayToColor(gray);
1052: }
1053: } else {
1054: if (gFlags.lineColorSet) {
1055: NX_ZONEMALLOC([self zone], lineColor, NXColor, 1);
1056: *lineColor = NXReadColor(stream);
1057: if (NXEqualColor(*lineColor, NX_COLORCLEAR)) {
1058: free(lineColor);
1059: lineColor = NULL;
1060: [self setOutlined:NO];
1061: }
1062: }
1063: if (gFlags.fillColorSet) {
1064: NX_ZONEMALLOC([self zone], fillColor, NXColor, 1);
1065: *fillColor = NXReadColor(stream);
1066: if (NXEqualColor(*fillColor, NX_COLORCLEAR) || (NXAlphaComponent(*fillColor) == 0.0)) {
1067: free(fillColor);
1068: fillColor = NULL;
1069: [self setFill:FILL_NONE];
1070: // } else if (!gFlags.eofill && !gFlags.fill) { // why did I add this code here?
1071: // gFlags.fill = YES;
1072: }
1073: }
1074: }
1075:
1076: return self;
1077: }
1078:
1079: /* Routines which may need subclassing for different Graphic types. */
1080:
1081: - (BOOL)constrainByDefault
1082: {
1083: return NO;
1084: }
1085:
1086: - constrainCorner:(int)corner toAspectRatio:(float)aspect
1087: /*
1088: * Modifies the bounds rectangle by moving the specified corner so that
1089: * the Graphic maintains the specified aspect ratio. This is used during
1090: * constrained resizing. Can be overridden if the aspect ratio is not
1091: * sufficient to constrain resizing.
1092: */
1093: {
1094: int newcorner;
1095: float actualAspect;
1096:
1097: if (!bounds.size.height || !bounds.size.width || !aspect) return self;
1098: actualAspect = bounds.size.width / bounds.size.height;
1099: if (actualAspect == aspect) return self;
1100:
1101: switch (corner) {
1102: case LEFT_SIDE:
1103: bounds.origin.x -= bounds.size.height * aspect-bounds.size.width;
1104: case RIGHT_SIDE:
1105: bounds.size.width = bounds.size.height * aspect;
1106: if (bounds.size.width) NXIntegralRect(&bounds);
1107: return self;
1108: case BOTTOM_SIDE:
1109: bounds.origin.y -= bounds.size.width / aspect-bounds.size.height;
1110: case TOP_SIDE:
1111: bounds.size.height = bounds.size.width / aspect;
1112: if (bounds.size.height) NXIntegralRect(&bounds);
1113: return self;
1114: case LOWER_LEFT:
1115: corner = 0;
1116: case 0:
1117: case UPPER_RIGHT:
1118: case UPPER_LEFT:
1119: case LOWER_RIGHT:
1120: if (actualAspect > aspect) {
1121: newcorner = ((corner|KNOB_DY_ONCE)&(~(KNOB_DY_TWICE)));
1122: } else {
1123: newcorner = ((corner|KNOB_DX_ONCE)&(~(KNOB_DX_TWICE)));
1124: }
1125: return [self constrainCorner:newcorner toAspectRatio:aspect];
1126: default:
1127: return self;
1128: }
1129: }
1130:
1131: #define RESIZE_MASK (NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK|NX_TIMERMASK)
1132:
1133: - resize:(NXEvent *)event by:(int)corner in:(GraphicView *)view
1134: /*
1135: * Resizes the graphic by the specified corner. If corner == CREATE,
1136: * then it is resized by the UPPER_RIGHT corner, but the initial size
1137: * is reset to 1 by 1.
1138: */
1139: {
1140: NXPoint p, last;
1141: float aspect = 0.0;
1142: Window *window = [view window];
1143: BOOL constrain, canScroll;
1144: DrawStatusType oldDrawStatus;
1145: NXTrackingTimer *timer = NULL;
1146: NXRect eb, starteb, oldeb, visibleRect;
1147:
1148: if (!gFlags.active || !gFlags.selected || !corner) return self;
1149:
1150: constrain = ((event->flags & NX_ALTERNATEMASK) &&
1151: ((bounds.size.width && bounds.size.height) || corner == CREATE));
1152: if ([self constrainByDefault]) constrain = !constrain;
1153: if (constrain) aspect = bounds.size.width / bounds.size.height;
1154: if (corner == CREATE) {
1155: bounds.size.width = bounds.size.height = 1.0;
1156: corner = UPPER_RIGHT;
1157: }
1158:
1159: gFlags.selected = NO;
1160:
1161: [self getExtendedBounds:&eb];
1162: [view lockFocus];
1163: gFlags.active = NO;
1164: [view cache:&eb andUpdateLinks:NO];
1165: gFlags.active = YES;
1166: starteb = eb;
1167: [self draw:NULL];
1168: [window flushWindow];
1169:
1170: oldDrawStatus = DrawStatus;
1171: DrawStatus = Resizing;
1172:
1173: [view getVisibleRect:&visibleRect];
1174: canScroll = !NXEqualRect(&visibleRect, &bounds);
1175: if (canScroll && !timer) timer = NXBeginTimer(NULL, 0.1, 0.1);
1176:
1177: last.x = last.y = -1.0;
1178: while (event->type != NX_MOUSEUP) {
1179: p = event->location;
1180: event = [NXApp getNextEvent:RESIZE_MASK];
1181: if (event->type == NX_TIMER) event->location = p;
1182: p = event->location;
1183: [view convertPoint:&p fromView:nil];
1184: [view grid:&p];
1185: if (p.x != last.x || p.y != last.y) {
1186: corner = [self moveCorner:corner to:&p constrain:constrain];
1187: if (constrain) [self constrainCorner:corner toAspectRatio:aspect];
1188: oldeb = eb;
1189: [self getExtendedBounds:&eb];
1190: [window disableFlushWindow];
1191: [view drawSelf:&oldeb :1];
1192: if (canScroll) {
1193: [view scrollPointToVisible:&p]; // actually we want to keep the "edges" of the
1194: // Graphic being resized that were visible when
1195: // the resize started visible throughout the
1196: // resizing time (this will be difficult if those
1197: // edges flip from being the left edge to the
1198: // right edge in the middle of the resize!).
1199: }
1200: [self draw:NULL];
1201: [view tryToPerform:@selector(updateRulers:) with:(void *)&bounds];
1202: [window reenableFlushWindow];
1203: [window flushWindow];
1204: last = p;
1205: NXPing();
1206: }
1207: }
1208:
1209: if (canScroll && timer) {
1210: NXEndTimer(timer);
1211: timer = NULL;
1212: }
1213:
1214: gFlags.selected = YES;
1215: DrawStatus = oldDrawStatus;
1216:
1217: [view cache:&eb andUpdateLinks:NO]; // redraw after resizing a Graphic
1218: NXUnionRect(&eb, &starteb);
1219: [view updateTrackedLinks:&starteb];
1220: [view tryToPerform:@selector(updateRulers:) with:nil];
1221: [window flushWindow];
1222: [view unlockFocus];
1223:
1224: return self;
1225: }
1226:
1227: - (BOOL)create:(NXEvent *)event in:(GraphicView *)view
1228: /*
1229: * This method rarely needs to be subclassed.
1230: * It sets up an initial bounds, and calls resize:by:in:.
1231: */
1232: {
1233: BOOL valid;
1234: NXCoord gridSpacing;
1235:
1236: bounds.origin = event->location;
1237: [view convertPoint:&bounds.origin fromView:nil];
1238: [view grid:&bounds.origin];
1239:
1240: gridSpacing = (NXCoord)[view gridSpacing];
1241: bounds.size.height = gridSpacing;
1242: bounds.size.width = gridSpacing * [self naturalAspectRatio];
1243:
1244: [self resize:event by:CREATE in:view];
1245:
1246: valid = [self isValid];
1247:
1248: if (valid) {
1249: gFlags.selected = YES;
1250: gFlags.active = YES;
1251: } else {
1252: gFlags.selected = NO;
1253: gFlags.active = NO;
1254: [view display];
1255: }
1256:
1257: return valid;
1258: }
1259:
1260: - (BOOL)hit:(const NXPoint *)p
1261: {
1262: return (!gFlags.locked && gFlags.active && NXMouseInRect(p, &bounds, NO));
1263: }
1264:
1265: - (BOOL)isOpaque
1266: {
1267: return [self fill] ? YES : NO;
1268: }
1269:
1270: - (BOOL)isValid
1271: /*
1272: * Called after a Graphic is created to see if it is valid (this usually
1273: * means "is it big enough?").
1274: */
1275: {
1276: return (bounds.size.width > MINSIZE && bounds.size.height > MINSIZE);
1277: }
1278:
1279: - (float)naturalAspectRatio
1280: /*
1281: * A natural aspect ratio of zero means it doesn't have a natural aspect ratio.
1282: */
1283: {
1284: return 0.0;
1285: }
1286:
1287: - (int)moveCorner:(int)corner to:(const NXPoint *)p constrain:(BOOL)flag
1288: /*
1289: * Moves the specified corner to the specified point.
1290: * Returns the position of the corner after it was moved.
1291: */
1292: {
1293: int newcorner = corner;
1294:
1295: if ((corner & KNOB_DX_ONCE) && (corner & KNOB_DX_TWICE)) {
1296: bounds.size.width += p->x - (bounds.origin.x + bounds.size.width);
1297: if (bounds.size.width <= 0.0) {
1298: newcorner &= ~ (KNOB_DX_ONCE | KNOB_DX_TWICE);
1299: bounds.origin.x += bounds.size.width;
1300: bounds.size.width = - bounds.size.width;
1301: }
1302: } else if (!(corner & KNOB_DX_ONCE)) {
1303: bounds.size.width += bounds.origin.x - p->x;
1304: bounds.origin.x = p->x;
1305: if (bounds.size.width <= 0.0) {
1306: newcorner |= KNOB_DX_ONCE | KNOB_DX_TWICE;
1307: bounds.origin.x += bounds.size.width;
1308: bounds.size.width = - bounds.size.width;
1309: }
1310: }
1311:
1312: if ((corner & KNOB_DY_ONCE) && (corner & KNOB_DY_TWICE)) {
1313: bounds.size.height += p->y - (bounds.origin.y + bounds.size.height);
1314: if (bounds.size.height <= 0.0) {
1315: newcorner &= ~ (KNOB_DY_ONCE | KNOB_DY_TWICE);
1316: bounds.origin.y += bounds.size.height;
1317: bounds.size.height = - bounds.size.height;
1318: }
1319: } else if (!(corner & KNOB_DY_ONCE)) {
1320: bounds.size.height += bounds.origin.y - p->y;
1321: bounds.origin.y = p->y;
1322: if (bounds.size.height <= 0.0) {
1323: newcorner |= KNOB_DY_ONCE | KNOB_DY_TWICE;
1324: bounds.origin.y += bounds.size.height;
1325: bounds.size.height = - bounds.size.height;
1326: }
1327: }
1328:
1329: if (newcorner != LOWER_LEFT) newcorner &= 0xf;
1330: if (!newcorner) newcorner = LOWER_LEFT;
1331:
1332: return newcorner;
1333: }
1334:
1335: - unitDraw
1336: /*
1337: * If a Graphic just wants to draw itself in the bounding box of
1338: * {{0.0,0.0},{1.0,1.0}}, it can simply override this method.
1339: * Everything else will work fine.
1340: */
1341: {
1342: return self;
1343: }
1344:
1345: - draw
1346: /*
1347: * Almost all Graphics need to override this method.
1348: * It does the Graphic-specific drawing.
1349: * By default, it scales the coordinate system and calls unitDraw.
1350: */
1351: {
1352: if (bounds.size.width >= 1.0 && bounds.size.height >= 1.0) {
1353: PStranslate(bounds.origin.x, bounds.origin.y);
1354: PSscale(bounds.size.width, bounds.size.height);
1355: [self unitDraw];
1356: }
1357: return self;
1358: }
1359:
1360: - (BOOL)edit:(NXEvent *)event in:(View *)view
1361: /*
1362: * Any Graphic which has editable text should override this method
1363: * to edit that text. TextGraphic is an example.
1364: */
1365: {
1366: return NO;
1367: }
1368:
1369: @end
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.