|
|
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.