home *** CD-ROM | disk | FTP | other *** search
- #import "draw.h"
-
- /* Optimally viewed in a wide window. Make your window big enough so that this comment fits on one line without wrapping. */
-
- /*
- * Image is a simple graphic which takes PostScript or
- * TIFF images and draws them in a bounding box (it scales
- * the image if the bounding box is changed). It is
- * implemented using the NXImage class. Using NXImage
- * here is especially nice since it images its PostScript
- * in a separate context (thus, any errors that PostScript
- * generates will not affect our main drawing context).
- */
-
- @implementation Image : Graphic
-
- /* Initialize the class */
-
- + initialize
- {
- [Image setVersion:7];
- return self;
- }
-
- /* Factory methods. */
-
- + highlightedLinkButtonImage:(NXSize *)size
- /*
- * Just makes an NXLinkButtonH NXImage the same size as
- * the size passed in. I suppose this could just be a
- * function.
- */
- {
- static NXImage *retval = nil;
- if (!retval) {
- retval = [[NXImage findImageNamed:"NXLinkButtonH"] copy];
- [retval setScalable:YES];
- [retval setDataRetained:YES];
- }
- [retval setSize:size];
- return retval;
- }
-
- + (BOOL)canInitFromPasteboard:(Pasteboard *)pboard
- {
- return [NXImage canInitFromPasteboard:pboard];
- }
-
- static BOOL checkImage(NXImage *anImage)
- /*
- * Locking focus on an NXImage forces it to draw and thus verifies
- * whether there are any PostScript or TIFF errors in the source of
- * the image. lockFocus returns YES only if there are no errors.
- */
- {
- if ([anImage lockFocus]) {
- [anImage unlockFocus];
- return YES;
- }
- return NO;
- }
-
- /* Creation/Initialization Methods */
-
- - init
- /*
- * This creates basically an "empty" Image.
- * This is the designated initializer for Image.
- * Be careful, however, because by the time this
- * returns, a newly initialized Image may not be
- * fully initialized (it'll be "valid," just not
- * necessarily fully initialized). If you want that
- * behaviour, override finishedWithInit.
- */
- {
- [super init];
- originalSize.width = originalSize.height = 1.0;
- bounds.size = originalSize;
- return self;
- }
-
- - finishedWithInit
- /*
- * Called when a newly initialized Image is fully
- * initialized and ready to roll. For subclassers
- * only.
- */
- {
- return self;
- }
-
- - initEmpty
- /*
- * Creates a blank Image.
- */
- {
- [self init];
- return [self finishedWithInit];
- }
-
- - initFromStream:(NXStream *)stream
- /*
- * Creates a new NXImage and sets it to be scalable and to retain
- * its data (which means that when we archive it, it will actually
- * write the TIFF or PostScript data into the stream).
- */
- {
- [self init];
-
- if (stream) {
- image = [NXImage allocFromZone:[self zone]];
- if ((image = [image initFromStream:stream])) {
- [image setDataRetained:YES];
- if (checkImage(image)) {
- [image getSize:&originalSize];
- [image setScalable:YES];
- bounds.size = originalSize;
- return [self finishedWithInit];
- }
- }
- }
-
- [self free];
-
- return nil;
- }
-
- - initFromPasteboard:(Pasteboard *)pboard;
- /*
- * Creates a new NXImage and sets it to be scalable and to retain
- * its data (which means that when we archive it, it will actually
- * write the TIFF or PostScript data into the stream).
- */
- {
- [self init];
-
- if (pboard) {
- image = [NXImage allocFromZone:[self zone]];
- if ((image = [image initFromPasteboard:pboard])) {
- [image setDataRetained:YES];
- if (checkImage(image)) {
- [image getSize:&originalSize];
- [image setScalable:YES];
- bounds.size = originalSize;
- return [self finishedWithInit];
- }
- }
- }
-
- [self free];
-
- return nil;
- }
-
- - initFromFile:(const char *)file
- /*
- * Creates an NXImage by reading data from an .eps or .tiff file.
- */
- {
- [self init];
-
- image = [[NXImage allocFromZone:[self zone]] init];
- if ([image loadFromFile:file]) {
- [image setDataRetained:YES];
- if (checkImage(image)) {
- [image getSize:&originalSize];
- [image setScalable:YES];
- bounds.size = originalSize;
- return [self finishedWithInit];
- }
- }
-
- [self free];
-
- return nil;
- }
-
- - doInitFromImage:(NXImage *)anImage
- /*
- * Common code for initFromImage: and unarchiving.
- */
- {
- if (anImage) {
- image = anImage;
- [image getSize:&originalSize];
- [image setScalable:YES];
- [image setDataRetained:YES];
- bounds.size = originalSize;
- } else {
- [self free];
- self = nil;
- }
- return self;
- }
-
- - initFromImage:(NXImage *)anImage
- /*
- * Initializes an Image from a specific NXImage.
- */
- {
- [self init];
- return [[self doInitFromImage:anImage] finishedWithInit];
- }
-
- - initFromIcon:(NXImage *)anImage
- /*
- * Same as initFromImage:, but we remember that this particular
- * NXImage was actually a file icon (which enables us to double-click
- * on it to open the icon, see handleEvent:).
- */
- {
- if ([self initFromImage:anImage]) {
- amIcon = YES;
- return self;
- } else {
- return nil;
- }
- }
-
- - initWithLinkButton
- /*
- * Creates an image which is just the link button.
- * This is only applicable with Object Links.
- */
- {
- if ([self initFromImage:[[NXImage findImageNamed:"NXLinkButton"] copy]]) {
- amLinkButton = YES;
- return self;
- } else {
- return nil;
- }
- }
-
- - (NXRect)resetImage:(NXImage *)newImage
- /*
- * Called by the "reinit" methods to reset all of our instance
- * variables based on using a new NXImage for our image.
- */
- {
- NXRect eBounds, neBounds;
-
- [image free];
- image = newImage;
- [self getExtendedBounds:&eBounds];
- [image getSize:&neBounds.size];
- neBounds.size.width *= bounds.size.width / originalSize.width;
- neBounds.size.height *= bounds.size.height / originalSize.height;
- neBounds.origin.x = bounds.origin.x - floor((neBounds.size.width - bounds.size.width) / 2.0 + 0.5);
- neBounds.origin.y = bounds.origin.y - floor((neBounds.size.height - bounds.size.height) / 2.0 + 0.5);
- [self setBounds:&neBounds];
- [self getExtendedBounds:&neBounds];
- NXUnionRect(&eBounds, &neBounds);
- [image setDataRetained:YES];
- [image getSize:&originalSize];
- [image setScalable:YES];
-
- return neBounds;
- }
-
- - (NXRect)reinitFromPasteboard:(Pasteboard *)pboard
- /*
- * Reset all of our instance variable based on extract an
- * NXImage from data in the the passed pboard. Happens when
- * we update a link through Object Links.
- */
- {
- NXRect neBounds;
- NXImage *newImage;
-
- newImage = [NXImage allocFromZone:[self zone]];
- if ((newImage = [newImage initFromPasteboard:pboard])) {
- [newImage setDataRetained:YES];
- if (checkImage(newImage)) return [self resetImage:newImage];
- }
-
- [newImage free];
- neBounds.origin.x = neBounds.origin.y = 0.0;
- neBounds.size.width = neBounds.size.height = 0.0;
-
- return neBounds;
- }
-
- - (NXRect)reinitFromFile:(const char *)file
- /*
- * Reset all of our instance variable based on extract an
- * NXImage from the data in the passed file. Happens when
- * we update a link through Object Links.
- */
- {
- NXRect neBounds;
- NXImage *newImage;
-
- newImage = [[NXImage allocFromZone:[self zone]] init];
- if ([newImage loadFromFile:file]) {
- [newImage setDataRetained:YES];
- if (checkImage(newImage)) return [self resetImage:newImage];
- }
-
- [newImage free];
- neBounds.origin.x = neBounds.origin.y = 0.0;
- neBounds.size.width = neBounds.size.height = 0.0;
-
- return neBounds;
- }
-
- /* All those allocation/initialization method and only this one free method. */
-
- - free
- {
- [image free];
- return [super free];
- }
-
- /* Link methods */
-
- - setLink:(NXDataLink *)aLink
- /*
- * It's "might" be linked because we're linked now, but might
- * have our link broken in the future and the mightBeLinked flag
- * is only advisory and is never cleared. It is used just so that
- * we know we might want to try to reestablish a link with this
- * Graphic after a cut/paste. No biggie if there really is no
- * link associated with this any more. In gvLinks.m, see
- * readLinkForGraphic:fromPasteboard:useNewIdentifier:, and in
- * gvPasteboard.m, see pasteFromPasteboard:andLink:at:.
- * If this Image is a link button, then we obviously never need
- * to update the link because we don't actually show the data
- * associated with the link (we just show that little link button).
- */
- {
- NXDataLink *oldLink = link;
- link = aLink;
- gFlags.mightBeLinked = YES;
- if (amLinkButton) [link setUpdateMode:NX_UpdateNever];
- return oldLink;
- }
-
- - (NXDataLink *)link
- {
- return link;
- }
-
- /* Event-handling */
-
- - trackLinkButton:(NXEvent *)event at:(const NXPoint *)startPoint inView:(View *)view
- /*
- * This method tracks that little link button. Note that the link button is a diamond,
- * but we track the whole rectangle. This is unfortunate, but we can't be sure that,
- * in the future, the shape of the link button might not change (thus, what we really
- * need is a NeXTSTEP function to track the thing!). Anyway, we track it and if the
- * mouse goes up inside the button, we openSource on the link (we wouldn't be here if
- * we didn't have a link).
- */
- {
- NXPoint p;
- NXImage *realImage, *highImage, *imageToDraw;
-
- p = *startPoint;
- realImage = image;
- highImage = [[self class] highlightedLinkButtonImage:&bounds.size];
- image = imageToDraw = highImage;
- [self draw];
- [[view window] flushWindow];
- do {
- event = [NXApp getNextEvent:NX_MOUSEDRAGGEDMASK|NX_MOUSEUPMASK];
- p = event->location;
- [view convertPoint:&p fromView:nil];
- imageToDraw = NXMouseInRect(&p, &bounds, NO) ? highImage : realImage;
- if (imageToDraw != image) {
- image = imageToDraw;
- [self draw];
- [[view window] flushWindow];
- }
- } while (event->type != NX_MOUSEUP);
-
- if (imageToDraw == highImage) {
- [link openSource];
- image = realImage;
- [self draw];
- [[view window] flushWindow];
- }
-
- return self;
- }
-
- - (BOOL)handleEvent:(NXEvent *)event at:(const NXPoint *)p inView:(View *)view
- {
- if (NXMouseInRect(p, &bounds, NO)) {
- if (amLinkButton && !gFlags.selected && !(event->flags & (NX_CONTROLMASK|NX_SHIFTMASK|NX_ALTERNATEMASK))) {
- [self trackLinkButton:event at:p inView:view];
- return YES;
- } else if (link && (event->data.mouse.click == 2) && (amIcon || (event->flags & NX_CONTROLMASK))) {
- [NXApp getNextEvent:NX_MOUSEUPMASK];
- [link openSource];
- return YES;
- }
- }
- return NO;
- }
-
- /* Methods overridden from superclass to support links. */
-
- - (int)cornerMask
- /*
- * Link buttons are too small to have corners AND sides, so
- * we only let link buttons have knobbies on the corners.
- */
- {
- if (amLinkButton) {
- return LOWER_LEFT_MASK|UPPER_LEFT_MASK|UPPER_RIGHT_MASK|LOWER_RIGHT_MASK;
- } else {
- return [super cornerMask];
- }
- }
-
- - (NXRect *)getExtendedBounds:(NXRect *)theRect
- /*
- * We have to augment this because we might have a link frame
- * (if show links is on), so we have to extend our extended bounds
- * a bit.
- */
- {
- NXRect linkBounds, *retval;
- float linkFrameThickness = NXLinkFrameThickness();
-
- linkBounds = bounds;
- linkBounds.origin.x -= linkFrameThickness;
- linkBounds.size.width += linkFrameThickness * 2.0;
- linkBounds.origin.y -= linkFrameThickness;
- linkBounds.size.height += linkFrameThickness;
-
- retval = [super getExtendedBounds:theRect];
-
- return NXUnionRect(&linkBounds, retval);
- }
-
- - (BOOL)constrainByDefault;
- /*
- * Icons and link buttons look funny outside their natural
- * aspect ratio, so we constrain them (by default) to keep
- * their natural ratio. You can still use the Alternate key
- * to NOT constrain these.
- */
- {
- return (amLinkButton || amIcon);
- }
-
- /* Methods overridden from superclass */
-
- - (BOOL)isValid
- {
- return image ? YES : NO;
- }
-
- - (BOOL)isOpaque
- {
- return [[image bestRepresentation] isOpaque];
- }
-
- - (float)naturalAspectRatio
- {
- if (!originalSize.height) return 0.0;
- return originalSize.width / originalSize.height;
- }
-
- - draw
- /*
- * If we are resizing, we just draw a gray box.
- * If not, then we simply see if our bounds have changed
- * and update the NXImage object if they have. Then,
- * if we do not allow alpha (i.e. this is a TIFF image),
- * we paint a white background square (we don't allow
- * alpha in our TIFF images since it won't print and
- * Draw is WYSIWYG). Finally, we SOVER the image.
- * If we are not keeping the cache around, we tell
- * NXImage to toss its cached version of the image
- * via the message recache.
- *
- * If we are linked to something and the user has chosen
- * "Show Links", then linkOutlinesAreVisible, so we must
- * draw a link border around ourself.
- */
- {
- NXRect r;
- NXPoint p;
- NXSize currentSize;
-
- if (bounds.size.width < 1.0 || bounds.size.height < 1.0) return self;
-
- if (DrawStatus == Resizing) {
- PSsetgray(NX_DKGRAY);
- PSsetlinewidth(0.0);
- PSrectstroke(bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height);
- } else if (image) {
- p = bounds.origin;
- [image getSize:¤tSize];
- if (currentSize.width != bounds.size.width || currentSize.height != bounds.size.height) {
- if ([image isScalable]) {
- [image setSize:&bounds.size];
- } else {
- p.x = bounds.origin.x + floor((bounds.size.width - currentSize.width) / 2.0 + 0.5);
- p.y = bounds.origin.y + floor((bounds.size.height - currentSize.height) / 2.0 + 0.5);
- }
- }
- if ([[image bestRepresentation] isOpaque]) {
- PSsetgray(NX_WHITE);
- NXRectFill(&bounds);
- }
- [image composite:NX_SOVER toPoint:&p];
- if (dontCache && NXDrawingStatus == NX_DRAWING) [image recache];
- if ((NXDrawingStatus == NX_DRAWING) && !amLinkButton && [[link manager] areLinkOutlinesVisible]) {
- r.origin.x = floor(bounds.origin.x);
- r.origin.y = floor(bounds.origin.y);
- r.size.width = floor(bounds.origin.x + bounds.size.width + 0.99) - r.origin.x;
- r.size.height = floor(bounds.origin.y + bounds.size.height + 0.99) - r.origin.y;
- NXFrameLinkRect(&r, YES); // YES means "is a destination link"
- }
- }
-
- return self;
- }
-
- /* Direct writing of EPS or TIFF. */
-
- - (BOOL)canEmitEPS
- /*
- * If we have a representation that can provide EPS directly, then,
- * if we are copying PostScript to the Pasteboard and this Image is the
- * only Graphic selected, then we might as well just have the EPS which
- * represents this Image go straight to the Pasteboard rather than
- * wrapping it up in the copyPSCodeInside: wrappers. Of course, we
- * can only do that if we haven't been resized.
- *
- * See gvPasteboard.m's writePSToStream:.
- */
- {
- List *reps = [image representationList];
- int i = [reps count];
-
- if (originalSize.width == bounds.size.width && originalSize.height == bounds.size.height) {
- while (i--) {
- if ([[reps objectAt:i] respondsTo:@selector(getEPS:length:)]) {
- return YES;
- }
- }
- }
-
- return NO;
- }
-
- - writeEPSToStream:(NXStream *)stream
- /*
- * If canEmitEPS above returns YES, then we can write ourself out directly
- * as EPS. This method does that.
- */
- {
- List *reps = [image representationList];
- int i = [reps count];
- char *data;
- int length;
-
- while (i--) {
- if ([[reps objectAt:i] respondsTo:@selector(getEPS:length:)]) {
- [[reps objectAt:i] getEPS:&data length:&length];
- NXWrite(stream, data, length);
- return self; // should I free data before returning?
- }
- }
-
- return self;
- }
-
- - (BOOL)canEmitTIFF
- /*
- * Similar to canEmitEPS, except its for TIFF.
- */
- {
- return (originalSize.width == bounds.size.width && originalSize.height == bounds.size.height);
- }
-
- - writeTIFFToStream:(NXStream *)stream
- /*
- * Ditto above.
- */
- {
- [image writeTIFF:stream allRepresentations:YES];
- return self;
- }
-
- /* Caching. */
-
- - setCacheable:(BOOL)flag
- {
- dontCache = flag ? NO : YES;
- return self;
- }
-
- - (BOOL)isCacheable
- {
- return !dontCache;
- }
-
- /* Archiving. */
-
- - write:(NXTypedStream *)stream
- /*
- * All that is needed to archive the NXImage.
- */
- {
- [super write:stream];
- NXWriteType(stream, "c", &amLinkButton);
- NXWriteType(stream, "c", &amIcon);
- if (!amLinkButton) {
- NXWriteObject(stream, image);
- NXWriteSize(stream, &originalSize);
- }
- return self;
- }
-
- - read:(NXTypedStream *)stream
- /*
- * This contains lots of compatibility code for
- * interim versions. See if you can figure out the
- * various ways we approached archiving link info!
- */
- {
- BOOL alphaOk;
- NXRect savedBounds;
- int version, linkNumber;
-
- [super read:stream];
- version = NXTypedStreamClassVersion(stream, "Image");
- if (version > 5) NXReadType(stream, "c", &amLinkButton);
- if (version > 6) NXReadType(stream, "c", &amIcon);
- if (amLinkButton) {
- savedBounds = bounds;
- [self doInitFromImage:[[NXImage findImageNamed:"NXLinkButton"] copy]];
- bounds = savedBounds;
- } else {
- image = NXReadObject(stream);
- NXReadSize(stream, &originalSize);
- }
- if (version <= 2) NXReadTypes(stream, "c", &alphaOk);
- if (version == 4) {
- NXReadObject(stream); // used to be the NXDataLink
- } else if (version > 2 && version < 6) {
- NXReadTypes(stream, "i", &linkNumber);
- }
-
- return self;
- }
-
- @end
-