home *** CD-ROM | disk | FTP | other *** search
/ NeXTSTEP 3.0 / NeXTSTEP3.0.iso / NextDeveloper / Examples / AppKit / Draw / Image.m < prev    next >
Text File  |  1992-07-20  |  16KB  |  633 lines

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