|
|
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 on one line without wrapping. */
4:
5: /*
6: * Image is a simple graphic which takes PostScript or
7: * TIFF images and draws them in a bounding box (it scales
8: * the image if the bounding box is changed). It is
9: * implemented using the NXImage class. Using NXImage
10: * here is especially nice since it images its PostScript
11: * in a separate context (thus, any errors that PostScript
12: * generates will not affect our main drawing context).
13: */
14:
15: @implementation Image : Graphic
16:
17: /* Initialize the class */
18:
19: + initialize
20: {
21: [Image setVersion:7];
22: return self;
23: }
24:
25: /* Factory methods. */
26:
27: + highlightedLinkButtonImage:(NXSize *)size
28: /*
29: * Just makes an NXLinkButtonH NXImage the same size as
30: * the size passed in. I suppose this could just be a
31: * function.
32: */
33: {
34: static NXImage *retval = nil;
35: if (!retval) {
36: retval = [[NXImage findImageNamed:"NXLinkButtonH"] copy];
37: [retval setScalable:YES];
38: [retval setDataRetained:YES];
39: }
40: [retval setSize:size];
41: return retval;
42: }
43:
44: + (BOOL)canInitFromPasteboard:(Pasteboard *)pboard
45: {
46: return [NXImage canInitFromPasteboard:pboard];
47: }
48:
49: static BOOL checkImage(NXImage *anImage)
50: /*
51: * Locking focus on an NXImage forces it to draw and thus verifies
52: * whether there are any PostScript or TIFF errors in the source of
53: * the image. lockFocus returns YES only if there are no errors.
54: */
55: {
56: if ([anImage lockFocus]) {
57: [anImage unlockFocus];
58: return YES;
59: }
60: return NO;
61: }
62:
63: /* Creation/Initialization Methods */
64:
65: - init
66: /*
67: * This creates basically an "empty" Image.
68: * This is the designated initializer for Image.
69: * Be careful, however, because by the time this
70: * returns, a newly initialized Image may not be
71: * fully initialized (it'll be "valid," just not
72: * necessarily fully initialized). If you want that
73: * behaviour, override finishedWithInit.
74: */
75: {
76: [super init];
77: originalSize.width = originalSize.height = 1.0;
78: bounds.size = originalSize;
79: return self;
80: }
81:
82: - finishedWithInit
83: /*
84: * Called when a newly initialized Image is fully
85: * initialized and ready to roll. For subclassers
86: * only.
87: */
88: {
89: return self;
90: }
91:
92: - initEmpty
93: /*
94: * Creates a blank Image.
95: */
96: {
97: [self init];
98: return [self finishedWithInit];
99: }
100:
101: - initFromStream:(NXStream *)stream
102: /*
103: * Creates a new NXImage and sets it to be scalable and to retain
104: * its data (which means that when we archive it, it will actually
105: * write the TIFF or PostScript data into the stream).
106: */
107: {
108: [self init];
109:
110: if (stream) {
111: image = [NXImage allocFromZone:[self zone]];
112: if ((image = [image initFromStream:stream])) {
113: [image setDataRetained:YES];
114: if (checkImage(image)) {
115: [image getSize:&originalSize];
116: [image setScalable:YES];
117: bounds.size = originalSize;
118: return [self finishedWithInit];
119: }
120: }
121: }
122:
123: [self free];
124:
125: return nil;
126: }
127:
128: - initFromPasteboard:(Pasteboard *)pboard;
129: /*
130: * Creates a new NXImage and sets it to be scalable and to retain
131: * its data (which means that when we archive it, it will actually
132: * write the TIFF or PostScript data into the stream).
133: */
134: {
135: [self init];
136:
137: if (pboard) {
138: image = [NXImage allocFromZone:[self zone]];
139: if ((image = [image initFromPasteboard:pboard])) {
140: [image setDataRetained:YES];
141: if (checkImage(image)) {
142: [image getSize:&originalSize];
143: [image setScalable:YES];
144: bounds.size = originalSize;
145: return [self finishedWithInit];
146: }
147: }
148: }
149:
150: [self free];
151:
152: return nil;
153: }
154:
155: - initFromFile:(const char *)file
156: /*
157: * Creates an NXImage by reading data from an .eps or .tiff file.
158: */
159: {
160: [self init];
161:
162: image = [[NXImage allocFromZone:[self zone]] init];
163: if ([image loadFromFile:file]) {
164: [image setDataRetained:YES];
165: if (checkImage(image)) {
166: [image getSize:&originalSize];
167: [image setScalable:YES];
168: bounds.size = originalSize;
169: return [self finishedWithInit];
170: }
171: }
172:
173: [self free];
174:
175: return nil;
176: }
177:
178: - doInitFromImage:(NXImage *)anImage
179: /*
180: * Common code for initFromImage: and unarchiving.
181: */
182: {
183: if (anImage) {
184: image = anImage;
185: [image getSize:&originalSize];
186: [image setScalable:YES];
187: [image setDataRetained:YES];
188: bounds.size = originalSize;
189: } else {
190: [self free];
191: self = nil;
192: }
193: return self;
194: }
195:
196: - initFromImage:(NXImage *)anImage
197: /*
198: * Initializes an Image from a specific NXImage.
199: */
200: {
201: [self init];
202: return [[self doInitFromImage:anImage] finishedWithInit];
203: }
204:
205: - initFromIcon:(NXImage *)anImage
206: /*
207: * Same as initFromImage:, but we remember that this particular
208: * NXImage was actually a file icon (which enables us to double-click
209: * on it to open the icon, see handleEvent:).
210: */
211: {
212: if ([self initFromImage:anImage]) {
213: amIcon = YES;
214: return self;
215: } else {
216: return nil;
217: }
218: }
219:
220: - initWithLinkButton
221: /*
222: * Creates an image which is just the link button.
223: * This is only applicable with Object Links.
224: */
225: {
226: if ([self initFromImage:[[NXImage findImageNamed:"NXLinkButton"] copy]]) {
227: amLinkButton = YES;
228: return self;
229: } else {
230: return nil;
231: }
232: }
233:
234: - (NXRect)resetImage:(NXImage *)newImage
235: /*
236: * Called by the "reinit" methods to reset all of our instance
237: * variables based on using a new NXImage for our image.
238: */
239: {
240: NXRect eBounds, neBounds;
241:
242: [image free];
243: image = newImage;
244: [self getExtendedBounds:&eBounds];
245: [image getSize:&neBounds.size];
246: neBounds.size.width *= bounds.size.width / originalSize.width;
247: neBounds.size.height *= bounds.size.height / originalSize.height;
248: neBounds.origin.x = bounds.origin.x - floor((neBounds.size.width - bounds.size.width) / 2.0 + 0.5);
249: neBounds.origin.y = bounds.origin.y - floor((neBounds.size.height - bounds.size.height) / 2.0 + 0.5);
250: [self setBounds:&neBounds];
251: [self getExtendedBounds:&neBounds];
252: NXUnionRect(&eBounds, &neBounds);
253: [image setDataRetained:YES];
254: [image getSize:&originalSize];
255: [image setScalable:YES];
256:
257: return neBounds;
258: }
259:
260: - (NXRect)reinitFromPasteboard:(Pasteboard *)pboard
261: /*
262: * Reset all of our instance variable based on extract an
263: * NXImage from data in the the passed pboard. Happens when
264: * we update a link through Object Links.
265: */
266: {
267: NXRect neBounds;
268: NXImage *newImage;
269:
270: newImage = [NXImage allocFromZone:[self zone]];
271: if ((newImage = [newImage initFromPasteboard:pboard])) {
272: [newImage setDataRetained:YES];
273: if (checkImage(newImage)) return [self resetImage:newImage];
274: }
275:
276: [newImage free];
277: neBounds.origin.x = neBounds.origin.y = 0.0;
278: neBounds.size.width = neBounds.size.height = 0.0;
279:
280: return neBounds;
281: }
282:
283: - (NXRect)reinitFromFile:(const char *)file
284: /*
285: * Reset all of our instance variable based on extract an
286: * NXImage from the data in the passed file. Happens when
287: * we update a link through Object Links.
288: */
289: {
290: NXRect neBounds;
291: NXImage *newImage;
292:
293: newImage = [[NXImage allocFromZone:[self zone]] init];
294: if ([newImage loadFromFile:file]) {
295: [newImage setDataRetained:YES];
296: if (checkImage(newImage)) return [self resetImage:newImage];
297: }
298:
299: [newImage free];
300: neBounds.origin.x = neBounds.origin.y = 0.0;
301: neBounds.size.width = neBounds.size.height = 0.0;
302:
303: return neBounds;
304: }
305:
306: /* All those allocation/initialization method and only this one free method. */
307:
308: - free
309: {
310: [image free];
311: return [super free];
312: }
313:
314: /* Link methods */
315:
316: - setLink:(NXDataLink *)aLink
317: /*
318: * It's "might" be linked because we're linked now, but might
319: * have our link broken in the future and the mightBeLinked flag
320: * is only advisory and is never cleared. It is used just so that
321: * we know we might want to try to reestablish a link with this
322: * Graphic after a cut/paste. No biggie if there really is no
323: * link associated with this any more. In gvLinks.m, see
324: * readLinkForGraphic:fromPasteboard:useNewIdentifier:, and in
325: * gvPasteboard.m, see pasteFromPasteboard:andLink:at:.
326: * If this Image is a link button, then we obviously never need
327: * to update the link because we don't actually show the data
328: * associated with the link (we just show that little link button).
329: */
330: {
331: NXDataLink *oldLink = link;
332: link = aLink;
333: gFlags.mightBeLinked = YES;
334: if (amLinkButton) [link setUpdateMode:NX_UpdateNever];
335: return oldLink;
336: }
337:
338: - (NXDataLink *)link
339: {
340: return link;
341: }
342:
343: /* Event-handling */
344:
345: - trackLinkButton:(NXEvent *)event at:(const NXPoint *)startPoint inView:(View *)view
346: /*
347: * This method tracks that little link button. Note that the link button is a diamond,
348: * but we track the whole rectangle. This is unfortunate, but we can't be sure that,
349: * in the future, the shape of the link button might not change (thus, what we really
350: * need is a NeXTSTEP function to track the thing!). Anyway, we track it and if the
351: * mouse goes up inside the button, we openSource on the link (we wouldn't be here if
352: * we didn't have a link).
353: */
354: {
355: NXPoint p;
356: NXImage *realImage, *highImage, *imageToDraw;
357:
358: p = *startPoint;
359: realImage = image;
360: highImage = [[self class] highlightedLinkButtonImage:&bounds.size];
361: image = imageToDraw = highImage;
362: [self draw];
363: [[view window] flushWindow];
364: do {
365: event = [NXApp getNextEvent:NX_MOUSEDRAGGEDMASK|NX_MOUSEUPMASK];
366: p = event->location;
367: [view convertPoint:&p fromView:nil];
368: imageToDraw = NXMouseInRect(&p, &bounds, NO) ? highImage : realImage;
369: if (imageToDraw != image) {
370: image = imageToDraw;
371: [self draw];
372: [[view window] flushWindow];
373: }
374: } while (event->type != NX_MOUSEUP);
375:
376: if (imageToDraw == highImage) {
377: [link openSource];
378: image = realImage;
379: [self draw];
380: [[view window] flushWindow];
381: }
382:
383: return self;
384: }
385:
386: - (BOOL)handleEvent:(NXEvent *)event at:(const NXPoint *)p inView:(View *)view
387: {
388: if (NXMouseInRect(p, &bounds, NO)) {
389: if (amLinkButton && !gFlags.selected && !(event->flags & (NX_CONTROLMASK|NX_SHIFTMASK|NX_ALTERNATEMASK))) {
390: [self trackLinkButton:event at:p inView:view];
391: return YES;
392: } else if (link && (event->data.mouse.click == 2) && (amIcon || (event->flags & NX_CONTROLMASK))) {
393: [NXApp getNextEvent:NX_MOUSEUPMASK];
394: [link openSource];
395: return YES;
396: }
397: }
398: return NO;
399: }
400:
401: /* Methods overridden from superclass to support links. */
402:
403: - (int)cornerMask
404: /*
405: * Link buttons are too small to have corners AND sides, so
406: * we only let link buttons have knobbies on the corners.
407: */
408: {
409: if (amLinkButton) {
410: return LOWER_LEFT_MASK|UPPER_LEFT_MASK|UPPER_RIGHT_MASK|LOWER_RIGHT_MASK;
411: } else {
412: return [super cornerMask];
413: }
414: }
415:
416: - (NXRect *)getExtendedBounds:(NXRect *)theRect
417: /*
418: * We have to augment this because we might have a link frame
419: * (if show links is on), so we have to extend our extended bounds
420: * a bit.
421: */
422: {
423: NXRect linkBounds, *retval;
424: float linkFrameThickness = NXLinkFrameThickness();
425:
426: linkBounds = bounds;
427: linkBounds.origin.x -= linkFrameThickness;
428: linkBounds.size.width += linkFrameThickness * 2.0;
429: linkBounds.origin.y -= linkFrameThickness;
430: linkBounds.size.height += linkFrameThickness;
431:
432: retval = [super getExtendedBounds:theRect];
433:
434: return NXUnionRect(&linkBounds, retval);
435: }
436:
437: - (BOOL)constrainByDefault;
438: /*
439: * Icons and link buttons look funny outside their natural
440: * aspect ratio, so we constrain them (by default) to keep
441: * their natural ratio. You can still use the Alternate key
442: * to NOT constrain these.
443: */
444: {
445: return (amLinkButton || amIcon);
446: }
447:
448: /* Methods overridden from superclass */
449:
450: - (BOOL)isValid
451: {
452: return image ? YES : NO;
453: }
454:
455: - (BOOL)isOpaque
456: {
457: return [[image bestRepresentation] isOpaque];
458: }
459:
460: - (float)naturalAspectRatio
461: {
462: if (!originalSize.height) return 0.0;
463: return originalSize.width / originalSize.height;
464: }
465:
466: - draw
467: /*
468: * If we are resizing, we just draw a gray box.
469: * If not, then we simply see if our bounds have changed
470: * and update the NXImage object if they have. Then,
471: * if we do not allow alpha (i.e. this is a TIFF image),
472: * we paint a white background square (we don't allow
473: * alpha in our TIFF images since it won't print and
474: * Draw is WYSIWYG). Finally, we SOVER the image.
475: * If we are not keeping the cache around, we tell
476: * NXImage to toss its cached version of the image
477: * via the message recache.
478: *
479: * If we are linked to something and the user has chosen
480: * "Show Links", then linkOutlinesAreVisible, so we must
481: * draw a link border around ourself.
482: */
483: {
484: NXRect r;
485: NXPoint p;
486: NXSize currentSize;
487:
488: if (bounds.size.width < 1.0 || bounds.size.height < 1.0) return self;
489:
490: if (DrawStatus == Resizing) {
491: PSsetgray(NX_DKGRAY);
492: PSsetlinewidth(0.0);
493: PSrectstroke(bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height);
494: } else if (image) {
495: p = bounds.origin;
496: [image getSize:¤tSize];
497: if (currentSize.width != bounds.size.width || currentSize.height != bounds.size.height) {
498: if ([image isScalable]) {
499: [image setSize:&bounds.size];
500: } else {
501: p.x = bounds.origin.x + floor((bounds.size.width - currentSize.width) / 2.0 + 0.5);
502: p.y = bounds.origin.y + floor((bounds.size.height - currentSize.height) / 2.0 + 0.5);
503: }
504: }
505: if ([[image bestRepresentation] isOpaque]) {
506: PSsetgray(NX_WHITE);
507: NXRectFill(&bounds);
508: }
509: [image composite:NX_SOVER toPoint:&p];
510: if (dontCache && NXDrawingStatus == NX_DRAWING) [image recache];
511: if ((NXDrawingStatus == NX_DRAWING) && !amLinkButton && [[link manager] areLinkOutlinesVisible]) {
512: r.origin.x = floor(bounds.origin.x);
513: r.origin.y = floor(bounds.origin.y);
514: r.size.width = floor(bounds.origin.x + bounds.size.width + 0.99) - r.origin.x;
515: r.size.height = floor(bounds.origin.y + bounds.size.height + 0.99) - r.origin.y;
516: NXFrameLinkRect(&r, YES); // YES means "is a destination link"
517: }
518: }
519:
520: return self;
521: }
522:
523: /* Direct writing of EPS or TIFF. */
524:
525: - (BOOL)canEmitEPS
526: /*
527: * If we have a representation that can provide EPS directly, then,
528: * if we are copying PostScript to the Pasteboard and this Image is the
529: * only Graphic selected, then we might as well just have the EPS which
530: * represents this Image go straight to the Pasteboard rather than
531: * wrapping it up in the copyPSCodeInside: wrappers. Of course, we
532: * can only do that if we haven't been resized.
533: *
534: * See gvPasteboard.m's writePSToStream:.
535: */
536: {
537: List *reps = [image representationList];
538: int i = [reps count];
539:
540: if (originalSize.width == bounds.size.width && originalSize.height == bounds.size.height) {
541: while (i--) {
542: if ([[reps objectAt:i] respondsTo:@selector(getEPS:length:)]) {
543: return YES;
544: }
545: }
546: }
547:
548: return NO;
549: }
550:
551: - writeEPSToStream:(NXStream *)stream
552: /*
553: * If canEmitEPS above returns YES, then we can write ourself out directly
554: * as EPS. This method does that.
555: */
556: {
557: List *reps = [image representationList];
558: int i = [reps count];
559: char *data;
560: int length;
561:
562: while (i--) {
563: if ([[reps objectAt:i] respondsTo:@selector(getEPS:length:)]) {
564: [[reps objectAt:i] getEPS:&data length:&length];
565: NXWrite(stream, data, length);
566: return self; // should I free data before returning?
567: }
568: }
569:
570: return self;
571: }
572:
573: - (BOOL)canEmitTIFF
574: /*
575: * Similar to canEmitEPS, except its for TIFF.
576: */
577: {
578: return (originalSize.width == bounds.size.width && originalSize.height == bounds.size.height);
579: }
580:
581: - writeTIFFToStream:(NXStream *)stream
582: /*
583: * Ditto above.
584: */
585: {
586: [image writeTIFF:stream allRepresentations:YES];
587: return self;
588: }
589:
590: /* Caching. */
591:
592: - setCacheable:(BOOL)flag
593: {
594: dontCache = flag ? NO : YES;
595: return self;
596: }
597:
598: - (BOOL)isCacheable
599: {
600: return !dontCache;
601: }
602:
603: /* Archiving. */
604:
605: - write:(NXTypedStream *)stream
606: /*
607: * All that is needed to archive the NXImage.
608: */
609: {
610: [super write:stream];
611: NXWriteType(stream, "c", &amLinkButton);
612: NXWriteType(stream, "c", &amIcon);
613: if (!amLinkButton) {
614: NXWriteObject(stream, image);
615: NXWriteSize(stream, &originalSize);
616: }
617: return self;
618: }
619:
620: - read:(NXTypedStream *)stream
621: /*
622: * This contains lots of compatibility code for
623: * interim versions. See if you can figure out the
624: * various ways we approached archiving link info!
625: */
626: {
627: BOOL alphaOk;
628: NXRect savedBounds;
629: int version, linkNumber;
630:
631: [super read:stream];
632: version = NXTypedStreamClassVersion(stream, "Image");
633: if (version > 5) NXReadType(stream, "c", &amLinkButton);
634: if (version > 6) NXReadType(stream, "c", &amIcon);
635: if (amLinkButton) {
636: savedBounds = bounds;
637: [self doInitFromImage:[[NXImage findImageNamed:"NXLinkButton"] copy]];
638: bounds = savedBounds;
639: } else {
640: image = NXReadObject(stream);
641: NXReadSize(stream, &originalSize);
642: }
643: if (version <= 2) NXReadTypes(stream, "c", &alphaOk);
644: if (version == 4) {
645: NXReadObject(stream); // used to be the NXDataLink
646: } else if (version > 2 && version < 6) {
647: NXReadTypes(stream, "i", &linkNumber);
648: }
649:
650: return self;
651: }
652:
653: @end
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.