This is Image.m in view mode; [Download] [Up]
#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 */ - 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). */ { [super init]; if (!stream) { originalSize.width = originalSize.height = 1.0; bounds.size = originalSize; return self; } else { 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; } } } [self free]; return nil; } - init /* * This creates basically an "empty" Image. */ { return [self initFromStream:NULL]; } - 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). */ { [super init]; if (!pboard) { originalSize.width = originalSize.height = 1.0; bounds.size = originalSize; return self; } else { 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; } } } [self free]; return nil; } - initFromFile:(const char *)file /* * Creates an NXImage by reading data from an .eps or .tiff file. */ { [super 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; } } [self free]; return nil; } - doInitFromImage:(NXImage *)anImage /* * Common code for initFromImage: and initFromIcon:. */ { 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. */ { [super init]; return [self doInitFromImage:anImage]; } - 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
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.