home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OpenStep 4.2J (Developer)
/
os42jdev.iso
/
NextDeveloper
/
Examples
/
AppKit
/
Draw
/
GraphicView.m
< prev
next >
Wrap
Text File
|
1996-08-12
|
73KB
|
2,474 lines
#import <AppKit/psopsNeXT.h> // for PShide/showcursor
#import "draw.h"
@interface FlippedView : NSView
- (BOOL)isFlipped;
@end
@implementation FlippedView : NSView
- (BOOL)isFlipped
{
return YES;
}
@end
/* This file is best read in a window as wide as this comment (so that this comment fits on one line). */
@interface GraphicView(PrivateMethods)
/* Private methods */
- (NSView *)createEditView;
- (NSRect)getBBoxOfArray:(NSArray *)list extended:(BOOL)extended;
- (NSImage *)selectionCache; // the shared cache used to cache the selection only
- (void)recacheSelection:(BOOL)updateLinks; // recaches the selection into the view cache, not the selection cache
- (void)compositeSelection:(NSRect)sbounds; // composites the selection from the selection cache
- (void)cacheSelection; // caches the selection into the selection cache
- (void)dirty:sender;
- (void)resetGUP;
- (BOOL)move:(NSEvent *)event ;
- (void)moveGraphicsBy:(NSPoint)vector andDraw:(BOOL)drawFlag;
- (void)dragSelect:(NSEvent *)event;
- (void)alignGraphicsBy:(AlignmentType)alignType edge:(float *)edge;
- (void)alignBy:(AlignmentType)alignType;
@end
NSString *DrawPboardType = @"Draw Graphic List Type version 3.0";
BOOL InMsgPrint = NO; /* whether we are in msgPrint: */
#define LEFTARROW 172
#define RIGHTARROW 174
#define UPARROW 173
#define DOWNARROW 175
@implementation GraphicView : NSView
/*
* The GraphicView class is the core of a DrawDocument.
*
* It overrides the View methods related to drawing and event handling
* and allows manipulation of Graphic objects.
*
* The user is allowed to select objects, move them around, group and
* ungroup them, change their font, and cut and paste them to the pasteboard.
* It understands multiple formats including PostScript and TIFF as well as
* its own internal format. The GraphicView can also import PostScript and
* TIFF documents and manipulate them as Graphic objects.
*
* This is a very skeleton implementation and is intended purely for
* example purposes. It should be very easy to add new Graphic objects
* merely by subclassing the Graphic class. No code need be added to the
* GraphicView class when a new Graphic subclass is added.
*
* Moving is accomplished using a selection cache which is shared among
* all instances of GraphicView in the application. The objects in the
* selection are drawn using opaque ink on a transparent background so
* that when they are moved around, the user can see through them to the
* objects that are not being moved.
*
* All of the drawing is done in an NSImage which is merely
* composited back to the screen. This makes for very fast redraw of
* areas obscured either by the selection moving or the user's scrolling.
*
* The glist instance variable is just an ordered list of all the Graphics
* in the GraphicView. The slist is an ordered list of the Graphic objects
* in the selection. In the original Draw code it was almost always kept
* in the same order as the glist, but could be jumbled by doing a shift-drag
* select to add to an existing selection. We are now extremely careful about
* keeping the slist in the same order as the glist.
*
* cacheImage is the NSImage into which the objects are
* drawn. Flags: grid is the distance between pixels in the grid
* imposed on drawing; cacheing is used so that drawSelf:: knows when
* to composite from the cache and when to draw into the
* cache; groupInSlist is used to keep track of whether a
* Group Graphic is in the slist so that it knows when to highlight
* the Ungroup entry in the menu.
*
* This class should be able to be used outside the context of this
* application since it takes great pains not to depend on any other objects
* in the application.
*/
/*
* Of course, one should NEVER use global variables in an application, but
* the following is necessary. DrawStatus is
* analogous to the Application Kit's NSDrawingStatus and reflects whether
* we are in some modal loop. By definition, that modal loop is "atomic"
* since we own the mouse during its duration (of course, all bets are off
* if we have multiple mice!).
*/
DrawStatusType DrawStatus = Normal; /* global state reflecting what we
are currently doing (resizing, etc.) */
const float DEFAULT_GRID_GRAY = 0.8333;
static Class currentGraphic = nil; /* won't be used if NSApp knows how
to keep track of the currentGraphic */
static float KeyMotionDeltaDefault = 0.0;
/* Code-cleaning macros */
#define GRID (gvFlags.gridDisabled ? 1.0 : (gvFlags.grid ? (float)gvFlags.grid : 1.0))
#define grid(point) \
(point).x = floor(((point).x / GRID) + 0.5) * GRID; \
(point).y = floor(((point).y / GRID) + 0.5) * GRID;
static NSRect regionFromCorners(NSPoint p1, NSPoint p2)
/*
* Returns the rectangle which has p1 and p2 as its corners.
*/
{
NSRect region;
region.size.width = p1.x - p2.x;
region.size.height = p1.y - p2.y;
if (region.size.width < 0.0) {
region.origin.x = p2.x + region.size.width;
region.size.width = ABS(region.size.width);
} else {
region.origin.x = p2.x;
}
if (region.size.height < 0.0) {
region.origin.y = p2.y + region.size.height;
region.size.height = ABS(region.size.height);
} else {
region.origin.y = p2.y;
}
return region;
}
static BOOL checkForGroup(NSArray *array)
/*
* Looks through the given list searching for objects of the Group class.
* We use this to keep the gvFlags.groupInSlist flag up to date when things
* are removed from the slist (the list of selected objects). That way
* we can efficiently keep the Ungroup menu item up to date.
*/
{
int i = [array count];
while (i--) if ([[array objectAtIndex:i] isKindOfClass:[Group class]]) return YES;
return NO;
}
/* Factory methods. */
/* Alignment methods */
+ (SEL)actionFromAlignType:(AlignmentType)alignType
{
switch (alignType) {
case LEFT: return @selector(moveLeftEdgeTo:);
case RIGHT: return @selector(moveRightEdgeTo:);
case BOTTOM: return @selector(moveBottomEdgeTo:);
case TOP: return @selector(moveTopEdgeTo:);
case HORIZONTAL_CENTERS: return @selector(moveVerticalCenterTo:);
case VERTICAL_CENTERS: return @selector(moveHorizontalCenterTo:);
case BASELINES: return @selector(moveBaselineTo:);
}
return (SEL)NULL;
}
/* Creation methods. */
+ (void)initClassVars
/*
* Sets up any default values.
*/
{
static BOOL registered = NO;
NSArray * validSendTypes = [[[NSMutableArray alloc] initWithObjects:NSPostScriptPboardType, NSTIFFPboardType, DrawPboardType, nil] autorelease];
NSArray * validReturnTypes = [[[NSMutableArray alloc] initWithObjects:DrawPboardType, nil] autorelease];
if (!KeyMotionDeltaDefault) {
NSString * value = [[NSUserDefaults standardUserDefaults] objectForKey:@"KeyMotionDelta"];
if (value) KeyMotionDeltaDefault = [value floatValue];
KeyMotionDeltaDefault = MAX(KeyMotionDeltaDefault, 1.0);
}
if (!registered) {
registered = YES;
[NSApp registerServicesMenuSendTypes:validSendTypes returnTypes:[NSImage imagePasteboardTypes]];
[NSApp registerServicesMenuSendTypes:validSendTypes returnTypes:validReturnTypes];
}
}
- (id)initWithFrame:(NSRect)frameRect {
[super initWithFrame:frameRect];
glist = [[NSMutableArray allocWithZone:[self zone]] init];
slist = [[NSMutableArray allocWithZone:[self zone]] init];
cacheImage = [[NSImage allocWithZone:[self zone]] initWithSize:[self bounds].size];
[self cache:[self bounds] andUpdateLinks:NO];
gvFlags.grid = 10;
gvFlags.gridDisabled = 1;
[self allocateGState];
gridGray = DEFAULT_GRID_GRAY;
PSInit();
currentGraphic = [Rectangle class]; /* default graphic */
currentGraphic = [self currentGraphic]; /* trick to allow NSApp to control currentGraphic */
editView = [self createEditView];
[[self class] initClassVars];
[self registerForDragging];
spellDocTag = 0;
return self;
}
/* Free method */
- (void)dealloc
{
if (gupCoords) {
NSZoneFree([self zone], gupCoords);
NSZoneFree([self zone], gupOps);
NSZoneFree([self zone], gupBBox);
}
[glist removeAllObjects];
[slist removeAllObjects];
[glist release];
[slist release];
[cacheImage release];
if (![editView superview]) [editView release];
[super dealloc];
}
/* Used by Change's */
- (void)setGroupInSlist:(BOOL)setting
{
gvFlags.groupInSlist = setting;
}
- (void)resetGroupInSlist
{
gvFlags.groupInSlist = checkForGroup(slist);
}
- (void)resetLockedFlag
{
int i, count;
gvFlags.locked = NO;
count = [glist count];
for (i = 0; (i < count) && (!gvFlags.locked); i++)
if ([[glist objectAtIndex:i] isLocked])
gvFlags.locked = YES;
}
- (void)redrawGraphics:graphicsList afterChangeAgent:changeAgent performs:(SEL)aSelector
{
NSRect afterBounds, beforeBounds;
if ([(NSArray *)graphicsList count]) {
beforeBounds = [self getBBoxOfArray:graphicsList];
[changeAgent performSelector:aSelector];
afterBounds = [self getBBoxOfArray:graphicsList];
afterBounds = NSUnionRect(beforeBounds, afterBounds);
[self cache:afterBounds]; // (cache and) redraw after change object did something
} else {
[changeAgent performSelector:aSelector];
}
}
/* Hack to support growable Text objects. */
- (NSView *)createEditView
/*
* editView is essentially a dumb, FLIPPED (with extra emphasis on the
* flipped) subview of our GraphicView which completely covers it and
* which automatically sizes itself to always completely cover the
* GraphicView. It is necessary since growable Text objects only work
* when they are subviews of a flipped view.
*
* See TextGraphic for more details about why we need editView
* (it is purely a workaround for a limitation of the Text object).
*/
{
NSView *view;
NSRect viewFrame = [self frame];
[self setAutoresizesSubviews:YES];
view = [[FlippedView allocWithZone:[self zone]] initWithFrame:(NSRect){{0, 0}, {viewFrame.size.width, viewFrame.size.height}}];
[view setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
[self addSubview:view];
return view;
}
/* Public interface methods. */
- (BOOL)isEmpty
{
return [glist count] == 0;
}
- (BOOL)hasEmptySelection
{
return [slist count] == 0;
}
- (void)dirty
{
id delegate = [[self window] delegate];
if ([delegate respondsToSelector:@selector(dirty:)]) [delegate dirty:self];
}
- (void)getSelection
/*
* Resets slist by going through the glist and locating all the Graphics
* which respond YES to the isSelected method.
*/
{
int i;
Graphic *graphic;
[slist removeAllObjects];
gvFlags.groupInSlist = NO;
i = [glist count];
while (i--) {
graphic = [glist objectAtIndex:i];
if ([graphic isSelected]) {
[slist insertObject:graphic atIndex:0];
gvFlags.groupInSlist = gvFlags.groupInSlist || [graphic isKindOfClass:[Group class]];
}
}
}
- (NSRect)getBBoxOfArray:(NSArray *)array
{
return [self getBBoxOfArray:array extended:YES];
}
- (void)graphicsPerform:(SEL)aSelector
/*
* Performs the given aSelector on each member of the slist, then
* recaches and redraws the larger of the area covered by the objects before
* the selector was applied and the area covered by the objects after the
* selector was applied. If you want to perform a method on each item
* in the slist and NOT redraw, then use the List method makeObjectsPerform:.
*/
{
int i, count;
Graphic *graphic;
NSRect affectedBounds;
count = [slist count];
if (count) {
affectedBounds = [[slist objectAtIndex:0] extendedBounds];
for (i = 1; i < count; i++) {
graphic = [slist objectAtIndex:i];
affectedBounds = NSUnionRect([graphic extendedBounds], affectedBounds);
}
for (i = 0; i < count; i++) {
graphic = [slist objectAtIndex:i];
[graphic performSelector:aSelector];
affectedBounds = NSUnionRect([graphic extendedBounds], affectedBounds);
}
[self cache:affectedBounds]; // (cache and) redraw after a graphicsPerform:
}
}
- (void)graphicsPerform:(SEL)aSelector with:(void *)argument
{
int i, count;
Graphic *graphic;
NSRect affectedBounds;
count = [slist count];
if (count) {
affectedBounds = [[slist objectAtIndex:0] extendedBounds];
for (i = 1; i < count; i++) {
graphic = [slist objectAtIndex:i];
affectedBounds = NSUnionRect([graphic extendedBounds], affectedBounds);
}
for (i = 0; i < count; i++) {
graphic = [slist objectAtIndex:i];
[graphic performSelector:aSelector withObject:argument];
affectedBounds = NSUnionRect([graphic extendedBounds], affectedBounds);
}
[self cache:affectedBounds]; // (cache and) redraw after a graphicsPerform:with:
}
}
- (void)cache:(NSRect)rect andUpdateLinks:(BOOL)updateLinks;
/*
* Draws all the Graphics intersected by rect into the off-screen cache,
* then composites the rect back to the screen (but does NOT flushWindow).
* If updateLinks is on, then we check to see if the redrawn area intersects
* an area that someone has created a link to (see gvLinks.m).
*/
{
gvFlags.cacheing = YES;
[self drawRect:rect];
gvFlags.cacheing = NO;
if ([self canDraw]) {
[self lockFocus];
[self drawRect:rect];
[self unlockFocus];
} else {
[self setNeedsDisplayInRect:rect];
}
if (updateLinks && !gvFlags.suspendLinkUpdate) [self updateTrackedLinks:rect];
}
- (void)cache:(NSRect)rect;
{
return [self cache:rect andUpdateLinks:YES];
}
- cacheAndFlush:(NSRect)rect
{
[self cache:rect]; // cacheAndFlush:
[[self window] flushWindow];
return self;
}
- (void)insertGraphic:(Graphic *)graphic
/*
* Inserts the specified graphic into the glist and draws it.
* The new graphic will join the selection, not replace it.
*/
{
if (graphic) {
if ([graphic isSelected]) [slist insertObject:graphic atIndex:0];
[glist insertObject:graphic atIndex:0];
[graphic wasAddedTo:self];
[self cache:[graphic extendedBounds]]; // insertGraphic:
if ([graphic isKindOfClass:[Group class]]) gvFlags.groupInSlist = YES;
[[self window] flushWindow];
}
}
- (void)removeGraphic:(Graphic *)graphic
/*
* Removes the graphic from the GraphicView and redraws.
*/
{
int i;
NSRect eb;
Graphic *g = nil;
if (graphic) {
i = [glist count];
while (g != graphic && i--) g = [glist objectAtIndex:i];
if (g == graphic) {
eb = [g extendedBounds];
[glist removeObjectAtIndex:i];
[graphic wasRemovedFrom:self];
[slist removeObject:g];
if ([g isKindOfClass:[Group class]]) gvFlags.groupInSlist = checkForGroup(slist);
[self cache:eb]; // removeGraphic:
[[self window] flushWindow];
}
}
}
- (Graphic *)selectedGraphic
/*
* If there is one and only one Graphic selected, this method returns it.
*/
{
if ([slist count] == 1) {
Graphic *graphic = [slist objectAtIndex:0];
return [graphic isKindOfClass:[Group class]] ? nil : graphic;
} else {
return nil;
}
}
- (NSMutableArray *)selectedGraphics
/*
* Result valid only immediately after call. GraphicView
* reserves the right to free the list without warning.
*/
{
return slist;
}
- (NSMutableArray *)graphics
/*
* Result valid only immediately after call. GraphicView
* reserves the right to free the list without warning.
*/
{
return glist;
}
/* Methods to modify the grid of the GraphicView. */
- (int)gridSpacing
{
return gvFlags.grid;
}
- (BOOL)gridIsVisible
{
return gvFlags.showGrid;
}
- (BOOL)gridIsEnabled
{
return !gvFlags.gridDisabled;
}
- (float)gridGray
{
return gridGray;
}
- (void)setGridSpacing:(int)gridSpacing
{
id change;
if (gridSpacing != gvFlags.grid && gridSpacing > 0 && gridSpacing < 256) {
change = [[GridChange alloc] initGraphicView:self];
[change startChange];
gvFlags.grid = gridSpacing;
if (gvFlags.showGrid) {
[self resetGUP];
[self cache:[self bounds] andUpdateLinks:NO];
[[self window] flushWindow];
}
[change endChange];
}
}
- (void)setGridEnabled:(BOOL)flag
{
id change;
change = [[GridChange alloc] initGraphicView:self];
[change startChange];
gvFlags.gridDisabled = flag ? NO : YES;
[change endChange];
}
- (void)setGridVisible:(BOOL)flag
{
id change;
if (gvFlags.showGrid != flag) {
change = [[GridChange alloc] initGraphicView:self];
[change startChange];
gvFlags.showGrid = flag;
if (flag) [self resetGUP];
[self cache:[self bounds] andUpdateLinks:NO];
[[self window] flushWindow];
[change endChange];
}
}
- (void)setGridGray:(float)gray
{
id change;
if (gray != gridGray) {
change = [[GridChange alloc] initGraphicView:self];
[change startChange];
gridGray = gray;
if (gvFlags.showGrid) {
[self cache:[self bounds] andUpdateLinks:NO];
[[self window] flushWindow];
}
[change endChange];
}
}
- (void)setGridSpacing:(int)gridSpacing andGray:(float)gray
{
id change;
if (gray != gridGray || (gridSpacing != gvFlags.grid && gridSpacing > 0 && gridSpacing < 256)) {
change = [[GridChange alloc] initGraphicView:self];
[change startChange];
gridGray = gray;
if (gvFlags.grid != gridSpacing && gridSpacing > 0 && gridSpacing < 256) {
gvFlags.grid = gridSpacing;
if (gvFlags.showGrid) [self resetGUP];
}
if (gvFlags.showGrid) {
[self cache:[self bounds] andUpdateLinks:NO];
[[self window] flushWindow];
}
[change endChange];
}
}
- (NSPoint)grid:(NSPoint)p
{
grid(p);
return p;
}
/* Public methods for importing foreign data types into the GraphicView */
- (Graphic *)placeGraphic:(Graphic *)graphic at:(const NSPoint *)location
/*
* Places the graphic centered at the given location on the page.
* If the graphic is too big, the user is asked whether the graphic
* should be scaled.
*/
{
int scale;
float sx, sy, factor;
NSRect gbounds, visibleRect, bounds = [self bounds];
id change;
if (graphic) {
gbounds = [graphic extendedBounds];
if (gbounds.size.width > bounds.size.width || gbounds.size.height > bounds.size.height) {
scale = NSRunAlertPanel(LOAD_IMAGE, IMAGE_TOO_LARGE, SCALE, DONT_SCALE, CANCEL);
if (scale < 0) {
[graphic release];
return nil;
} else if (scale > 0) {
sx = (bounds.size.width / gbounds.size.width) * 0.95;
sy = (bounds.size.height / gbounds.size.height) * 0.95;
factor = MIN(sx, sy);
gbounds.size.width *= factor;
gbounds.size.height *= factor;
[graphic sizeTo:&gbounds.size];
}
}
if (location) {
[graphic centerAt:*location];
} else {
visibleRect = [self visibleRect];
visibleRect.origin.x += floor(visibleRect.size.width / 2.0 + 0.5);
visibleRect.origin.y += floor(visibleRect.size.height / 2.0 + 0.5);
[graphic centerAt:visibleRect.origin];
}
change = [[CreateGraphicsChange alloc] initGraphicView:self graphic:graphic];
[change startChangeIn:self];
[self deselectAll:self];
[graphic select];
[self insertGraphic:graphic];
[self scrollGraphicToVisible:graphic];
[change endChange];
}
return graphic;
}
/* Methods overridden from superclass. */
- (void)setFrameSize:(NSSize)newSize
/*
* Overrides View's sizeTo:: so that the cacheImage is resized when
* the View is resized.
*/
{
NSRect bounds = [self bounds];
if (newSize.width != bounds.size.width || newSize.height != bounds.size.height) {
[super setFrameSize:(NSSize){ newSize.width, newSize.height }];
bounds = [self bounds];
[cacheImage setSize:bounds.size];
[self resetGUP];
[self cache:bounds andUpdateLinks:NO];
}
}
- (void)mouseDown:(NSEvent *)event
/*
* This method handles a mouse down.
*
* If a current tool is in effect, then the mouse down causes a new
* Graphic to begin being created. Otherwise, the selection is modified
* either by adding elements to it or removing elements from it, or moving
* it. Here are the rules:
*
* Tool in effect
* Shift OFF
* create a new Graphic which becomes the new selection
* Shift ON
* create a new Graphic and ADD it to the current selection
* Control ON
* leave creation mode, and start selection
* Otherwise
* Shift OFF
* a. Click on a selected Graphic -> select graphic further back
* b. Click on an unselected Graphic -> that Graphic becomes selection
* Shift ON
* a. Click on a selected Graphic -> remove it from selection
* b. Click on unselected Graphic -> add it to selection
* Alternate ON
* if no affected graphic, causes drag select to select only objects
* completely contained within the dragged box.
*
* Essentially, everything works as one might expect except the ability to
* select a Graphic which is deeper in the list (i.e. further toward the
* back) by clicking on the currently selected Graphic.
*
* This is a very hairy mouseDown:. Most need not be this scary.
*/
{
NSPoint p;
int i, count, corner;
id change;
NSWindow *window = [self window];
Class factory;
Graphic *g = nil, *startg = nil;
BOOL shift, control, gotHit = NO, deepHit = NO, didDrag = NO;
/*
* You only need to do the following line in a mouseDown: method if
* you receive this message because one of your subviews gets the
* mouseDown: and does not respond to it (thus, it gets passed up the
* responder chain to you). In this case, our editView receives the
* mouseDown:, but doesn't do anything about it, and when it comes
* to us, we want to become the first responder.
*
* Normally you won't have a subview which doesn't do anything with
* mouseDown:, in which case, you need only return YES from the
* method acceptsFirstResponder (see that method below) and will NOT
* need to do the following makeFirstResponder:. In other words,
* don't put the following line in your mouseDown: implementation!
*
* Sorry about confusing this issue ...
*/
if ([window firstResponder] != self) {
if ([event clickCount] < 2) {
[window makeFirstResponder:self];
} else {
[[window firstResponder] mouseDown:event];
return;
}
}
shift = ([event modifierFlags] & NSShiftKeyMask) ? YES : NO;
control = ([event modifierFlags] & NSControlKeyMask) ? YES : NO;
p = [event locationInWindow];
p = [self convertPoint:p fromView:nil];
i = 0; // See if a Graphic wants to handle this event itself
[self lockFocus];
count = [glist count];
while (i < count) {
g = [glist objectAtIndex:i++];
if ([g handleEvent:event at:p inView:self]) return;
}
g = nil;
[self unlockFocus];
factory = [self currentGraphic];
if (!control && (factory || ([event clickCount] == 2))) {
id editFactory = factory;
if (([event clickCount] == 2) && ![editFactory isEditable]) editFactory = [TextGraphic class];
if ([editFactory isEditable]) { /* if editable, try to edit one */
i = 0;
count = [glist count];
while (i < count) {
g = [glist objectAtIndex:i++];
if ([g isKindOfClass:editFactory] && [g hit:p]) {
if ([g isSelected]) {
[g deselect];
[self cache:[g extendedBounds] andUpdateLinks:NO];
[slist removeObject:g];
}
[g edit:event in:editView];
goto done;
}
}
g = nil;
}
}
if (!control && factory) {
if (factory && !g) { /* not editing or no editable graphic found */
g = [[factory allocWithZone:[self zone]] init];
if ([NSApp respondsToSelector:@selector(inspectorPanel)]) {
[((id)[[NSApp inspectorPanel] delegate]) initializeGraphic:g];
}
if ([g create:event in:self]) {
change = [[CreateGraphicsChange alloc] initGraphicView:self graphic:g];
[change startChange];
if (!shift) [self deselectAll:self];
[self insertGraphic:g];
[g edit:NULL in:editView];
[change endChange];
} else {
[g release];
}
}
} else { /* selecting/resizing/moving */
i = 0;
count = [glist count];
while (i < count && !gotHit) {
g = [glist objectAtIndex:i];
corner = [g knobHit:p];
if (corner > 0) { /* corner hit */
gotHit = YES;
change = [[ResizeGraphicsChange alloc] initGraphicView:self graphic:g];
[change startChange];
[g resize:event by:corner in:self];
[change endChange];
} else if (corner) {
i++;
} else { /* complete miss */
break;
} /* non-corner opaque hit */
}
i = 0;
count = [glist count];
while (i < count && !gotHit && !deepHit) {
g = [glist objectAtIndex:i++];
if ([g isSelected] && [g hit:p]) {
if (shift) {
gotHit = YES;
[g deselect];
[self cache:[g extendedBounds] andUpdateLinks:NO];
[slist removeObject:g];
if ([g isKindOfClass:[Group class]]) gvFlags.groupInSlist = checkForGroup(slist);
} else {
gotHit = [self move:event];
if (!gotHit) {
deepHit = ![g isOpaque];
if (!deepHit) gotHit = YES;
}
}
}
}
startg = g;
count = [glist count];
i = 0;
if (!gotHit) while (i < count && !gotHit) {
g = [glist objectAtIndex:i];
if (![g isSelected] && [g hit:p]) {
gotHit = YES;
if (!shift) {
[self deselectAll:self];
[slist addObject:g];
gvFlags.groupInSlist = [g isKindOfClass:[Group class]];
}
[g select];
if (shift) [self getSelection];
if (deepHit || ![self move:event]) {
[self cache:[g extendedBounds] andUpdateLinks:NO];
}
} else {
i++;
}
};
if (!gotHit && !deepHit) {
if (!shift) {
[self lockFocus];
[self deselectAll:self];
[self unlockFocus];
didDrag = YES;
}
[self dragSelect:event];
}
}
done:
if (!didDrag && dragRect) {
NSZoneFree(NSDefaultMallocZone(), dragRect);
dragRect = NULL;
}
gvFlags.selectAll = NO;
[window flushWindow];
}
- (void)drawRect:(NSRect)rect
/*
* Draws the GraphicView.
*
* If cacheing is on or if ![[NSDPSContext currentContext] isDrawingToScreen],
* then all the graphics which intersect the specified rectangles will be drawn
* (and clipped to those rectangles). Otherwise, the specified rectangles
* are composited to the screen from the off-screen cache. The invalidRect
* stuff is to clean up any temporary drawing we have in the view
* (currently used only to show a source selection in the links mechanism--
* see showSelection: in gvLinks.m).
*/
{
int i;
NSWindow *window = [self window];
NSRect visibleRect;
if (!gvFlags.cacheing && invalidRect && !NSEqualRects(rect, *invalidRect)) {
*invalidRect = NSUnionRect(rect, *invalidRect);
[self drawRect:*invalidRect];
[window flushWindow];
NSZoneFree(NSDefaultMallocZone(), invalidRect);
invalidRect = NULL;
return;
}
if (gvFlags.cacheing || ![[NSDPSContext currentContext] isDrawingToScreen]) {
if ([[NSDPSContext currentContext] isDrawingToScreen]) {
[cacheImage lockFocus];
NSRectClip(rect);
PSsetgray(NSWhite);
NSRectFill(rect);
if (gvFlags.showGrid && gvFlags.grid >= 4) {
PSsetlinewidth(0.0);
PSsetgray(gridGray);
PSDoUserPath(gupCoords, gupLength, dps_short, gupOps, gupLength >> 1, gupBBox, dps_ustroke);
PSsetgray(NSWhite);
}
}
i = [glist count];
while (i--) [[glist objectAtIndex:i] draw:rect];
[Graphic showFastKnobFills];
if ([[NSDPSContext currentContext] isDrawingToScreen]) {
[cacheImage unlockFocus];
}
}
if (!gvFlags.cacheing && [[NSDPSContext currentContext] isDrawingToScreen]) {
visibleRect = [self visibleRect];
if (!NSEqualRects(rect, visibleRect)) {
rect = NSIntersectionRect(visibleRect, rect);
}
if (!NSIsEmptyRect(rect)) {
[cacheImage compositeToPoint:rect.origin fromRect:rect operation:NSCompositeCopy];
i = [glist count]; // pick up uncached graphics (used in resize:, etc.)
while (i--) {
Graphic *g = [glist objectAtIndex:i];
if (![g isCached]) [g draw:rect];
}
}
}
}
static NSPoint keyMoveBy;
- (void)keyDown:(NSEvent *)event
/*
* Let's the key-binding handler handles key events.
* It will call things like moveForward:, deleteBackward:, etc.
* Since moving the selection and such can be expensive, we
* coalesce consecutive key down events (up to 20 in a row)
* so that they'll be processed without a draw happening
* at each increment.
*/
{
int flags = [event modifierFlags];
if ((flags & NSAlternateKeyMask) || ([[self window] firstResponder] != self)) {
[super keyDown:event];
} else {
int throttle = 20; // don't suck more than 20 consecutive key events
NSMutableArray *eventList = [NSMutableArray arrayWithCapacity:throttle];
[eventList addObject:event];
while (--throttle) {
event = [[self window] nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast] inMode:NSDPSRunLoopMode dequeue:NO];
if (!event) {
break;
} else if ([event type] == NSKeyUp) {
[[self window] nextEventMatchingMask:NSKeyUpMask]; // discard these
continue;
}
if ([event type] != NSKeyDown) { // only grab consecutive key events
break;
}
event = [[self window] nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast] inMode:NSDPSRunLoopMode dequeue:YES];
[eventList addObject:event];
}
keyMoveBy.x = keyMoveBy.y = 0.0;
[self interpretKeyEvents:eventList];
if (keyMoveBy.x || keyMoveBy.y) {
float delta = KeyMotionDeltaDefault;
delta = floor(delta / GRID) * GRID;
delta = MAX(delta, GRID);
keyMoveBy.x *= delta;
keyMoveBy.y *= delta;
[self moveGraphicsBy:keyMoveBy andDraw:YES];
[[self window] flushWindow];
PSWait();
}
}
}
- (void)deleteForward:(id)sender
{
[self delete:self];
}
- (void)deleteBackward:(id)sender
{
[self delete:self];
}
- (void)moveLeft:(id)sender;
{
keyMoveBy.x -= 1.0;
}
- (void)moveRight:(id)sender;
{
keyMoveBy.x += 1.0;
}
- (void)moveUp:(id)sender;
{
keyMoveBy.y += 1.0;
}
- (void)moveDown:(id)sender;
{
keyMoveBy.y -= 1.0;
}
/* Accepting becoming the First Responder */
- (BOOL)acceptsFirstResponder
/*
* GraphicView always wants to become the first responder when it is
* clicked on in a window, so it returns YES from this method.
*/
{
return YES;
}
- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent {
return YES;
}
/* Printing */
- (void)endPrologue
/*
* Spit out the custom PostScript defs.
*/
{
PSInit();
[super endPrologue];
}
/*
* These two method set and get the factory object used to create new
* Graphic objects (i.e. the subclass of Graphic to use).
* They are kind of weird since they check to see if the
* Application object knows what the current graphic is. If it does, then
* it lets it handle these methods. Otherwise, it determines the
* current graphic by querying the sender to find out what its title is
* and converts that title to the name to a factory object. This allows
* the GraphicView to stand on its own, but also use an application wide
* tool palette if available.
* If the GraphicView handles the current graphic by itself, it does so
* by querying the sender of setCurrentGraphic: to find out its title.
* It assumes, then, that that title is the name of the factory object to
* use and calls NSClassFromString() to get a pointer to it.
* If the application is not control what our current graphic is, then
* we restrict creations to be made only when the control key is down.
* Otherwise, it is the other way around (control key leaves creation
* mode). This is due to the fact that the application can be smart
* enough to set appropriate cursors when a tool is on. The GraphicView
* can't be.
*/
- (Class)currentGraphic
{
if ([NSApp respondsToSelector:@selector(currentGraphic)]) {
return [NSApp currentGraphic];
} else {
return currentGraphic;
}
}
/* sender could be an NSMenuItem or a matrix or something */
- (void)setCurrentGraphic:sender
{
id item;
if ([sender respondsTo:@selector(selectedCell)]) {
item = [sender selectedCell];
} else {
item = sender;
}
if ([item respondsTo:@selector(title)]) {
currentGraphic = NSClassFromString([item title]);
}
}
/* These methods write out the form information. */
- (BOOL)hasFormEntries
{
int i;
for (i = [glist count]-1; i >= 0; i--) {
if ([[glist objectAtIndex:i] isFormEntry]) return YES;
}
return NO;
}
- (void)writeFormEntriesToFile:(NSString *)file
{
int i;
NSMutableString *string;
NSRect bounds = [self bounds];
string = [NSMutableString stringWithFormat:@"Page Size: w = %d, h = %d\n", (int)bounds.size.width, (int)bounds.size.height];
for (i = [glist count]-1; i >= 0; i--) {
[[glist objectAtIndex:i] writeFormEntryToMutableString:string];
}
[string writeToFile:file atomically:NO];
}
/* Some graphics (notably Image) want to write out files when we save. */
- (BOOL)hasGraphicsWhichWriteFiles
{
int i;
for (i = [glist count]-1; i >= 0; i--) {
if ([[glist objectAtIndex:i] writesFiles]) return YES;
}
return NO;
}
- (void)allowGraphicsToWriteFilesIntoDirectory:(NSString *)directory
{
int i;
for (i = [glist count]-1; i >= 0; i--) {
[[glist objectAtIndex:i] writeFilesToDirectory:directory];
}
}
/*
* Target/Action methods.
*/
- (void)delete:(id)sender
{
int i;
Graphic *graphic;
id change;
i = [slist count];
if (i > 0) {
change = [[DeleteGraphicsChange alloc] initGraphicView:self];
[change startChange];
[self graphicsPerform:@selector(deactivate)];
[slist makeObjectsPerform:@selector(activate)];
while (i--) {
graphic = [slist objectAtIndex:i];
[glist removeObject:graphic];
[graphic wasRemovedFrom:self];
}
if (originalPaste == [slist objectAtIndex:0]) [slist removeObjectAtIndex:0];
[slist removeAllObjects];
gvFlags.groupInSlist = NO;
[[self window] flushWindow];
[change endChange];
}
}
- (void)selectAll:(id)sender
/*
* Selects all the items in the glist.
*/
{
int i;
Graphic *g;
NSRect visibleRect;
i = [glist count];
if (!i) return;
[slist removeAllObjects];
[cacheImage lockFocus];
while (i--) {
g = [glist objectAtIndex:i];
if (![g isLocked]) {
[g select];
[g draw:NSZeroRect];
[slist insertObject:g atIndex:0];
gvFlags.groupInSlist = gvFlags.groupInSlist || [g isKindOfClass:[Group class]];
}
}
[Graphic showFastKnobFills];
[cacheImage unlockFocus];
visibleRect = [self visibleRect];
if ([self canDraw]) {
[self lockFocus];
[self drawRect:visibleRect];
[self unlockFocus];
} else {
[self setNeedsDisplay:YES];
}
if (sender != self) [[self window] flushWindow];
gvFlags.selectAll = YES;
}
- (void)deselectAll:sender
/*
* Deselects all the items in the slist.
*/
{
NSRect sbounds;
if ([slist count] > 0) {
sbounds = [self getBBoxOfArray:slist];
[slist makeObjectsPerform:@selector(deselect)];
[self cache:sbounds andUpdateLinks:NO];
[slist removeAllObjects];
gvFlags.groupInSlist = NO;
if (sender != self) [[self window] flushWindow];
}
}
- (void)lockGraphic:sender
/*
* Locks all the items in the selection so that they can't be selected
* or resized or moved. Useful if there are some Graphics which are getting
* in your way. Undo this with unlockGraphic:.
*/
{
id change;
if ([slist count] > 0) {
change = [[LockGraphicsChange alloc] initGraphicView:self];
[change startChange];
gvFlags.locked = YES;
[slist makeObjectsPerform:@selector(lockGraphic)];
[self deselectAll:sender];
[change endChange];
}
}
- (void)unlockGraphic:sender
{
id change;
change = [[UnlockGraphicsChange alloc] initGraphicView:self];
[change startChange];
[glist makeObjectsPerform:@selector(unlockGraphic)];
gvFlags.locked = NO;
[change endChange];
}
- (void)bringToFront:sender
/*
* Brings each of the items in the slist to the front of the glist.
* The item in the front of the slist will be the new front element
* in the glist.
*/
{
id change, temp;
int i;
i = [slist count];
if (i) {
change = [[BringToFrontGraphicsChange alloc] initGraphicView:self];
[change startChange];
while (i--) {
temp = [slist objectAtIndex:i];
[glist removeObject:temp];
[glist insertObject:temp atIndex:0];
}
[self recacheSelection];
[change endChange];
}
}
- (void)sendToBack:sender
{
int i, count;
id change, temp;
count = [slist count];
if (count > 0) {
change = [[SendToBackGraphicsChange alloc] initGraphicView:self];
[change startChange];
for (i = 0; i < count; i++){
temp = [slist objectAtIndex:i];
[glist removeObject:temp];
[glist addObject:temp];
}
[self recacheSelection];
[change endChange];
}
}
- (void)group:sender
/*
* Creates a new Group object with the current slist as its member list.
* See the Group class for more info.
*/
{
int i;
Graphic *graphic;
id change;
i = [slist count];
if (i > 1) {
change = [[GroupGraphicsChange alloc] initGraphicView:self];
[change startChange];
while (i--) [glist removeObject:[slist objectAtIndex:i]];
graphic = [[Group allocWithZone:[self zone]] initList:slist];
[change noteGroup:graphic];
[glist insertObject:graphic atIndex:0];
[slist removeAllObjects];
[slist addObject:graphic];
gvFlags.groupInSlist = YES;
[self cache:[graphic extendedBounds]];
if (sender != self) [[self window] flushWindow];
[change endChange];
}
}
- (void)ungroup:sender
/*
* Goes through the slist and ungroups any Group objects in it.
* Does not descend any further than that (i.e. all the Group objects
* in the slist are ungrouped, but any Group objects in those ungrouped
* objects are NOT ungrouped).
*/
{
int i, k;
NSRect sbounds;
id graphic;
id change;
if (gvFlags.groupInSlist && [slist count]) {
change = [[UngroupGraphicsChange alloc] initGraphicView:self];
[change startChange];
sbounds = [self getBBoxOfArray:slist];
i = [slist count];
while (i--) {
graphic = [slist objectAtIndex:i];
if ([graphic isKindOfClass:[Group class]]) {
k = [glist indexOfObject:graphic];
[glist removeObjectAtIndex:k];
[graphic transferSubGraphicsTo:glist at:k];
}
}
[self cache:sbounds];
if (sender != self) [[self window] flushWindow];
[self getSelection];
[change endChange];
}
}
/* sender could be an NSMenuItem or a matrix or something */
- (void)align:sender
{
id item;
if ([sender respondsTo:@selector(selectedCell)]) {
item = [sender selectedCell];
} else {
item = sender;
}
if ([item respondsTo:@selector(tag)]) {
[self alignBy:(AlignmentType)[item tag]];
}
}
- (void)changeAspectRatio:sender
{
id change;
change = [[AspectRatioGraphicsChange alloc] initGraphicView:self];
[change startChange];
[self graphicsPerform:@selector(sizeToNaturalAspectRatio)];
[change endChange];
[[self window] flushWindow];
}
- (void)alignToGrid:sender
{
id change;
change = [[AlignGraphicsChange alloc] initGraphicView:self];
[change startChange];
[self graphicsPerform:@selector(alignToGrid:) with:self];
[[self window] flushWindow];
[change endChange];
}
- (void)sizeToGrid:sender
{
id change;
change = [[DimensionsGraphicsChange alloc] initGraphicView:self];
[change startChange];
[self graphicsPerform:@selector(sizeToGrid:) with:self];
[[self window] flushWindow];
[change endChange];
}
- (void)enableGrid:sender
/*
* If the tag of the sender is non-zero, then gridding is enabled.
* If the tag is zero, then gridding is disabled.
* sender might be an NSMenuItem or a matrix of cells.
*/
{
if ([sender respondsTo:@selector(selectedTag)]) {
[self setGridEnabled:[sender selectedTag] ? YES : NO];
} else if ([sender respondsTo:@selector(tag)]) {
[self setGridEnabled:[sender tag] ? YES : NO];
}
}
- (void)hideGrid:sender
/*
* If the tag of the sender is non-zero, then the grid is made visible
* otherwise, it is hidden (but still conceivable in effect).
*/
{
if ([sender respondsTo:@selector(selectedTag)]) {
[self setGridVisible:[sender selectedTag] ? YES : NO];
} else if ([sender respondsTo:@selector(tag)]) {
[self setGridVisible:[sender tag] ? YES : NO];
}
}
- showLinks:sender
/*
* If the tag of the sender is non-zero, then linked items are
* shown with a border around them (see redrawLinkOutlines: in gvLinks.m).
*/
{
if ([sender respondsTo:@selector(selectedTag)]) {
[linkManager setLinkOutlinesVisible:[sender selectedTag] ? YES : NO];
} else if ([sender respondsTo:@selector(tag)]) {
[linkManager setLinkOutlinesVisible:[sender tag] ? YES : NO];
}
return self;
}
- (int)spellDocumentTag {
if (!spellDocTag) {
spellDocTag = [NSSpellChecker uniqueSpellDocumentTag];
}
return spellDocTag;
}
- (void)ignoreSpelling:(id)sender {
[[NSSpellChecker sharedSpellChecker] ignoreWord:[sender stringValue] inSpellDocumentWithTag:[self spellDocumentTag]];
}
- (void)checkSpelling:(id)sender {
int i;
float curY, newY, maxY = 0.0;
float curX, newX, maxX = 0.0;
NSRect egbounds, gbounds;
id fr = [[self window] firstResponder];
Graphic *graphic, *editingGraphic, *newEditingGraphic = nil;
NSRange range = {0,0};
// 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.
static id startGraphic = nil;
static BOOL oneMoreTime = NO;
if ([fr isKindOfClass:[NSText class]]) {
NSRange selRange = [fr selectedRange];
range = [[NSSpellChecker sharedSpellChecker] checkSpellingOfString:[fr string] startingAt:(selRange.location + selRange.length) language:nil wrap:NO inSpellDocumentWithTag:[self spellDocumentTag] wordCount:NULL];
if (range.length > 0) {
[fr setSelectedRange:range];
[[NSSpellChecker sharedSpellChecker] updateSpellingPanelWithMisspelledWord:[[fr string] substringWithRange:range]];
startGraphic = nil;
oneMoreTime = NO;
return;
}
}
if ([fr isKindOfClass:[NSText class]]) {
editingGraphic = [fr delegate];
if (!startGraphic) {
startGraphic = editingGraphic;
oneMoreTime = YES;
}
egbounds = [editingGraphic bounds];
curY = egbounds.origin.y + egbounds.size.height;
curX = egbounds.origin.x;
} else {
curX = 0.0;
curY = 10000.0;
}
maxY = 0.0; maxX = 10000.0;
for (i = [glist count]-1; i >= 0; i--) {
graphic = [glist objectAtIndex:i];
if ([graphic isKindOfClass:[TextGraphic class]]) {
gbounds = [graphic bounds];
newY = gbounds.origin.y + gbounds.size.height;
newX = gbounds.origin.x;
if ((newY > maxY || (newY == maxY && newX < maxX)) && (newY < curY || (newY == curY && newX > curX))) {
maxY = newY;
maxX = newX;
newEditingGraphic = graphic;
}
}
}
[[self window] makeFirstResponder:self];
if (newEditingGraphic) {
[newEditingGraphic edit:NULL in:editView];
}
if ((startGraphic != newEditingGraphic) || oneMoreTime) {
if (startGraphic == newEditingGraphic) {
oneMoreTime = NO;
}
[self checkSpelling:sender];
} else {
// We seem to have been once around the track already with no misspellings found.
[[NSSpellChecker sharedSpellChecker] updateSpellingPanelWithMisspelledWord:@""];
startGraphic = nil;
}
}
- (void)showGuessPanel:(id)sender
{
[[[NSSpellChecker sharedSpellChecker] spellingPanel] makeKeyAndOrderFront:sender];
}
/* Cover-Sheet items (see TextGraphic.m). */
- (void)doAddCoverSheetEntry:(id <NSMenuItem>)invokingMenuItem localizable:(BOOL)flag
{
NSString *entry;
entry = (NSString *)[invokingMenuItem tag]; // Yikes, casting int to NSString *!
if ([entry isEqual:@""]) {
entry = [invokingMenuItem title];
}
[self placeGraphic:[[TextGraphic allocWithZone:[self zone]] initFormEntry:entry localizable:flag] at:NULL];
}
- (void)addLocalizableCoverSheetEntry:(id <NSMenuItem>)invokingMenuItem
{
[self doAddCoverSheetEntry:invokingMenuItem localizable:YES];
}
- (void)addCoverSheetEntry:(id <NSMenuItem>)invokingMenuItem
{
[self doAddCoverSheetEntry:invokingMenuItem localizable:NO];
}
/*
* Target/Action methods to change Graphic parameters from a Control.
* If the sender is a PopUpList, then the indexOfSelectedItem is used to
* determine the value to use (for linecap, linearrow, etc.) otherwise, the
* sender's floatValue or intValue is used (whichever is appropriate).
* This allows interface builders the flexibility to design different
* ways of setting those values.
*/
- (void)takeGridValueFrom:sender
{
[self setGridSpacing:[sender intValue]];
}
- (void)takeGridGrayFrom:sender
{
[self setGridGray:[sender floatValue]];
}
- (void)takeGrayValueFrom:sender
{
float value;
value = [sender floatValue];
[self graphicsPerform:@selector(setGray:) with:&value];
[[self window] flushWindow];
}
- (void)takeLineWidthFrom:sender
{
id change;
float width = [sender floatValue];
change = [[LineWidthGraphicsChange alloc] initGraphicView:self lineWidth:width];
[change startChange];
[self graphicsPerform:@selector(setLineWidth:) with:&width];
[[self window] flushWindow];
[change endChange];
}
- (void)takeLineJoinFrom:sender
{
int joinValue;
id change;
if ([sender respondsToSelector:@selector(indexOfSelectedItem)])
joinValue = [sender indexOfSelectedItem];
else
joinValue = [sender intValue];
change = [[LineJoinGraphicsChange alloc] initGraphicView:self lineJoin:joinValue];
[change startChange];
[self graphicsPerform:@selector(setLineJoin:) with:(void *)joinValue];
[[self window] flushWindow];
[change endChange];
}
- (void)takeLineCapFrom:sender
{
int capValue;
id change;
if ([sender respondsToSelector:@selector(indexOfSelectedItem)])
capValue = [sender indexOfSelectedItem];
else
capValue = [sender intValue];
change = [[LineCapGraphicsChange alloc] initGraphicView:self lineCap:capValue];
[change startChange];
[self graphicsPerform:@selector(setLineCap:) with:(void *)capValue];
[[self window] flushWindow];
[change endChange];
}
- (void)takeLineArrowFrom:sender
{
int arrowValue;
id change;
if ([sender respondsToSelector:@selector(indexOfSelectedItem)])
arrowValue = [sender indexOfSelectedItem];
else
arrowValue = [sender intValue];
change = [[ArrowGraphicsChange alloc] initGraphicView:self lineArrow:arrowValue];
[change startChange];
[self graphicsPerform:@selector(setLineArrow:) with:(void *)arrowValue];
[[self window] flushWindow];
[change endChange];
}
- (void)takeFillValueFrom:sender
{
int fillValue;
id change;
if ([sender respondsToSelector:@selector(indexOfSelectedItem)])
fillValue = [sender indexOfSelectedItem];
else
fillValue = [sender intValue];
change = [[FillGraphicsChange alloc] initGraphicView:self fill:fillValue];
[change startChange];
[self graphicsPerform:@selector(setFill:) with:(void *)fillValue];
[[self window] flushWindow];
[change endChange];
}
- (void)takeFrameValueFrom:sender
{
if ([sender respondsToSelector:@selector(indexOfSelectedItem)]) {
[self graphicsPerform:@selector(setFramed:) with:(void *)[sender indexOfSelectedItem]];
} else {
[self graphicsPerform:@selector(setFramed:) with:(void *)[sender intValue]];
}
[[self window] flushWindow];
}
- (void)takeLineColorFrom:sender
{
id change;
NSColor *color = [sender color];
change = [[LineColorGraphicsChange alloc] initGraphicView:self color:color];
[change startChange];
[self graphicsPerform:@selector(setLineColor:) with:color];
[[self window] flushWindow];
[change endChange];
}
- (void)takeFillColorFrom:sender
{
id change;
NSColor *color = [sender color];
change = [[FillGraphicsChange alloc] initGraphicView:self];
[change startChange];
[self graphicsPerform:@selector(setFillColor:) with:color];
[[self window] flushWindow];
[change endChange];
}
- (void)takeFormEntryStatusFrom:sender
{
[self graphicsPerform:@selector(setFormEntry:) with:(void *)[sender intValue]];
[[self window] flushWindow];
}
- (void)changeFont:(id)sender
{
id change;
if ([[self window] firstResponder] == self) {
change = [[MultipleChange alloc] initChangeName:FONT_OP];
[change startChange];
[self graphicsPerform:@selector(changeFont:) with:sender];
[[self window] flushWindow];
[change endChange];
}
}
/* Archiver-related methods. */
#define GRAPHICS_KEY @"Graphics"
- (void)convertSelf:(ConversionDirection)setting propertyList:(id)plist
{
PL_FLAG(plist, gvFlags.gridDisabled, @"GridDisabled", setting);
PL_FLAG(plist, gvFlags.locked, @"SomeGraphicsAreLocked", setting);
PL_FLAG(plist, gvFlags.showGrid, @"GridVisible", setting);
PL_FLAG(plist, gvFlags.groupInSlist, @"GroupInTheSelection", setting);
PL_FLOAT(plist, gridGray, @"GridGray", setting);
PL_INT(plist, gvFlags.grid, @"GridSize", setting);
}
- (id)propertyList
{
NSMutableDictionary *plist = [NSMutableDictionary dictionaryWithCapacity:7];
[self convertSelf:ToPropertyList propertyList:plist];
[plist setObject:propertyListFromArray(glist) forKey:GRAPHICS_KEY];
return plist;
}
- (NSString *)description
{
Graphic *graphic;
NSMutableDictionary *plist = [NSMutableDictionary dictionaryWithCapacity:8];
NSMutableArray *array;
NSEnumerator *enumerator;
[self convertSelf:ToPropertyList propertyList:plist];
array = [NSMutableArray arrayWithCapacity:[glist count]];
enumerator = [glist objectEnumerator];
while ((graphic = [enumerator nextObject])) {
[array addObject:[NSString stringWithFormat:@"0x%x", (int)graphic]];
}
[plist setObject:array forKey:GRAPHICS_KEY]; // don't expand them in description
array = [NSMutableArray arrayWithCapacity:[slist count]];
enumerator = [slist objectEnumerator];
while ((graphic = [enumerator nextObject])) {
[array addObject:[NSString stringWithFormat:@"0x%x", (int)graphic]];
}
[plist setObject:array forKey:@"Selection"];
return [plist description];
}
- initWithFrame:(NSRect)frame fromPropertyList:(id)plist inDirectory:(NSString *)directory
{
[super initWithFrame:frame];
[self convertSelf:FromPropertyList propertyList:plist];
glist = arrayFromPropertyList([plist objectForKey:GRAPHICS_KEY], directory, [self zone]);
slist = [[NSMutableArray allocWithZone:[self zone]] initWithCapacity:[glist count]];
[self getSelection];
gvFlags.grid = 10;
gvFlags.gridDisabled = 1;
gridGray = DEFAULT_GRID_GRAY;
[self resetGUP];
[self allocateGState];
PSInit();
currentGraphic = [Rectangle class]; /* default graphic */
currentGraphic = [self currentGraphic]; /* trick to allow NSApp to control currentGraphic */
editView = [self createEditView];
if (!InMsgPrint) {
NSRect bounds = [self bounds];
cacheImage = [[NSImage allocWithZone:[self zone]] initWithSize:bounds.size];
[self cache:bounds andUpdateLinks:NO];
}
[[self class] initClassVars];
[self registerForDragging];
spellDocTag = 0;
return self;
}
/* Methods to deal with being/becoming the First Responder */
/* Strings that appear in menus. */
static NSString *hideGrid;
static NSString *showGrid;
static NSString *turnGridOff;
static NSString *turnGridOn;
static NSString *showLinks;
static NSString *hideLinks;
static BOOL menuStringsInitted = NO;
static void initMenuItemStrings(void)
{
hideGrid = HIDE_GRID;
showGrid = SHOW_GRID;
turnGridOff = TURN_GRID_OFF;
turnGridOn = TURN_GRID_ON;
showLinks = SHOW_LINKS;
hideLinks = HIDE_LINKS;
menuStringsInitted = YES;
}
/* Validates whether a menu command makes sense now */
static BOOL updateMenuItem(id <NSMenuItem> menuItem, NSString *zeroItem, NSString *oneItem, BOOL state)
{
if (state) {
if ([menuItem tag] != 0) {
[menuItem setTitleWithMnemonic:zeroItem];
[menuItem setTag:0];
[menuItem setEnabled:NO]; // causes it to get redrawn
}
} else {
if ([menuItem tag] != 1) {
[menuItem setTitleWithMnemonic:oneItem];
[menuItem setTag:1];
[menuItem setEnabled:NO]; // causes it to get redrawn
}
}
return YES;
}
- (BOOL)validateMenuItem:(id <NSMenuItem>)anItem
/*
* Can be called to see if the specified action is valid on this view now.
* It returns NO if the GraphicView knows that action is not valid now,
* otherwise it returns YES. Note the use of the Pasteboard change
* count so that the GraphicView does not have to look into the Pasteboard
* every time paste: is validated.
*/
{
NSPasteboard *pb;
int i, count, gcount;
SEL action = [anItem action];
static BOOL pboardHasPasteableType = NO;
static BOOL pboardHasPasteableLink = NO;
static int cachedPasteboardChangeCount = - 1;
if (!menuStringsInitted) initMenuItemStrings();
if (action == @selector(bringToFront:)) {
if ((count = [slist count]) && [glist count] > count) {
for (i = 0; i < count; i++) {
if ([slist objectAtIndex:i] != [glist objectAtIndex:i]) {
return YES;
}
}
}
return NO;
} else if (action == @selector(sendToBack:)) {
if ((count = [slist count]) && (gcount = [glist count]) > count) {
for (i = 1; i <= count; i++) {
if ([slist objectAtIndex:count-i] != [glist objectAtIndex:gcount-i]) {
return YES;
}
}
}
return NO;
} else if (action == @selector(group:) ||
action == @selector(align:)) {
return([slist count] > 1);
} else if (action == @selector(ungroup:)) {
return(gvFlags.groupInSlist && [slist count] > 0);
} else if (action == @selector(checkSpelling:)) {
for (i = [glist count]-1; i >= 0; i--) if ([[glist objectAtIndex:i] isKindOfClass:[TextGraphic class]]) return YES;
return NO;
} else if (action == @selector(deselectAll:) ||
action == @selector(lockGraphic:) ||
action == @selector(changeAspectRatio:) ||
action == @selector(cut:) ||
action == @selector(copy:)) {
return([slist count] > 0);
} else if (action == @selector(alignToGrid:) ||
action == @selector(sizeToGrid:)) {
return(GRID > 1 && [slist count] > 0);
} else if (action == @selector(unlockGraphic:)) {
return gvFlags.locked;
} else if (action == @selector(selectAll:)) {
return([glist count] > [slist count]);
} else if (action == @selector(paste:) || action == @selector(pasteAndLink:) || action == @selector(link:)) {
pb = [NSPasteboard generalPasteboard];
count = [pb changeCount];
if (count != cachedPasteboardChangeCount) {
cachedPasteboardChangeCount = count;
pboardHasPasteableType = (DrawPasteType([pb types]) != NULL);
pboardHasPasteableLink = pboardHasPasteableType ? IncludesType([pb types], NSDataLinkPboardType) : NO;
}
return (action == @selector(paste:)) ? pboardHasPasteableType : pboardHasPasteableLink;
} else if (action == @selector(hideGrid:)) {
return (gvFlags.grid >= 4) ? updateMenuItem(anItem, hideGrid, showGrid, [self gridIsVisible]) : NO;
} else if (action == @selector(enableGrid:)) {
return (gvFlags.grid > 1) ? updateMenuItem(anItem, turnGridOff, turnGridOn, [self gridIsEnabled]) : NO;
} else if (action == @selector(showLinks:)) {
return linkManager ? updateMenuItem(anItem, hideLinks, showLinks, [linkManager areLinkOutlinesVisible]) : NO;
}
return YES;
}
/* Useful scrolling routines. */
- (void)scrollGraphicToVisible:(Graphic *)graphic
{
NSPoint p;
NSRect bounds = [self bounds];
p = bounds.origin;
NSContainsRect([graphic extendedBounds], bounds);
p.x -= bounds.origin.x;
p.y -= bounds.origin.y;
if (p.x || p.y) {
[graphic moveBy:&p];
bounds.origin.x += p.x;
bounds.origin.y += p.y;
[self scrollRectToVisible:[graphic extendedBounds]];
}
}
- (void)scrollPointToVisible:(NSPoint)point
{
NSRect r;
r.origin.x = point.x - 5.0;
r.origin.y = point.y - 5.0;
r.size.width = r.size.height = 10.0;
[self scrollRectToVisible:r];
}
- (void)scrollSelectionToVisible
{
NSRect sbounds;
if ([slist count]) {
sbounds = [self getBBoxOfArray:slist];
[self scrollRectToVisible:sbounds];
}
}
/* Private selection management methods. */
- (NSImage *)selectionCache
{
static NSImage *selectioncache = nil;
if (!selectioncache) selectioncache = [[NSImage allocWithZone:[self zone]] initWithSize:[self getBBoxOfArray:slist].size];
return selectioncache;
}
- (NSRect)getBBoxOfArray:(NSArray *)array extended:(BOOL)extended
/*
* Returns a rectangle which encloses all the objects in the list.
*/
{
int i;
NSRect bbox, eb;
i = [array count];
if (i) {
if (extended) {
bbox = [[array objectAtIndex:--i] extendedBounds];
while (i--) bbox = NSUnionRect([[array objectAtIndex:i] extendedBounds], bbox);
} else {
bbox = [[array objectAtIndex:--i] bounds];
while (i--) {
eb = [[array objectAtIndex:i] bounds];
bbox = NSUnionRect(eb, bbox);
}
}
} else {
bbox.size = NSZeroSize;
}
return bbox;
}
- (void)recacheSelection:(BOOL)updateLinks
/*
* Redraws the selection in the off-screen cache (not the selection cache),
* then composites it back on to the screen.
*/
{
NSRect sbounds;
if ([slist count]) {
sbounds = [self getBBoxOfArray:slist];
gvFlags.cacheing = YES;
[self drawRect:sbounds];
gvFlags.cacheing = NO;
[self displayRect:sbounds];
if (updateLinks && !gvFlags.suspendLinkUpdate) [self updateTrackedLinks:sbounds];
}
}
- (void)recacheSelection
{
return [self recacheSelection:YES];
}
- (void)compositeSelection:(NSRect)sbounds
/*
* Composites from the selection cache whatever part of sbounds is
* currently visible in the View. Assumes we're lockFocus'ed on self.
*/
{
NSRect rect = sbounds;
rect.origin = NSZeroPoint;
[[self selectionCache] compositeToPoint:sbounds.origin fromRect:rect operation:NSCompositeSourceOver];
[[self window] flushWindow];
PSWait();
}
- (void)cacheList:(NSArray *)array into:(NSImage *)aCache withTransparentBackground:(BOOL)transparentBackground
/*
* Caches the selection into the application-wide selection cache
* window (a window which has alpha in it). See also: selectionCache:.
* It draws the objects without their knobbies in the selection cache,
* but it leaves them selected.
*/
{
int i;
NSRect sbounds;
NSSize scsize;
sbounds = [self getBBoxOfArray:array];
scsize = [aCache size];
if (scsize.width < sbounds.size.width || scsize.height < sbounds.size.height) {
[aCache setSize:(NSSize){MAX(scsize.width, sbounds.size.width), MAX(scsize.height, sbounds.size.height)}];
}
[aCache lockFocus];
PSsetgray(NSWhite);
PSsetalpha(transparentBackground ? 0.0 : 1.0); /* 0.0 means fully transparent */
PStranslate(- sbounds.origin.x, - sbounds.origin.y);
sbounds.size.width += 1.0;
sbounds.size.height += 1.0;
NSRectFill(sbounds);
sbounds.size.width -= 1.0;
sbounds.size.height -= 1.0;
PSsetalpha(1.0); /* set back to fully opaque */
i = [array count];
while (i--) {
Graphic *g = [array objectAtIndex:i];
[g deselect];
[g draw:NSZeroRect];
[g select];
}
[Graphic showFastKnobFills];
PStranslate(sbounds.origin.x, sbounds.origin.y);
[aCache unlockFocus];
}
- (void)cacheList:(NSArray *)array into:(NSImage *)aCache
{
[self cacheList:array into:aCache withTransparentBackground:YES];
}
- (void)cacheSelection
{
[self cacheList:slist into:[self selectionCache] withTransparentBackground:YES];
}
/* Other private methods. */
- (void)cacheGraphic:(Graphic *)graphic
/*
* Draws the graphic into the off-screen cache, then composites
* it back to the screen.
* NOTE: This ONLY works if the graphic is on top of the list!
* That is why it is a private method ...
*/
{
[cacheImage lockFocus];
[graphic draw:NSZeroRect];
[Graphic showFastKnobFills];
[cacheImage unlockFocus];
[self displayRect:[graphic extendedBounds]];
}
- (void)resetGUP
/*
* The "GUP" is the Grid User Path. It is a user path which draws a grid
* the size of the bounds of the GraphicView. This gets called whenever
* the View is resized or the grid spacing is changed. It sets up all
* the arguments to DPSDoUserPath() called in drawSelf::.
*/
{
int x, y, i, j;
short w, h;
NSZone *zone = [self zone];
NSRect bounds = [self bounds];
if (gvFlags.grid < 4) return;
x = (int)bounds.size.width / (gvFlags.grid ? gvFlags.grid : 1);
y = (int)bounds.size.height / (gvFlags.grid ? gvFlags.grid : 1);
gupLength = (x << 2) + (y << 2);
if (gupCoords) {
NSZoneFree(zone, gupCoords);
NSZoneFree(zone, gupOps);
NSZoneFree(zone, gupBBox);
}
gupCoords = NSZoneMalloc(zone, (gupLength) * sizeof(short));
gupOps = NSZoneMalloc(zone, (gupLength >> 1) * sizeof(char));
gupBBox = NSZoneMalloc(zone, (4) * sizeof(short));
w = bounds.size.width;
h = bounds.size.height;
j = 0;
for (i = 1; i <= y; i++) {
gupCoords[j++] = 0.0;
gupCoords[j++] = i * (gvFlags.grid ? gvFlags.grid : 1);
gupCoords[j++] = w;
gupCoords[j] = gupCoords[j-2];
j++;
}
for (i = 1; i <= x; i++) {
gupCoords[j++] = i * (gvFlags.grid ? gvFlags.grid : 1);
gupCoords[j++] = 0.0;
gupCoords[j] = gupCoords[j-2];
j++;
gupCoords[j++] = h;
}
i = gupLength >> 1;
while (i) {
gupOps[--i] = dps_lineto;
gupOps[--i] = dps_moveto;
}
gupBBox[0] = gupBBox[1] = 0;
gupBBox[2] = bounds.size.width + 1;
gupBBox[3] = bounds.size.height + 1;
}
#define MOVE_MASK NSLeftMouseUpMask|NSLeftMouseDraggedMask
- (BOOL)move:(NSEvent *)event
/*
* Moves the selection by cacheing the selected graphics into the
* selection cache, then compositing them repeatedly as the user
* moves the mouse. The tracking loop uses TIMER events to autoscroll
* at regular intervals. TIMER events do not have valid mouse coordinates,
* so the last coordinates are saved and restored when there is a TIMER event.
*/
{
NSEvent *peek;
float dx, dy;
NSWindow *window = [self window];
BOOL inTimerLoop = NO;
NSPoint p, start, last, sboundspad;
NSRect minbounds, sbounds, startbounds, visibleRect;
BOOL canScroll, tracking = YES, alternate, horizConstrain = NO, vertConstrain = NO, hideCursor;
last = [event locationInWindow];
alternate = ([event modifierFlags] & NSAlternateKeyMask) ? YES : NO;
event = [window nextEventMatchingMask:MOVE_MASK];
if ([event type] == NSLeftMouseUp) return NO;
hideCursor = [[NSUserDefaults standardUserDefaults] objectForKey:@"HideCursorOnMove"] ? YES : NO;
if (hideCursor) [NSCursor hide];
last = [self convertPoint:last fromView:nil];
last = [self grid:last];
[self lockFocus];
[self cacheSelection];
gvFlags.suspendLinkUpdate = YES; /* we'll update links when the move is complete */
[self graphicsPerform:@selector(deactivate)];
gvFlags.suspendLinkUpdate = NO;
sbounds = [self getBBoxOfArray:slist];
startbounds = sbounds;
minbounds = [self getBBoxOfArray:slist extended:NO];
sboundspad.x = minbounds.origin.x - sbounds.origin.x;
sboundspad.y = minbounds.origin.y - sbounds.origin.y;
[self compositeSelection:sbounds];
visibleRect = [self visibleRect];
canScroll = !NSEqualRects(visibleRect, [self bounds]);
start = sbounds.origin;
while (tracking) {
p = [event locationInWindow];
p = [self convertPoint:p fromView:nil];
p = [self grid:p];
dx = p.x - last.x;
dy = p.y - last.y;
if (dx || dy) {
[self drawRect:sbounds];
if (alternate && (dx || dy)) {
if (ABS(dx) > ABS(dy)) {
horizConstrain = YES;
dy = 0.0;
} else {
vertConstrain = YES;
dx = 0.0;
}
alternate = NO;
} else if (horizConstrain) {
dy = 0.0;
} else if (vertConstrain) {
dx = 0.0;
}
sbounds = NSOffsetRect(sbounds, dx, dy);
minbounds.origin.x = sbounds.origin.x + sboundspad.x;
minbounds.origin.y = sbounds.origin.y + sboundspad.y;
[self tryToPerform:@selector(updateRulers:) with:(void *)&minbounds];
if (!canScroll || NSContainsRect(visibleRect, sbounds)) {
[self compositeSelection:sbounds];
if(inTimerLoop){
[NSEvent stopPeriodicEvents];
inTimerLoop = NO;
}
}
last = p;
}
tracking = ([event type] != NSLeftMouseUp);
if (tracking) {
if (canScroll && !NSContainsRect(visibleRect, sbounds)) {
[window disableFlushWindow];
[self scrollPointToVisible:p]; // actually we want to keep the "edges" of the
// Graphic being resized that were visible when
// the move started visible throughout the
// moving session, this current solution is
// easy, but sub-standard
visibleRect = [self visibleRect];
[self compositeSelection:sbounds];
[window update];
[window enableFlushWindow];
[window flushWindow];
if(!inTimerLoop){
[NSEvent startPeriodicEventsAfterDelay:0.1 withPeriod:0.1];
inTimerLoop = YES;
}
}
p = [event locationInWindow];
if (!(peek = [window nextEventMatchingMask:MOVE_MASK untilDate:[NSDate date] inMode:NSEventTrackingRunLoopMode dequeue:NO])) {
event = [window nextEventMatchingMask:MOVE_MASK|NSPeriodicMask];
} else {
event = [window nextEventMatchingMask:MOVE_MASK];
}
if ([event type] == NSPeriodic) event = periodicEventWithLocationSetToPoint(event, p);
}
}
if (canScroll && inTimerLoop){
[NSEvent stopPeriodicEvents];
inTimerLoop = NO;
}
if (hideCursor) [NSCursor unhide];
p.x = sbounds.origin.x - start.x;
p.y = sbounds.origin.y - start.y;
if (p.x || p.y)
[self moveGraphicsBy:p andDraw:NO];
gvFlags.suspendLinkUpdate = YES; /* we'll update links when the move is complete */
[self graphicsPerform:@selector(activate)];
gvFlags.suspendLinkUpdate = NO;
startbounds = NSUnionRect(sbounds, startbounds);
[self updateTrackedLinks:startbounds];
[self tryToPerform:@selector(updateRulers:) with:nil];
[[self window] flushWindow];
[self unlockFocus];
return YES;
}
- (void)moveGraphicsBy:(NSPoint)vector andDraw:(BOOL)drawFlag
{
id change;
change = [[MoveGraphicsChange alloc] initGraphicView:self vector:vector];
[change startChange];
if (drawFlag) {
[self graphicsPerform:@selector(moveBy:) with:(id)&vector];
} else {
[slist makeObjectsPerform:@selector(moveBy:) withObject:(id)&vector];
}
[change endChange];
}
#define DRAG_MASK (NSLeftMouseUpMask|NSLeftMouseDraggedMask|NSPeriodicMask)
- (void)dragSelect:(NSEvent *)event
/*
* Allows the user the drag out a box to select all objects either
* intersecting the box, or fully contained within the box (depending
* on the state of the ALTERNATE key). After the selection is made,
* the slist is updated.
*/
{
int i;
Graphic *graphic;
NSWindow *window = [self window];
NSPoint p, last, start;
BOOL inTimerLoop = NO;
NSRect eb;
NSRect visibleRect, region, oldRegion;
BOOL mustContain, shift, canScroll, oldRegionSet = NO;
p = start = [event locationInWindow];
start = [self convertPoint:start fromView:nil];
last = start;
region = regionFromCorners(last, start);
shift = ([event modifierFlags] & NSShiftKeyMask) ? YES : NO;
mustContain = ([event modifierFlags] & NSAlternateKeyMask) ? YES : NO;
[self lockFocus];
visibleRect = [self visibleRect];
canScroll = !NSEqualRects(visibleRect, [self bounds]);
if (canScroll && !inTimerLoop) {
[NSEvent startPeriodicEventsAfterDelay:0.1 withPeriod:0.1];
inTimerLoop = YES;
}
PSsetgray(NSLightGray);
PSsetlinewidth(0.0);
event = [window nextEventMatchingMask:DRAG_MASK];
while ([event type] != NSLeftMouseUp) {
if ([event type] == NSPeriodic) event = periodicEventWithLocationSetToPoint(event, p);
p = [event locationInWindow];
p = [self convertPoint:p fromView:nil];
if (p.x != last.x || p.y != last.y) {
region = regionFromCorners(p, start);
[window disableFlushWindow];
if (oldRegionSet) {
oldRegion = NSInsetRect(oldRegion, -1.0, -1.0);
[self drawRect:oldRegion];
}
if (canScroll) {
[self scrollRectToVisible:region];
[self scrollPointToVisible:p];
}
PSrectstroke(region.origin.x, region.origin.y, region.size.width, region.size.height);
[self tryToPerform:@selector(updateRulers:) with:(void *)®ion];
[window enableFlushWindow];
[window flushWindow];
oldRegion = region; oldRegionSet = YES;
last = p;
PSWait();
}
p = [event locationInWindow];
event = [window nextEventMatchingMask:DRAG_MASK];
}
if (canScroll && inTimerLoop){
[NSEvent stopPeriodicEvents];
inTimerLoop = NO;
}
for (i = [glist count] - 1; i >= 0; i--) {
graphic = [glist objectAtIndex:i];
eb = [graphic extendedBounds];
if (![graphic isLocked] && ![graphic isSelected] &&
((mustContain && NSContainsRect(region, eb)) ||
(!mustContain && !NSIsEmptyRect(NSIntersectionRect(region, eb))))) {
[graphic select];
}
}
[self getSelection];
if (!dragRect) dragRect = NSZoneMalloc(NSDefaultMallocZone(), ((1) * sizeof(NSRect)));
*dragRect = region;
region = NSInsetRect(region, -1.0, -1.0);
[self drawRect:region];
[self recacheSelection:NO];
[self tryToPerform:@selector(updateRulers:) with:nil];
[self unlockFocus];
}
- (void)alignGraphicsBy:(AlignmentType)alignType edge:(float *)edge
{
SEL action;
id change;
change = [[AlignGraphicsChange alloc] initGraphicView:self];
[change startChange];
action = [GraphicView actionFromAlignType:alignType];
[self graphicsPerform:action with:edge];
[change endChange];
}
- (void)alignBy:(AlignmentType)alignType
{
int i;
NSRect rect;
Graphic *graphic;
float minEdge = 10000.0;
float maxEdge = 0.0;
float baseline = 0.0;
for (i = [slist count]-1; i >= 0 && !baseline; i--) {
graphic = [slist objectAtIndex:i];
rect = [graphic bounds];
switch (alignType) {
case LEFT:
if (rect.origin.x < minEdge)
minEdge = rect.origin.x;
break;
case RIGHT:
if (rect.origin.x + rect.size.width > maxEdge)
maxEdge = rect.origin.x + rect.size.width;
break;
case BOTTOM:
if (rect.origin.y < minEdge)
minEdge = rect.origin.y;
break;
case TOP:
if (rect.origin.y + rect.size.height > maxEdge)
maxEdge = rect.origin.y + rect.size.height;
break;
case HORIZONTAL_CENTERS:
if (rect.origin.y + floor(rect.size.height / 2.0) < minEdge)
minEdge = rect.origin.y + floor(rect.size.height / 2.0);
break;
case VERTICAL_CENTERS:
if (rect.origin.x + floor(rect.size.width / 2.0) < minEdge)
minEdge = rect.origin.x + floor(rect.size.width / 2.0);
break;
case BASELINES:
baseline = [graphic baseline];
break;
}
}
switch (alignType) {
case LEFT:
case BOTTOM:
case HORIZONTAL_CENTERS:
case VERTICAL_CENTERS:
[self alignGraphicsBy:alignType edge:&minEdge];
break;
case RIGHT:
case TOP:
[self alignGraphicsBy:alignType edge:&maxEdge];
break;
case BASELINES:
if (baseline) [self alignGraphicsBy:alignType edge:&baseline];
}
[[self window] flushWindow];
}
@end
extern NSEvent *periodicEventWithLocationSetToPoint(NSEvent *oldEvent, NSPoint point) {
return [NSEvent otherEventWithType:[oldEvent type] location:point modifierFlags:[oldEvent modifierFlags]
timestamp:[oldEvent timestamp] windowNumber:[oldEvent windowNumber] context:[oldEvent context]
subtype:[oldEvent subtype] data1:[oldEvent data1] data2:[oldEvent data2]];
}