home *** CD-ROM | disk | FTP | other *** search
/ OpenStep 4.2J (Developer) / os42jdev.iso / NextDeveloper / Examples / AppKit / Draw / Graphic.m < prev    next >
Text File  |  1996-04-18  |  33KB  |  1,261 lines

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