home *** CD-ROM | disk | FTP | other *** search
/ OpenStep 4.2J (Developer) / os42jdev.iso / NextDeveloper / Examples / AppKit / Draw / Image.m < prev    next >
Text File  |  1996-04-18  |  17KB  |  644 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 NSImage class.  Using NSImage
  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. /* Factory methods. */
  18.  
  19. + (NSImage *)highlightedLinkButtonImage:(NSSize)size
  20. /*
  21.  * Just makes an NSHighlightedLinkButton NSImage the same size as
  22.  * the size passed in.  I suppose this could just be a
  23.  * function.
  24.  */
  25. {
  26.     static NSImage *retval = nil;
  27.     if (!retval) {
  28.     retval = [NSImage imageNamed:@"NSHighlightedLinkButton"];
  29.     [retval setScalesWhenResized:YES];
  30.     [retval setDataRetained:YES];
  31.     }
  32.     [retval setSize:size];
  33.     return retval;
  34. }
  35.  
  36. + (BOOL)canInitWithPasteboard:(NSPasteboard *)pboard
  37. {
  38.     return [NSImage canInitWithPasteboard:pboard];
  39. }
  40.  
  41. static BOOL checkImage(NSImage *anImage)
  42. /*
  43.  * Locking focus on an NSImage forces it to draw and thus verifies
  44.  * whether there are any PostScript or TIFF errors in the source of
  45.  * the image.  lockFocus returns YES only if there are no errors.
  46.  */
  47. {
  48.     if ([anImage isValid]) { 
  49.     [anImage lockFocus];
  50.     [anImage unlockFocus];
  51.     return YES;
  52.     }
  53.     return NO;
  54. }
  55.  
  56. /* Creation/Initialization Methods */
  57.  
  58. - (id)init
  59. /*
  60.  * This creates basically an "empty" Image.
  61.  * This is the designated initializer for Image.
  62.  * Be careful, however, because by the time this
  63.  * returns, a newly initialized Image may not be
  64.  * fully initialized (it'll be "valid," just not
  65.  * necessarily fully initialized).  If you want that
  66.  * behaviour, override finishedWithInit.
  67.  */
  68. {
  69.     [super init];
  70.     originalSize.width = originalSize.height = 1.0;
  71.     bounds.size = originalSize;
  72.     return self;
  73. }
  74.  
  75. - finishedWithInit
  76. /*
  77.  * Called when a newly initialized Image is fully
  78.  * initialized and ready to roll.  For subclassers
  79.  * only.
  80.  */
  81. {
  82.      return self;
  83. }
  84.  
  85. - initEmpty
  86. /*
  87.  * Creates a blank Image.
  88.  */
  89. {
  90.     [self init];
  91.     return [self finishedWithInit];
  92. }
  93.  
  94. - (id)initWithData:(NSData *)data
  95. /*
  96.  * Creates a new NSImage and sets it to be scalable and to retain
  97.  * its data (which means that when we archive it, it will actually
  98.  * write the TIFF or PostScript data into the stream).
  99.  */
  100. {
  101.     [self init];
  102.  
  103.     if (data) {
  104.     image = [NSImage allocWithZone:(NSZone *)[self zone]];
  105.     if ((image = [image initWithData:data])) {
  106.         [image setDataRetained:YES];
  107.         if (checkImage(image)) {
  108.         originalSize = [image size];
  109.         [image setScalesWhenResized:YES];
  110.         bounds.size = originalSize;
  111.         return [self finishedWithInit];
  112.         }
  113.     }
  114.     }
  115.     [self release];
  116.     return nil;
  117. }
  118.  
  119. - (id)initWithPasteboard:(NSPasteboard *)pboard;
  120. /*
  121.  * Creates a new NSImage and sets it to be scalable and to retain
  122.  * its data (which means that when we archive it, it will actually
  123.  * write the TIFF or PostScript data into the stream).
  124.  */
  125. {
  126.     [self init];
  127.  
  128.     if (pboard) {
  129.     image = [NSImage allocWithZone:(NSZone *)[self zone]];
  130.     if ((image = [image initWithPasteboard:pboard])) {
  131.         [image setDataRetained:YES];
  132.         if (checkImage(image)) {
  133.         originalSize = [image size];
  134.         [image setScalesWhenResized:YES];
  135.         bounds.size = originalSize;
  136.         return [self finishedWithInit];
  137.         }
  138.     }
  139.     }
  140.     [self release];
  141.     return nil;
  142. }
  143.  
  144. - (id)initWithFile:(NSString *)file
  145. /*
  146.  * Creates an NSImage by reading data from an .eps or .tiff file.
  147.  */
  148. {
  149.     [self init];
  150.  
  151.     image = [[NSImage allocWithZone:(NSZone *)[self zone]] init];
  152.     [image addRepresentations:[NSImageRep imageRepsWithContentsOfFile:file]];
  153.     [image setDataRetained:YES];
  154.     if (checkImage(image)) {
  155.     originalSize = [image size];
  156.     [image setScalesWhenResized:YES];
  157.     bounds.size = originalSize;
  158.     return [self finishedWithInit];
  159.     }
  160.     [self release];
  161.     return nil;
  162. }
  163.  
  164. - doInitFromImage:(NSImage *)anImage
  165. /*
  166.  * Common code for initFromImage: and unarchiving.
  167.  */
  168. {
  169.     if (anImage) {
  170.     image = [anImage copy];
  171.     originalSize = [image size];
  172.     [image setScalesWhenResized:YES];
  173.     [image setDataRetained:YES];
  174.     bounds.size = originalSize;
  175.     } else {
  176.     [self release];
  177.     self = nil;
  178.     }
  179.     return self;
  180. }
  181.  
  182. - initFromImage:(NSImage *)anImage
  183. /*
  184.  * Initializes an Image from a specific NSImage.
  185.  */
  186. {
  187.     [self init];
  188.     return [[self doInitFromImage:anImage] finishedWithInit];
  189. }
  190.  
  191. - initFromIcon:(NSImage *)anImage
  192. /*
  193.  * Same as initFromImage:, but we remember that this particular
  194.  * NSImage was actually a file icon (which enables us to double-click
  195.  * on it to open the icon, see handleEvent:).
  196.  */
  197. {
  198.     if ([self initFromImage:anImage]) {
  199.     amIcon = YES;
  200.     return self;
  201.     } else {
  202.     return nil;
  203.     }
  204. }
  205.  
  206. - initWithLinkButton
  207. /*
  208.  * Creates an image which is just the link button.
  209.  * This is only applicable with Object Links.
  210.  */
  211. {
  212.     if ([self initFromImage:[[NSImage imageNamed:@"NSLinkButton"] copy]]) {
  213.     amLinkButton = YES;
  214.     return self;
  215.     } else {
  216.     return nil;
  217.     }
  218. }
  219.  
  220. - (NSRect)resetImage:(NSImage *)newImage
  221. /*
  222.  * Called by the "reinit" methods to reset all of our instance
  223.  * variables based on using a new NSImage for our image.
  224.  */
  225. {
  226.     NSRect eBounds;
  227.     NSRect neBounds;
  228.  
  229.     /* TOPS-WARNING!!!  NSObject conversion:  This release used to be a free. */ [image release];
  230.     image = newImage;
  231.     eBounds = [self extendedBounds];
  232.     neBounds.size = [image size];
  233.     neBounds.size.width *= bounds.size.width / originalSize.width;
  234.     neBounds.size.height *= bounds.size.height / originalSize.height;
  235.     neBounds.origin.x = bounds.origin.x - floor((neBounds.size.width - bounds.size.width) / 2.0 + 0.5);
  236.     neBounds.origin.y = bounds.origin.y - floor((neBounds.size.height - bounds.size.height) / 2.0 + 0.5);
  237.     [self setBounds:neBounds];
  238.     neBounds = [self extendedBounds];
  239.     neBounds = NSUnionRect(eBounds, neBounds);
  240.     [image setDataRetained:YES];
  241.     originalSize = [image size];
  242.     [image setScalesWhenResized:YES];
  243.  
  244.     return neBounds;
  245. }
  246.  
  247. - (NSRect)reinitWithPasteboard:(NSPasteboard *)pboard
  248. /*
  249.  * Reset all of our instance variable based on extract an
  250.  * NSImage from data in the the passed pboard.  Happens when
  251.  * we update a link through Object Links.
  252.  */
  253. {
  254.     NSRect neBounds;
  255.     NSImage *newImage;
  256.  
  257.     newImage = [NSImage allocWithZone:(NSZone *)[self zone]];
  258.     if ((newImage = [newImage initWithPasteboard:pboard])) {
  259.     [newImage setDataRetained:YES];
  260.     if (checkImage(newImage)) {
  261.             return [self resetImage:newImage];
  262.     }
  263.     }
  264.  
  265.     [newImage release];
  266.     neBounds.origin.x = neBounds.origin.y = 0.0;
  267.     neBounds.size.width = neBounds.size.height = 0.0;
  268.  
  269.     return neBounds;
  270. }
  271.  
  272. - (NSRect)reinitFromFile:(NSString *)file
  273. /*
  274.  * Reset all of our instance variable based on extract an
  275.  * NSImage from the data in the passed file.  Happens when
  276.  * we update a link through Object Links.
  277.  */
  278. {
  279.     NSRect neBounds;
  280.     NSImage *newImage;
  281.  
  282.     newImage = [[NSImage allocWithZone:(NSZone *)[self zone]] init];
  283.     [newImage addRepresentations:[NSImageRep imageRepsWithContentsOfFile:file]];
  284.     [newImage setDataRetained:YES];
  285.     if (checkImage(newImage)) return [self resetImage:newImage];
  286.  
  287.     [newImage release];
  288.     neBounds.origin.x = neBounds.origin.y = 0.0;
  289.     neBounds.size.width = neBounds.size.height = 0.0;
  290.  
  291.     return neBounds;
  292. }
  293.  
  294. /* All those allocation/initialization method and only this one free method. */
  295.  
  296. - (void)dealloc
  297. {
  298.     [image release];
  299.     [super dealloc];
  300. }
  301.  
  302. /* Link methods */
  303.  
  304. - (void)setLink:(NSDataLink *)aLink
  305. /*
  306.  * It's "might" be linked because we're linked now, but might
  307.  * have our link broken in the future and the mightBeLinked flag
  308.  * is only advisory and is never cleared.  It is used just so that
  309.  * we know we might want to try to reestablish a link with this
  310.  * Graphic after a cut/paste.  No biggie if there really is no
  311.  * link associated with this any more.  In gvLinks.m, see
  312.  * readLinkForGraphic:fromPasteboard:useNewIdentifier:, and in
  313.  * gvPasteboard.m, see pasteFromPasteboard:andLink:at:.
  314.  * If this Image is a link button, then we obviously never need
  315.  * to update the link because we don't actually show the data
  316.  * associated with the link (we just show that little link button).
  317.  */
  318. {
  319.     if (aLink) {
  320.         link = [aLink retain];
  321.         gFlags.mightBeLinked = YES;
  322.         if (amLinkButton) [link setUpdateMode:NSUpdateNever];
  323.    }
  324. }
  325.  
  326. - (NSDataLink *)link
  327. {
  328.     return link;
  329. }
  330.  
  331. /* Event-handling */
  332.  
  333. - trackLinkButton:(NSEvent *)event at:(NSPoint)startPoint inView:(NSView *)view
  334. /*
  335.  * This method tracks that little link button.  Note that the link button is a diamond,
  336.  * but we track the whole rectangle.  This is unfortunate, but we can't be sure that,
  337.  * in the future, the shape of the link button might not change (thus, what we really
  338.  * need is a NeXTSTEP function to track the thing!).  Anyway, we track it and if the 
  339.  * mouse goes up inside the button, we openSource on the link (we wouldn't be here if
  340.  * we didn't have a link).
  341.  */
  342. {
  343.     NSImage *realImage, *highImage, *imageToDraw;
  344.  
  345.     realImage = image;
  346.     highImage = [[self class] highlightedLinkButtonImage:bounds.size];
  347.     image = imageToDraw = highImage;
  348.     [self draw];
  349.     [[view window] flushWindow];
  350.     do {
  351.     event = [[view window] nextEventMatchingMask:NSLeftMouseDraggedMask|NSLeftMouseUpMask];
  352.     startPoint = [event locationInWindow];
  353.     startPoint = [view convertPoint:startPoint fromView:nil];
  354.     imageToDraw = NSMouseInRect(startPoint, bounds, NO) ? highImage : realImage;
  355.     if (imageToDraw != image) {
  356.         image = imageToDraw;
  357.         [self draw];
  358.         [[view window] flushWindow];
  359.     }
  360.     } while ([event type] != NSLeftMouseUp);
  361.  
  362.     if (imageToDraw == highImage) {
  363.     [link openSource];
  364.     image = realImage;
  365.     [self draw];
  366.     [[view window] flushWindow];
  367.     }
  368.  
  369.     return self;
  370. }
  371.  
  372. - (BOOL)handleEvent:(NSEvent *)event at:(NSPoint)p inView:(NSView *)view
  373. {
  374.     if (NSMouseInRect(p, bounds, NO)) {
  375.     if (amLinkButton && !gFlags.selected && !([event modifierFlags] & (NSControlKeyMask|NSShiftKeyMask|NSAlternateKeyMask))) {
  376.         [self trackLinkButton:event at:p inView:view];
  377.         return YES;
  378.     } else if (link && ([event clickCount] == 2) && (amIcon || ([event modifierFlags] & NSControlKeyMask))) {
  379.         [[view window] nextEventMatchingMask:NSLeftMouseUpMask];
  380.         [link openSource];
  381.         return YES;
  382.     }
  383.     }
  384.     return NO;
  385. }
  386.  
  387. /* Methods overridden from superclass to support links. */
  388.  
  389. - (int)cornerMask
  390. /*
  391.  * Link buttons are too small to have corners AND sides, so
  392.  * we only let link buttons have knobbies on the corners.
  393.  */
  394. {
  395.     if (amLinkButton) {
  396.     return LOWER_LEFT_MASK|UPPER_LEFT_MASK|UPPER_RIGHT_MASK|LOWER_RIGHT_MASK;
  397.     } else {
  398.     return [super cornerMask];
  399.     }
  400. }
  401.  
  402. - (NSRect)extendedBounds
  403. /*
  404.  * We have to augment this because we might have a link frame
  405.  * (if show links is on), so we have to extend our extended bounds
  406.  * a bit.
  407.  */
  408. {
  409.     NSRect linkBounds;
  410.     float linkFrameThickness = NSLinkFrameThickness();
  411.  
  412.     linkBounds = bounds;
  413.     linkBounds.origin.x -= linkFrameThickness;
  414.     linkBounds.size.width += linkFrameThickness * 2.0;
  415.     linkBounds.origin.y -= linkFrameThickness;
  416.     linkBounds.size.height += linkFrameThickness;
  417.  
  418.     return NSUnionRect(linkBounds, [super extendedBounds]);
  419. }
  420.  
  421. - (BOOL)constrainByDefault;
  422. /*
  423.  * Icons and link buttons look funny outside their natural
  424.  * aspect ratio, so we constrain them (by default) to keep
  425.  * their natural ratio.  You can still use the Alternate key
  426.  * to NOT constrain these.
  427.  */
  428. {
  429.     return (amLinkButton || amIcon);
  430. }
  431.  
  432. /* Methods overridden from superclass */
  433.  
  434. - (BOOL)isValid
  435. {
  436.     return image ? YES : NO;
  437. }
  438.  
  439. - (BOOL)isOpaque
  440. {
  441.     return [[image bestRepresentationForDevice:nil] isOpaque];
  442. }
  443.  
  444. - (float)naturalAspectRatio
  445. {
  446.     if (!originalSize.height) return 0.0;
  447.     return originalSize.width / originalSize.height;
  448. }
  449.  
  450. - draw
  451. /*
  452.  * If we are resizing, we just draw a gray box.
  453.  * If not, then we simply see if our bounds have changed
  454.  * and update the NSImage object if they have.  Then,
  455.  * if we do not allow alpha (i.e. this is a TIFF image),
  456.  * we paint a white background square (we don't allow
  457.  * alpha in our TIFF images since it won't print and
  458.  * Draw is WYSIWYG).  Finally, we SOVER the image.
  459.  * If we are not keeping the cache around, we tell
  460.  * NSImage to toss its cached version of the image
  461.  * via the message recache.
  462.  *
  463.  * If we are linked to something and the user has chosen
  464.  * "Show Links", then linkOutlinesAreVisible, so we must
  465.  * draw a link border around ourself.
  466.  */
  467. {
  468.     NSRect r;
  469.     NSPoint p;
  470.     NSSize currentSize;
  471.  
  472.     if (bounds.size.width < 1.0 || bounds.size.height < 1.0) return self;
  473.  
  474.     if (DrawStatus == Resizing) {
  475.     PSsetgray(NSDarkGray);
  476.     PSsetlinewidth(0.0);
  477.     PSrectstroke(bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height);
  478.     } else if (image) {
  479.     p = bounds.origin;
  480.     currentSize = [image size];
  481.     if (currentSize.width != bounds.size.width || currentSize.height != bounds.size.height) {
  482.         if ([image scalesWhenResized]) {
  483.         [image setSize:bounds.size];
  484.         } else {
  485.         p.x = bounds.origin.x + floor((bounds.size.width - currentSize.width) / 2.0 + 0.5);
  486.         p.y = bounds.origin.y + floor((bounds.size.height - currentSize.height) / 2.0 + 0.5);
  487.         }
  488.     }
  489.     if ([[image bestRepresentationForDevice:nil] isOpaque]) {
  490.         PSsetgray(NSWhite);
  491.         NSRectFill(bounds);
  492.     }
  493.     [image compositeToPoint:p operation:NSCompositeSourceOver];
  494.     if (dontCache && [[NSDPSContext currentContext] isDrawingToScreen]) [image recache];
  495.     if (([[NSDPSContext currentContext] isDrawingToScreen]) && !amLinkButton && [[link manager] areLinkOutlinesVisible]) {
  496.         r.origin.x = floor(bounds.origin.x);
  497.         r.origin.y = floor(bounds.origin.y);
  498.         r.size.width = floor(bounds.origin.x + bounds.size.width + 0.99) - r.origin.x;
  499.         r.size.height = floor(bounds.origin.y + bounds.size.height + 0.99) - r.origin.y;
  500.         NSFrameLinkRect(r, YES);    // YES means "is a destination link"
  501.     }
  502.     }
  503.  
  504.     return self;
  505. }
  506.  
  507. /* Direct writing of EPS or TIFF. */
  508.  
  509. - (BOOL)canEmitEPS
  510. /*
  511.  * If we have a representation that can provide EPS directly, then,
  512.  * if we are copying PostScript to the Pasteboard and this Image is the
  513.  * only Graphic selected, then we might as well just have the EPS which
  514.  * represents this Image go straight to the Pasteboard rather than
  515.  * wrapping it up in the copyPSCodeInside: wrappers.  Of course, we
  516.  * can only do that if we haven't been resized.
  517.  *
  518.  * See gvPasteboard.m's dataForEPS.
  519.  */
  520. {
  521.     NSArray *reps = [image representations];
  522.     int i = [reps count];
  523.  
  524.     if (originalSize.width == bounds.size.width && originalSize.height == bounds.size.height) {
  525.     while (i--) {
  526.         if ([[reps objectAtIndex:i] respondsToSelector:@selector(getEPS:length:)]) {
  527.         return YES;
  528.         }
  529.     }
  530.     }
  531.  
  532.     return NO;
  533. }
  534.  
  535. - (NSData *)dataForEPS
  536. /*
  537.  * If canEmitEPS above returns YES, then we can write ourself out directly
  538.  * as EPS.  This method does that.
  539.  */
  540. {
  541.     NSArray *reps = [image representations];
  542.     int i = [reps count];
  543.  
  544.     while (i--) {
  545.     if ([[reps objectAtIndex:i] respondsToSelector:@selector(EPSRepresentation)]) {
  546.         return [[reps objectAtIndex:i] EPSRepresentation];
  547.     }
  548.     }
  549.  
  550.     return nil;
  551. }
  552.  
  553. - (BOOL)canEmitTIFF
  554. /*
  555.  * Similar to canEmitEPS, except its for TIFF.
  556.  */
  557. {
  558.     return (originalSize.width == bounds.size.width && originalSize.height == bounds.size.height);
  559. }
  560.  
  561. - (NSData *)dataForTIFF
  562. /*
  563.  * Ditto above.
  564.  */
  565. {
  566.     return [image TIFFRepresentation];
  567. }
  568.  
  569. /* Caching. */
  570.  
  571. - (void)setCacheable:(BOOL)flag
  572. {
  573.     dontCache = flag ? NO : YES; 
  574. }
  575.  
  576. - (BOOL)isCacheable
  577. {
  578.     return !dontCache;
  579. }
  580.  
  581. /* Archiving. */
  582.  
  583. - (BOOL)writesFiles
  584. {
  585.     return (image && !amLinkButton) ? YES : NO;
  586. }
  587.  
  588. - (void)writeFilesToDirectory:(NSString *)directory
  589. {
  590.     NSString *filename = [directory stringByAppendingPathComponent:[imageFile lastPathComponent]];
  591.     if (![[NSFileManager defaultManager] fileExistsAtPath:filename]) {
  592.         [NSArchiver archiveRootObject:image toFile:filename];
  593.     }
  594. }
  595.  
  596. #define FILE_KEY @"ImageFileName"
  597. #define SIZE_KEY @"OriginalSize"
  598.  
  599. - (void)convertSelf:(ConversionDirection)direction propertyList:(id)plist
  600. {
  601.     [super convertSelf:direction propertyList:plist];
  602.     PL_FLAG(plist, amLinkButton, @"IsLinkButton", direction);
  603.     PL_FLAG(plist, amIcon, @"IsIcon", direction);
  604. }
  605.  
  606. - (id)propertyList
  607. {
  608.     NSMutableDictionary *plist;
  609.  
  610.     plist = [super propertyList];
  611.     if (!amLinkButton) {
  612.         [plist setObject:propertyListFromNSSize(originalSize) forKey:SIZE_KEY];
  613.         if (!imageFile) imageFile = [[NSString stringWithFormat:@"Image%d", identifier] retain];
  614.         [plist setObject:imageFile forKey:FILE_KEY];
  615.     }
  616.  
  617.     return plist;
  618. }
  619.  
  620. - (NSString *)description
  621. {
  622.     return [(NSObject *)[self propertyList] description];
  623. }
  624.  
  625. - initFromPropertyList:(id)plist inDirectory:(NSString *)directory
  626. {
  627.     NSRect savedBounds;
  628.  
  629.     [super initFromPropertyList:plist inDirectory:directory];
  630.     if (amLinkButton) {
  631.         savedBounds = bounds;
  632.         [self doInitFromImage:[NSImage imageNamed:@"NSLinkButton"]];
  633.         bounds = savedBounds;
  634.     } else {
  635.         originalSize = sizeFromPropertyList([plist objectForKey:SIZE_KEY]);
  636.         imageFile = [[plist objectForKey:FILE_KEY] retain];
  637.         image = [[NSUnarchiver unarchiveObjectWithFile:[directory stringByAppendingPathComponent:imageFile]] retain];
  638.     }
  639.  
  640.     return self;
  641. }
  642.  
  643. @end
  644.