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

  1. #import "draw.h"
  2.  
  3. extern NXAtom NXDataLinkPboardType;
  4.  
  5. /* This file is best read in a window as wide as this comment (so that this comment fits on one line). */
  6.  
  7. @interface GraphicView(PrivateMethods)
  8.  
  9. /* Private methods */
  10.  
  11. - selectionCache;
  12. - getBBox:(NXRect *)bbox of:(List *)list extended:(BOOL)extended;
  13. - recacheSelection:(BOOL)updateLinks;
  14. - compositeSelection:(const NXRect *)sbounds from:(int)gstate;
  15. - (int)cacheSelection;
  16. - dirty:sender;
  17.  
  18. - resetGUP;
  19. - (BOOL)move:(NXEvent *)event;
  20. - moveGraphicsBRUFXPoint *)vector andDraw:(BOOL)drawFlag;
  21. - dragSelect:(NXEvent *)event;
  22. - alignGraphicsBy:(AlignmentType)alignType edge:(NXCoord *)edge; 
  23. - alignBy:(AlignmentType)alignType; 
  24.  
  25. @end
  26.  
  27. const char *DrawPboardType = "Draw Graphic List Type version 3.0";
  28.  
  29. BOOL InMsgPrint = NO;        /* whether we are in msgPrint: */
  30.  
  31. #define LEFTARROW    172
  32. #define RIGHTARROW    174
  33. #define UPARROW        173
  34. #define DOWNARROW    175
  35.  
  36. @implementation GraphicView : View
  37. /*
  38.  * The GraphicView class is the core of a DrawDocument.
  39.  *
  40.  * It overrides the View methods related to drawing and event handling
  41.  * and allows manipulation of Graphic objects.
  42.  *
  43.  * The user is allowed to select objects, move them around, group and
  44.  * ungroup them, change their font, and cut and paste them to the pasteboard.
  45.  * It understands multiple formats including PostScript and TIFF as well as
  46.  * its own internal format.  The GraphicView can also import PostScript and
  47.  * TIFF documents and manipulate them as Graphic objects.
  48.  *
  49.  * This is a very skeleton implementation and is intended purely for
  50.  * example purposes.  It should be very easy to add new Graphic objects
  51.  * merely by subclassing the Graphic class.  No code need be added to the
  52.  * GraphicView class when a new Graphic subclass is added.
  53.  *
  54.  * Moving is accomplished using a selection cache which is shared among
  55.  * all instances of GraphicView in the application.  The objects in the
  56.  * selection are drawn using opaque ink on a transparent background so
  57.  * that when they are moved around, the user can see through them to the
  58.  * objects that are not being moved.
  59.  *
  60.  * All of the drawing is done in an off-screen window which is merely
  61.  * composited back to the screen.  This makes for very fast redraw of
  62.  * areas obscured either by the selection moving or the user's scrolling.
  63.  *
  64.  * The glist instance variable is just an ordered list of all the Graphics
  65.  * in the GraphicView.  The slist is an ordered list of the Graphic objects
  66.  * in the selection.  In the original Draw code it was almost always kept 
  67.  * in the same order as the glist, but could be jumbled by doing a shift-drag 
  68.  * select to add to an existing selection. We are now extremely careful about 
  69.  * keeping the slist in the same order as the glist.
  70.  *
  71.  * cacheWindow is the off-screen window into which the objects are
  72.  * drawn.  Flags:  grid is the distance betwRUGpixels in the grid
  73.  * imposed on drawing; cacheing is used so that drawSelf:: knows when
  74.  * to composite from the off-screen cache and when to draw into the
  75.  * off-screen cache; groupInSlist is used to keep track of whether a
  76.  * Group Graphic is in the slist so that it knows when to highlight
  77.  * the Ungroup entry in the menu.
  78.  *
  79.  * This class should be able to be used outside the context of this
  80.  * application since it takes great pains not to depend on any other objects
  81.  * in the application.
  82.  */
  83.  
  84. /*
  85.  * Of course, one should NEVER use global variables in an application, but
  86.  * the following is necessary.  DrawStatus is
  87.  * analogous to the Application Kit's NXDrawingStatus and reflects whether
  88.  * we are in some modal loop.  By definition, that modal loop is "atomic"
  89.  * since we own the mouse during its duration (of course, all bets are off
  90.  * if we have multiple mice!).
  91.  */
  92.  
  93. DrawStatusType DrawStatus = Normal;  /* global state reflecting what we
  94.                     are currently doing (resizing, etc.) */
  95.  
  96. const float DEFAULT_GRID_GRAY = 0.8333;
  97.  
  98. static Graphic *currentGraphic = nil;    /* won't be used if NXApp knows how
  99.                        to keep track of the currentGraphic */
  100.  
  101. static float KeyMotionDeltaDefault = 0.0;
  102.  
  103. /* Private C functions needed to implement methods in this class. */
  104.  
  105. static Window *createCache(NXSize *size, NXZone *zone)
  106. /*
  107.  * Creates an appropriately size off-screen window to cache bits in.
  108.  */
  109. {
  110.     NXRect cacheRect;
  111.  
  112.     cacheRect.origin.x = 0.0;
  113.     cacheRect.origin.y = 0.0;
  114.     cacheRect.size = *size;
  115.  
  116.     return [[[Window allocFromZone:zone] initContent:&cacheRect
  117.                            style:NX_PLAINSTYLE
  118.                          backing:NX_RETAINED
  119.                       buttonMask:0
  120.                            defer:NO] reenableDisplay];
  121. }
  122.  
  123. /* Code-cleaning macros */
  124.  
  125. #define stopTimer(timer) if (timer) { \
  126.     NXEndTimer(timer); \
  127.     timer = NULL; \
  128. }
  129.  
  130. #define startTimer(timer) if (!timer) timer = NXBeginTimer(NULL, 0.1, 0.1);
  131.  
  132. #define GRID (gvFlags.gridDisabled ? 1.0 : (gvFlags.grid ? (NXCoord)gvFlags.grid : 1.0))
  133.  
  134. #define grid(point) \
  135.     (point).x = floor(((point).x / GRID) + 0.5) * GRID; \
  136.     (point).y = floor(((point).y / GRID) + 0.5) * GRID;
  137.  
  138. static void getRegion(NXRect *region, const NXPoint *p1, const NXPoint *p2)
  139. /*
  140.  * Returns the rectangle which has p1 and p2 as its corners.
  141.  */
  142. {
  143.     region->size.width = p1->x - p2->x;
  144.     region->size.height =RUH>y - p2->y;
  145.     if (region->size.width < 0.0) {
  146.     region->origin.x = p2->x + region->size.width;
  147.     region->size.width = ABS(region->size.width);
  148.     } else {
  149.     region->origin.x = p2->x;
  150.     }
  151.     if (region->size.height < 0.0) {
  152.     region->origin.y = p2->y + region->size.height;
  153.     region->size.height = ABS(region->size.height);
  154.     } else {
  155.     region->origin.y = p2->y;
  156.     }
  157. }
  158.  
  159. static BOOL checkForGroup(List *list)
  160. /*
  161.  * Looks through the given list searching for objects of the Group class.
  162.  * We use this to keep the gvFlags.groupInSlist flag up to date when things
  163.  * are removed from the slist (the list of selected objects).  That way
  164.  * we can efficiently keep the Ungroup menu item up to date.
  165.  */
  166. {
  167.     int i = [list count];
  168.     while (i--) if ([[list objectAt:i] isKindOf:[Group class]]) return YES;
  169.     return NO;
  170. }
  171.  
  172. /* Hack to support growable Text objects. */
  173.  
  174. static View *createEditView(GraphicView *self)
  175. /*
  176.  * editView is essentially a dumb, FLIPPED (with extra emphasis on the
  177.  * flipped) subview of our GraphicView which completely covers it and
  178.  * which automatically sizes itself to always completely cover the
  179.  * GraphicView.  It is necessary since growable Text objects only work
  180.  * when they are subviews of a flipped view.
  181.  *
  182.  * See TextGraphic for more details about why we need editView
  183.  * (it is purely a workaround for a limitation of the Text object).
  184.  */
  185. {
  186.     View *view;
  187.  
  188.     [self setAutoresizeSubviews:YES];
  189.     view = [[View allocFromZone:[self zone]] initFrame:&(self->frame)];
  190.     [view setFlipped:YES];
  191.     [view setAutosizing:NX_WIDTHSIZABLE|NX_HEIGHTSIZABLE];
  192.     [self addSubview:view];
  193.  
  194.     return view;
  195. }
  196.  
  197. /* Factory methods. */
  198.  
  199. + initialize
  200. /*
  201.  * We up the version of the class so that we can read old .draw files.
  202.  * See the read: method for how we use the version.
  203.  */
  204. {
  205.     [GraphicView setVersion:1];
  206.     DrawPboardType = NXUniqueStringNoCopy(DrawPboardType);
  207.     return self;
  208. }
  209.  
  210. /* Alignment methods */
  211.  
  212. + (SEL)actionFromAlignType:(AlignmentType)alignType
  213. {
  214.     switch (alignType) {
  215.     case LEFT: return @selector(moveLeftEdgeTo:);
  216.     case RIGHT: return @selector(moveRightEdgeTo:);
  217.     case BOTTOM: return @selector(moveBottomEdgeTo:);
  218.     case TOP: return @selector(moveTopEdgeTo:);
  219.     case HORIZONTAL_CENTERS: return @selector(moveVerticalCenterTo:);
  220.     case VERTICAL_CENTERS: return @selector(moveHorizontalCenteRUI);
  221.     case BASELINES: return @selector(moveBaselineTo:);
  222.     }
  223.     return (SEL)NULL;
  224. }
  225.  
  226. /* Creation methods. */
  227.  
  228. static void initClassVars()
  229. /*
  230.  * Sets up any default values.
  231.  */
  232. {
  233.     static BOOL registered = NO;
  234.     const char *validSendTypes[4];
  235.     const char *validReturnTypes[4];
  236.  
  237.     if (!KeyMotionDeltaDefault) {
  238.     const char *value = NXGetDefaultValue([NXApp appName], "KeyMotionDelta");
  239.     if (value) KeyMotionDeltaDefault = atof(value);
  240.     KeyMotionDeltaDefault = MAX(KeyMotionDeltaDefault, 1.0);
  241.     }
  242.     if (!registered) {
  243.     registered = YES;
  244.     validSendTypes[0] = NXPostScriptPboardType;
  245.     validSendTypes[1] = NXTIFFPboardType;
  246.     validSendTypes[2] = DrawPboardType;
  247.     validSendTypes[3] = NULL;
  248.     [NXApp registerServicesMenuSendTypes:validSendTypes andReturnTypes:[NXImage imagePasteboardTypes]];
  249.     validReturnTypes[0] = DrawPboardType;
  250.     validReturnTypes[1] = NULL;
  251.     [NXApp registerServicesMenuSendTypes:validSendTypes andReturnTypes:validReturnTypes];
  252.     }
  253. }
  254.  
  255. - initFrame:(const NXRect *)frameRect
  256. /*
  257.  * Initializes the view's instance variables.
  258.  * This view is considered important enough to allocate it a gstate.
  259.  */
  260. {
  261.     [super initFrame:frameRect];
  262.     glist = [[List allocFromZone:[self zone]] init];
  263.     slist = [[List allocFromZone:[self zone]] init];
  264.     cacheWindow = createCache(&bounds.size, [self zone]);
  265.     gvFlags.grid = 10;
  266.     gvFlags.gridDisabled = 1;
  267.     [self allocateGState];
  268.     gridGray = DEFAULT_GRID_GRAY;
  269.     PSInit();
  270.     currentGraphic = [Rectangle class];          /* default graphic */
  271.     currentGraphic = [self currentGraphic];   /* trick to allow NXApp to control currentGraphic */
  272.     editView = createEditView(self);
  273.     initClassVars();
  274.     [self registerForDragging];
  275.     return self;
  276. }
  277.  
  278. /* Free method */
  279.  
  280. - free
  281. {
  282.     if (gupCoords) {
  283.     NX_FREE(gupCoords);
  284.     NX_FREE(gupOps);
  285.     NX_FREE(gupBBox);
  286.     }
  287.     [glist freeObjects];
  288.     [glist free];
  289.     [slist free];
  290.     [cacheWindow free];
  291.     if (![editView superview]) [editView free];
  292.     return [super free];
  293. }
  294.  
  295. /* Used by Change's */
  296.  
  297. - setGroupInSlist:(BOOL)setting
  298. {
  299.     gvFlags.groupInSlist = setting;
  300.     return self;
  301. }
  302.  
  303. - resetGroupInSlist
  304. {
  305.     gvFlags.groupInSlist = checkForGroup(slist);
  306.     return self;
  307. }
  308.  
  309. - resetLockedFlag
  310. {
  311.     int i, count;
  312.  
  313.     gvFlags.locked = NO;
  314.     count = [glist count];
  315.     for (i = 0; (i < count) && (!gvFlags.locked); i+RUP        if ([[glist objectAt:i] isLocked])
  316.         gvFlags.locked = YES;
  317.     
  318.     return self;
  319. }
  320.  
  321. - redrawGraphics:graphicsList afterChangeAgent:changeAgent performs:(SEL)aSelector 
  322. {
  323.     NXRect afterBounds, beforeBounds;
  324.  
  325.     if ([graphicsList count]) {
  326.         [self getBBox:&beforeBounds of:graphicsList];
  327.     [changeAgent perform:aSelector];
  328.         [self getBBox:&afterBounds of:graphicsList];
  329.     NXUnionRect(&beforeBounds, &afterBounds);
  330.     [self cache:&afterBounds];    // recache after change object did something
  331.     } else {
  332.     [changeAgent perform:aSelector];
  333.     }
  334.     
  335.     return self;    
  336. }
  337.  
  338. /* Public interface methods. */
  339.  
  340. - (BOOL)isEmpty
  341. {
  342.     return [glist count] == 0;
  343. }
  344.  
  345. - (BOOL)hasEmptySelection
  346. {
  347.     return [slist count] == 0;
  348. }
  349.  
  350. - dirty
  351. {
  352.     if ([[window delegate] respondsTo:@selector(dirty:)]) [[window delegate] dirty:self];
  353.     return self;
  354. }
  355.  
  356. - getSelection
  357. /*
  358.  * Resets slist by going through the glist and locating all the Graphics
  359.  * which respond YES to the isSelected method.
  360.  */
  361. {
  362.     int i;
  363.     Graphic *graphic;
  364.  
  365.     [slist free];
  366.     slist = [[List allocFromZone:[self zone]] init];
  367.     gvFlags.groupInSlist = NO;
  368.     i = [glist count];
  369.     while (i--) {
  370.     graphic = [glist objectAt:i];
  371.     if ([graphic isSelected]) {
  372.         [slist insertObject:graphic at:0];
  373.         gvFlags.groupInSlist = gvFlags.groupInSlist || [graphic isKindOf:[Group class]];
  374.     }
  375.     }
  376.  
  377.     return self;
  378. }
  379.  
  380. - getBBox:(NXRect *)bbox of:(List *)list
  381. {
  382.     return [self getBBox:bbox of:list extended:YES];
  383. }
  384.  
  385. - graphicsPerform:(SEL)aSelector
  386. /*
  387.  * Performs the given aSelector on each member of the slist, then
  388.  * recaches and redraws the larger of the area covered by the objects before
  389.  * the selector was applied and the area covered by the objects after the
  390.  * selector was applied.  If you want to perform a method on each item
  391.  * in the slist and NOT redraw, then use the List method makeObjectsPerform:.
  392.  */
  393. {
  394.     int i, count;
  395.     Graphic *graphic;
  396.     NXRect eb, affectedBounds;
  397.  
  398.     count = [slist count];
  399.     if (count) {
  400.     [[slist objectAt:0] getExtendedBounds:&affectedBounds];
  401.     for (i = 1; i < count; i++) {
  402.         graphic = [slist objectAt:i];
  403.         NXUnionRect([graphic getExtendedBounds:&eb], &affectedBounds);
  404.     }
  405.     for (i = 0; i < count; i++) {
  406.         graphic = [slist objectAt:i];
  407.         [graphic perform:aSelector];
  408.         NXUnionRect([graphic getExtendedBoRUQ:&eb], &affectedBounds);
  409.     }
  410.     [self cache:&affectedBounds];    // graphicsPerform:
  411.     }    
  412.  
  413.     return self;
  414. }
  415.  
  416. - graphicsPerform:(SEL)aSelector with:(void *)argument
  417. {
  418.     int i, count;
  419.     Graphic *graphic;
  420.     NXRect eb, affectedBounds;
  421.  
  422.     count = [slist count];
  423.     if (count) {
  424.     [[slist objectAt:0] getExtendedBounds:&affectedBounds];
  425.     for (i = 1; i < count; i++) {
  426.         graphic = [slist objectAt:i];
  427.         NXUnionRect([graphic getExtendedBounds:&eb], &affectedBounds);
  428.     }
  429.     for (i = 0; i < count; i++) {
  430.         graphic = [slist objectAt:i];
  431.         [graphic perform:aSelector with:argument];
  432.         NXUnionRect([graphic getExtendedBounds:&eb], &affectedBounds);
  433.     }
  434.     [self cache:&affectedBounds];    // graphicsPerform:with:
  435.     }
  436.  
  437.     return self;
  438. }
  439.  
  440. - cache:(const NXRect *)rect andUpdateLinks:(BOOL)updateLinks
  441. /*
  442.  * Draws all the Graphics intersected by rect into the off-screen cache,
  443.  * then composites the rect back to the screen (but does NOT flushWindow).
  444.  * If updateLinks is on, then we check to see if the redrawn area intersects
  445.  * an area that someone has created a link to (see gvLinks.m).
  446.  */
  447. {
  448.     gvFlags.cacheing = YES;
  449.     [self drawSelf:rect :1];
  450.     gvFlags.cacheing = NO;
  451.  
  452.     if ([self canDraw]) {
  453.     [self lockFocus];
  454.     [self drawSelf:rect :1];
  455.     [self unlockFocus];
  456.     }
  457.  
  458.     if (updateLinks && !gvFlags.suspendLinkUpdate) [self updateTrackedLinks:rect];
  459.  
  460.     return self;
  461. }
  462.  
  463. - cache:(const NXRect *)rect
  464. {
  465.     return [self cache:rect andUpdateLinks:YES];
  466. }
  467.  
  468. - cacheAndFlush:(const NXRect *)rect
  469. {
  470.     [self cache:rect];    // cacheAndFlush:
  471.     [window flushWindow];
  472.     return self;
  473. }
  474.  
  475. - insertGraphic:(Graphic *)graphic
  476. /*
  477.  * Inserts the specified graphic into the glist and draws it.
  478.  * The new graphic will join the selection, not replace it.
  479.  */
  480. {
  481.     NXRect eb;
  482.  
  483.     if (graphic) {
  484.     if ([graphic isSelected]) [slist insertObject:graphic at:0];
  485.     [glist insertObject:graphic at:0];
  486.     [graphic wasAddedTo:self];
  487.     [self cache:[graphic getExtendedBounds:&eb]];    // insertGraphic:
  488.     if ([graphic isKindOf:[Group class]]) gvFlags.groupInSlist = YES;
  489.     [window flushWindow];
  490.     }
  491.  
  492.     return self;
  493. }
  494.  
  495. - removeGraphic:(Graphic *)graphic
  496. /*
  497.  * Removes the graphic from the GraphicView and redraws.
  498.  */
  499. {
  500.     int i;
  501.     NXRect eb;
  502.     Graphic *g = nil;
  503.  
  504.     if (!graphic) return self;
  505.  
  506.     i = [glist count];
  507.     while (g != graphic && i--) g = RURst objectAt:i];
  508.     if (g == graphic) {
  509.     [g getExtendedBounds:&eb];
  510.     [glist removeObjectAt:i];
  511.     [graphic wasRemovedFrom:self];
  512.     [slist removeObject:g];
  513.     if ([g isKindOf:[Group class]]) gvFlags.groupInSlist = checkForGroup(slist);
  514.     [self cache:&eb];    // removeGraphic:
  515.     [window flushWindow];
  516.     }
  517.  
  518.     return self;
  519. }
  520.  
  521. - (Graphic *)selectedGraphic
  522. /*
  523.  * If there is one and only one Graphic selected, this method returns it.
  524.  */
  525. {
  526.     if ([slist count] == 1) {
  527.     Graphic *graphic = [slist objectAt:0];
  528.     return [graphic isKindOf:[Group class]] ? nil : graphic;
  529.     } else {
  530.     return nil;
  531.     }
  532. }
  533.  
  534. - (List *)selectedGraphics
  535. /*
  536.  * Result valid only immediately after call. GraphicView 
  537.  * reserves the right to free the list without warning.
  538.  */
  539. {
  540.     return slist;
  541. }
  542.  
  543. - (List *)graphics
  544. /*
  545.  * Result valid only immediately after call. GraphicView 
  546.  * reserves the right to free the list without warning.
  547.  */
  548. {
  549.     return glist;
  550. }
  551.  
  552. /* Methods to modify the grid of the GraphicView. */
  553.  
  554. - (int)gridSpacing
  555. {
  556.     return gvFlags.grid;
  557. }
  558.  
  559. - (BOOL)gridIsVisible
  560. {
  561.     return gvFlags.showGrid;
  562. }
  563.  
  564. - (BOOL)gridIsEnabled
  565. {
  566.     return !gvFlags.gridDisabled;
  567. }
  568.  
  569. - (float)gridGray
  570. {
  571.     return gridGray;
  572. }
  573.  
  574. - setGridSpacing:(int)gridSpacing
  575. {
  576.     id change;
  577.     
  578.     if (gridSpacing != gvFlags.grid && gridSpacing > 0 && gridSpacing < 256) {
  579.     change = [[GridChange alloc] initGraphicView:self];
  580.     [change startChange];
  581.         gvFlags.grid = gridSpacing;
  582.         if (gvFlags.showGrid) {
  583.         [self resetGUP];
  584.         [self cache:&bounds andUpdateLinks:NO];
  585.         [window flushWindow];
  586.         }
  587.     [change endChange];
  588.     }
  589.  
  590.     return self;
  591. }
  592.  
  593. - setGridEnabled:(BOOL)flag
  594. {
  595.     id change;
  596.     
  597.     change = [[GridChange alloc] initGraphicView:self];
  598.     [change startChange];
  599.         gvFlags.gridDisabled = flag ? NO : YES;
  600.     [change endChange];
  601.  
  602.     return self;
  603. }
  604.  
  605. - setGridVisible:(BOOL)flag
  606. {
  607.     id change;
  608.     
  609.     if (gvFlags.showGrid != flag) {
  610.     change = [[GridChange alloc] initGraphicView:self];
  611.     [change startChange];
  612.         gvFlags.showGrid = flag;
  613.         if (flag) [self resetGUP];
  614.         [self cache:&bounds andUpdateLinks:NO];
  615.         [window flushWindow];
  616.     [change endChange];
  617.     }
  618.  
  619.     return self;
  620. }
  621.  
  622. - setGridGray:(float)gray
  623. {
  624.     id change;
  625.     
  626.     if (gray != gridGray) {
  627.     change = [[GridChange alloc] initGraphicView:self];
  628.     [change startChange];
  629.         gridGray = gray;
  630.      RUSf (gvFlags.showGrid) {
  631.         [self cache:&bounds andUpdateLinks:NO];
  632.         [window flushWindow];
  633.         }
  634.     [change endChange];
  635.     }
  636.  
  637.     return self;
  638. }
  639.  
  640. - setGridSpacing:(int)gridSpacing andGray:(float)gray
  641. {
  642.     id change;
  643.     
  644.     if (gray != gridGray || (gridSpacing != gvFlags.grid && gridSpacing > 0 && gridSpacing < 256)) {
  645.     change = [[GridChange alloc] initGraphicView:self];
  646.     [change startChange];
  647.         gridGray = gray;
  648.         if (gvFlags.grid != gridSpacing && gridSpacing > 0 && gridSpacing < 256) {
  649.         gvFlags.grid = gridSpacing;
  650.         if (gvFlags.showGrid) [self resetGUP];
  651.         }
  652.         if (gvFlags.showGrid) {
  653.         [self cache:&bounds andUpdateLinks:NO];
  654.         [window flushWindow];
  655.         }
  656.     [change endChange];
  657.     }
  658.  
  659.     return self;
  660. }
  661.  
  662. - grid:(NXPoint *)p
  663. {
  664.     grid(*p);
  665.     return self;
  666. }
  667.  
  668. /* Public methods for importing foreign data types into the GraphicView */
  669.  
  670. #define LOAD_IMAGE NXLocalString("Import Data", NULL, "Title of the alert which lets the user know that the image he has incorporated into his document does not fit.")
  671. #define IMAGE_TOO_LARGE NXLocalString("The imported data is too large to fit on the page.  Resize it to fit?", NULL, "Alert message which asks user if he wants to scale an image or some text that he is incorporating into his document to fit within the size of the document.")
  672. #define SCALE NXLocalString("Resize", NULL, "Button choice which allows the user to scale an image he is incorporating into his document so that it will fit within the size of the document.")
  673. #define DONT_SCALE NXLocalString("Don't Resize", NULL, "Button choice which allows the user to leave an image he is incorporating into his document the same size even though it will not fit within the size of his document.")
  674.  
  675. - placeGraphic:(Graphic *)graphic at:(const NXPoint *)location
  676. /*
  677.  * Places the graphic centered at the given location on the page.
  678.  * If the graphic is too big, the user is asked whether the graphic
  679.  * should be scaled.
  680.  */
  681. {
  682.     int scale;
  683.     NXPoint offset;
  684.     float sx, sy, factor;
  685.     NXRect gbounds, myBounds, visibleRect;
  686.     id change;
  687.  
  688.     if (graphic) {
  689.     [graphic getExtendedBounds:&gbounds];
  690.     if (gbounds.size.width > bounds.size.width || gbounds.size.height > bounds.size.height) {
  691.         scale = NXRunAlertPanel(LOAD_IMAGE, IMAGE_TOO_LARGE, SCALE, DONT_SCALE, CANCEL);
  692.         if (scale < 0) {
  693.         [graphic free];
  694.         retRUTnil;
  695.         } else if (scale > 0) {
  696.         sx = (bounds.size.width / gbounds.size.width) * 0.95;
  697.         sy = (bounds.size.height / gbounds.size.height) * 0.95;
  698.         factor = MIN(sx, sy);
  699.         gbounds.size.width *= factor;
  700.         gbounds.size.height *= factor;
  701.         [graphic sizeTo:&gbounds.size];
  702.         }
  703.     }
  704.     if (location) {
  705.         [graphic centerAt:location];
  706.     } else {
  707.         [self getVisibleRect:&visibleRect];
  708.         visibleRect.origin.x += floor(visibleRect.size.width / 2.0 + 0.5);
  709.         visibleRect.origin.y += floor(visibleRect.size.height / 2.0 + 0.5);
  710.         [graphic centerAt:&visibleRect.origin];
  711.     }
  712.     [graphic getExtendedBounds:&gbounds];
  713.     myBounds = bounds;
  714.     NXContainRect(&myBounds, &gbounds);
  715.     offset.x = bounds.origin.x - myBounds.origin.x;
  716.     offset.y = bounds.origin.y - myBounds.origin.y;
  717.     if (offset.x || offset.y) [graphic moveBy:&offset];
  718.  
  719.     change = [[CreateGraphicsChange alloc] initGraphicView:self graphic:graphic];
  720.     [change startChangeIn:self];
  721.         [self deselectAll:self];
  722.         [graphic select];
  723.         [self insertGraphic:graphic];
  724.         [self scrollGraphicToVisible:graphic];
  725.     [change endChange];
  726.     }
  727.  
  728.     return graphic;
  729. }
  730.  
  731. /* Methods overridden from superclass. */
  732.  
  733. - sizeTo:(NXCoord)width :(NXCoord)height
  734. /*
  735.  * Overrides View's sizeTo:: so that the cacheWindow is resized when
  736.  * the View is resized.
  737.  */
  738. {
  739.     if (width != bounds.size.width || height != bounds.size.height) {
  740.     [super sizeTo:width :height];
  741.     [cacheWindow free];
  742.     cacheWindow = createCache(&bounds.size, [self zone]);
  743.     [self resetGUP];
  744.     [self cache:&bounds andUpdateLinks:NO];
  745.     }
  746.     return self;
  747. }
  748.  
  749. - mouseDown:(NXEvent *)event
  750. /*
  751.  * This method handles a mouse down.
  752.  *
  753.  * If a current tool is in effect, then the mouse down causes a new
  754.  * Graphic to begin being created.  Otherwise, the selection is modified
  755.  * either by adding elements to it or removing elements from it, or moving
  756.  * it.  Here are the rules:
  757.  *
  758.  * Tool in effect
  759.  *    Shift OFF
  760.  *    create a new Graphic which becomes the new selection
  761.  *    Shift ON
  762.  *    create a new Graphic and ADD it to the current selection
  763.  *    Control ON
  764.  *    leave creation mode, and start selection
  765.  * Otherwise
  766.  *    Shift OFF
  767.  *    a. Click on a selected Graphic -> select graphic further back
  768.  *    b. Click on an unselected Graphic -> that Graphic becomes selection
  769.  *    Shift ON
  770.  *    a. Click on a selected Graphic -> remove it from selectiRUU*    b. Click on unselected Graphic -> add it to selection
  771.  *    Alternate ON
  772.  *    if no affected graphic, causes drag select to select only objects
  773.  *    completely contained within the dragged box.
  774.  *
  775.  * Essentially, everything works as one might expect except the ability to
  776.  * select a Graphic which is deeper in the list (i.e. further toward the
  777.  * back) by clicking on the currently selected Graphic.
  778.  *
  779.  * This is a very hairy mouseDown:.  Most need not be this scary.
  780.  */
  781. {
  782.     NXPoint p;
  783.     NXRect eb;
  784.     int i, corner, oldMask;
  785.     id change, factory;
  786.     Graphic *g = nil, *startg = nil;
  787.     BOOL shift, control, gotHit = NO, deepHit = NO, didDrag = NO;
  788.  
  789.     /*
  790.      * You only need to do the following line in a mouseDown: method if
  791.      * you receive this message because one of your subviews gets the
  792.      * mouseDown: and does not respond to it (thus, it gets passed up the
  793.      * responder chain to you).  In this case, our editView receives the
  794.      * mouseDown:, but doesn't do anything about it, and when it comes
  795.      * to us, we want to become the first responder.
  796.      *
  797.      * Normally you won't have a subview which doesn't do anything with
  798.      * mouseDown:, in which case, you need only return YES from the
  799.      * method acceptsFirstResponder (see that method below) and will NOT
  800.      * need to do the following makeFirstResponder:.  In other words,
  801.      * don't put the following line in your mouseDown: implementation!
  802.      *
  803.      * Sorry about confusing this issue ... 
  804.      */
  805.  
  806.     if ([window firstResponder] != self && event->data.mouse.click < 2) [window makeFirstResponder:self];
  807.  
  808.     shift = (event->flags & NX_SHIFTMASK) ? YES : NO;
  809.     control = (event->flags & NX_CONTROLMASK) ? YES : NO;
  810.  
  811.     p = event->location;
  812.     [self convertPoint:&p fromView:nil];
  813.  
  814.     oldMask = [window addToEventMask:NX_MOUSEDRAGGEDMASK|NX_MOUSEUPMASK];
  815.  
  816.     i = 0;    // See if a Graphic wants to handle this event itself
  817.     [self lockFocus];
  818.     do {
  819.     g = [glist objectAt:i++];
  820.     if ([g handleEvent:event at:&p inView:self]) return self;
  821.     } while (g != nil);
  822.     [self unlockFocus];
  823.  
  824.     factory = [self currentGraphic];
  825.     if (!control && (factory || (event->data.mouse.click == 2))) {
  826.     id editFactory = factory;
  827.     if ((event->data.mouse.click == 2) && ![editFactory isEditable]) editFactory = [TextGraphic class];
  828.     if ([editFactory isERUVble]) {    /* if editable, try to edit one */
  829.         i = 0;
  830.         g = [glist objectAt:i++];
  831.         while (g != nil) {
  832.         if ([g isKindOf:editFactory] && [g hit:&p]) {
  833.             if ([g isSelected]) {
  834.             [g deselect];
  835.             [self cache:[g getExtendedBounds:&eb] andUpdateLinks:NO];
  836.             [slist removeObject:g];
  837.             }
  838.             [g edit:event in:editView];
  839.             goto done;
  840.         }
  841.         g = [glist objectAt:i++];
  842.         }
  843.     }
  844.     }
  845.     if (!control && factory) {
  846.     if (factory && !g) {    /* not editing or no editable graphic found */
  847.         g = [[factory allocFromZone:[self zone]] init];
  848.         if ([NXApp respondsTo:@selector(inspectorPanel)]) {
  849.         [[[NXApp inspectorPanel] delegate] initializeGraphic:g];
  850.         }
  851.         if ([g create:event in:self]) {
  852.         change = [[CreateGraphicsChange alloc] initGraphicView:self graphic:g];
  853.         [change startChange];
  854.             if (!shift) [self deselectAll:self];
  855.             [self insertGraphic:g];
  856.             [g edit:NULL in:editView];
  857.         [change endChange];
  858.         } else {
  859.         [g free];
  860.         }
  861.     }
  862.     } else {        /* selecting/resizing/moving */
  863.     i = 0;
  864.     g = [glist objectAt:i++];
  865.     while (g != nil && !gotHit) {
  866.         corner = [g knobHit:&p];
  867.         if (corner > 0) {            /* corner hit */
  868.         gotHit = YES;
  869.         change = [[ResizeGraphicsChange alloc] initGraphicView:self graphic:g];
  870.         [change startChange];
  871.             [g resize:event by:corner in:self];
  872.         [change endChange];
  873.         } else if (corner) {        /* complete miss */
  874.         g = [glist objectAt:i++];
  875.         } else g = nil;            /* non-corner opaque hit */
  876.     }
  877.     i = 0;
  878.     if (!gotHit) g = [glist objectAt:i++];
  879.     while (g && !gotHit && !deepHit) {
  880.         if ([g isSelected] && [g hit:&p]) {
  881.         if (shift) {
  882.             gotHit = YES;
  883.             [g deselect];
  884.             [self cache:[g getExtendedBounds:&eb] andUpdateLinks:NO];
  885.             [slist removeObject:g];
  886.             if ([g isKindOf:[Group class]]) gvFlags.groupInSlist = checkForGroup(slist);
  887.         } else {
  888.             gotHit = [self move:event];
  889.             if (!gotHit) {
  890.             deepHit = ![g isOpaque];
  891.             if (!deepHit) gotHit = YES;
  892.             } 
  893.         }
  894.         }
  895.         g = [glist objectAt:i++];
  896.     }
  897.     startg = g;
  898.     if (!gotHit) do {
  899.         if (!g) {
  900.         i = 0;
  901.         g = [glist objectAt:i++];
  902.         }
  903.         if (![g isSelected] && [g hit:&p]) {
  904.         gotHit = YES;
  905.         if (!shift) {
  906.             [self deselectAll:self];
  907.             [slist addObject:g];
  908.             gvFlags.groupInSlist = [g isKindOf:[Group class]];
  909.         }
  910.         [g select];
  911.         if (shift) [self getSelection];
  912.         if (deepHit ||RUWelf move:event]) {
  913.             [self cache:[g getExtendedBounds:&eb] andUpdateLinks:NO];
  914.         }
  915.         } else {
  916.         g = [glist objectAt:i++];
  917.         }
  918.     } while (!gotHit && g != startg);
  919.  
  920.     if (!gotHit && !deepHit) {
  921.         if (!shift) {
  922.         [self lockFocus];
  923.         [self deselectAll:self];
  924.         [self unlockFocus];
  925.         didDrag = YES;
  926.         }
  927.         [self dragSelect:event];
  928.     }
  929.     }
  930. done:
  931.     if (!didDrag && dragRect) {
  932.     NX_FREE(dragRect);
  933.     dragRect = NULL;
  934.     }
  935.     gvFlags.selectAll = NO;
  936.  
  937.     [window flushWindow];
  938.     [window setEventMask:oldMask];
  939.  
  940.     return self;
  941. }
  942.  
  943. - drawSelf:(const NXRect *)rects :(int)rectCount
  944. /*
  945.  * Draws the GraphicView.
  946.  *
  947.  * If cacheing is on or if NXDrawingStatus != NX_DRAWING, then all the
  948.  * graphics which intersect the specified rectangles will be drawn (and
  949.  * clipped to those rectangles).  Otherwise, the specified rectangles
  950.  * are composited to the screen from the off-screen cache.  The invalidRect
  951.  * stuff is to clean up any temporary drawing we have in the view
  952.  * (currently used only to show a source selection in the links mechanism--
  953.  * see showSelection: in gvLinks.m).
  954.  */
  955. {
  956.     NXRect *rp;
  957.     NXRect r, visibleRect;
  958.     int i, j, gstate;
  959.  
  960.     if (rects == NULL) return self;
  961.  
  962.     if (!gvFlags.cacheing && invalidRect && (rects != invalidRect)) {
  963.     NXUnionRect(rects, invalidRect);
  964.     [self drawSelf:invalidRect :1];
  965.     [window flushWindow];
  966.     NX_FREE(invalidRect);
  967.     invalidRect = NULL;
  968.     return self;
  969.     }
  970.  
  971.     if (gvFlags.cacheing || NXDrawingStatus != NX_DRAWING) {
  972.     if (NXDrawingStatus == NX_DRAWING) {
  973.         [[cacheWindow contentView] lockFocus];
  974.         PSsetgray(NX_WHITE);
  975.         for (j = (rectCount > 1) ? 1 : 0; j < rectCount; j++) {
  976.         NXRectFill(&rects[j]);
  977.         if (gvFlags.showGrid && gvFlags.grid >= 4) {
  978.             NXRectClip(&rects[j]);
  979.             PSsetlinewidth(0.0);
  980.             PSsetgray(gridGray);
  981.             DPSDoUserPath(gupCoords, gupLength, dps_short,
  982.                   gupOps, gupLength >> 1, gupBBox, dps_ustroke);
  983.             PSsetgray(NX_WHITE);
  984.         }
  985.         }
  986.     }
  987.     for (j = (rectCount > 1) ? 1 : 0; j < rectCount; j++) {
  988.         NXRectClip(&rects[j]);
  989.         i = [glist count];
  990.         while (i--) [[glist objectAt:i] draw:&rects[j]];
  991.     }
  992.     [Graphic showFastKnobFills];
  993.     if (NXDrawingStatus == NX_DRAWING) [[cacheWindow contentView] unlockFocus];
  994.     }
  995.  
  996.     if (!gvFlags.cacheing && NXDrawingStatus == NX_DRAWING) {
  997.     gstate = [cacheWindow gState];
  998.     [self getVisibRUXct:&visibleRect];
  999.     for (j = 0; j < rectCount; j++) {
  1000.         rp = &r;
  1001.         r = rects[j];
  1002.         if (!NXEqualRect(rp, &visibleRect)) rp = NXIntersectionRect(&visibleRect, rp);
  1003.         if (rp) {
  1004.         PScomposite(NX_X(rp), NX_Y(rp), NX_WIDTH(rp), NX_HEIGHT(rp), gstate,
  1005.                 NX_X(rp), NX_Y(rp), NX_COPY);
  1006.         }
  1007.     }
  1008.     }
  1009.  
  1010.     return self;
  1011. }
  1012.  
  1013. - keyDown:(NXEvent *)event
  1014. /*
  1015.  * Handles one of the arrow keys being pressed.
  1016.  * Note that since it might take a while to actually move the selection
  1017.  * (if it is large), we check to see if a bunch of arrow key events have
  1018.  * stacked up and move them all at once.
  1019.  */
  1020. {
  1021.     NXPoint p;
  1022.     NXEvent e;
  1023.     NXCoord delta;
  1024.     BOOL gotOne, first;
  1025.     NXEvent* eptr = event;
  1026.  
  1027.     if ((event->data.key.charSet != NX_ASCIISET ||
  1028.      event->data.key.charCode != 127) &&
  1029.     (event->data.key.charSet != NX_SYMBOLSET ||
  1030.      (event->data.key.charCode != LEFTARROW &&
  1031.       event->data.key.charCode != RIGHTARROW &&
  1032.       event->data.key.charCode != DOWNARROW &&
  1033.       event->data.key.charCode != UPARROW))) {
  1034.     return [super keyDown:event];
  1035.     }
  1036.  
  1037.     if (event->data.key.charSet == NX_ASCIISET) return [self delete:self];
  1038.  
  1039.     p.x = p.y = 0.0;
  1040.     delta = KeyMotionDeltaDefault;
  1041.     delta = floor(delta / GRID) * GRID;
  1042.     delta = MAX(delta, GRID);
  1043.  
  1044.     first = YES;
  1045.     do {
  1046.     gotOne = NO;
  1047.     if (eptr->data.key.charSet == NX_SYMBOLSET) {
  1048.         switch (eptr->data.key.charCode) {
  1049.         case LEFTARROW:
  1050.             p.x -= delta;
  1051.             gotOne = YES;
  1052.             break;
  1053.         case RIGHTARROW:
  1054.             p.x += delta;
  1055.             gotOne = YES;
  1056.             break;
  1057.         case UPARROW:
  1058.             p.y += delta;
  1059.             gotOne = YES;
  1060.             break;
  1061.         case DOWNARROW:
  1062.             p.y -= delta;
  1063.             gotOne = YES;
  1064.             break;
  1065.         default:
  1066.             break;
  1067.         }
  1068.     }
  1069.     if (eptr && gotOne && !first) [NXApp getNextEvent:NX_KEYDOWNMASK];
  1070.     first = NO;
  1071.     } while (gotOne && (eptr = [NXApp peekNextEvent:NX_KEYDOWNMASK into:&e]));
  1072.  
  1073.     if (p.x || p.y) {
  1074.     [self moveGraphicsBy:&p andDraw:YES];
  1075.     [[self window] flushWindow];
  1076.     NXPing();
  1077.     }
  1078.  
  1079.     return self;
  1080. }
  1081.  
  1082. /* Accepting becoming the First Responder */
  1083.  
  1084. - (BOOL)acceptsFirstResponder
  1085. /*
  1086.  * GraphicView always wants to become the first responder when it is
  1087.  * clicked on in a window, so it returns YES from this method.
  1088.  */
  1089. {
  1090.     return YES;
  1091. }
  1092.  
  1093. - (BOOL)acceptsFirstMouse
  1094. /*
  1095.  * GraphicView really only wants to accept first mouse if that
  1096.  * first mouse starts a drag, butRUYre is no way to detect that
  1097.  * case, so we're stuck accepting all first mouse events!
  1098.  */
  1099. {
  1100.     return YES;
  1101. }
  1102.  
  1103. /* Printing */
  1104.  
  1105. - beginPrologueBBox:(NXRect *)boundingBox creationDate:(char *)dateCreated
  1106.     createdBy:(char *)anApplication fonts:(char *)fontNames
  1107.     forWhom:(char *)user pages:(int )numPages title:(char *)aTitle
  1108. /*
  1109.  * Include the window title as the name of the document when printing.
  1110.  */
  1111. {
  1112.     char *s;
  1113.     char name[MAXPATHLEN+1];
  1114.     const char *title = [window title];
  1115.  
  1116.     strcpy(name, title);
  1117.     s = strchr(name, ' ');
  1118.     if (s) *s = '\0';
  1119.     return [super beginPrologueBBox:boundingBox creationDate:dateCreated
  1120.         createdBy:anApplication fonts:fontNames
  1121.         forWhom:user pages:numPages title:name];
  1122. }
  1123.  
  1124. - beginSetup
  1125. /*
  1126.  * Spit out the custom PostScript defs.
  1127.  */
  1128. {
  1129.     [super beginSetup];
  1130.     PSInit();
  1131.     return self;
  1132. }
  1133.  
  1134. /*
  1135.  * These two method set and get the factory object used to create new
  1136.  * Graphic objects (i.e. the subclass of Graphic to use).
  1137.  * They are kind of weird since they check to see if the
  1138.  * Application object knows what the current graphic is.  If it does, then
  1139.  * it lets it handle these methods.  Otherwise, it determines the
  1140.  * current graphic by querying the sender to find out what its title is
  1141.  * and converts that title to the name to a factory object.  This allows
  1142.  * the GraphicView to stand on its own, but also use an application wide
  1143.  * tool palette if available.
  1144.  * If the GraphicView handles the current graphic by itself, it does so
  1145.  * by querying the sender of setCurrentGraphic: to find out its title.
  1146.  * It assumes, then, that that title is the name of the factory object to
  1147.  * use and calls objc_getClass() to get a pointer to it.
  1148.  * If the application is not control what our current graphic is, then
  1149.  * we restrict creations to be made only when the control key is down.
  1150.  * Otherwise, it is the other way around (control key leaves creation
  1151.  * mode).  This is due to the fact that the application can be smart
  1152.  * enough to set appropriate cursors when a tool is on.  The GraphicView
  1153.  * can't be.
  1154.  */
  1155.  
  1156. - (Graphic *)currentGraphic
  1157. {
  1158.     if ([NXApp respondsTo:@selector(currentGraphic)]) {
  1159.     return [NXApp currentGraphic];
  1160.     } else {
  1161.     return currentGraphic;
  1162.     }
  1163. }
  1164.  
  1165. - setCurrentGraphic:sender
  1166. {
  1167.     currentGraphic = objc_getClass([[sender selectedCell] title]);
  1168.     return self;
  1169. }RU` These methods write out the form information. */
  1170.  
  1171. - (BOOL)hasFormEntries
  1172. {
  1173.     int i;
  1174.     for (i = [glist count]-1; i >= 0; i--) {
  1175.     if ([[glist objectAt:i] isFormEntry]) return YES;
  1176.     }
  1177.     return NO;
  1178. }
  1179.  
  1180. - writeFormEntriesToFile:(const char *)file
  1181. {
  1182.     int i;
  1183.     NXStream *stream;
  1184.  
  1185.     stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
  1186.     NXPrintf(stream, "Page Size: w = %d, h = %d\n", (int)bounds.size.width, (int)bounds.size.height);
  1187.     for (i = [glist count]-1; i >= 0; i--) {
  1188.     [[glist objectAt:i] writeFormEntryToStream:stream];
  1189.     }
  1190.     NXSaveToFile(stream, file);
  1191.     NXCloseMemory(stream, NX_FREEBUFFER);
  1192.  
  1193.     return self;
  1194. }
  1195.  
  1196. /*
  1197.  * Target/Action methods.
  1198.  */
  1199.  
  1200. - delete:sender
  1201. {
  1202.     int i;
  1203.     Graphic *graphic;
  1204.     id change;
  1205.  
  1206.     i = [slist count];
  1207.     if (i > 0) {
  1208.     change = [[DeleteGraphicsChange alloc] initGraphicView:self];
  1209.     [change startChange];
  1210.         [self graphicsPerform:@selector(deactivate)];
  1211.         [slist makeObjectsPerform:@selector(activate)];
  1212.         while (i--) {
  1213.         graphic = [slist objectAt:i];
  1214.         [glist removeObject:graphic];
  1215.         [graphic wasRemovedFrom:self];
  1216.         }
  1217.         if (originalPaste == [slist objectAt:0]) [slist removeObjectAt:0];
  1218.         [slist free];
  1219.         slist = [[List allocFromZone:[self zone]] init];
  1220.         gvFlags.groupInSlist = NO;
  1221.         [window flushWindow];
  1222.         [change endChange];
  1223.     return self;
  1224.     } else {
  1225.         return nil;
  1226.     }
  1227. }
  1228.  
  1229. - selectAll:sender
  1230. /*
  1231.  * Selects all the items in the glist.
  1232.  */
  1233. {
  1234.     int i;
  1235.     Graphic *g;
  1236.     NXRect visibleRect;
  1237.  
  1238.     i = [glist count];
  1239.     if (!i) return self;
  1240.  
  1241.     [slist free];
  1242.     slist = [[List allocFromZone:[self zone]] init];
  1243.     [[cacheWindow contentView] lockFocus];
  1244.     while (i--) {
  1245.     g = [glist objectAt:i];
  1246.     if (![g isLocked]) {
  1247.         [g select];
  1248.         [g draw:NULL];
  1249.         [slist insertObject:g at:0];
  1250.         gvFlags.groupInSlist = gvFlags.groupInSlist || [g isKindOf:[Group class]];
  1251.     }
  1252.     }
  1253.     [Graphic showFastKnobFills];
  1254.     [[cacheWindow contentView] unlockFocus];
  1255.     [self getVisibleRect:&visibleRect];
  1256.     if ([self canDraw]) {
  1257.     [self lockFocus];
  1258.     [self drawSelf:&visibleRect :1];
  1259.     [self unlockFocus];
  1260.     }
  1261.     if (sender != self) [window flushWindow];
  1262.     gvFlags.selectAll = YES;
  1263.     
  1264.     return self;
  1265. }
  1266.  
  1267. - deselectAll:sender
  1268. /*
  1269.  * Deselects all the items in the slist.
  1270.  */
  1271. {
  1272.     NXRect sbounds;
  1273.  
  1274.     if ([slist count] > 0) {
  1275.     [self getBBox:&RUands of:slist];
  1276.     [slist makeObjectsPerform:@selector(deselect)];
  1277.     [self cache:&sbounds andUpdateLinks:NO];
  1278.     [slist free];
  1279.     slist = [[List allocFromZone:[self zone]] init];
  1280.     gvFlags.groupInSlist = NO;
  1281.     if (sender != self) [window flushWindow];
  1282.     }
  1283.  
  1284.     return self;
  1285. }
  1286.  
  1287. - lock:sender
  1288. /*
  1289.  * Locks all the items in the selection so that they can't be selected
  1290.  * or resized or moved.  Useful if there are some Graphics which are getting
  1291.  * in your way.  Undo this with unlock:.
  1292.  */
  1293. {
  1294.     id change;
  1295.  
  1296.     if ([slist count] > 0) {
  1297.     change = [[LockGraphicsChange alloc] initGraphicView:self];
  1298.     [change startChange];
  1299.         gvFlags.locked = YES;
  1300.         [slist makeObjectsPerform:@selector(lock)];
  1301.         [self deselectAll:sender];
  1302.     [change endChange];
  1303.     }
  1304.  
  1305.     return self;
  1306. }
  1307.  
  1308. - unlock:sender
  1309. {
  1310.     id change;
  1311.  
  1312.     change = [[UnlockGraphicsChange alloc] initGraphicView:self];
  1313.     [change startChange];
  1314.     [glist makeObjectsPerform:@selector(unlock)];
  1315.     gvFlags.locked = NO;
  1316.     [change endChange];
  1317.  
  1318.     return self;
  1319. }
  1320.  
  1321. - bringToFront:sender
  1322. /*
  1323.  * Brings each of the items in the slist to the front of the glist.
  1324.  * The item in the front of the slist will be the new front element
  1325.  * in the glist.
  1326.  */
  1327. {
  1328.     id change;
  1329.     int i;
  1330.  
  1331.     i = [slist count];
  1332.     if (i) {
  1333.     change = [[BringToFrontGraphicsChange alloc] initGraphicView:self];
  1334.     [change startChange];
  1335.         while (i--) [glist insertObject:[glist removeObject:[slist objectAt:i]] at:0];
  1336.         [self recacheSelection];
  1337.     [change endChange];
  1338.     }
  1339.  
  1340.     return self;
  1341. }
  1342.  
  1343. - sendToBack:sender
  1344. {
  1345.     int i, count;
  1346.     id change;
  1347.  
  1348.     count = [slist count];
  1349.     if (count > 0) {
  1350.     change = [[SendToBackGraphicsChange alloc] initGraphicView:self];
  1351.     [change startChange];
  1352.         for (i = 0; i < count; i++) [glist addObject:[glist removeObject:[slist objectAt:i]]];
  1353.         [self recacheSelection];
  1354.     [change endChange];
  1355.     }
  1356.  
  1357.     return self;
  1358. }
  1359.  
  1360. - group:sender
  1361. /*
  1362.  * Creates a new Group object with the current slist as its member list.
  1363.  * See the Group class for more info.
  1364.  */
  1365. {
  1366.     int i;
  1367.     NXRect eb;
  1368.     Graphic *graphic;
  1369.     id change;
  1370.  
  1371.     i = [slist count];
  1372.     if (i > 1) {
  1373.     change = [[GroupGraphicsChange alloc] initGraphicView:self];
  1374.     [change startChange];
  1375.         while (i--) [glist removeObject:[slist objectAt:i]];
  1376.         graphic = [[Group allocFromZone:[self zone]] initList:slist];
  1377.         [change noteGroup:graphRUb
  1378.         [glist insertObject:graphic at:0];
  1379.         slist = [[List allocFromZone:[self zone]] init];
  1380.         [slist addObject:graphic];
  1381.         gvFlags.groupInSlist = YES;
  1382.         [self cache:[graphic getExtendedBounds:&eb]];
  1383.         if (sender != self) [window flushWindow];
  1384.     [change endChange];
  1385.     }
  1386.  
  1387.     return self;
  1388. }
  1389.  
  1390.  
  1391. - ungroup:sender
  1392. /*
  1393.  * Goes through the slist and ungroups any Group objects in it.
  1394.  * Does not descend any further than that (i.e. all the Group objects
  1395.  * in the slist are ungrouped, but any Group objects in those ungrouped
  1396.  * objects are NOT ungrouped).
  1397.  */
  1398. {
  1399.     int i, k;
  1400.     NXRect sbounds;
  1401.     id graphic;
  1402.     id change;
  1403.  
  1404.     if (!gvFlags.groupInSlist || [slist count] == 0)
  1405.         return nil;
  1406.     
  1407.     change = [[UngroupGraphicsChange alloc] initGraphicView:self];
  1408.     [change startChange];
  1409.     [self getBBox:&sbounds of:slist];
  1410.     i = [slist count];
  1411.     while (i--) {
  1412.         graphic = [slist objectAt:i];
  1413.         if ([graphic isKindOf:[Group class]]) {
  1414.         k = [glist indexOf:graphic];
  1415.         [glist removeObjectAt:k];
  1416.         [graphic transferSubGraphicsTo:glist at:k];
  1417.         }
  1418.     }
  1419.         [self cache:&sbounds];
  1420.     if (sender != self) [window flushWindow];
  1421.     [self getSelection];
  1422.     [change endChange];
  1423.     
  1424.     return self;
  1425. }
  1426.  
  1427. - align:sender
  1428. {
  1429.     [self alignBy:(AlignmentType)[[sender selectedCell] tag]];
  1430.     return self;
  1431. }
  1432.  
  1433. - changeAspectRatio:sender
  1434. {
  1435.     id change;
  1436.     
  1437.     change = [[AspectRatioGraphicsChange alloc] initGraphicView:self];
  1438.     [change startChange];
  1439.     [self graphicsPerform:@selector(sizeToNaturalAspectRatio)];
  1440.     [change endChange];
  1441.  
  1442.     [window flushWindow];
  1443.     return self;
  1444. }
  1445.  
  1446. - alignToGrid:sender
  1447. {
  1448.     id change;
  1449.     
  1450.     change = [[AlignGraphicsChange alloc] initGraphicView:self];
  1451.     [change startChange];
  1452.     [self graphicsPerform:@selector(alignToGrid:) with:self];
  1453.     [window flushWindow];
  1454.     [change endChange];
  1455.     
  1456.     return self;
  1457. }
  1458.  
  1459. - sizeToGrid:sender
  1460. {
  1461.     id change;
  1462.  
  1463.     change = [[DimensionsGraphicsChange alloc] initGraphicView:self];
  1464.     [change startChange];
  1465.     [self graphicsPerform:@selector(sizeToGrid:) with:self];
  1466.     [window flushWindow];
  1467.     [change endChange];
  1468.     
  1469.     return self;
  1470. }
  1471.  
  1472. - enableGrid:sender
  1473. /*
  1474.  * If the tag of the sender is non-zero, then gridding is enabled.
  1475.  * If the tag is zero, then gridding is disabled.
  1476.  */
  1477. {
  1478.     [self setGridEnabled:[sender selectedTag] ? YES : NO];
  1479.     return self;
  1480. }
  1481.  
  1482. - hidRUcd:sender
  1483. /*
  1484.  * If the tag of the sender is non-zero, then the grid is made visible
  1485.  * otherwise, it is hidden (but still conceivable in effect).
  1486.  */
  1487. {
  1488.     [self setGridVisible:[sender selectedTag] ? YES : NO];
  1489.     return self;
  1490. }
  1491.  
  1492. - showLinks:sender
  1493. /*
  1494.  * If the tag of the sender is non-zero, then linked items are
  1495.  * shown with a border around them (see redrawLinkOutlines: in gvLinks.m).
  1496.  */
  1497. {
  1498.     [linkManager setLinkOutlinesVisible:[sender selectedTag] ? YES : NO];
  1499.     return self;
  1500. }
  1501.  
  1502. - spellCheck:sender
  1503. {
  1504.     int i;
  1505.     NXCoord curY, newY, maxY = 0.0;
  1506.     NXCoord curX, newX, maxX = 0.0;
  1507.     NXRect egbounds, gbounds;
  1508.     id fr = [window firstResponder];
  1509.     Graphic *graphic, *editingGraphic, *newEditingGraphic = nil;
  1510.  
  1511.     if (![fr isKindOf:[Text class]] || ![[NXSpellChecker sharedInstance] checkSpelling:NX_CheckSpellingToEnd of:fr]) {
  1512.     if ([fr isKindOf:[Text class]]) {
  1513.         editingGraphic = [fr delegate];
  1514.         [editingGraphic getBounds:&egbounds];
  1515.         curY = egbounds.origin.y + egbounds.size.height;
  1516.         curX = egbounds.origin.x;
  1517.     } else {
  1518.         curX = 0.0;
  1519.         curY = 10000.0;
  1520.     }
  1521.     maxY = 0.0; maxX = 10000.0;
  1522.     for (i = [glist count]-1; i >= 0; i--) {
  1523.         graphic = [glist objectAt:i];
  1524.         if ([graphic isKindOf:[TextGraphic class]]) {
  1525.         [graphic getBounds:&gbounds];
  1526.         newY = gbounds.origin.y + gbounds.size.height;
  1527.         newX = gbounds.origin.x;
  1528.         if ((newY > maxY || (newY == maxY && newX < maxX)) && (newY < curY || (newY == curY && newX > curX))) {
  1529.             maxY = newY;
  1530.             maxX = newX;
  1531.             newEditingGraphic = graphic;
  1532.         }
  1533.         }
  1534.     }
  1535.     [window makeFirstResponder:self];
  1536.     if (newEditingGraphic) {
  1537.         [newEditingGraphic edit:NULL in:editView];
  1538.         return [self spellCheck:sender];
  1539.     }
  1540.     }
  1541.  
  1542.     return self;
  1543. }
  1544.  
  1545. - showGuessPanel:sender
  1546. {
  1547.     return [[[NXSpellChecker sharedInstance] spellingPanel] makeKeyAndOrderFront:sender];
  1548. }
  1549.  
  1550. /* Cover-Sheet items (see TextGraphic.m). */
  1551.  
  1552. - doAddCoverSheetEntry:sender localizable:(BOOL)flag
  1553. {
  1554.     const char *entry = [[sender selectedCell] altTitle];
  1555.     if (!entry || !*entry) entry = [[sender selectedCell] title];
  1556.     [self placeGraphic:[[TextGraphic allocFromZone:[self zone]] initFormEntry:entry localizable:flag] at:NULL];
  1557.     return self;
  1558. }
  1559.  
  1560. - addLocalizableCoverSheetEntry:sender
  1561. {
  1562.     return [self doAddCoverSheetEntry:sender localizable:YES];
  1563. }
  1564.  
  1565. - addCoverSheetEntry:sender
  1566. {
  1567.   RUdturn [self doAddCoverSheetEntry:sender localizable:NO];
  1568. }
  1569.  
  1570. /* The venerable print: method. */
  1571.  
  1572. - print:sender
  1573. {
  1574.     return [self printPSCode:sender];
  1575. }
  1576.  
  1577. /*
  1578.  * Target/Action methods to change Graphic parameters from a Control.
  1579.  * If the sender is a Matrix, then the selectedRow is used to determine
  1580.  * the value to use (for linecap, linearrow, etc.) otherwise, the
  1581.  * sender's floatValue or intValue is used (whichever is appropriate).
  1582.  * This allows interface builders the flexibility to design different
  1583.  * ways of setting those values.
  1584.  */
  1585.  
  1586. - takeGridValueFrom:sender
  1587. {
  1588.     [self setGridSpacing:[sender intValue]];
  1589.     return self;
  1590. }
  1591.  
  1592. - takeGridGrayFrom:sender
  1593. {
  1594.     [self setGridGray:[sender floatValue]];
  1595.     return self;
  1596. }
  1597.  
  1598. - takeGrayValueFrom:sender
  1599. {
  1600.     float value;
  1601.  
  1602.     value = [sender floatValue];
  1603.     [self graphicsPerform:@selector(setGray:) with:&value];
  1604.     [window flushWindow];
  1605.  
  1606.     return self;
  1607. }
  1608.  
  1609. - takeLineWidthFrom:sender
  1610. {
  1611.     id change;
  1612.     float width = [sender floatValue];
  1613.  
  1614.     change = [[LineWidthGraphicsChange alloc] initGraphicView:self lineWidth:width];
  1615.     [change startChange];
  1616.     [self graphicsPerform:@selector(setLineWidth:) with:&width];
  1617.     [window flushWindow];
  1618.     [change endChange];
  1619.  
  1620.     return self;
  1621. }
  1622.  
  1623. - takeLineJoinFrom:sender
  1624. {
  1625.     int joinValue;
  1626.     id change;
  1627.  
  1628.     if ([sender respondsTo:@selector(selectedRow)])
  1629.         joinValue = [sender selectedRow];
  1630.     else
  1631.         joinValue = [sender intValue];
  1632.     
  1633.     change = [[LineJoinGraphicsChange alloc] initGraphicView:self lineJoin:joinValue];
  1634.     [change startChange];
  1635.     [self graphicsPerform:@selector(setLineJoin:) with:(void *)joinValue];
  1636.     [window flushWindow];
  1637.     [change endChange];
  1638.  
  1639.     return self;
  1640. }
  1641.  
  1642. - takeLineCapFrom:sender
  1643. {
  1644.     int capValue;
  1645.     id change;
  1646.  
  1647.     if ([sender respondsTo:@selector(selectedRow)])
  1648.         capValue = [sender selectedRow];
  1649.     else
  1650.         capValue = [sender intValue];
  1651.     
  1652.     change = [[LineCapGraphicsChange alloc] initGraphicView:self lineCap:capValue];
  1653.     [change startChange];
  1654.     [self graphicsPerform:@selector(setLineCap:) with:(void *)capValue];
  1655.     [window flushWindow];
  1656.     [change endChange];
  1657.  
  1658.     return self;
  1659. }
  1660.  
  1661. - takeLineArrowFrom:sender
  1662. {
  1663.     int arrowValue;
  1664.     id change;
  1665.  
  1666.     if ([sender respondsTo:@selector(selectedRow)])
  1667.         arrowValue = [sender selectedRow];
  1668.     else
  1669.         arrowValue = RUeder intValue];
  1670.     
  1671.     change = [[ArrowGraphicsChange alloc] initGraphicView:self lineArrow:arrowValue];
  1672.     [change startChange];
  1673.     [self graphicsPerform:@selector(setLineArrow:) with:(void *)arrowValue];
  1674.     [window flushWindow];
  1675.     [change endChange];
  1676.  
  1677.     return self;
  1678. }
  1679.  
  1680. - takeFillValueFrom:sender
  1681. {
  1682.     int fillValue;
  1683.     id change;
  1684.  
  1685.     if ([sender respondsTo:@selector(selectedRow)])
  1686.         fillValue = [sender selectedRow];
  1687.     else
  1688.         fillValue = [sender intValue];
  1689.     
  1690.     change = [[FillGraphicsChange alloc] initGraphicView:self fill:fillValue];
  1691.     [change startChange];
  1692.     [self graphicsPerform:@selector(setFill:) with:(void *)fillValue];
  1693.     [window flushWindow];
  1694.     [change endChange];
  1695.  
  1696.     return self;
  1697. }
  1698.  
  1699. - takeFrameValueFrom:sender
  1700. {
  1701.     if ([sender respondsTo:@selector(selectedRow)]) {
  1702.     [self graphicsPerform:@selector(setFramed:) with:(void *)[sender selectedRow]];
  1703.     } else {
  1704.     [self graphicsPerform:@selector(setFramed:) with:(void *)[sender intValue]];
  1705.     }
  1706.     [window flushWindow];
  1707.     return self;
  1708. }
  1709.  
  1710. - takeLineColorFrom:sender
  1711. {
  1712.     id change;
  1713.     NXColor color = [sender color];
  1714.  
  1715.     change = [[LineColorGraphicsChange alloc] initGraphicView:self color:&color];
  1716.     [change startChange];
  1717.     [self graphicsPerform:@selector(setLineColor:) with:&color];
  1718.     [window flushWindow];
  1719.     [change endChange];
  1720.  
  1721.     return self;
  1722. }
  1723.  
  1724. - takeFillColorFrom:sender
  1725. {
  1726.     id change;
  1727.     NXColor color = [sender color];
  1728.  
  1729.     change = [[FillGraphicsChange alloc] initGraphicView:self];
  1730.     [change startChange];
  1731.     [self graphicsPerform:@selector(setFillColor:) with:&color];
  1732.     [window flushWindow];
  1733.     [change endChange];
  1734.  
  1735.     return self;
  1736. }
  1737.  
  1738. - takeFormEntryStatusFrom:sender
  1739. {
  1740.     [self graphicsPerform:@selector(setFormEntry:) with:(void *)[sender intValue]];
  1741.     [window flushWindow];
  1742.     return self;
  1743. }
  1744.  
  1745. - changeFont:sender
  1746. {
  1747.     id change;
  1748.  
  1749.     if ([window firstResponder] == self) {
  1750.     change = [[MultipleChange alloc] initChangeName:FONT_OPERATION];
  1751.     [change startChange];
  1752.         [self graphicsPerform:@selector(changeFont:) with:sender];
  1753.         [window flushWindow];
  1754.     [change endChange];
  1755.     }
  1756.     return self;
  1757. }
  1758.  
  1759. /* Archiver-related methods. */
  1760.  
  1761. - awake
  1762. /*
  1763.  * After the GraphicView is unarchived, its cache must be created.
  1764.  * If we are loading in this GraphicView just to print it, then we need
  1765.  * not load up our cache.
  1766.  */
  1767. {
  1768.     PSRUf();
  1769.     if (!InMsgPrint) {
  1770.     cacheWindow = createCache(&bounds.size, [self zone]);
  1771.     [self cache:&bounds andUpdateLinks:NO];
  1772.     }
  1773.     initClassVars();
  1774.     [self registerForDragging];
  1775.     return [super awake];
  1776. }
  1777.  
  1778. - write:(NXTypedStream *)stream
  1779. /*
  1780.  * Writes out the glist and the flags.
  1781.  * No need to write out the slist since it can be regenerated from the glist.
  1782.  * We also ensure that no Text object that might be a subview of the
  1783.  * editView gets written out by removing all subviews of the editView.
  1784.  */
  1785. {
  1786.     [super write:stream];
  1787.     NXWriteTypes(stream, "@sf", &glist, &gvFlags, &gridGray);
  1788.     NXWriteObject(stream, editView);
  1789.     return self;
  1790. }
  1791.  
  1792. - read:(NXTypedStream *)stream
  1793. /*
  1794.  * Reads in the glist and the flags, and regenerates the slist from the glist.
  1795.  */
  1796. {
  1797.     int i;
  1798.     List *evsvs;
  1799.     Graphic *graphic, *newGraphic;
  1800.  
  1801.     [super read:stream];
  1802.     NXReadTypes(stream, "@sf", &glist, &gvFlags, &gridGray);
  1803.     for (i = [glist count]-1; i >= 0; i--) {
  1804.     graphic = [glist objectAt:i];
  1805.     newGraphic = [graphic replaceWithImage];
  1806.     if (graphic != newGraphic) {
  1807.         if (graphic) {
  1808.         [glist replaceObjectAt:i with:newGraphic];
  1809.         } else {
  1810.         [glist removeObjectAt:i];
  1811.         }
  1812.     }
  1813.     }
  1814.     [self getSelection];
  1815.     [self resetGUP];
  1816.     if (NXTypedStreamClassVersion(stream, [self name]) < 1) {
  1817.     editView = createEditView(self);
  1818.     } else {
  1819.     editView = NXReadObject(stream);
  1820.     }
  1821.  
  1822.     evsvs = [editView subviews];
  1823.     for (i = [evsvs count]-1; i >= 0; i--) [[evsvs objectAt:i] free];
  1824.  
  1825.     return self;
  1826. }
  1827.  
  1828. /* Methods to deal with being/becoming the First Responder */
  1829.  
  1830. /* Strings that appear in menus. */
  1831.  
  1832. static const char *HIDE_GRID;
  1833. static const char *SHOW_GRID;
  1834. static const char *TURN_GRID_OFF;
  1835. static const char *TURN_GRID_ON;
  1836. static const char *SHOW_LINKS;
  1837. static const char *HIDE_LINKS;
  1838. static BOOL menuStringsInitted = NO;
  1839.  
  1840. static void initMenuItemStrings(void)
  1841. {
  1842.     HIDE_GRID = NXLocalString("Hide Grid", NULL, "Menu item which hides the background grid which the user can overlay on his document.");
  1843.     SHOW_GRID = NXLocalString("Show Grid", NULL, "Menu item which shows a background grid which the user can overlay on his document.");
  1844.     TURN_GRID_OFF = NXLocalString("Turn Grid Off", NULL, "Menu item which turns off the background grid so that items do not move and resize on even grid boundaries.  It does not hide the griRUg it is currently showing.");
  1845.     TURN_GRID_ON = NXLocalString("Turn Grid On", NULL, "Menu item which turns a background grid on so that the user's actions are rounded to even grid boundaries.  It does not show the grid if the grid is currently hidden.");
  1846.     SHOW_LINKS = NXLocalString("Show Links", NULL, "Menu item which turns on the borders around linked items (using Object Links).");
  1847.     HIDE_LINKS = NXLocalString("Hide Links", NULL, "Menu item which turns off the borders around linked items (using Object Links).");
  1848.     menuStringsInitted = YES;
  1849. }
  1850.  
  1851. /* Validates whether a menu command makes sense now */
  1852.  
  1853. static BOOL updateMenuItem(MenuCell *menuCell, const char *zeroItem, const char *oneItem, BOOL state)
  1854. {
  1855.     if (state) {
  1856.     if (strcmp([menuCell title], zeroItem) || [menuCell tag] != 0) {
  1857.         [menuCell setTitle:zeroItem];
  1858.         [menuCell setTag:0];
  1859.         [menuCell setEnabled:NO];    // causes it to get redrawn
  1860.     }
  1861.     } else {
  1862.     if (strcmp([menuCell title], oneItem) || [menuCell tag] != 1) {
  1863.         [menuCell setTitle:oneItem];
  1864.         [menuCell setTag:1];
  1865.         [menuCell setEnabled:NO];    // causes it to get redrawn
  1866.     }
  1867.     }
  1868.  
  1869.     return YES;
  1870. }
  1871.  
  1872. - (BOOL)validateCommand:(MenuCell *)menuCell
  1873. /*
  1874.  * Can be called to see if the specified action is valid on this view now.
  1875.  * It returns NO if the GraphicView knows that action is not valid now,
  1876.  * otherwise it returns YES.  Note the use of the Pasteboard change
  1877.  * count so that the GraphicView does not have to look into the Pasteboard
  1878.  * every time paste: is validated.
  1879.  */
  1880. {
  1881.     Pasteboard *pb;
  1882.     int i, count, gcount;
  1883.     SEL action = [menuCell action];
  1884.     static BOOL pboardHasPasteableType = NO;
  1885.     static BOOL pboardHasPasteableLink = NO;
  1886.     static int cachedPasteboardChangeCount = -1;
  1887.  
  1888.     if (!menuStringsInitted) initMenuItemStrings();
  1889.     if (action == @selector(bringToFront:)) {
  1890.     if ((count = [slist count]) && [glist count] > count) {
  1891.         for (i = 0; i < count; i++) {
  1892.         if ([slist objectAt:i] != [glist objectAt:i]) {
  1893.             return YES;
  1894.         }
  1895.         }
  1896.     }
  1897.     return NO;
  1898.     } else if (action == @selector(sendToBack:)) {
  1899.     if ((count = [slist count]) && (gcount = [glist count]) > count) {
  1900.         for (i = 1; i <= count; i++) {
  1901.         if ([slist objectAt:count-i] != [glist objectAt:gcount-i]) {
  1902.             return YES;
  1903.         }
  1904.         }
  1905.     }
  1906.     return NO;
  1907.     } else if (action == @selector(group:) ||
  1908.     actioRUh @selector(align:)) {
  1909.     return([slist count] > 1);
  1910.     } else if (action == @selector(ungroup:)) {
  1911.     return(gvFlags.groupInSlist && [slist count] > 0);
  1912.     } else if (action == @selector(spellCheck:)) {
  1913.     for (i = [glist count]-1; i >= 0; i--) if ([[glist objectAt:i] isKindOf:[TextGraphic class]]) return YES;
  1914.     return NO;
  1915.     } else if (action == @selector(deselectAll:) ||
  1916.     action == @selector(lock:) ||
  1917.     action == @selector(changeAspectRatio:) ||
  1918.     action == @selector(cut:) ||
  1919.     action == @selector(copy:)) {
  1920.     return([slist count] > 0);
  1921.     } else if (action == @selector(alignToGrid:) ||
  1922.     action == @selector(sizeToGrid:)) {
  1923.     return(GRID > 1 && [slist count] > 0);
  1924.     } else if (action == @selector(unlock:)) {
  1925.     return gvFlags.locked;
  1926.     } else if (action == @selector(selectAll:)) {
  1927.     return([glist count] > [slist count]);
  1928.     } else if (action == @selector(paste:) || action == @selector(pasteAndLink:) || action == @selector(link:)) {
  1929.     pb = [Pasteboard new];
  1930.     count = [pb changeCount];
  1931.     if (count != cachedPasteboardChangeCount) {
  1932.         cachedPasteboardChangeCount = count;
  1933.         pboardHasPasteableType = (DrawPasteType([pb types]) != NULL);
  1934.         pboardHasPasteableLink = pboardHasPasteableType ? IncludesType([pb types], NXDataLinkPboardType) : NO;
  1935.     }
  1936.     return (action == @selector(paste:)) ? pboardHasPasteableType : pboardHasPasteableLink;
  1937.     } else if (action == @selector(hideGrid:)) {
  1938.     return (gvFlags.grid >= 4) ? updateMenuItem(menuCell, HIDE_GRID, SHOW_GRID, [self gridIsVisible]) : NO;
  1939.     } else if (action == @selector(enableGrid:)) {
  1940.     return (gvFlags.grid > 1) ? updateMenuItem(menuCell, TURN_GRID_OFF, TURN_GRID_ON, [self gridIsEnabled]) : NO;
  1941.     } else if (action == @selector(showLinks:)) {
  1942.     return linkManager ? updateMenuItem(menuCell, HIDE_LINKS, SHOW_LINKS, [linkManager areLinkOutlinesVisible]) : NO;
  1943.     } else if (action == @selector(print:)) {
  1944.     return([glist count] > 0);
  1945.     }
  1946.  
  1947.     return YES;
  1948. }
  1949.  
  1950. /* Useful scrolling routines. */
  1951.  
  1952. - scrollGraphicToVisible:(Graphic *)graphic
  1953. {
  1954.     NXPoint p;
  1955.     NXRect eb;
  1956.  
  1957.     p = bounds.origin;
  1958.     NXContainRect([graphic getExtendedBounds:&eb], &bounds);
  1959.     p.x -= bounds.origin.x;
  1960.     p.y -= bounds.origin.y;
  1961.     if (p.x || p.y) {
  1962.     [graphic moveBy:&p];
  1963.     bounds.origin.x += p.x;
  1964.     bounds.origin.y += p.y;
  1965.     [self scrollRectToVisible:[graphic getExtendedBounds:&eb]];
  1966.     }
  1967.  
  1968.     returRUilf;
  1969. }
  1970.  
  1971. - scrollPointToVisible:(const NXPoint *)point
  1972. {
  1973.     NXRect r;
  1974.  
  1975.     r.origin.x = point->x - 5.0;
  1976.     r.origin.y = point->y - 5.0;
  1977.     r.size.width = r.size.height = 10.0;
  1978.  
  1979.     return [self scrollRectToVisible:&r];
  1980. }
  1981.  
  1982. - scrollSelectionToVisible
  1983. {
  1984.     NXRect sbounds;
  1985.  
  1986.     if ([slist count]) {
  1987.     [self getBBox:&sbounds of:slist];
  1988.     return [self scrollRectToVisible:&sbounds];
  1989.     }
  1990.  
  1991.     return self;
  1992. }
  1993.  
  1994. /* Private selection management methods. */
  1995.  
  1996. - createCacheWindow:(Window *)selectioncache
  1997. /*
  1998.  * Shares an off-screen window used to draw the selection in so that it
  1999.  * can be dragged around.  If the current off-screen window is equal in
  2000.  * size or larger than the passed size, then it is simply returned.
  2001.  * Otherwise, it is resized to be the specified size.
  2002.  */
  2003. {
  2004.     NXRect rect;
  2005.  
  2006.     if (!selectioncache) {
  2007.     rect = bounds;
  2008.     selectioncache = [Window newContent:&rect
  2009.                       style:NX_PLAINSTYLE
  2010.                     backing:NX_RETAINED
  2011.                  buttonMask:0
  2012.                       defer:NO];
  2013.     [selectioncache reenableDisplay];
  2014.     } else {
  2015.     [selectioncache getFrame:&rect];
  2016.     if (rect.size.width < bounds.size.width || rect.size.height < bounds.size.height) {
  2017.         [selectioncache sizeWindow:MAX(rect.size.width, bounds.size.width)
  2018.                       :MAX(rect.size.height, bounds.size.height)];
  2019.     }
  2020.     }
  2021.  
  2022.     return selectioncache;
  2023. }
  2024.  
  2025. - selectionCache
  2026. {
  2027.     static Window *selectioncache = nil;
  2028.     return selectioncache = [self createCacheWindow:selectioncache];
  2029. }
  2030.  
  2031. - getBBox:(NXRect *)bbox of:(List *)list extended:(BOOL)extended
  2032. /*
  2033.  * Returns a rectangle which encloses all the objects in the list.
  2034.  */
  2035. {
  2036.     int i;
  2037.     NXRect eb;
  2038.  
  2039.     i = [list count];
  2040.     if (i) {
  2041.     if (extended) {
  2042.         [[list objectAt:--i] getExtendedBounds:bbox];
  2043.         while (i--) NXUnionRect([[list objectAt:i] getExtendedBounds:&eb], bbox);
  2044.     } else {
  2045.         [[list objectAt:--i] getBounds:bbox];
  2046.         while (i--) {
  2047.         [[list objectAt:i] getBounds:&eb];
  2048.         NXUnionRect(&eb, bbox);
  2049.         }
  2050.     }
  2051.     } else {
  2052.     bbox->size.width = bbox->size.height = 0.0;
  2053.     }
  2054.  
  2055.     return self;
  2056. }
  2057.  
  2058. - recacheSelection:(BOOL)updateLinks
  2059.  /*
  2060.   * Redraws the selection in the off-screen cache (not the selection cache),
  2061.   * then composites it back on to the screen.
  2062.   */
  2063. {
  2064.     NXRect sbounds;
  2065.     
  2066.     if ([slist count]) {
  2067.     [self getBBox:&sbounds of:slist];
  2068.     gvFlags.cacheing = YES;
  2069.     [self drawSelf:&sbounds :1];
  2070.     gvFlags.cacheing = RUp    [self display:&sbounds :1];
  2071.     if (updateLinks && !gvFlags.suspendLinkUpdate) [self updateTrackedLinks:&sbounds];
  2072.     }
  2073.  
  2074.     return self;
  2075. }
  2076.  
  2077. - recacheSelection
  2078. {
  2079.     return [self recacheSelection:YES];
  2080. }
  2081.  
  2082. - compositeSelection:(const NXRect *)sbounds from:(int)gstate
  2083. /*
  2084.  * Composites from the specified gstate whatever part of sbounds is
  2085.  * currently visible in the View.
  2086.  */
  2087. {
  2088.     PScomposite(0.0, 0.0, NX_WIDTH(sbounds), NX_HEIGHT(sbounds),
  2089.                 gstate, NX_X(sbounds), NX_Y(sbounds), NX_SOVER);
  2090.     [window flushWindow];
  2091.     NXPing();
  2092.     return self;
  2093. }
  2094.  
  2095. - (int)cacheList:(List *)aList into:(Window *)selectionCache withTransparentBackground:(BOOL)transparentBackground
  2096.  /*
  2097.   * Caches the selection into the application-wide selection cache
  2098.   * window (a window which has alpha in it).  See also: selectionCache:.
  2099.   * It draws the objects without their knobbies in the selection cache,
  2100.   * but it leaves them selected.  Returns the gstate of the selection
  2101.   * cache.
  2102.   */
  2103. {
  2104.     int i;
  2105.     NXRect sbounds, scframe;
  2106.  
  2107.     [self getBBox:&sbounds of:aList];
  2108.     [selectionCache getFrame:&scframe];
  2109.     if (scframe.size.width < sbounds.size.width || scframe.size.height < sbounds.size.height) {
  2110.     [selectionCache sizeWindow:MAX(scframe.size.width, sbounds.size.width)
  2111.                   :MAX(scframe.size.height, sbounds.size.height)];
  2112.     }
  2113.     [[selectionCache contentView] lockFocus];
  2114.     PSsetgray(NX_WHITE);
  2115.     PSsetalpha(transparentBackground ? 0.0 : 1.0);    /* 0.0 means fully transparent */
  2116.     PStranslate(- sbounds.origin.x, - sbounds.origin.y);
  2117.     sbounds.size.width += 1.0;
  2118.     sbounds.size.height += 1.0;
  2119.     NXRectFill(&sbounds);
  2120.     sbounds.size.width -= 1.0;
  2121.     sbounds.size.height -= 1.0;
  2122.     PSsetalpha(1.0);                    /* set back to fully opaque */
  2123.     i = [aList count];
  2124.     while (i--) [[[[aList objectAt:i] deselect] draw:NULL] select];
  2125.     [Graphic showFastKnobFills];
  2126.     PStranslate(sbounds.origin.x, sbounds.origin.y);
  2127.     [[selectionCache contentView] unlockFocus];
  2128.  
  2129.     return [selectionCache gState];
  2130. }
  2131.  
  2132. - (int)cacheList:(List *)aList into:(Window *)selectionCache
  2133. {
  2134.     return [self cacheList:aList into:selectionCache withTransparentBackground:YES];
  2135. }
  2136.  
  2137. - (int)cacheSelection
  2138. {
  2139.     return [self cacheList:slist into:[self selectionCache] withTransparentBackground:YES];
  2140. }
  2141.  
  2142. /* Other private methods. */
  2143.  
  2144. - cacheGraphic:(Graphic *)grRUqc
  2145.  /*
  2146.   * Draws the graphic into the off-screen cache, then composites
  2147.   * it back to the screen.
  2148.   * NOTE: This ONLY works if the graphic is on top of the list!
  2149.   * That is why it is a private method ...
  2150.   */
  2151. {
  2152.     NXRect eb;
  2153.  
  2154.     [[cacheWindow contentView] lockFocus];
  2155.     [graphic draw:NULL];
  2156.     [Graphic showFastKnobFills];
  2157.     [[cacheWindow contentView] unlockFocus];
  2158.     [self display:[graphic getExtendedBounds:&eb] :1];
  2159.  
  2160.     return self;
  2161. }
  2162.  
  2163. - resetGUP
  2164. /*
  2165.  * The "GUP" is the Grid User Path.  It is a user path which draws a grid
  2166.  * the size of the bounds of the GraphicView.  This gets called whenever
  2167.  * the View is resized or the grid spacing is changed.  It sets up all
  2168.  * the arguments to DPSDoUserPath() called in drawSelf::.
  2169.  */
  2170. {
  2171.     int x, y, i, j;
  2172.     short w, h;
  2173.  
  2174.     if (gvFlags.grid < 4) return self;
  2175.  
  2176.     x = (int)bounds.size.width / (gvFlags.grid ? gvFlags.grid : 1);
  2177.     y = (int)bounds.size.height / (gvFlags.grid ? gvFlags.grid : 1);
  2178.     gupLength = (x << 2) + (y << 2);
  2179.     if (gupCoords) {
  2180.     NX_FREE(gupCoords);
  2181.     NX_FREE(gupOps);
  2182.     NX_FREE(gupBBox);
  2183.     }
  2184.     NX_ZONEMALLOC([self zone], gupCoords, short, gupLength);
  2185.     NX_ZONEMALLOC([self zone], gupOps, char, gupLength >> 1);
  2186.     NX_ZONEMALLOC([self zone], gupBBox, short, 4);
  2187.     w = bounds.size.width;
  2188.     h = bounds.size.height;
  2189.     j = 0;
  2190.     for (i = 1; i <= y; i++) {
  2191.     gupCoords[j++] = 0.0;
  2192.     gupCoords[j++] = i * (gvFlags.grid ? gvFlags.grid : 1);
  2193.     gupCoords[j++] = w;
  2194.     gupCoords[j] = gupCoords[j-2];
  2195.     j++;
  2196.     }
  2197.     for (i = 1; i <= x; i++) {
  2198.     gupCoords[j++] = i * (gvFlags.grid ? gvFlags.grid : 1);
  2199.     gupCoords[j++] = 0.0;
  2200.     gupCoords[j] = gupCoords[j-2];
  2201.     j++;
  2202.     gupCoords[j++] = h;
  2203.     }
  2204.     i = gupLength >> 1;
  2205.     while (i) {
  2206.     gupOps[--i] = dps_lineto;
  2207.     gupOps[--i] = dps_moveto;
  2208.     }
  2209.     gupBBox[0] = gupBBox[1] = 0;
  2210.     gupBBox[2] = bounds.size.width + 1;
  2211.     gupBBox[3] = bounds.size.height + 1;
  2212.  
  2213.     return self;
  2214. }
  2215.  
  2216. #define MOVE_MASK NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK
  2217.  
  2218. - (BOOL)move:(NXEvent *)event
  2219. /*
  2220.  * Moves the selection by cacheing the selected graphics into the
  2221.  * selection cache, then compositing them repeatedly as the user
  2222.  * moves the mouse.  The tracking loop uses TIMER events to autoscroll
  2223.  * at regular intervals.  TIMER events do not have valid mouse coordinates,
  2224.  * so the last coordinates are saved and restored when there is a TIMER evRUr
  2225.  */
  2226. {
  2227.     int gstate;
  2228.     NXEvent peek;
  2229.     NXCoord dx, dy;
  2230.     NXTrackingTimer *timer = NULL;
  2231.     NXPoint p, start, last, sboundspad;
  2232.     NXRect minbounds, sbounds, startbounds, visibleRect;
  2233.     BOOL canScroll, tracking = YES, alternate, horizConstrain = NO, vertConstrain = NO, hideCursor;
  2234.  
  2235.     last = event->location;
  2236.     alternate = (event->flags & NX_ALTERNATEMASK) ? YES : NO;
  2237.  
  2238.     event = [NXApp getNextEvent:MOVE_MASK];
  2239.     if (event->type == NX_MOUSEUP) return NO;
  2240.  
  2241.     hideCursor = NXGetDefaultValue([NXApp appName], "HideCursorOnMove") ? YES : NO;
  2242.     if (hideCursor) PShidecursor();
  2243.  
  2244.     [self convertPoint:&last fromView:nil];
  2245.     [self grid:&last];
  2246.  
  2247.     [self lockFocus];
  2248.  
  2249.     gstate = [self cacheSelection];
  2250.     gvFlags.suspendLinkUpdate = YES;    /* we'll update links when the move is complete */
  2251.     [self graphicsPerform:@selector(deactivate)];
  2252.     gvFlags.suspendLinkUpdate = NO;
  2253.     [self getBBox:&sbounds of:slist];
  2254.     startbounds = sbounds;
  2255.     [self getBBox:&minbounds of:slist extended:NO];
  2256.     sboundspad.x = minbounds.origin.x - sbounds.origin.x;
  2257.     sboundspad.y = minbounds.origin.y - sbounds.origin.y;
  2258.     [self compositeSelection:&sbounds from:gstate];
  2259.  
  2260.     [self getVisibleRect:&visibleRect];
  2261.     canScroll = !NXEqualRect(&visibleRect, &bounds);
  2262.  
  2263.     start = sbounds.origin;
  2264.  
  2265.     while (tracking) {
  2266.     p = event->location;
  2267.     [self convertPoint:&p fromView:nil];
  2268.     [self grid:&p];
  2269.     dx = p.x - last.x;
  2270.     dy = p.y - last.y;
  2271.     if (dx || dy) {
  2272.         [self drawSelf:&sbounds :1];
  2273.         if (alternate && (dx || dy)) {
  2274.         if (ABS(dx) > ABS(dy)) {
  2275.             horizConstrain = YES;
  2276.             dy = 0.0;
  2277.         } else {
  2278.             vertConstrain = YES;
  2279.             dx = 0.0;
  2280.         }
  2281.         alternate = NO;
  2282.         } else if (horizConstrain) {
  2283.         dy = 0.0;
  2284.         } else if (vertConstrain) {
  2285.         dx = 0.0;
  2286.         }
  2287.         NXOffsetRect(&sbounds, dx, dy);
  2288.         minbounds.origin.x = sbounds.origin.x + sboundspad.x;
  2289.         minbounds.origin.y = sbounds.origin.y + sboundspad.y;
  2290.         [self tryToPerform:@selector(updateRulers:) with:(void *)&minbounds];
  2291.         if (!canScroll || NXContainsRect(&visibleRect, &sbounds)) {
  2292.         [self compositeSelection:&sbounds from:gstate];
  2293.         stopTimer(timer);
  2294.         }
  2295.         last = p;
  2296.     }
  2297.     tracking = (event->type != NX_MOUSEUP);
  2298.     if (tracking) {
  2299.         if (canScroll && !NXContainsRect(&visibleRect, &sbounds)) {
  2300.         [window disableFlushWindow];
  2301.         [self scrollPointToVisiRUs&p]; // actually we want to keep the "edges" of the
  2302.                         // Graphic being resized that were visible when
  2303.                         // the move started visible throughout the
  2304.                         // moving session
  2305.         [self getVisibleRect:&visibleRect];
  2306.         [self compositeSelection:&sbounds from:gstate];
  2307.         [[window reenableFlushWindow] flushWindow];
  2308.         startTimer(timer);
  2309.         }
  2310.         p = event->location;
  2311.         if (![NXApp peekNextEvent:MOVE_MASK into:&peek]) {
  2312.         event = [NXApp getNextEvent:MOVE_MASK|NX_TIMERMASK];
  2313.         } else {
  2314.         event = [NXApp getNextEvent:MOVE_MASK];
  2315.         }
  2316.         if (event->type == NX_TIMER) event->location = p;
  2317.     }
  2318.     }
  2319.  
  2320.     if (canScroll) stopTimer(timer);
  2321.  
  2322.     if (hideCursor) PSshowcursor();
  2323.  
  2324.     p.x = sbounds.origin.x - start.x;
  2325.     p.y = sbounds.origin.y - start.y;
  2326.     if (p.x || p.y)
  2327.         [self moveGraphicsBy:&p andDraw:NO];
  2328.  
  2329.     gvFlags.suspendLinkUpdate = YES;    /* we'll update links when the move is complete */
  2330.     [self graphicsPerform:@selector(activate)];
  2331.     gvFlags.suspendLinkUpdate = NO;
  2332.     NXUnionRect(&sbounds, &startbounds);
  2333.     [self updateTrackedLinks:&startbounds];
  2334.  
  2335.     [self tryToPerform:@selector(updateRulers:) with:nil];
  2336.  
  2337.     [window flushWindow];
  2338.     [self unlockFocus];
  2339.  
  2340.     return YES;
  2341. }
  2342.  
  2343. - moveGraphicsBy:(NXPoint *)vector andDraw:(BOOL)drawFlag
  2344. {
  2345.     id change;
  2346.  
  2347.     change = [[MoveGraphicsChange alloc] initGraphicView:self vector:vector];
  2348.     [change startChange];
  2349.     if (drawFlag) {
  2350.         [self graphicsPerform:@selector(moveBy:) with:(id)vector];
  2351.     } else {
  2352.         [slist makeObjectsPerform:@selector(moveBy:) with:(id)vector];
  2353.     }
  2354.     [change endChange];
  2355.  
  2356.     return self;
  2357. }
  2358.  
  2359. #define DRAG_MASK (NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK|NX_TIMERMASK)
  2360.  
  2361. - dragSelect:(NXEvent *)event
  2362. /*
  2363.  * Allows the user the drag out a box to select all objects either
  2364.  * intersecting the box, or fully contained within the box (depending
  2365.  * on the state of the ALTERNATE key).  After the selection is made,
  2366.  * the slist is updated.
  2367.  */
  2368. {
  2369.     int i;
  2370.     Graphic *graphic;
  2371.     NXPoint p, last, start;
  2372.     NXTrackingTimer *timer = NULL;
  2373.     NXRect visibleRect, eb, region, oldRegion;
  2374.     BOOL mustContain, shift, canScroll, oldRegionSet = NO;
  2375.  
  2376.     p = start = event->location;
  2377.     [self convertPoint:&start fromView:nil];
  2378.     last = start;
  2379.  
  2380.     shift = (event->flags & NX_SHIFTMASK) ? YES : NO;
  2381.     mustContain = (event->flags & NX_ALTERNATEMASK) ? YES : NO;
  2382.  
  2383.     [self lockFocus];
  2384.  
  2385.     [self getVisibleRect:&visibleRect];
  2386.     canScroll = !NXEqualRect(&visibleRect, &bounds);
  2387.     if (canScroll) startTimer(timer);
  2388.  
  2389.     PSsetgray(NX_LTGRAY);
  2390.     PSsetlinewidth(0.0);
  2391.  
  2392.     event = [NXApp getNextEvent:DRAG_MASK];
  2393.     while (event->type != NX_MOUSEUP) {
  2394.     if (event->type == NX_TIMER) event->location = p;
  2395.     p = event->location;
  2396.     [self convertPoint:&p fromView:nil];
  2397.     if (p.x != last.x || p.y != last.y) {
  2398.         getRegion(®ion, &p, &start);
  2399.         [window disableFlushWindow];
  2400.         if (oldRegionSet) {
  2401.         NXInsetRect(&oldRegion, -1.0, -1.0);
  2402.         [self drawSelf:&oldRegion :1];
  2403.         }
  2404.         if (canScroll) {
  2405.         [self scrollRectToVisible:®ion];
  2406.         [self scrollPointToVisible:&p];
  2407.         }
  2408.         PSrectstroke(region.origin.x, region.origin.y, region.size.width, region.size.height);
  2409.         [self tryToPerform:@selector(updateRulers:) with:(void *)®ion];
  2410.         [[window reenableFlushWindow] flushWindow];
  2411.         oldRegion = region; oldRegionSet = YES;
  2412.         last = p;
  2413.         NXPing();
  2414.     }
  2415.     p = event->location;
  2416.     event = [NXApp getNextEvent:DRAG_MASK];
  2417.     }
  2418.  
  2419.     if (canScroll) stopTimer(timerRUD    for (i = [glist count] - 1; i >= 0; i--) {
  2420.      graphic = [glist objectAt:i];
  2421.     [graphic getExtendedBounds:&eb];
  2422.     if (![graphic isLocked] && ![graphic isSelected] && 
  2423.         ((mustContain && NXContainsRect(®ion, &eb)) ||
  2424.          (!mustContain && NXIntersectsRect(®ion, &eb)))) {
  2425.         [graphic select];
  2426.     }
  2427.     }
  2428.     [self getSelection];
  2429.  
  2430.     if (!dragRect) NX_MALLOC(dragRect, NXRect, 1);
  2431.     *dragRect = region;
  2432.  
  2433.     NXInsetRect(®ion, -1.0, -1.0);
  2434.     [self drawSelf:®ion :1];
  2435.     [self recacheSelection:NO];
  2436.  
  2437.     [self tryToPerform:@selector(updateRulers:) with:nil];
  2438.  
  2439.     [self unlockFocus];
  2440.  
  2441.     return self;
  2442. }
  2443.  
  2444. - alignGraphicsBy:(AlignmentType)alignType edge:(NXCoord *)edge
  2445. {
  2446.     SEL    action;
  2447.     id change;
  2448.     
  2449.     change = [[AlignGraphicsChange alloc] initGraphicView:self];
  2450.     [change startChange];
  2451.         action = [GraphicView actionFromAlignType:alignType];
  2452.     [self graphicsPerform:action with:edge];
  2453.     [change endChange];
  2454.  
  2455.     return self;
  2456. }
  2457.  
  2458. - alignBy:(AlignmentType)alignType
  2459. {
  2460.     int i;
  2461.     NXRect rect;
  2462.     Graphic *graphic;
  2463.     NXCoord minEdge = 10000.0;
  2464.     NXCoord maxEdge = 0.0;
  2465.     NXCoord baseline = 0.0;
  2466.  
  2467.     for (i = [slist count]-1; i >= 0 && !baseline; i--) {
  2468.     graphic = [slist objectAt:i];
  2469.     [graphic getBounds:&rect];
  2470.     switch (alignType) {
  2471.         case LEFT:
  2472.          if (rect.origin.x < minEdge) 
  2473.             minEdge = rect.origin.x;
  2474.             break;
  2475.         case RIGHT:
  2476.         if (rect.origin.x + rect.size.width > maxEdge) 
  2477.             maxEdge = rect.origin.x + rect.size.width;
  2478.             break;
  2479.         case BOTTOM:
  2480.         if (rect.origin.y < minEdge) 
  2481.             minEdge = rect.origin.y;
  2482.             break;
  2483.         case TOP:
  2484.         if (rect.origin.y + rect.size.height > maxEdge) 
  2485.             maxEdge = rect.origin.y + rect.size.height;
  2486.             break;
  2487.         case HORIZONTAL_CENTERS:
  2488.         if (rect.origin.y + floor(rect.size.height / 2.0) < minEdge)
  2489.             minEdge = rect.origin.y + floor(rect.size.height / 2.0);
  2490.             break;
  2491.         case VERTICAL_CENTERS:
  2492.         if (rect.origin.x + floor(rect.size.width / 2.0) < minEdge)
  2493.             minEdge = rect.origin.x + floor(rect.size.width / 2.0);
  2494.             break;
  2495.         case BASELINES:
  2496.         baseline = [graphic baseline];
  2497.             break;
  2498.     }
  2499.     }
  2500.  
  2501.     switch (alignType) {
  2502.         case LEFT:
  2503.         case BOTTOM:
  2504.         case HORIZONTAL_CENTERS:
  2505.         case VERTICAL_CENTERS:
  2506.         [self alignGraphicsBy:alignType edge:&minEdge];
  2507.         break;RUE     case RIGHT:
  2508.         case TOP:
  2509.         [self alignGraphicsBy:alignType edge:&maxEdge];
  2510.         break;
  2511.     case BASELINES:
  2512.         if (baseline) [self alignGraphicsBy:alignType edge:&baseline];
  2513.     }
  2514.     [window flushWindow];
  2515.  
  2516.     return self;
  2517. }
  2518.  
  2519. @end
  2520.