home *** CD-ROM | disk | FTP | other *** search
/ OpenStep (Enterprise) / OpenStepENTCD.toast / OEDEV / DEV.Z / GraphicView.m < prev    next >
Text File  |  1996-08-13  |  73KB  |  2,474 lines

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