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