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

  1. #import "draw.h"
  2.  
  3. @implementation Graphic : Object
  4.  
  5. static int KNOB_WIDTH = 0.0;
  6. static int KNOB_HEIGHT = 0.0;
  7.  
  8. #define MINSIZE 5.0    /* minimum size of a Graphic */
  9.  
  10. NXCursor *CrossCursor = nil;    /* global since subclassers may need it */
  11.  
  12. /* Optimization method. */
  13.  
  14. /*
  15.  * The fastKnobFill optimization just keeps a list of black and dark gray
  16.  * rectangles (the knobbies are made out of black and dark gray rectangles)
  17.  * and emits them in a single NXRectFillList() which is much faster than
  18.  * doing individual rectfills (we also save the repeated setgrays).
  19.  */
  20.  
  21. static NXRect *blackRectList = NULL;
  22. static int blackRectSize = 0;
  23. static int blackRectCount = 0;
  24. static NXRect *dkgrayRectList = NULL;
  25. static int dkgrayRectSize = 0;
  26. static int dkgrayRectCount = 0;
  27.  
  28. + fastKnobFill:(const NXRect *)aRect isBlack:(BOOL)isBlack
  29. {
  30.     if (!aRect) return self;
  31.  
  32.     if (isBlack) {
  33.     if (!blackRectList) {
  34.         blackRectSize = 16;
  35.         NX_ZONEMALLOC([NXApp zone], blackRectList, NXRect, blackRectSize);
  36.     } else {
  37.         while (blackRectCount >= blackRectSize) blackRectSize <<= 1;
  38.         NX_ZONEREALLOC([NXApp zone], blackRectList, NXRect, blackRectSize);
  39.     }
  40.     blackRectList[blackRectCount++] = *aRect;
  41.     } else {
  42.     if (!dkgrayRectList) {
  43.         dkgrayRectSize = 16;
  44.         NX_ZONEMALLOC([NXApp zone], dkgrayRectList, NXRect, dkgrayRectSize);
  45.     } else {
  46.         while (dkgrayRectCount >= dkgrayRectSize) dkgrayRectSize <<= 1;
  47.         NX_ZONEREALLOC([NXApp zone], dkgrayRectList, NXRect, dkgrayRectSize);
  48.     }
  49. RU)rayRectList[dkgrayRectCount++] = *aRect;
  50.     }
  51.  
  52.     return self;
  53. }
  54.  
  55. + showFastKnobFills
  56. {
  57.     PSsetgray(NX_BLACK);
  58.     NXRectFillList(blackRectList, blackRectCount);
  59.     PSsetgray(NX_DKGRAY);
  60.     NXRectFillList(dkgrayRectList, dkgrayRectCount);
  61.     blackRectCount = 0;
  62.     dkgrayRectCount = 0;
  63.     return self;
  64. }
  65.  
  66. /* Factory methods. */
  67.  
  68. + initialize
  69. /*
  70.  * This sets the class version so that we can compatibly read
  71.  * old Graphic objects out of an archive.
  72.  */
  73. {
  74.     [Graphic setVersion:3];
  75.     return self;
  76. }
  77.  
  78. + (BOOL)isEditable
  79. /*
  80.  * Any Graphic which can be edited should return YES from this
  81.  * and its instances should do something in the response to the
  82.  * edit:in: method.
  83.  */
  84. {
  85.     return NO;
  86. }
  87.  
  88. + cursor
  89. /*
  90.  * Any Graphic that doesn't have a special cursor gets the default cross.
  91.  */
  92. {
  93.     NXPoint spot;
  94.  
  95.     if (!CrossCursor) {
  96.     CrossCursor = [NXCursor newFromImage:[NXImage newFromSection:"cross.tiff"]];
  97.     spot.x = 7.0; spot.y = 7.0;
  98.     [CrossCursor setHotSpot:&spot];
  99.     }
  100.  
  101.     return CrossCursor;
  102. }
  103.  
  104. static void initClassVars()
  105. {
  106.     const char *value;
  107.     NXCoord w = 2.0, h = 2.0;
  108.  
  109.     if (!KNOB_WIDTH) {
  110.     value = NXGetDefaultValue([NXApp appName], "KnobWidth");
  111.     if (value) w = floor(atof(value) / 2.0);
  112.     value = NXGetDefaultValue([NXApp appName], "KnobHeight");
  113.     if (value) h = floor(atof(value) / 2.0);
  114.     w = MAX(w, 1.0); h = MAX(h, 1.0);
  115.     KNOB_WIDTH = w * 2.0 + 1.0;    /* size must be odd */
  116.     KNOB_HEIGHT = h * 2.0 + 1.0;
  117.     }
  118. }
  119.  
  120. /*
  121.  * The currentGraphicIdentifier is a number that is kept unique for a given
  122.  * Draw document by being monotonically increasing and is bumped each time a
  123.  * new Graphic is created.  The method of the same name is used during the
  124.  * archiving of a Draw document to write out what the number is at save-time.
  125.  * updateCurrentGraphicIdentifer: is used at document load time to reset
  126.  * the number to that level (if it's already higher, then we don't need to
  127.  * bump it).
  128.  */
  129.  
  130. static int currentGraphicIdentifier = 1;
  131.  
  132. + (int)currentGraphicIdentifier
  133. {
  134.     return currentGraphicIdentifier;
  135. }
  136.  
  137. + updateCurrentGraphicIdentifier:(int)newMaxIdentifier
  138. {
  139.     if (newMaxIdentifier > currentGraphicIdentifier) currentGraphicIdentifier = newMaxIdentifier;
  140.     return self;
  141. }
  142.  
  143. - init
  144. {
  145.     [super init];
  146.     gFlags.active = YES;
  147.     gFlags.selected = YES;
  148.     initClassVars();
  149.     identifier = currentGraphicIdeRU0ier++;
  150.     return self;
  151. }
  152.  
  153. - awake
  154. {
  155.     initClassVars();
  156.     return [super awake];
  157. }
  158.  
  159. /* Private C functions and macros used to implement methods in this class. */
  160.  
  161. static void drawKnobs(const NXRect *rect, int cornerMask, BOOL black)
  162. /*
  163.  * Draws either the knobs or their shadows (not both).
  164.  */
  165. {
  166.     NXRect knob;
  167.     NXCoord dx, dy;
  168.     BOOL oddx, oddy;
  169.  
  170.     knob = *rect;
  171.     dx = knob.size.width / 2.0;
  172.     dy = knob.size.height / 2.0;
  173.     oddx = (floor(dx) != dx);
  174.     oddy = (floor(dy) != dy);
  175.     knob.size.width = KNOB_WIDTH;
  176.     knob.size.height = KNOB_HEIGHT;
  177.     knob.origin.x -= ((KNOB_WIDTH - 1.0) / 2.0);
  178.     knob.origin.y -= ((KNOB_HEIGHT - 1.0) / 2.0);
  179.  
  180.     if (cornerMask & LOWER_LEFT_MASK) [Graphic fastKnobFill:&knob isBlack:black];
  181.     knob.origin.y += dy;
  182.     if (oddy) knob.origin.y -= 0.5;
  183.     if (cornerMask & LEFT_SIDE_MASK) [Graphic fastKnobFill:&knob isBlack:black];
  184.     knob.origin.y += dy;
  185.     if (oddy) knob.origin.y += 0.5;
  186.     if (cornerMask & UPPER_LEFT_MASK) [Graphic fastKnobFill:&knob isBlack:black];
  187.     knob.origin.x += dx;
  188.     if (oddx) knob.origin.x -= 0.5;
  189.     if (cornerMask & TOP_SIDE_MASK) [Graphic fastKnobFill:&knob isBlack:black];
  190.     knob.origin.x += dx;
  191.     if (oddx) knob.origin.x += 0.5;
  192.     if (cornerMask & UPPER_RIGHT_MASK) [Graphic fastKnobFill:&knob isBlack:black];
  193.     knob.origin.y -= dy;
  194.     if (oddy) knob.origin.y -= 0.5;
  195.     if (cornerMask & RIGHT_SIDE_MASK) [Graphic fastKnobFill:&knob isBlack:black];
  196.     knob.origin.y -= dy;
  197.     if (oddy) knob.origin.y += 0.5;
  198.     if (cornerMask & LOWER_RIGHT_MASK) [Graphic fastKnobFill:&knob isBlack:black];
  199.     knob.origin.x -= dx;
  200.     if (oddx) knob.origin.x += 0.5;
  201.     if (cornerMask & BOTTOM_SIDE_MASK) [Graphic fastKnobFill:&knob isBlack:black];
  202. }
  203.  
  204. /* Private methods sometimes overridden by subclassers */
  205.  
  206. - setGraphicsState
  207. /*
  208.  * Emits a gsave, must be balanced by grestore.
  209.  */
  210. {
  211.     PSSetParameters(gFlags.linecap, gFlags.linejoin, linewidth);
  212.     return self;
  213. }
  214.  
  215. - setLineColor
  216. {
  217.     if (lineColor) {
  218.     NXSetColor(*lineColor);
  219.     } else {
  220.     NXSetColor(NX_COLORBLACK);
  221.     }
  222.     return self;
  223. }
  224.  
  225. - setFillColor
  226. {
  227.     if (fillColor) NXSetColor(*fillColor);
  228.     return self;
  229. }
  230.  
  231. - (int)cornerMask
  232. /*
  233.  * Returns a mask of the corners which should have a knobby in them.
  234.  */
  235. {
  236.     return ALL_CORNERS;
  237. }
  238.  
  239. /* Data link methods -- see Links.rtfRU1 gvLinks.m for more info */
  240.  
  241. /*
  242.  * Most Graphics aren't linked (i.e. their visual display is
  243.  * not determined by some other document).  See Image and
  244.  * TextGraphic for examples of Graphics that sometimes do.
  245.  */
  246.  
  247. - setLink:(NXDataLink *)aLink
  248. {
  249.     return nil;
  250. }
  251.  
  252. - (NXDataLink *)link
  253. {
  254.     return nil;
  255. }
  256.  
  257. - (Graphic *)graphicLinkedBy:(NXDataLink *)aLink
  258. /*
  259.  * The reason we implement this method (instead of just relying on
  260.  * saying if ([graphic link] == aLink)) is for the sake of Group
  261.  * objects which may have a linked Graphic embedded in them.
  262.  */
  263. {
  264.     NXDataLink *link = [self link];
  265.  
  266.     if (link) {
  267.     if (!aLink) {    /* !aLink means any link */
  268.         return ([link disposition] != NX_LinkBroken) ? self : nil;
  269.     } else {
  270.         return (aLink == link) ? self : nil;
  271.     }
  272.     }
  273.  
  274.     return nil;
  275. }
  276.  
  277. - reviveLink:(NXDataLinkManager *)linkManager
  278. /*
  279.  * We never archive link information (but, of course, the unique identifer
  280.  * is always archived with a Graphic).  Thus, when our document is reloaded,
  281.  * we just asked the NXDataLinkManager which NXDataLink object is associated
  282.  * with the NXSelection which represents this Graphic.
  283.  */
  284. {
  285.     if (![self link]) [self setLink:[linkManager findDestinationLinkWithSelection:[self selection]]];
  286.     return self;
  287. }
  288.  
  289. - (NXSelection *)selection
  290. /*
  291.  * Just creates an NXSelection "bag o' bits" with our unique identifier in it.
  292.  */
  293. {
  294.     char buffer[200];
  295.     sprintf(buffer, "%d %d", ByGraphic, [self identifier]);
  296.     return [[NXSelection allocFromZone:[self zone]] initWithDescription:buffer length:strlen(buffer)+1];
  297. }
  298.  
  299. - (BOOL)mightBeLinked
  300. /*
  301.  * This is set whenever our Graphic has a link set in it.
  302.  * It is never cleared.
  303.  * We use it during copy/paste to determine whether we have
  304.  * to check with the data link manager to possibly reestablish
  305.  * a link to this object.
  306.  */
  307. {
  308.     return gFlags.mightBeLinked;
  309. }
  310.  
  311. /* Notification messages */
  312.  
  313. /*
  314.  * These methods are sent when a Graphic is added to or removed
  315.  * from a GraphicView (respectively).  Currently we only use them
  316.  * to break and reestablish links if any.
  317.  */
  318.  
  319. - wasAddedTo:(GraphicView *)sender
  320. {
  321.     NXDataLink *link;
  322.     NXDataLinkManager *linkManager;
  323.  
  324.     if ((linkManager = [sender linkManager]) && (link = [self link])) {
  325.     if ([link disposition] == NX_LinkBroken) {
  326.         [linkManager addLink:link at:[self selection]];
  327.     }
  328.     }
  329.  
  330. RU2return self;
  331. }
  332.  
  333. - wasRemovedFrom:(GraphicView *)sender
  334. {
  335.     [[self link] break];
  336.     return self;
  337. }
  338.  
  339. /* Methods for uniquely identifying a Graphic. */
  340.  
  341. - resetIdentifier
  342. {
  343.     identifier = currentGraphicIdentifier++;
  344.     return self;
  345. }
  346.  
  347. - writeIdentifierTo:(char *)buffer
  348. /*
  349.  * This method is necessary to support a Group which never writes out
  350.  * its own identifier, but, instead has its components each write out
  351.  * their own identifier.
  352.  */
  353. {
  354.     sprintf(buffer, "%d", identifier);
  355.     return self;
  356. }
  357.  
  358. - (int)identifier
  359. {
  360.     return identifier;
  361. }
  362.  
  363. - (Graphic *)graphicIdentifiedBy:(int)anIdentifier
  364. {
  365.     return (identifier == anIdentifier) ? self : nil;
  366. }
  367.  
  368. /* Event handling */
  369.  
  370. - (BOOL)handleEvent:(NXEvent *)event at:(const NXPoint *)p inView:(View *)view
  371. /*
  372.  * Currently the only Graphic's that handle events are Image Graphic's that
  373.  * are linked to something else (they follow the link on double-click and
  374.  * the track the mouse for link buttons, for example).  This method should
  375.  * return YES only if it tracked the mouse until it went up.
  376.  */
  377. {
  378.     return NO;
  379. }
  380.  
  381. /* Number of Graphics this Graphic represents (always 1 for non-Group). */
  382.  
  383. - (int)graphicCount
  384. /*
  385.  * This is really only here to support Groups.  It is used by the
  386.  * Object Link stuff purely to know how much space will be needed to
  387.  * create an NXSelection with the unique identifiers of all the objects
  388.  * in the selection.
  389.  */
  390. {
  391.     return 1;
  392. }
  393.  
  394. /* Public routines mostly called by GraphicView's. */
  395.  
  396. - (const char *)title
  397. {
  398.     return NXLoadLocalStringFromTableInBundle(NULL, nil, [self name], NULL);
  399. }
  400.  
  401. - (BOOL)isSelected
  402. {
  403.     return gFlags.selected;
  404. }
  405.  
  406. - (BOOL)isActive
  407. {
  408.     return gFlags.active;
  409. }
  410.  
  411. - (BOOL)isLocked
  412. {
  413.     return gFlags.locked;
  414. }
  415.  
  416. - select
  417. {
  418.     gFlags.selected = YES;
  419.     return self;
  420. }
  421.  
  422. - deselect
  423. {
  424.     gFlags.selected = NO;
  425.     return self;
  426. }
  427.  
  428. - activate
  429. /*
  430.  * Activation is used to *temporarily* take a Graphic out of the GraphicView.
  431.  */
  432. {
  433.     gFlags.active = YES;
  434.     return self;
  435. }
  436.  
  437. - deactivate
  438. {
  439.     gFlags.active = NO;
  440.     return self;
  441. }
  442.  
  443. - lock
  444. /*
  445.  * A locked graphic cannot be selected, resized or moved.
  446.  */
  447. {
  448.     gFlags.locked = YES;
  449.     return self;
  450. }
  451.  
  452. - unlock
  453. {
  454.     gFlags.locked = NO;
  455.     return self;
  456. }
  457.  
  458. /* See TextGraphic for more info about form entries. */
  459.  
  460. - (BOOL)isFormEntry
  461. {
  462.     return NO;
  463. }
  464.  
  465. - setFormEntry:(int)flag
  466. RU3  return self;
  467. }
  468.  
  469. - (BOOL)hasFormEntries
  470. {
  471.     return NO;
  472. }
  473.  
  474. - (BOOL)writeFormEntryToStream:(NXStream *)stream
  475. {
  476.     return NO;
  477. }
  478.  
  479. /* See Group and Image for more info about cacheability. */
  480.  
  481. - setCacheable:(BOOL)flag
  482. {
  483.     return self;
  484. }
  485.  
  486. - (BOOL)isCacheable
  487. {
  488.     return YES;
  489. }
  490.  
  491. /* Getting and setting the bounds. */
  492.  
  493. - getBounds:(NXRect *)theRect
  494. {
  495.     *theRect = bounds;
  496.     return self;
  497. }
  498.  
  499. - setBounds:(const NXRect *)aRect
  500. {
  501.     bounds = *aRect;
  502.     return self;
  503. }
  504.  
  505. - (NXRect *)getExtendedBounds:(NXRect *)theRect
  506. /*
  507.  * Returns, by reference, the rectangle which encloses the Graphic
  508.  * AND ITS KNOBBIES and its increased line width (if appropriate).
  509.  */
  510. {
  511.     if (bounds.size.width < 0.0) {
  512.     theRect->origin.x = bounds.origin.x + bounds.size.width;
  513.     theRect->size.width = - bounds.size.width;
  514.     } else {
  515.     theRect->origin.x = bounds.origin.x;
  516.     theRect->size.width = bounds.size.width;
  517.     }
  518.     if (bounds.size.height < 0.0) {
  519.     theRect->origin.y = bounds.origin.y + bounds.size.height;
  520.     theRect->size.height = - bounds.size.height;
  521.     } else {
  522.     theRect->origin.y = bounds.origin.y;
  523.     theRect->size.height = bounds.size.height;
  524.     }
  525.  
  526.     theRect->size.width = MAX(1.0, theRect->size.width);
  527.     theRect->size.height = MAX(1.0, theRect->size.height);
  528.  
  529.     NXInsetRect(theRect, - ((KNOB_WIDTH - 1.0) + linewidth + 1.0),
  530.              - ((KNOB_HEIGHT - 1.0) + linewidth + 1.0));
  531.  
  532.     if (gFlags.arrow) {
  533.     if (linewidth) {
  534.         NXInsetRect(theRect, - linewidth * 2.5, - linewidth * 2.5);
  535.     } else {
  536.         NXInsetRect(theRect, - 13.0, - 13.0);
  537.     }
  538.     }
  539.  
  540.     NXIntegralRect(theRect);
  541.  
  542.     return theRect;
  543. }
  544.  
  545. - (int)knobHit:(const NXPoint *)p
  546. /*
  547.  * Returns 0 if point is in bounds, and Graphic isOpaque, and no knobHit.
  548.  * Returns -1 if outside bounds or not opaque or not active.
  549.  * Returns corner number if there is a hit on a corner.
  550.  * We have to be careful when the bounds are off an odd size since the
  551.  * knobs on the sides are one pixel larger.
  552.  */
  553. {
  554.     NXRect eb;
  555.     NXRect knob;
  556.     NXCoord dx, dy;
  557.     BOOL oddx, oddy;
  558.     int cornerMask = [self cornerMask];
  559.  
  560.     [self getExtendedBounds:&eb];
  561.  
  562.     if (!gFlags.active) {
  563.     return -1;
  564.     } else if (!gFlags.selected) {
  565.         return (NXMouseInRect(p, &bounds, NO) && [self isOpaque]) ? 0 : -1;
  566.     } else {
  567.         if (!NXMouseInRect(p, &eb, NO)) return -1;
  568.     }
  569.  
  570.     knob = bounds;
  571.     dx = knobRU4e.width / 2.0;
  572.     dy = knob.size.height / 2.0;
  573.     oddx = (floor(dx) != dx);
  574.     oddy = (floor(dy) != dy);
  575.     knob.size.width = KNOB_WIDTH;
  576.     knob.size.height = KNOB_HEIGHT;
  577.     knob.origin.x -= ((KNOB_WIDTH - 1.0) / 2.0);
  578.     knob.origin.y -= ((KNOB_HEIGHT - 1.0) / 2.0);
  579.  
  580.     if ((cornerMask & LOWER_LEFT_MASK) && NXMouseInRect(p, &knob, NO))
  581.     return(LOWER_LEFT);
  582.     knob.origin.y += dy;
  583.     if (oddy) knob.origin.y -= 0.5;
  584.     if ((cornerMask & LEFT_SIDE_MASK) && NXMouseInRect(p, &knob, NO))
  585.     return(LEFT_SIDE);
  586.     knob.origin.y += dy;
  587.     if (oddy) knob.origin.y += 0.5;
  588.     if ((cornerMask & UPPER_LEFT_MASK) && NXMouseInRect(p, &knob, NO))
  589.     return(UPPER_LEFT);
  590.     knob.origin.x += dx;
  591.     if (oddx) knob.origin.x -= 0.5;
  592.     if ((cornerMask & TOP_SIDE_MASK) && NXMouseInRect(p, &knob, NO))
  593.     return(TOP_SIDE);
  594.     knob.origin.x += dx;
  595.     if (oddx) knob.origin.x += 0.5;
  596.     if ((cornerMask & UPPER_RIGHT_MASK) && NXMouseInRect(p, &knob, NO))
  597.     return(UPPER_RIGHT);
  598.     knob.origin.y -= dy;
  599.     if (oddy) knob.origin.y -= 0.5;
  600.     if ((cornerMask & RIGHT_SIDE_MASK) && NXMouseInRect(p, &knob, NO))
  601.     return(RIGHT_SIDE);
  602.     knob.origin.y -= dy;
  603.     if (oddy) knob.origin.y += 0.5;
  604.     if ((cornerMask & LOWER_RIGHT_MASK) && NXMouseInRect(p, &knob, NO))
  605.     return(LOWER_RIGHT);
  606.     knob.origin.x -= dx;
  607.     if (oddx) knob.origin.x += 0.5;
  608.     if ((cornerMask & BOTTOM_SIDE_MASK) && NXMouseInRect(p, &knob, NO))
  609.     return(BOTTOM_SIDE);
  610.  
  611.     return NXMouseInRect(p, &bounds, NO) ? ([self isOpaque] ? 0 : -1) : -1;
  612. }
  613.  
  614. /* This method is analogous to display (not drawSelf::) in View. */
  615.  
  616. - draw:(const NXRect *)rect
  617. /*
  618.  * Draws the graphic inside rect.  If rect is NULL, then it draws the
  619.  * entire Graphic.  If the Graphic is not intersected by rect, then it
  620.  * is not drawn at all.  If the Graphic is selected, it is drawn with
  621.  * its knobbies.  This method is not intended to be overridden.  It
  622.  * calls the overrideable method "draw" which doesn't have to worry
  623.  * about drawing the knobbies.
  624.  *
  625.  * Note the showFastKnobFills optimization here.  If this Graphic is
  626.  * opaque then there is a possibility that it might obscure knobbies
  627.  * of Graphics underneath it, so we must emit the cached rectfills
  628.  * before drawing this Graphic.
  629.  */
  630. {
  631.     NXRect r;
  632.  
  633.     [self getExtendedBounds:&r];
  634.     if (gFlags.active && (!rect || NXIntersectsRect(recRU5r))) {
  635.     if ([self isOpaque]) [Graphic showFastKnobFills];
  636.     [self setGraphicsState];    /* does a gsave */
  637.     [self draw];
  638.     PSgrestore();            /* so we need a grestore here */
  639.     if (NXDrawingStatus == NX_DRAWING) {
  640.         if (gFlags.selected) {
  641.         r.origin.x = floor(bounds.origin.x);
  642.         r.origin.y = floor(bounds.origin.y);
  643.         r.size.width = floor(bounds.origin.x + bounds.size.width + 0.99) - r.origin.x;
  644.         r.size.height = floor(bounds.origin.y + bounds.size.height + 0.99) - r.origin.y;
  645.         r.origin.x += 1.0;
  646.         r.origin.y -= 1.0;
  647.         drawKnobs(&r, [self cornerMask], YES);        /* shadows */
  648.         r.origin.x = floor(bounds.origin.x);
  649.         r.origin.y = floor(bounds.origin.y);
  650.         r.size.width = floor(bounds.origin.x + bounds.size.width + 0.99) - r.origin.x;
  651.         r.size.height = floor(bounds.origin.y + bounds.size.height + 0.99) - r.origin.y;
  652.         drawKnobs(&r, [self cornerMask], NO);    /* knobs */
  653.         }
  654.     }
  655.     return self;
  656.     }
  657.  
  658.     return nil;
  659. }
  660.  
  661. /*
  662.  * Returns whether this Graphic can emit, all by itself, fully
  663.  * encapsulated PostScript (or fully conforming TIFF) representing
  664.  * itself.  This is an optimization for copy/paste.
  665.  */
  666.  
  667. - (BOOL)canEmitEPS
  668. {
  669.     return NO;
  670. }
  671.  
  672. - (BOOL)canEmitTIFF
  673. {
  674.     return NO;
  675. }
  676.  
  677. /* Sizing, aligning and moving. */
  678.  
  679. - moveLeftEdgeTo:(const NXCoord *)x
  680. {
  681.     bounds.origin.x = *x;
  682.     return self;
  683. }
  684.  
  685. - moveRightEdgeTo:(const NXCoord *)x
  686. {
  687.     bounds.origin.x = *x - bounds.size.width;
  688.     return self;
  689. }
  690.  
  691. - moveTopEdgeTo:(const NXCoord *)y
  692. {
  693.     bounds.origin.y = *y - bounds.size.height;
  694.     return self;
  695. }
  696.  
  697. - moveBottomEdgeTo:(const NXCoord *)y
  698. {
  699.     bounds.origin.y = *y;
  700.     return self;
  701. }
  702.  
  703. - moveHorizontalCenterTo:(const NXCoord *)x
  704. {
  705.     bounds.origin.x = *x - floor(bounds.size.width / 2.0);
  706.     return self;
  707. }
  708.  
  709. - moveVerticalCenterTo:(const NXCoord *)y
  710. {
  711.     bounds.origin.y = *y - floor(bounds.size.height / 2.0);
  712.     return self;
  713. }
  714.  
  715. - (NXCoord)baseline
  716. {
  717.     return 0.0;
  718. }
  719.  
  720. - moveBaselineTo:(const NXCoord *)y
  721. {
  722.     return self;
  723. }
  724.  
  725. - moveBy:(const NXPoint *)offset
  726. {
  727.     bounds.origin.x += floor(offset->x);
  728.     bounds.origin.y += floor(offset->y);
  729.     return self;
  730. }
  731.  
  732. - moveTo:(const NXPoint *)p
  733. {
  734.     bounds.origin.x = floor(p->x);
  735.     bounds.origin.y = floor(p->y);
  736.     return self;
  737. }
  738.  
  739. - centerAt:(const NXPoint *)p
  740. {
  741.     bounds.origin.x = floor(p->x - bounds.size.width / 2.0);
  742.     bounds.origin.y = floor(p->y - bounds.size.heRU6 / 2.0);
  743.     return self;
  744. }
  745.  
  746. - sizeTo:(const NXSize *)size
  747. {
  748.     bounds.size.width = floor(size->width);
  749.     bounds.size.height = floor(size->height);
  750.     return self;
  751. }
  752.  
  753. - sizeToNaturalAspectRatio
  754. {
  755.     return [self constrainCorner:UPPER_RIGHT toAspectRatio:[self naturalAspectRatio]];
  756. }
  757.  
  758. - sizeToGrid:(GraphicView *)graphicView
  759. {
  760.     NXPoint p;
  761.  
  762.     [graphicView grid:&bounds.origin];
  763.     p.x = bounds.origin.x + bounds.size.width;
  764.     p.y = bounds.origin.y + bounds.size.height;
  765.     [graphicView grid:&p];
  766.     bounds.size.width = p.x - bounds.origin.x;
  767.     bounds.size.height = p.y - bounds.origin.y;
  768.  
  769.     return self;
  770. }
  771.  
  772. - alignToGrid:(GraphicView *)graphicView
  773. {
  774.     [graphicView grid:&bounds.origin];
  775.     return self;
  776. }
  777.  
  778. /* Compatibility method for old PSGraphic and Tiff classes. */
  779.  
  780. - replaceWithImage
  781. {
  782.     return self;
  783. }
  784.  
  785. /* Public routines. */
  786.  
  787. - setLineWidth:(const float *)value
  788. /*
  789.  * This is called with value indirected so that it can be called via
  790.  * a perform:with: method.  Kind of screwy, but ...
  791.  */
  792. {
  793.     if (value) linewidth = *value;
  794.     return self;
  795. }
  796.  
  797. - (float)lineWidth
  798. {
  799.     return linewidth;
  800. }
  801.  
  802. - setLineColor:(const NXColor *)color
  803. {
  804.     if (color) {
  805.     if (NXEqualColor(*color, NX_COLORBLACK)) {
  806.         NX_FREE(lineColor);
  807.         lineColor = NULL;
  808.         gFlags.nooutline = NO;
  809.     } else {
  810.         if (!lineColor) NX_ZONEMALLOC([self zone], lineColor, NXColor, 1);
  811.         *lineColor = *color;
  812.         gFlags.nooutline = NO;
  813.     }
  814.     }
  815.     return self;
  816. }
  817.  
  818. - (NXColor)lineColor
  819. {
  820.     return lineColor ? *lineColor : NX_COLORBLACK;
  821. }
  822.  
  823. - setFillColor:(const NXColor *)color
  824. {
  825.     if (color) {
  826.     if (!fillColor) NX_ZONEMALLOC([self zone], fillColor, NXColor, 1);
  827.     *fillColor = *color;
  828.     if (![self fill]) [self setFill:FILL_NZWR];
  829.     }
  830.     return self;
  831. }
  832.  
  833. - (NXColor)fillColor
  834. {
  835.     return fillColor ? *fillColor : NX_COLORWHITE;
  836. }
  837.  
  838. - (Graphic *)colorAcceptorAt:(const NXPoint *)point
  839. /*
  840.  * This method supports dragging and dropping colors on Graphics.
  841.  * Whatever object is returned from this may well be sent
  842.  * setFillColor: if the color actually gets dropped on it.
  843.  * See gvDrag.m's acceptsColor:atPoint: method.
  844.  */
  845. {
  846.     return nil;
  847. }
  848.  
  849. - changeFont:sender
  850. {
  851.     return self;
  852. }
  853.  
  854. - font
  855. {
  856.     return nil;
  857. }
  858.  
  859. - setGray:(const float *)value
  860. /*
  861.  * This is called with value indirected so that it can be called via
  862.  * a perform:with: method. RU7d of screwy, but ...
  863.  * Now that we have converted to using NXColor's, we'll interpret this
  864.  * method as a request to set the lineColor.
  865.  */
  866. {
  867.     NXColor color;
  868.  
  869.     if (value) {
  870.     color = NXConvertGrayToColor(*value);
  871.     [self setLineColor:&color];
  872.     }
  873.  
  874.     return self;
  875. }
  876.  
  877. - (float)gray
  878. {
  879.     float retval;
  880.  
  881.     if (lineColor) {
  882.     NXConvertColorToGray(*lineColor, &retval);
  883.     } else {
  884.     retval = NX_BLACK;
  885.     }
  886.  
  887.     return retval;
  888. }
  889.  
  890. - setFill:(int)mode
  891. {
  892.     switch (mode) {
  893.     case FILL_NONE:    gFlags.eofill = gFlags.fill = NO; break;
  894.     case FILL_EO:    gFlags.eofill = YES; gFlags.fill = NO; break;
  895.     case FILL_NZWR:    gFlags.eofill = NO; gFlags.fill = YES; break;
  896.     }
  897.     return self;
  898. }
  899.  
  900. - (int)fill
  901. {
  902.     if (gFlags.eofill) {
  903.     return FILL_EO;
  904.     } else if (gFlags.fill) {
  905.     return FILL_NZWR;
  906.     } else {
  907.     return FILL_NONE;
  908.     }
  909. }
  910.  
  911. - setOutlined:(BOOL)outlinedFlag
  912. {
  913.     gFlags.nooutline = outlinedFlag ? NO : YES;
  914.     return self;
  915. }
  916.  
  917. - (BOOL)isOutlined
  918. {
  919.     return gFlags.nooutline ? NO : YES;
  920. }
  921.  
  922. - setLineCap:(int)capValue
  923. {
  924.     if (capValue >= 0 && capValue <= 2) {
  925.     gFlags.linecap = capValue;
  926.     }
  927.     return self;
  928. }
  929.  
  930. - (int)lineCap
  931. {
  932.     return gFlags.linecap;
  933. }
  934.  
  935. - setLineArrow:(int)arrowValue
  936. {
  937.     if (arrowValue >= 0 && arrowValue <= 3) {
  938.     gFlags.arrow = arrowValue;
  939.     }
  940.     return self;
  941. }
  942.  
  943. - (int)lineArrow
  944. {
  945.     return gFlags.arrow;
  946. }
  947.  
  948. - setLineJoin:(int)joinValue
  949. {
  950.     if (joinValue >= 0 && joinValue <= 2) {
  951.     gFlags.linejoin = joinValue;
  952.     }
  953.     return self;
  954. }
  955.  
  956. - (int)lineJoin
  957. {
  958.     return gFlags.linejoin;
  959. }
  960.  
  961. /* Archiver-related methods. */
  962.  
  963. - write:(NXTypedStream *)stream
  964. /*
  965.  * Since a typical document has many Graphics, we want to try and make
  966.  * the archived document small, so we don't write out the linewidth and
  967.  * gray values if they are the most common 0 and NX_BLACK.  To accomplish
  968.  * this, we note that we haven't written them out by setting the
  969.  * bits in gFlags.
  970.  */
  971. {
  972.     [super write:stream];
  973.     gFlags.linewidthSet = (linewidth != 0.0);
  974.     gFlags.lineColorSet = lineColor ? YES : NO;
  975.     gFlags.fillColorSet = fillColor ? YES : NO;
  976.     NXWriteTypes(stream, "ffffii", &bounds.origin.x, &bounds.origin.y,
  977.     &bounds.size.width, &bounds.size.height, &gFlags, &identifier);
  978.     if (gFlags.linewidthSet) NXWriteTypes(stream, "f", &linewidth);
  979.     if (gFlags.lineColorSet) NXWriteColor(stream, *lineColor);
  980.     ifRU8lags.fillColorSet) NXWriteColor(stream, *fillColor);
  981.     return self;
  982. }
  983.  
  984. - read:(NXTypedStream *)stream
  985. {
  986.     int version;
  987.     float gray = NX_BLACK;
  988.  
  989.     [super read:stream];
  990.     version = NXTypedStreamClassVersion(stream, "Graphic");
  991.     if (version > 2) {
  992.     NXReadTypes(stream, "ffffii", &bounds.origin.x, &bounds.origin.y,
  993.         &bounds.size.width, &bounds.size.height, &gFlags, &identifier);
  994.     } else if (version > 1) {
  995.     NXReadTypes(stream, "ffffsi", &bounds.origin.x, &bounds.origin.y,
  996.         &bounds.size.width, &bounds.size.height, &gFlags, &identifier);
  997.     } else {
  998.     NXReadTypes(stream, "ffffs", &bounds.origin.x, &bounds.origin.y,
  999.         &bounds.size.width, &bounds.size.height, &gFlags);
  1000.     identifier = currentGraphicIdentifier++;
  1001.     }
  1002.     if (version > 1 && identifier >= currentGraphicIdentifier) currentGraphicIdentifier = identifier+1;
  1003.     if (gFlags.linewidthSet) NXReadTypes(stream, "f", &linewidth);
  1004.     if (version < 1) {
  1005.     if (gFlags.lineColorSet) NXReadTypes(stream, "f", &gray);
  1006.     if (gFlags.fillColorSet && (gFlags.eofill | gFlags.fill)) {
  1007.         NX_ZONEMALLOC([self zone], lineColor, NXColor, 1);
  1008.         *lineColor = NXConvertGrayToColor(NX_BLACK);
  1009.         NX_ZONEMALLOC([self zone], fillColor, NXColor, 1);
  1010.         *fillColor = NXConvertGrayToColor(gray);
  1011.     } else if (gFlags.eofill | gFlags.fill) {
  1012.         NX_ZONEMALLOC([self zone], fillColor, NXColor, 1);
  1013.         *fillColor = NXConvertGrayToColor(gray);
  1014.         [self setOutlined:NO];
  1015.     } else {
  1016.         NX_ZONEMALLOC([self zone], lineColor, NXColor, 1);
  1017.         *lineColor = NXConvertGrayToColor(gray);
  1018.     }
  1019.     } else {
  1020.     if (gFlags.lineColorSet) {
  1021.         NX_ZONEMALLOC([self zone], lineColor, NXColor, 1);
  1022.         *lineColor = NXReadColor(stream);
  1023.         if (NXEqualColor(*lineColor, NX_COLORCLEAR)) {
  1024.         free(lineColor);
  1025.         lineColor = NULL;
  1026.         [self setOutlined:NO];
  1027.         }
  1028.     }
  1029.     if (gFlags.fillColorSet) {
  1030.         NX_ZONEMALLOC([self zone], fillColor, NXColor, 1);
  1031.         *fillColor = NXReadColor(stream);
  1032.         if (NXEqualColor(*fillColor, NX_COLORCLEAR) || (NXAlphaComponent(*fillColor) == 0.0)) {
  1033.         free(fillColor);
  1034.         fillColor = NULL;
  1035.         [self setFill:FILL_NONE];
  1036. //        } else if (!gFlags.eofill && !gFlags.fill) {    // why did I add this code here?
  1037. //        gFlags.fill = YES;
  1038.         }
  1039.     }
  1040.     }
  1041.  
  1042.     return self;
  1043. }
  1044.  
  1045. /* Routines which may need subclassing for different Graphic types. */
  1046.  
  1047. - (BOOL)constrainByDefault
  1048. {
  1049.    RU9urn NO;
  1050. }
  1051.  
  1052. - constrainCorner:(int)corner toAspectRatio:(float)aspect
  1053. /*
  1054.  * Modifies the bounds rectangle by moving the specified corner so that
  1055.  * the Graphic maintains the specified aspect ratio.  This is used during
  1056.  * constrained resizing.  Can be overridden if the aspect ratio is not
  1057.  * sufficient to constrain resizing.
  1058.  */
  1059. {
  1060.     int newcorner;
  1061.     float actualAspect;
  1062.  
  1063.     if (!bounds.size.height || !bounds.size.width || !aspect) return self;
  1064.     actualAspect = bounds.size.width / bounds.size.height;
  1065.     if (actualAspect == aspect) return self;
  1066.  
  1067.     switch (corner) {
  1068.     case LEFT_SIDE:
  1069.     bounds.origin.x -= bounds.size.height * aspect-bounds.size.width;
  1070.     case RIGHT_SIDE:
  1071.     bounds.size.width = bounds.size.height * aspect;
  1072.     if (bounds.size.width) NXIntegralRect(&bounds);
  1073.     return self;
  1074.     case BOTTOM_SIDE:
  1075.     bounds.origin.y -= bounds.size.width / aspect-bounds.size.height;
  1076.     case TOP_SIDE:
  1077.     bounds.size.height = bounds.size.width / aspect;
  1078.     if (bounds.size.height) NXIntegralRect(&bounds);
  1079.     return self;
  1080.     case LOWER_LEFT:
  1081.     corner = 0;
  1082.     case 0:
  1083.     case UPPER_RIGHT:
  1084.     case UPPER_LEFT:
  1085.     case LOWER_RIGHT:
  1086.     if (actualAspect > aspect) {
  1087.         newcorner = ((corner|KNOB_DY_ONCE)&(~(KNOB_DY_TWICE)));
  1088.     } else {
  1089.         newcorner = ((corner|KNOB_DX_ONCE)&(~(KNOB_DX_TWICE)));
  1090.     }
  1091.     return [self constrainCorner:newcorner toAspectRatio:aspect];
  1092.     default:
  1093.     return self;
  1094.     }
  1095. }
  1096.  
  1097. #define RESIZE_MASK (NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK|NX_TIMERMASK)
  1098.  
  1099. - resize:(NXEvent *)event by:(int)corner in:(GraphicView *)view
  1100. /*
  1101.  * Resizes the graphic by the specified corner.  If corner == CREATE,
  1102.  * then it is resized by the UPPER_RIGHT corner, but the initial size
  1103.  * is reset to 1 by 1.
  1104.  */
  1105. {
  1106.     NXPoint p, last;
  1107.     float aspect = 0.0;
  1108.     Window *window = [view window];
  1109.     BOOL constrain, canScroll;
  1110.     DrawStatusType oldDrawStatus;
  1111.     NXTrackingTimer *timer = NULL;
  1112.     NXRect eb, starteb, oldeb, visibleRect;
  1113.  
  1114.     if (!gFlags.active || !gFlags.selected || !corner) return self;
  1115.  
  1116.     constrain = ((event->flags & NX_ALTERNATEMASK) &&
  1117.     ((bounds.size.width && bounds.size.height) || corner == CREATE));
  1118.     if ([self constrainByDefault]) constrain = !constrain;
  1119.     if (constrain) aspect = bounds.size.width / bounds.size.height;
  1120.     if (corner == CREATE) {
  1121.     bounds.size.width = bounds.size.height = 1.0;
  1122.     corner = UPPER_RIGHT;
  1123.     RU@   gFlags.selected = NO;
  1124.  
  1125.     [self getExtendedBounds:&eb];
  1126.     [view lockFocus];
  1127.     gFlags.active = NO;
  1128.     [view cache:&eb andUpdateLinks:NO];
  1129.     gFlags.active = YES;
  1130.     starteb = eb;
  1131.     [self draw:NULL];
  1132.     [window flushWindow];
  1133.  
  1134.     oldDrawStatus = DrawStatus;
  1135.     DrawStatus = Resizing;
  1136.  
  1137.     [view getVisibleRect:&visibleRect];
  1138.     canScroll = !NXEqualRect(&visibleRect, &bounds);
  1139.     if (canScroll && !timer) timer = NXBeginTimer(NULL, 0.1, 0.1);
  1140.  
  1141.     while (event->type != NX_MOUSEUP) {
  1142.     p = event->location;
  1143.     event = [NXApp getNextEvent:RESIZE_MASK];
  1144.     if (event->type == NX_TIMER) event->location = p;
  1145.     p = event->location;
  1146.     [view convertPoint:&p fromView:nil];
  1147.     [view grid:&p];
  1148.     if (p.x != last.x || p.y != last.y) {
  1149.         corner = [self moveCorner:corner to:&p constrain:constrain];
  1150.         if (constrain) [self constrainCorner:corner toAspectRatio:aspect];
  1151.         oldeb = eb;
  1152.         [self getExtendedBounds:&eb];
  1153.         [window disableFlushWindow];
  1154.         [view drawSelf:&oldeb :1];
  1155.         if (canScroll) {
  1156.         [view scrollPointToVisible:&p]; // actually we want to keep the "edges" of the
  1157.                         // Graphic being resized that were visible when
  1158.                         // the resize started visible throughout the
  1159.                         // resizing time (this will be difficult if those
  1160.                         // edges flip from being the left edge to the
  1161.                         // right edge in the middle of the resize!).
  1162.         }
  1163.         [self draw:NULL];
  1164.         [view tryToPerform:@selector(updateRulers:) with:(void *)&bounds];
  1165.         [window reenableFlushWindow];
  1166.         [window flushWindow];
  1167.         last = p;
  1168.         NXPing();
  1169.     }
  1170.     }
  1171.  
  1172.     if (canScroll && timer) {
  1173.     NXEndTimer(timer);
  1174.     timer = NULL;
  1175.     }
  1176.  
  1177.     gFlags.selected = YES;
  1178.     DrawStatus = oldDrawStatus;
  1179.  
  1180.     [view cache:&eb andUpdateLinks:NO];    // redraw after resizing a Graphic
  1181.     NXUnionRect(&eb, &starteb);
  1182.     [view updateTrackedLinks:&starteb];
  1183.     [view tryToPerform:@selector(updateRulers:) with:nil];
  1184.     [window flushWindow];
  1185.     [view unlockFocus];
  1186.  
  1187.     return self;
  1188. }
  1189.  
  1190. - (BOOL)create:(NXEvent *)event in:(GraphicView *)view
  1191. /*
  1192.  * This method rarely needs to be subclassed.
  1193.  * It sets up an initial bounds, and calls resize:by:in:.
  1194.  */
  1195. {
  1196.     BOOL valid;
  1197.     NXCoord gridSpacing;
  1198.  
  1199.     bounds.origin = event->location;
  1200.     [view convertPoint:&bounds.origin fromView:nil];
  1201.     [view grid:&bounds.origin];
  1202.  
  1203.     gridSpacing = (NXCoord)[view gridSpacing];
  1204.  RUAounds.size.height = gridSpacing;
  1205.     bounds.size.width = gridSpacing * [self naturalAspectRatio];
  1206.  
  1207.     [self resize:event by:CREATE in:view];
  1208.  
  1209.     valid = [self isValid];
  1210.  
  1211.     if (valid) {
  1212.     gFlags.selected = YES;
  1213.     gFlags.active = YES;
  1214.     } else {
  1215.     gFlags.selected = NO;
  1216.     gFlags.active = NO;
  1217.     [view display];
  1218.     }
  1219.  
  1220.     return valid;
  1221. }
  1222.  
  1223. - (BOOL)hit:(const NXPoint *)p
  1224. {
  1225.     return (!gFlags.locked && gFlags.active && NXMouseInRect(p, &bounds, NO));
  1226. }
  1227.  
  1228. - (BOOL)isOpaque
  1229. {
  1230.     return [self fill] ? YES : NO;
  1231. }
  1232.  
  1233. - (BOOL)isValid
  1234. /*
  1235.  * Called after a Graphic is created to see if it is valid (this usually
  1236.  * means "is it big enough?").
  1237.  */
  1238. {
  1239.     return (bounds.size.width > MINSIZE && bounds.size.height > MINSIZE);
  1240. }
  1241.  
  1242. - (float)naturalAspectRatio
  1243. /*
  1244.  * A natural aspect ratio of zero means it doesn't have a natural aspect ratio.
  1245.  */
  1246. {
  1247.     return 0.0;
  1248. }
  1249.  
  1250. - (int)moveCorner:(int)corner to:(const NXPoint *)p constrain:(BOOL)flag
  1251. /*
  1252.  * Moves the specified corner to the specified point.
  1253.  * Returns the position of the corner after it was moved.
  1254.  */
  1255. {
  1256.     int newcorner = corner;
  1257.  
  1258.     if ((corner & KNOB_DX_ONCE) && (corner & KNOB_DX_TWICE)) {
  1259.     bounds.size.width += p->x - (bounds.origin.x + bounds.size.width);
  1260.     if (bounds.size.width <= 0.0) {
  1261.         newcorner &= ~ (KNOB_DX_ONCE | KNOB_DX_TWICE);
  1262.         bounds.origin.x += bounds.size.width;
  1263.         bounds.size.width = - bounds.size.width;
  1264.     }
  1265.     } else if (!(corner & KNOB_DX_ONCE)) {
  1266.     bounds.size.width += bounds.origin.x - p->x;
  1267.     bounds.origin.x = p->x;
  1268.     if (bounds.size.width <= 0.0) {
  1269.         newcorner |= KNOB_DX_ONCE | KNOB_DX_TWICE;
  1270.         bounds.origin.x += bounds.size.width;
  1271.         bounds.size.width = - bounds.size.width;
  1272.     }
  1273.     }
  1274.  
  1275.     if ((corner & KNOB_DY_ONCE) && (corner & KNOB_DY_TWICE)) {
  1276.     bounds.size.height += p->y - (bounds.origin.y + bounds.size.height);
  1277.     if (bounds.size.height <= 0.0) {
  1278.         newcorner &= ~ (KNOB_DY_ONCE | KNOB_DY_TWICE);
  1279.         bounds.origin.y += bounds.size.height;
  1280.         bounds.size.height = - bounds.size.height;
  1281.     }
  1282.     } else if (!(corner & KNOB_DY_ONCE)) {
  1283.     bounds.size.height += bounds.origin.y - p->y;
  1284.     bounds.origin.y = p->y;
  1285.     if (bounds.size.height <= 0.0) {
  1286.         newcorner |= KNOB_DY_ONCE | KNOB_DY_TWICE;
  1287.         bounds.origin.y += bounds.size.height;
  1288.         bounds.size.height = - bounds.size.height;
  1289.     }
  1290.     }
  1291.  
  1292.     if (newcorner != LOWER_LEFT) newcorner &= 0xf;
  1293.     if (RUBcorner) newcorner = LOWER_LEFT;
  1294.  
  1295.     return newcorner;
  1296. }
  1297.  
  1298. - unitDraw
  1299. /*
  1300.  * If a Graphic just wants to draw itself in the bounding box of
  1301.  * {{0.0,0.0},{1.0,1.0}}, it can simply override this method.
  1302.  * Everything else will work fine.
  1303.  */
  1304. {
  1305.     return self;
  1306. }
  1307.  
  1308. - draw
  1309. /*
  1310.  * Almost all Graphics need to override this method.
  1311.  * It does the Graphic-specific drawing.
  1312.  * By default, it scales the coordinate system and calls unitDraw.
  1313.  */
  1314. {
  1315.     if (bounds.size.width >= 1.0 && bounds.size.height >= 1.0) {
  1316.     PStranslate(bounds.origin.x, bounds.origin.y);
  1317.     PSscale(bounds.size.width, bounds.size.height);
  1318.     [self unitDraw];
  1319.     }
  1320.     return self;
  1321. }
  1322.  
  1323. - (BOOL)edit:(NXEvent *)event in:(View *)view
  1324. /*
  1325.  * Any Graphic which has editable text should override this method
  1326.  * to edit that text.  TextGraphic is an example.
  1327.  */
  1328. {
  1329.     return NO;
  1330. }
  1331.  
  1332. @end
  1333.