home *** CD-ROM | disk | FTP | other *** search
/ NeXTSTEP 3.3 (Developer) / NeXT_Developer-3.3.iso / NextDeveloper / Examples / AppKit / Draw / Graphic.m < prev    next >
Encoding:
Text File  |  1994-05-14  |  33.4 KB  |  1,370 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.     dkgrayRectList[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 = currentGraphicIdentifier++;
  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.rtf and 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. - readLinkFromPasteboard:(Pasteboard *)pboard usingManager:(NXDataLinkManager *)linkManager useNewIdentifier:(BOOL)useNewIdentifier
  312. /*
  313.  * This is called by pasteFromPasteboard: when we paste a Graphic (i.e. copied/pasted from
  314.  * another Draw document) in case that Graphic was linked to something when it was copied.
  315.  * Since we called writeLinksToPasteboard: when we put the Graphic into the pasteboard (see
  316.  * writeLinkToPasteboard:types: above) we can simply retrieve all the link information for
  317.  * that graphic by using the linkManager method addLinkPreviouslyAt:fromPasteboard:at:.
  318.  */
  319. {
  320.     NXDataLink *link;
  321.     NXSelection *oldSelection, *newSelection;
  322.  
  323.     oldSelection = [self selection];
  324.     if (linkManager && oldSelection) {
  325.     if (useNewIdentifier) [self resetIdentifier];
  326.     newSelection = [self selection];
  327.     link = [linkManager addLinkPreviouslyAt:oldSelection
  328.                      fromPasteboard:pboard
  329.                          at:newSelection];
  330.     if (!link) [newSelection free];
  331.     [self setLink:link];
  332.     }
  333.     if (useNewIdentifier) [oldSelection free];
  334.  
  335.     return self;
  336. }
  337.  
  338. /* Notification messages */
  339.  
  340. /*
  341.  * These methods are sent when a Graphic is added to or removed
  342.  * from a GraphicView (respectively).  Currently we only use them
  343.  * to break and reestablish links if any.
  344.  */
  345.  
  346. - wasAddedTo:(GraphicView *)sender
  347. {
  348.     NXDataLink *link;
  349.     NXDataLinkManager *linkManager;
  350.  
  351.     if ((linkManager = [sender linkManager]) && (link = [self link])) {
  352.     if ([link disposition] == NX_LinkBroken) {
  353.         [linkManager addLink:link at:[self selection]];
  354.     }
  355.     }
  356.  
  357.     return self;
  358. }
  359.  
  360. - wasRemovedFrom:(GraphicView *)sender
  361. {
  362.     [[self link] break];
  363.     return self;
  364. }
  365.  
  366. /* Methods for uniquely identifying a Graphic. */
  367.  
  368. - resetIdentifier
  369. {
  370.     identifier = currentGraphicIdentifier++;
  371.     return self;
  372. }
  373.  
  374. - writeIdentifierTo:(char *)buffer
  375. /*
  376.  * This method is necessary to support a Group which never writes out
  377.  * its own identifier, but, instead has its components each write out
  378.  * their own identifier.
  379.  */
  380. {
  381.     sprintf(buffer, "%d", identifier);
  382.     return self;
  383. }
  384.  
  385. - (int)identifier
  386. {
  387.     return identifier;
  388. }
  389.  
  390. - (Graphic *)graphicIdentifiedBy:(int)anIdentifier
  391. {
  392.     return (identifier == anIdentifier) ? self : nil;
  393. }
  394.  
  395. /* Event handling */
  396.  
  397. - (BOOL)handleEvent:(NXEvent *)event at:(const NXPoint *)p inView:(View *)view
  398. /*
  399.  * Currently the only Graphic's that handle events are Image Graphic's that
  400.  * are linked to something else (they follow the link on double-click and
  401.  * the track the mouse for link buttons, for example).  This method should
  402.  * return YES only if it tracked the mouse until it went up.
  403.  */
  404. {
  405.     return NO;
  406. }
  407.  
  408. /* Number of Graphics this Graphic represents (always 1 for non-Group). */
  409.  
  410. - (int)graphicCount
  411. /*
  412.  * This is really only here to support Groups.  It is used by the
  413.  * Object Link stuff purely to know how much space will be needed to
  414.  * create an NXSelection with the unique identifiers of all the objects
  415.  * in the selection.
  416.  */
  417. {
  418.     return 1;
  419. }
  420.  
  421. /* Public routines mostly called by GraphicView's. */
  422.  
  423. - (const char *)title
  424. {
  425.     return NXLoadLocalStringFromTableInBundle(NULL, nil, [self name], NULL);
  426. }
  427.  
  428. - (BOOL)isSelected
  429. {
  430.     return gFlags.selected;
  431. }
  432.  
  433. - (BOOL)isActive
  434. {
  435.     return gFlags.active;
  436. }
  437.  
  438. - (BOOL)isLocked
  439. {
  440.     return gFlags.locked;
  441. }
  442.  
  443. - select
  444. {
  445.     gFlags.selected = YES;
  446.     return self;
  447. }
  448.  
  449. - deselect
  450. {
  451.     gFlags.selected = NO;
  452.     return self;
  453. }
  454.  
  455. - activate
  456. /*
  457.  * Activation is used to *temporarily* take a Graphic out of the GraphicView.
  458.  */
  459. {
  460.     gFlags.active = YES;
  461.     return self;
  462. }
  463.  
  464. - deactivate
  465. {
  466.     gFlags.active = NO;
  467.     return self;
  468. }
  469.  
  470. - lock
  471. /*
  472.  * A locked graphic cannot be selected, resized or moved.
  473.  */
  474. {
  475.     gFlags.locked = YES;
  476.     return self;
  477. }
  478.  
  479. - unlock
  480. {
  481.     gFlags.locked = NO;
  482.     return self;
  483. }
  484.  
  485. /* See TextGraphic for more info about form entries. */
  486.  
  487. - (BOOL)isFormEntry
  488. {
  489.     return NO;
  490. }
  491.  
  492. - setFormEntry:(int)flag
  493. {
  494.     return self;
  495. }
  496.  
  497. - (BOOL)hasFormEntries
  498. {
  499.     return NO;
  500. }
  501.  
  502. - (BOOL)writeFormEntryToStream:(NXStream *)stream
  503. {
  504.     return NO;
  505. }
  506.  
  507. /* See Group and Image for more info about cacheability. */
  508.  
  509. - setCacheable:(BOOL)flag
  510. {
  511.     return self;
  512. }
  513.  
  514. - (BOOL)isCacheable
  515. {
  516.     return YES;
  517. }
  518.  
  519. /* Getting and setting the bounds. */
  520.  
  521. - getBounds:(NXRect *)theRect
  522. {
  523.     *theRect = bounds;
  524.     return self;
  525. }
  526.  
  527. - setBounds:(const NXRect *)aRect
  528. {
  529.     bounds = *aRect;
  530.     return self;
  531. }
  532.  
  533. - (NXRect *)getExtendedBounds:(NXRect *)theRect
  534. /*
  535.  * Returns, by reference, the rectangle which encloses the Graphic
  536.  * AND ITS KNOBBIES and its increased line width (if appropriate).
  537.  */
  538. {
  539.     if (bounds.size.width < 0.0) {
  540.     theRect->origin.x = bounds.origin.x + bounds.size.width;
  541.     theRect->size.width = - bounds.size.width;
  542.     } else {
  543.     theRect->origin.x = bounds.origin.x;
  544.     theRect->size.width = bounds.size.width;
  545.     }
  546.     if (bounds.size.height < 0.0) {
  547.     theRect->origin.y = bounds.origin.y + bounds.size.height;
  548.     theRect->size.height = - bounds.size.height;
  549.     } else {
  550.     theRect->origin.y = bounds.origin.y;
  551.     theRect->size.height = bounds.size.height;
  552.     }
  553.  
  554.     theRect->size.width = MAX(1.0, theRect->size.width);
  555.     theRect->size.height = MAX(1.0, theRect->size.height);
  556.  
  557.     NXInsetRect(theRect, - ((KNOB_WIDTH - 1.0) + linewidth + 1.0),
  558.              - ((KNOB_HEIGHT - 1.0) + linewidth + 1.0));
  559.  
  560.     if (gFlags.arrow) {
  561.     if (linewidth) {
  562.         NXInsetRect(theRect, - linewidth * 2.5, - linewidth * 2.5);
  563.     } else {
  564.         NXInsetRect(theRect, - 13.0, - 13.0);
  565.     }
  566.     }
  567.  
  568.     NXIntegralRect(theRect);
  569.  
  570.     return theRect;
  571. }
  572.  
  573. - (int)knobHit:(const NXPoint *)p
  574. /*
  575.  * Returns 0 if point is in bounds, and Graphic isOpaque, and no knobHit.
  576.  * Returns -1 if outside bounds or not opaque or not active.
  577.  * Returns corner number if there is a hit on a corner.
  578.  * We have to be careful when the bounds are off an odd size since the
  579.  * knobs on the sides are one pixel larger.
  580.  */
  581. {
  582.     NXRect eb;
  583.     NXRect knob;
  584.     NXCoord dx, dy;
  585.     BOOL oddx, oddy;
  586.     int cornerMask = [self cornerMask];
  587.  
  588.     [self getExtendedBounds:&eb];
  589.  
  590.     if (!gFlags.active) {
  591.     return -1;
  592.     } else if (!gFlags.selected) {
  593.         return (NXMouseInRect(p, &bounds, NO) && [self isOpaque]) ? 0 : -1;
  594.     } else {
  595.         if (!NXMouseInRect(p, &eb, NO)) return -1;
  596.     }
  597.  
  598.     knob = bounds;
  599.     dx = knob.size.width / 2.0;
  600.     dy = knob.size.height / 2.0;
  601.     oddx = (floor(dx) != dx);
  602.     oddy = (floor(dy) != dy);
  603.     knob.size.width = KNOB_WIDTH;
  604.     knob.size.height = KNOB_HEIGHT;
  605.     knob.origin.x -= ((KNOB_WIDTH - 1.0) / 2.0);
  606.     knob.origin.y -= ((KNOB_HEIGHT - 1.0) / 2.0);
  607.  
  608.     if ((cornerMask & LOWER_LEFT_MASK) && NXMouseInRect(p, &knob, NO))
  609.     return(LOWER_LEFT);
  610.     knob.origin.y += dy;
  611.     if (oddy) knob.origin.y -= 0.5;
  612.     if ((cornerMask & LEFT_SIDE_MASK) && NXMouseInRect(p, &knob, NO))
  613.     return(LEFT_SIDE);
  614.     knob.origin.y += dy;
  615.     if (oddy) knob.origin.y += 0.5;
  616.     if ((cornerMask & UPPER_LEFT_MASK) && NXMouseInRect(p, &knob, NO))
  617.     return(UPPER_LEFT);
  618.     knob.origin.x += dx;
  619.     if (oddx) knob.origin.x -= 0.5;
  620.     if ((cornerMask & TOP_SIDE_MASK) && NXMouseInRect(p, &knob, NO))
  621.     return(TOP_SIDE);
  622.     knob.origin.x += dx;
  623.     if (oddx) knob.origin.x += 0.5;
  624.     if ((cornerMask & UPPER_RIGHT_MASK) && NXMouseInRect(p, &knob, NO))
  625.     return(UPPER_RIGHT);
  626.     knob.origin.y -= dy;
  627.     if (oddy) knob.origin.y -= 0.5;
  628.     if ((cornerMask & RIGHT_SIDE_MASK) && NXMouseInRect(p, &knob, NO))
  629.     return(RIGHT_SIDE);
  630.     knob.origin.y -= dy;
  631.     if (oddy) knob.origin.y += 0.5;
  632.     if ((cornerMask & LOWER_RIGHT_MASK) && NXMouseInRect(p, &knob, NO))
  633.     return(LOWER_RIGHT);
  634.     knob.origin.x -= dx;
  635.     if (oddx) knob.origin.x += 0.5;
  636.     if ((cornerMask & BOTTOM_SIDE_MASK) && NXMouseInRect(p, &knob, NO))
  637.     return(BOTTOM_SIDE);
  638.  
  639.     return NXMouseInRect(p, &bounds, NO) ? ([self isOpaque] ? 0 : -1) : -1;
  640. }
  641.  
  642. /* This method is analogous to display (not drawSelf::) in View. */
  643.  
  644. - draw:(const NXRect *)rect
  645. /*
  646.  * Draws the graphic inside rect.  If rect is NULL, then it draws the
  647.  * entire Graphic.  If the Graphic is not intersected by rect, then it
  648.  * is not drawn at all.  If the Graphic is selected, it is drawn with
  649.  * its knobbies.  This method is not intended to be overridden.  It
  650.  * calls the overrideable method "draw" which doesn't have to worry
  651.  * about drawing the knobbies.
  652.  *
  653.  * Note the showFastKnobFills optimization here.  If this Graphic is
  654.  * opaque then there is a possibility that it might obscure knobbies
  655.  * of Graphics underneath it, so we must emit the cached rectfills
  656.  * before drawing this Graphic.
  657.  */
  658. {
  659.     NXRect r;
  660.  
  661.     [self getExtendedBounds:&r];
  662.     if (gFlags.active && (!rect || NXIntersectsRect(rect, &r))) {
  663.     if ([self isOpaque]) [Graphic showFastKnobFills];
  664.     [self setGraphicsState];    /* does a gsave */
  665.     [self draw];
  666.     PSgrestore();            /* so we need a grestore here */
  667.     if (NXDrawingStatus == NX_DRAWING) {
  668.         if (gFlags.selected) {
  669.         r.origin.x = floor(bounds.origin.x);
  670.         r.origin.y = floor(bounds.origin.y);
  671.         r.size.width = floor(bounds.origin.x + bounds.size.width + 0.99) - r.origin.x;
  672.         r.size.height = floor(bounds.origin.y + bounds.size.height + 0.99) - r.origin.y;
  673.         r.origin.x += 1.0;
  674.         r.origin.y -= 1.0;
  675.         drawKnobs(&r, [self cornerMask], YES);        /* shadows */
  676.         r.origin.x = floor(bounds.origin.x);
  677.         r.origin.y = floor(bounds.origin.y);
  678.         r.size.width = floor(bounds.origin.x + bounds.size.width + 0.99) - r.origin.x;
  679.         r.size.height = floor(bounds.origin.y + bounds.size.height + 0.99) - r.origin.y;
  680.         drawKnobs(&r, [self cornerMask], NO);    /* knobs */
  681.         }
  682.     }
  683.     return self;
  684.     }
  685.  
  686.     return nil;
  687. }
  688.  
  689. /*
  690.  * Returns whether this Graphic can emit, all by itself, fully
  691.  * encapsulated PostScript (or fully conforming TIFF) representing
  692.  * itself.  This is an optimization for copy/paste.
  693.  */
  694.  
  695. - (BOOL)canEmitEPS
  696. {
  697.     return NO;
  698. }
  699.  
  700. - (BOOL)canEmitTIFF
  701. {
  702.     return NO;
  703. }
  704.  
  705. /* Sizing, aligning and moving. */
  706.  
  707. - moveLeftEdgeTo:(const NXCoord *)x
  708. {
  709.     bounds.origin.x = *x;
  710.     return self;
  711. }
  712.  
  713. - moveRightEdgeTo:(const NXCoord *)x
  714. {
  715.     bounds.origin.x = *x - bounds.size.width;
  716.     return self;
  717. }
  718.  
  719. - moveTopEdgeTo:(const NXCoord *)y
  720. {
  721.     bounds.origin.y = *y - bounds.size.height;
  722.     return self;
  723. }
  724.  
  725. - moveBottomEdgeTo:(const NXCoord *)y
  726. {
  727.     bounds.origin.y = *y;
  728.     return self;
  729. }
  730.  
  731. - moveHorizontalCenterTo:(const NXCoord *)x
  732. {
  733.     bounds.origin.x = *x - floor(bounds.size.width / 2.0);
  734.     return self;
  735. }
  736.  
  737. - moveVerticalCenterTo:(const NXCoord *)y
  738. {
  739.     bounds.origin.y = *y - floor(bounds.size.height / 2.0);
  740.     return self;
  741. }
  742.  
  743. - (NXCoord)baseline
  744. {
  745.     return 0.0;
  746. }
  747.  
  748. - moveBaselineTo:(const NXCoord *)y
  749. {
  750.     return self;
  751. }
  752.  
  753. - moveBy:(const NXPoint *)offset
  754. {
  755.     bounds.origin.x += floor(offset->x);
  756.     bounds.origin.y += floor(offset->y);
  757.     return self;
  758. }
  759.  
  760. - moveTo:(const NXPoint *)p
  761. {
  762.     bounds.origin.x = floor(p->x);
  763.     bounds.origin.y = floor(p->y);
  764.     return self;
  765. }
  766.  
  767. - centerAt:(const NXPoint *)p
  768. {
  769.     bounds.origin.x = floor(p->x - bounds.size.width / 2.0);
  770.     bounds.origin.y = floor(p->y - bounds.size.height / 2.0);
  771.     return self;
  772. }
  773.  
  774. - sizeTo:(const NXSize *)size
  775. {
  776.     bounds.size.width = floor(size->width);
  777.     bounds.size.height = floor(size->height);
  778.     return self;
  779. }
  780.  
  781. - sizeToNaturalAspectRatio
  782. {
  783.     return [self constrainCorner:UPPER_RIGHT toAspectRatio:[self naturalAspectRatio]];
  784. }
  785.  
  786. - sizeToGrid:(GraphicView *)graphicView
  787. {
  788.     NXPoint p;
  789.  
  790.     [graphicView grid:&bounds.origin];
  791.     p.x = bounds.origin.x + bounds.size.width;
  792.     p.y = bounds.origin.y + bounds.size.height;
  793.     [graphicView grid:&p];
  794.     bounds.size.width = p.x - bounds.origin.x;
  795.     bounds.size.height = p.y - bounds.origin.y;
  796.  
  797.     return self;
  798. }
  799.  
  800. - alignToGrid:(GraphicView *)graphicView
  801. {
  802.     [graphicView grid:&bounds.origin];
  803.     return self;
  804. }
  805.  
  806. /* Compatibility method for old PSGraphic and Tiff classes. */
  807.  
  808. - replaceWithImage
  809. {
  810.     return self;
  811. }
  812.  
  813. /* Public routines. */
  814.  
  815. - setLineWidth:(const float *)value
  816. /*
  817.  * This is called with value indirected so that it can be called via
  818.  * a perform:with: method.  Kind of screwy, but ...
  819.  */
  820. {
  821.     if (value) linewidth = *value;
  822.     return self;
  823. }
  824.  
  825. - (float)lineWidth
  826. {
  827.     return linewidth;
  828. }
  829.  
  830. - setLineColor:(const NXColor *)color
  831. {
  832.     if (color) {
  833.     if (NXEqualColor(*color, NX_COLORBLACK)) {
  834.         NX_FREE(lineColor);
  835.         lineColor = NULL;
  836.         gFlags.nooutline = NO;
  837.     } else {
  838.         if (!lineColor) NX_ZONEMALLOC([self zone], lineColor, NXColor, 1);
  839.         *lineColor = *color;
  840.         gFlags.nooutline = NO;
  841.     }
  842.     }
  843.     return self;
  844. }
  845.  
  846. - (NXColor)lineColor
  847. {
  848.     return lineColor ? *lineColor : NX_COLORBLACK;
  849. }
  850.  
  851. - setFillColor:(const NXColor *)color
  852. {
  853.     if (color) {
  854.     if (!fillColor) NX_ZONEMALLOC([self zone], fillColor, NXColor, 1);
  855.     *fillColor = *color;
  856.     if (![self fill]) [self setFill:FILL_NZWR];
  857.     }
  858.     return self;
  859. }
  860.  
  861. - (NXColor)fillColor
  862. {
  863.     return fillColor ? *fillColor : NX_COLORWHITE;
  864. }
  865.  
  866. - (Graphic *)colorAcceptorAt:(const NXPoint *)point
  867. /*
  868.  * This method supports dragging and dropping colors on Graphics.
  869.  * Whatever object is returned from this may well be sent
  870.  * setFillColor: if the color actually gets dropped on it.
  871.  * See gvDrag.m's acceptsColor:atPoint: method.
  872.  */
  873. {
  874.     return nil;
  875. }
  876.  
  877. - changeFont:sender
  878. {
  879.     return self;
  880. }
  881.  
  882. - font
  883. {
  884.     return nil;
  885. }
  886.  
  887. - setGray:(const float *)value
  888. /*
  889.  * This is called with value indirected so that it can be called via
  890.  * a perform:with: method.  Kind of screwy, but ...
  891.  * Now that we have converted to using NXColor's, we'll interpret this
  892.  * method as a request to set the lineColor.
  893.  */
  894. {
  895.     NXColor color;
  896.  
  897.     if (value) {
  898.     color = NXConvertGrayToColor(*value);
  899.     [self setLineColor:&color];
  900.     }
  901.  
  902.     return self;
  903. }
  904.  
  905. - (float)gray
  906. {
  907.     float retval;
  908.  
  909.     if (lineColor) {
  910.     NXConvertColorToGray(*lineColor, &retval);
  911.     } else {
  912.     retval = NX_BLACK;
  913.     }
  914.  
  915.     return retval;
  916. }
  917.  
  918. - setFill:(int)mode
  919. {
  920.     switch (mode) {
  921.     case FILL_NONE:    gFlags.eofill = gFlags.fill = NO; break;
  922.     case FILL_EO:    gFlags.eofill = YES; gFlags.fill = NO; break;
  923.     case FILL_NZWR:    gFlags.eofill = NO; gFlags.fill = YES; break;
  924.     }
  925.     return self;
  926. }
  927.  
  928. - (int)fill
  929. {
  930.     if (gFlags.eofill) {
  931.     return FILL_EO;
  932.     } else if (gFlags.fill) {
  933.     return FILL_NZWR;
  934.     } else {
  935.     return FILL_NONE;
  936.     }
  937. }
  938.  
  939. - setOutlined:(BOOL)outlinedFlag
  940. {
  941.     gFlags.nooutline = outlinedFlag ? NO : YES;
  942.     return self;
  943. }
  944.  
  945. - (BOOL)isOutlined
  946. {
  947.     return gFlags.nooutline ? NO : YES;
  948. }
  949.  
  950. - setLineCap:(int)capValue
  951. {
  952.     if (capValue >= 0 && capValue <= 2) {
  953.     gFlags.linecap = capValue;
  954.     }
  955.     return self;
  956. }
  957.  
  958. - (int)lineCap
  959. {
  960.     return gFlags.linecap;
  961. }
  962.  
  963. - setLineArrow:(int)arrowValue
  964. {
  965.     if (arrowValue >= 0 && arrowValue <= 3) {
  966.     gFlags.arrow = arrowValue;
  967.     }
  968.     return self;
  969. }
  970.  
  971. - (int)lineArrow
  972. {
  973.     return gFlags.arrow;
  974. }
  975.  
  976. - setLineJoin:(int)joinValue
  977. {
  978.     if (joinValue >= 0 && joinValue <= 2) {
  979.     gFlags.linejoin = joinValue;
  980.     }
  981.     return self;
  982. }
  983.  
  984. - (int)lineJoin
  985. {
  986.     return gFlags.linejoin;
  987. }
  988.  
  989. /* Archiver-related methods. */
  990.  
  991. - write:(NXTypedStream *)stream
  992. /*
  993.  * Since a typical document has many Graphics, we want to try and make
  994.  * the archived document small, so we don't write out the linewidth and
  995.  * gray values if they are the most common 0 and NX_BLACK.  To accomplish
  996.  * this, we note that we haven't written them out by setting the
  997.  * bits in gFlags.
  998.  */
  999. {
  1000.     [super write:stream];
  1001.     gFlags.linewidthSet = (linewidth != 0.0);
  1002.     gFlags.lineColorSet = lineColor ? YES : NO;
  1003.     gFlags.fillColorSet = fillColor ? YES : NO;
  1004.     NXWriteTypes(stream, "ffffii", &bounds.origin.x, &bounds.origin.y,
  1005.     &bounds.size.width, &bounds.size.height, &gFlags, &identifier);
  1006.     if (gFlags.linewidthSet) NXWriteTypes(stream, "f", &linewidth);
  1007.     if (gFlags.lineColorSet) NXWriteColor(stream, *lineColor);
  1008.     if (gFlags.fillColorSet) NXWriteColor(stream, *fillColor);
  1009.     return self;
  1010. }
  1011.  
  1012. - read:(NXTypedStream *)stream
  1013. {
  1014.     int version;
  1015.     float gray = NX_BLACK;
  1016.  
  1017.     [super read:stream];
  1018.     version = NXTypedStreamClassVersion(stream, "Graphic");
  1019.     if (version > 2) {
  1020.     NXReadTypes(stream, "ffffii", &bounds.origin.x, &bounds.origin.y,
  1021.         &bounds.size.width, &bounds.size.height, &gFlags, &identifier);
  1022.     } else if (version > 1) {
  1023.     NXReadTypes(stream, "ffffsi", &bounds.origin.x, &bounds.origin.y,
  1024.         &bounds.size.width, &bounds.size.height, &gFlags, &identifier);
  1025. #ifdef __LITTLE_ENDIAN__
  1026.     *(unsigned int *)&gFlags = *(unsigned int *)&gFlags <<= 16;
  1027. #endif    
  1028.     } else {
  1029.     NXReadTypes(stream, "ffffs", &bounds.origin.x, &bounds.origin.y,
  1030.         &bounds.size.width, &bounds.size.height, &gFlags);
  1031. #ifdef __LITTLE_ENDIAN__
  1032.     *(unsigned int *)&gFlags = *(unsigned int *)&gFlags <<= 16;
  1033. #endif    
  1034.     identifier = currentGraphicIdentifier++;
  1035.     }
  1036.     if (version > 1 && identifier >= currentGraphicIdentifier) currentGraphicIdentifier = identifier+1;
  1037.     if (gFlags.linewidthSet) NXReadTypes(stream, "f", &linewidth);
  1038.     if (version < 1) {
  1039.     if (gFlags.lineColorSet) NXReadTypes(stream, "f", &gray);
  1040.     if (gFlags.fillColorSet && (gFlags.eofill | gFlags.fill)) {
  1041.         NX_ZONEMALLOC([self zone], lineColor, NXColor, 1);
  1042.         *lineColor = NXConvertGrayToColor(NX_BLACK);
  1043.         NX_ZONEMALLOC([self zone], fillColor, NXColor, 1);
  1044.         *fillColor = NXConvertGrayToColor(gray);
  1045.     } else if (gFlags.eofill | gFlags.fill) {
  1046.         NX_ZONEMALLOC([self zone], fillColor, NXColor, 1);
  1047.         *fillColor = NXConvertGrayToColor(gray);
  1048.         [self setOutlined:NO];
  1049.     } else {
  1050.         NX_ZONEMALLOC([self zone], lineColor, NXColor, 1);
  1051.         *lineColor = NXConvertGrayToColor(gray);
  1052.     }
  1053.     } else {
  1054.     if (gFlags.lineColorSet) {
  1055.         NX_ZONEMALLOC([self zone], lineColor, NXColor, 1);
  1056.         *lineColor = NXReadColor(stream);
  1057.         if (NXEqualColor(*lineColor, NX_COLORCLEAR)) {
  1058.         free(lineColor);
  1059.         lineColor = NULL;
  1060.         [self setOutlined:NO];
  1061.         }
  1062.     }
  1063.     if (gFlags.fillColorSet) {
  1064.         NX_ZONEMALLOC([self zone], fillColor, NXColor, 1);
  1065.         *fillColor = NXReadColor(stream);
  1066.         if (NXEqualColor(*fillColor, NX_COLORCLEAR) || (NXAlphaComponent(*fillColor) == 0.0)) {
  1067.         free(fillColor);
  1068.         fillColor = NULL;
  1069.         [self setFill:FILL_NONE];
  1070. //        } else if (!gFlags.eofill && !gFlags.fill) {    // why did I add this code here?
  1071. //        gFlags.fill = YES;
  1072.         }
  1073.     }
  1074.     }
  1075.  
  1076.     return self;
  1077. }
  1078.  
  1079. /* Routines which may need subclassing for different Graphic types. */
  1080.  
  1081. - (BOOL)constrainByDefault
  1082. {
  1083.     return NO;
  1084. }
  1085.  
  1086. - constrainCorner:(int)corner toAspectRatio:(float)aspect
  1087. /*
  1088.  * Modifies the bounds rectangle by moving the specified corner so that
  1089.  * the Graphic maintains the specified aspect ratio.  This is used during
  1090.  * constrained resizing.  Can be overridden if the aspect ratio is not
  1091.  * sufficient to constrain resizing.
  1092.  */
  1093. {
  1094.     int newcorner;
  1095.     float actualAspect;
  1096.  
  1097.     if (!bounds.size.height || !bounds.size.width || !aspect) return self;
  1098.     actualAspect = bounds.size.width / bounds.size.height;
  1099.     if (actualAspect == aspect) return self;
  1100.  
  1101.     switch (corner) {
  1102.     case LEFT_SIDE:
  1103.     bounds.origin.x -= bounds.size.height * aspect-bounds.size.width;
  1104.     case RIGHT_SIDE:
  1105.     bounds.size.width = bounds.size.height * aspect;
  1106.     if (bounds.size.width) NXIntegralRect(&bounds);
  1107.     return self;
  1108.     case BOTTOM_SIDE:
  1109.     bounds.origin.y -= bounds.size.width / aspect-bounds.size.height;
  1110.     case TOP_SIDE:
  1111.     bounds.size.height = bounds.size.width / aspect;
  1112.     if (bounds.size.height) NXIntegralRect(&bounds);
  1113.     return self;
  1114.     case LOWER_LEFT:
  1115.     corner = 0;
  1116.     case 0:
  1117.     case UPPER_RIGHT:
  1118.     case UPPER_LEFT:
  1119.     case LOWER_RIGHT:
  1120.     if (actualAspect > aspect) {
  1121.         newcorner = ((corner|KNOB_DY_ONCE)&(~(KNOB_DY_TWICE)));
  1122.     } else {
  1123.         newcorner = ((corner|KNOB_DX_ONCE)&(~(KNOB_DX_TWICE)));
  1124.     }
  1125.     return [self constrainCorner:newcorner toAspectRatio:aspect];
  1126.     default:
  1127.     return self;
  1128.     }
  1129. }
  1130.  
  1131. #define RESIZE_MASK (NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK|NX_TIMERMASK)
  1132.  
  1133. - resize:(NXEvent *)event by:(int)corner in:(GraphicView *)view
  1134. /*
  1135.  * Resizes the graphic by the specified corner.  If corner == CREATE,
  1136.  * then it is resized by the UPPER_RIGHT corner, but the initial size
  1137.  * is reset to 1 by 1.
  1138.  */
  1139. {
  1140.     NXPoint p, last;
  1141.     float aspect = 0.0;
  1142.     Window *window = [view window];
  1143.     BOOL constrain, canScroll;
  1144.     DrawStatusType oldDrawStatus;
  1145.     NXTrackingTimer *timer = NULL;
  1146.     NXRect eb, starteb, oldeb, visibleRect;
  1147.  
  1148.     if (!gFlags.active || !gFlags.selected || !corner) return self;
  1149.  
  1150.     constrain = ((event->flags & NX_ALTERNATEMASK) &&
  1151.     ((bounds.size.width && bounds.size.height) || corner == CREATE));
  1152.     if ([self constrainByDefault]) constrain = !constrain;
  1153.     if (constrain) aspect = bounds.size.width / bounds.size.height;
  1154.     if (corner == CREATE) {
  1155.     bounds.size.width = bounds.size.height = 1.0;
  1156.     corner = UPPER_RIGHT;
  1157.     }
  1158.  
  1159.     gFlags.selected = NO;
  1160.  
  1161.     [self getExtendedBounds:&eb];
  1162.     [view lockFocus];
  1163.     gFlags.active = NO;
  1164.     [view cache:&eb andUpdateLinks:NO];
  1165.     gFlags.active = YES;
  1166.     starteb = eb;
  1167.     [self draw:NULL];
  1168.     [window flushWindow];
  1169.  
  1170.     oldDrawStatus = DrawStatus;
  1171.     DrawStatus = Resizing;
  1172.  
  1173.     [view getVisibleRect:&visibleRect];
  1174.     canScroll = !NXEqualRect(&visibleRect, &bounds);
  1175.     if (canScroll && !timer) timer = NXBeginTimer(NULL, 0.1, 0.1);
  1176.  
  1177.     last.x = last.y = -1.0;
  1178.     while (event->type != NX_MOUSEUP) {
  1179.     p = event->location;
  1180.     event = [NXApp getNextEvent:RESIZE_MASK];
  1181.     if (event->type == NX_TIMER) event->location = p;
  1182.     p = event->location;
  1183.     [view convertPoint:&p fromView:nil];
  1184.     [view grid:&p];
  1185.     if (p.x != last.x || p.y != last.y) {
  1186.         corner = [self moveCorner:corner to:&p constrain:constrain];
  1187.         if (constrain) [self constrainCorner:corner toAspectRatio:aspect];
  1188.         oldeb = eb;
  1189.         [self getExtendedBounds:&eb];
  1190.         [window disableFlushWindow];
  1191.         [view drawSelf:&oldeb :1];
  1192.         if (canScroll) {
  1193.         [view scrollPointToVisible:&p]; // actually we want to keep the "edges" of the
  1194.                         // Graphic being resized that were visible when
  1195.                         // the resize started visible throughout the
  1196.                         // resizing time (this will be difficult if those
  1197.                         // edges flip from being the left edge to the
  1198.                         // right edge in the middle of the resize!).
  1199.         }
  1200.         [self draw:NULL];
  1201.         [view tryToPerform:@selector(updateRulers:) with:(void *)&bounds];
  1202.         [window reenableFlushWindow];
  1203.         [window flushWindow];
  1204.         last = p;
  1205.         NXPing();
  1206.     }
  1207.     }
  1208.  
  1209.     if (canScroll && timer) {
  1210.     NXEndTimer(timer);
  1211.     timer = NULL;
  1212.     }
  1213.  
  1214.     gFlags.selected = YES;
  1215.     DrawStatus = oldDrawStatus;
  1216.  
  1217.     [view cache:&eb andUpdateLinks:NO];    // redraw after resizing a Graphic
  1218.     NXUnionRect(&eb, &starteb);
  1219.     [view updateTrackedLinks:&starteb];
  1220.     [view tryToPerform:@selector(updateRulers:) with:nil];
  1221.     [window flushWindow];
  1222.     [view unlockFocus];
  1223.  
  1224.     return self;
  1225. }
  1226.  
  1227. - (BOOL)create:(NXEvent *)event in:(GraphicView *)view
  1228. /*
  1229.  * This method rarely needs to be subclassed.
  1230.  * It sets up an initial bounds, and calls resize:by:in:.
  1231.  */
  1232. {
  1233.     BOOL valid;
  1234.     NXCoord gridSpacing;
  1235.  
  1236.     bounds.origin = event->location;
  1237.     [view convertPoint:&bounds.origin fromView:nil];
  1238.     [view grid:&bounds.origin];
  1239.  
  1240.     gridSpacing = (NXCoord)[view gridSpacing];
  1241.     bounds.size.height = gridSpacing;
  1242.     bounds.size.width = gridSpacing * [self naturalAspectRatio];
  1243.  
  1244.     [self resize:event by:CREATE in:view];
  1245.  
  1246.     valid = [self isValid];
  1247.  
  1248.     if (valid) {
  1249.     gFlags.selected = YES;
  1250.     gFlags.active = YES;
  1251.     } else {
  1252.     gFlags.selected = NO;
  1253.     gFlags.active = NO;
  1254.     [view display];
  1255.     }
  1256.  
  1257.     return valid;
  1258. }
  1259.  
  1260. - (BOOL)hit:(const NXPoint *)p
  1261. {
  1262.     return (!gFlags.locked && gFlags.active && NXMouseInRect(p, &bounds, NO));
  1263. }
  1264.  
  1265. - (BOOL)isOpaque
  1266. {
  1267.     return [self fill] ? YES : NO;
  1268. }
  1269.  
  1270. - (BOOL)isValid
  1271. /*
  1272.  * Called after a Graphic is created to see if it is valid (this usually
  1273.  * means "is it big enough?").
  1274.  */
  1275. {
  1276.     return (bounds.size.width > MINSIZE && bounds.size.height > MINSIZE);
  1277. }
  1278.  
  1279. - (float)naturalAspectRatio
  1280. /*
  1281.  * A natural aspect ratio of zero means it doesn't have a natural aspect ratio.
  1282.  */
  1283. {
  1284.     return 0.0;
  1285. }
  1286.  
  1287. - (int)moveCorner:(int)corner to:(const NXPoint *)p constrain:(BOOL)flag
  1288. /*
  1289.  * Moves the specified corner to the specified point.
  1290.  * Returns the position of the corner after it was moved.
  1291.  */
  1292. {
  1293.     int newcorner = corner;
  1294.  
  1295.     if ((corner & KNOB_DX_ONCE) && (corner & KNOB_DX_TWICE)) {
  1296.     bounds.size.width += p->x - (bounds.origin.x + bounds.size.width);
  1297.     if (bounds.size.width <= 0.0) {
  1298.         newcorner &= ~ (KNOB_DX_ONCE | KNOB_DX_TWICE);
  1299.         bounds.origin.x += bounds.size.width;
  1300.         bounds.size.width = - bounds.size.width;
  1301.     }
  1302.     } else if (!(corner & KNOB_DX_ONCE)) {
  1303.     bounds.size.width += bounds.origin.x - p->x;
  1304.     bounds.origin.x = p->x;
  1305.     if (bounds.size.width <= 0.0) {
  1306.         newcorner |= KNOB_DX_ONCE | KNOB_DX_TWICE;
  1307.         bounds.origin.x += bounds.size.width;
  1308.         bounds.size.width = - bounds.size.width;
  1309.     }
  1310.     }
  1311.  
  1312.     if ((corner & KNOB_DY_ONCE) && (corner & KNOB_DY_TWICE)) {
  1313.     bounds.size.height += p->y - (bounds.origin.y + bounds.size.height);
  1314.     if (bounds.size.height <= 0.0) {
  1315.         newcorner &= ~ (KNOB_DY_ONCE | KNOB_DY_TWICE);
  1316.         bounds.origin.y += bounds.size.height;
  1317.         bounds.size.height = - bounds.size.height;
  1318.     }
  1319.     } else if (!(corner & KNOB_DY_ONCE)) {
  1320.     bounds.size.height += bounds.origin.y - p->y;
  1321.     bounds.origin.y = p->y;
  1322.     if (bounds.size.height <= 0.0) {
  1323.         newcorner |= KNOB_DY_ONCE | KNOB_DY_TWICE;
  1324.         bounds.origin.y += bounds.size.height;
  1325.         bounds.size.height = - bounds.size.height;
  1326.     }
  1327.     }
  1328.  
  1329.     if (newcorner != LOWER_LEFT) newcorner &= 0xf;
  1330.     if (!newcorner) newcorner = LOWER_LEFT;
  1331.  
  1332.     return newcorner;
  1333. }
  1334.  
  1335. - unitDraw
  1336. /*
  1337.  * If a Graphic just wants to draw itself in the bounding box of
  1338.  * {{0.0,0.0},{1.0,1.0}}, it can simply override this method.
  1339.  * Everything else will work fine.
  1340.  */
  1341. {
  1342.     return self;
  1343. }
  1344.  
  1345. - draw
  1346. /*
  1347.  * Almost all Graphics need to override this method.
  1348.  * It does the Graphic-specific drawing.
  1349.  * By default, it scales the coordinate system and calls unitDraw.
  1350.  */
  1351. {
  1352.     if (bounds.size.width >= 1.0 && bounds.size.height >= 1.0) {
  1353.     PStranslate(bounds.origin.x, bounds.origin.y);
  1354.     PSscale(bounds.size.width, bounds.size.height);
  1355.     [self unitDraw];
  1356.     }
  1357.     return self;
  1358. }
  1359.  
  1360. - (BOOL)edit:(NXEvent *)event in:(View *)view
  1361. /*
  1362.  * Any Graphic which has editable text should override this method
  1363.  * to edit that text.  TextGraphic is an example.
  1364.  */
  1365. {
  1366.     return NO;
  1367. }
  1368.  
  1369. @end
  1370.