home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
NeXTSTEP 3.0
/
NeXTSTEP3.0.iso
/
NextDeveloper
/
Examples
/
AppKit
/
Draw
/
Graphic.m
< prev
next >
Wrap
Text File
|
1992-07-20
|
33KB
|
1,333 lines
#import "draw.h"
@implementation Graphic : Object
static int KNOB_WIDTH = 0.0;
static int KNOB_HEIGHT = 0.0;
#define MINSIZE 5.0 /* minimum size of a Graphic */
NXCursor *CrossCursor = nil; /* global since subclassers may need it */
/* Optimization method. */
/*
* The fastKnobFill optimization just keeps a list of black and dark gray
* rectangles (the knobbies are made out of black and dark gray rectangles)
* and emits them in a single NXRectFillList() which is much faster than
* doing individual rectfills (we also save the repeated setgrays).
*/
static NXRect *blackRectList = NULL;
static int blackRectSize = 0;
static int blackRectCount = 0;
static NXRect *dkgrayRectList = NULL;
static int dkgrayRectSize = 0;
static int dkgrayRectCount = 0;
+ fastKnobFill:(const NXRect *)aRect isBlack:(BOOL)isBlack
{
if (!aRect) return self;
if (isBlack) {
if (!blackRectList) {
blackRectSize = 16;
NX_ZONEMALLOC([NXApp zone], blackRectList, NXRect, blackRectSize);
} else {
while (blackRectCount >= blackRectSize) blackRectSize <<= 1;
NX_ZONEREALLOC([NXApp zone], blackRectList, NXRect, blackRectSize);
}
blackRectList[blackRectCount++] = *aRect;
} else {
if (!dkgrayRectList) {
dkgrayRectSize = 16;
NX_ZONEMALLOC([NXApp zone], dkgrayRectList, NXRect, dkgrayRectSize);
} else {
while (dkgrayRectCount >= dkgrayRectSize) dkgrayRectSize <<= 1;
NX_ZONEREALLOC([NXApp zone], dkgrayRectList, NXRect, dkgrayRectSize);
}
RU)rayRectList[dkgrayRectCount++] = *aRect;
}
return self;
}
+ showFastKnobFills
{
PSsetgray(NX_BLACK);
NXRectFillList(blackRectList, blackRectCount);
PSsetgray(NX_DKGRAY);
NXRectFillList(dkgrayRectList, dkgrayRectCount);
blackRectCount = 0;
dkgrayRectCount = 0;
return self;
}
/* Factory methods. */
+ initialize
/*
* This sets the class version so that we can compatibly read
* old Graphic objects out of an archive.
*/
{
[Graphic setVersion:3];
return self;
}
+ (BOOL)isEditable
/*
* Any Graphic which can be edited should return YES from this
* and its instances should do something in the response to the
* edit:in: method.
*/
{
return NO;
}
+ cursor
/*
* Any Graphic that doesn't have a special cursor gets the default cross.
*/
{
NXPoint spot;
if (!CrossCursor) {
CrossCursor = [NXCursor newFromImage:[NXImage newFromSection:"cross.tiff"]];
spot.x = 7.0; spot.y = 7.0;
[CrossCursor setHotSpot:&spot];
}
return CrossCursor;
}
static void initClassVars()
{
const char *value;
NXCoord w = 2.0, h = 2.0;
if (!KNOB_WIDTH) {
value = NXGetDefaultValue([NXApp appName], "KnobWidth");
if (value) w = floor(atof(value) / 2.0);
value = NXGetDefaultValue([NXApp appName], "KnobHeight");
if (value) h = floor(atof(value) / 2.0);
w = MAX(w, 1.0); h = MAX(h, 1.0);
KNOB_WIDTH = w * 2.0 + 1.0; /* size must be odd */
KNOB_HEIGHT = h * 2.0 + 1.0;
}
}
/*
* The currentGraphicIdentifier is a number that is kept unique for a given
* Draw document by being monotonically increasing and is bumped each time a
* new Graphic is created. The method of the same name is used during the
* archiving of a Draw document to write out what the number is at save-time.
* updateCurrentGraphicIdentifer: is used at document load time to reset
* the number to that level (if it's already higher, then we don't need to
* bump it).
*/
static int currentGraphicIdentifier = 1;
+ (int)currentGraphicIdentifier
{
return currentGraphicIdentifier;
}
+ updateCurrentGraphicIdentifier:(int)newMaxIdentifier
{
if (newMaxIdentifier > currentGraphicIdentifier) currentGraphicIdentifier = newMaxIdentifier;
return self;
}
- init
{
[super init];
gFlags.active = YES;
gFlags.selected = YES;
initClassVars();
identifier = currentGraphicIdeRU0ier++;
return self;
}
- awake
{
initClassVars();
return [super awake];
}
/* Private C functions and macros used to implement methods in this class. */
static void drawKnobs(const NXRect *rect, int cornerMask, BOOL black)
/*
* Draws either the knobs or their shadows (not both).
*/
{
NXRect knob;
NXCoord dx, dy;
BOOL oddx, oddy;
knob = *rect;
dx = knob.size.width / 2.0;
dy = knob.size.height / 2.0;
oddx = (floor(dx) != dx);
oddy = (floor(dy) != dy);
knob.size.width = KNOB_WIDTH;
knob.size.height = KNOB_HEIGHT;
knob.origin.x -= ((KNOB_WIDTH - 1.0) / 2.0);
knob.origin.y -= ((KNOB_HEIGHT - 1.0) / 2.0);
if (cornerMask & LOWER_LEFT_MASK) [Graphic fastKnobFill:&knob isBlack:black];
knob.origin.y += dy;
if (oddy) knob.origin.y -= 0.5;
if (cornerMask & LEFT_SIDE_MASK) [Graphic fastKnobFill:&knob isBlack:black];
knob.origin.y += dy;
if (oddy) knob.origin.y += 0.5;
if (cornerMask & UPPER_LEFT_MASK) [Graphic fastKnobFill:&knob isBlack:black];
knob.origin.x += dx;
if (oddx) knob.origin.x -= 0.5;
if (cornerMask & TOP_SIDE_MASK) [Graphic fastKnobFill:&knob isBlack:black];
knob.origin.x += dx;
if (oddx) knob.origin.x += 0.5;
if (cornerMask & UPPER_RIGHT_MASK) [Graphic fastKnobFill:&knob isBlack:black];
knob.origin.y -= dy;
if (oddy) knob.origin.y -= 0.5;
if (cornerMask & RIGHT_SIDE_MASK) [Graphic fastKnobFill:&knob isBlack:black];
knob.origin.y -= dy;
if (oddy) knob.origin.y += 0.5;
if (cornerMask & LOWER_RIGHT_MASK) [Graphic fastKnobFill:&knob isBlack:black];
knob.origin.x -= dx;
if (oddx) knob.origin.x += 0.5;
if (cornerMask & BOTTOM_SIDE_MASK) [Graphic fastKnobFill:&knob isBlack:black];
}
/* Private methods sometimes overridden by subclassers */
- setGraphicsState
/*
* Emits a gsave, must be balanced by grestore.
*/
{
PSSetParameters(gFlags.linecap, gFlags.linejoin, linewidth);
return self;
}
- setLineColor
{
if (lineColor) {
NXSetColor(*lineColor);
} else {
NXSetColor(NX_COLORBLACK);
}
return self;
}
- setFillColor
{
if (fillColor) NXSetColor(*fillColor);
return self;
}
- (int)cornerMask
/*
* Returns a mask of the corners which should have a knobby in them.
*/
{
return ALL_CORNERS;
}
/* Data link methods -- see Links.rtfRU1 gvLinks.m for more info */
/*
* Most Graphics aren't linked (i.e. their visual display is
* not determined by some other document). See Image and
* TextGraphic for examples of Graphics that sometimes do.
*/
- setLink:(NXDataLink *)aLink
{
return nil;
}
- (NXDataLink *)link
{
return nil;
}
- (Graphic *)graphicLinkedBy:(NXDataLink *)aLink
/*
* The reason we implement this method (instead of just relying on
* saying if ([graphic link] == aLink)) is for the sake of Group
* objects which may have a linked Graphic embedded in them.
*/
{
NXDataLink *link = [self link];
if (link) {
if (!aLink) { /* !aLink means any link */
return ([link disposition] != NX_LinkBroken) ? self : nil;
} else {
return (aLink == link) ? self : nil;
}
}
return nil;
}
- reviveLink:(NXDataLinkManager *)linkManager
/*
* We never archive link information (but, of course, the unique identifer
* is always archived with a Graphic). Thus, when our document is reloaded,
* we just asked the NXDataLinkManager which NXDataLink object is associated
* with the NXSelection which represents this Graphic.
*/
{
if (![self link]) [self setLink:[linkManager findDestinationLinkWithSelection:[self selection]]];
return self;
}
- (NXSelection *)selection
/*
* Just creates an NXSelection "bag o' bits" with our unique identifier in it.
*/
{
char buffer[200];
sprintf(buffer, "%d %d", ByGraphic, [self identifier]);
return [[NXSelection allocFromZone:[self zone]] initWithDescription:buffer length:strlen(buffer)+1];
}
- (BOOL)mightBeLinked
/*
* This is set whenever our Graphic has a link set in it.
* It is never cleared.
* We use it during copy/paste to determine whether we have
* to check with the data link manager to possibly reestablish
* a link to this object.
*/
{
return gFlags.mightBeLinked;
}
/* Notification messages */
/*
* These methods are sent when a Graphic is added to or removed
* from a GraphicView (respectively). Currently we only use them
* to break and reestablish links if any.
*/
- wasAddedTo:(GraphicView *)sender
{
NXDataLink *link;
NXDataLinkManager *linkManager;
if ((linkManager = [sender linkManager]) && (link = [self link])) {
if ([link disposition] == NX_LinkBroken) {
[linkManager addLink:link at:[self selection]];
}
}
RU2return self;
}
- wasRemovedFrom:(GraphicView *)sender
{
[[self link] break];
return self;
}
/* Methods for uniquely identifying a Graphic. */
- resetIdentifier
{
identifier = currentGraphicIdentifier++;
return self;
}
- writeIdentifierTo:(char *)buffer
/*
* This method is necessary to support a Group which never writes out
* its own identifier, but, instead has its components each write out
* their own identifier.
*/
{
sprintf(buffer, "%d", identifier);
return self;
}
- (int)identifier
{
return identifier;
}
- (Graphic *)graphicIdentifiedBy:(int)anIdentifier
{
return (identifier == anIdentifier) ? self : nil;
}
/* Event handling */
- (BOOL)handleEvent:(NXEvent *)event at:(const NXPoint *)p inView:(View *)view
/*
* Currently the only Graphic's that handle events are Image Graphic's that
* are linked to something else (they follow the link on double-click and
* the track the mouse for link buttons, for example). This method should
* return YES only if it tracked the mouse until it went up.
*/
{
return NO;
}
/* Number of Graphics this Graphic represents (always 1 for non-Group). */
- (int)graphicCount
/*
* This is really only here to support Groups. It is used by the
* Object Link stuff purely to know how much space will be needed to
* create an NXSelection with the unique identifiers of all the objects
* in the selection.
*/
{
return 1;
}
/* Public routines mostly called by GraphicView's. */
- (const char *)title
{
return NXLoadLocalStringFromTableInBundle(NULL, nil, [self name], NULL);
}
- (BOOL)isSelected
{
return gFlags.selected;
}
- (BOOL)isActive
{
return gFlags.active;
}
- (BOOL)isLocked
{
return gFlags.locked;
}
- select
{
gFlags.selected = YES;
return self;
}
- deselect
{
gFlags.selected = NO;
return self;
}
- activate
/*
* Activation is used to *temporarily* take a Graphic out of the GraphicView.
*/
{
gFlags.active = YES;
return self;
}
- deactivate
{
gFlags.active = NO;
return self;
}
- lock
/*
* A locked graphic cannot be selected, resized or moved.
*/
{
gFlags.locked = YES;
return self;
}
- unlock
{
gFlags.locked = NO;
return self;
}
/* See TextGraphic for more info about form entries. */
- (BOOL)isFormEntry
{
return NO;
}
- setFormEntry:(int)flag
RU3 return self;
}
- (BOOL)hasFormEntries
{
return NO;
}
- (BOOL)writeFormEntryToStream:(NXStream *)stream
{
return NO;
}
/* See Group and Image for more info about cacheability. */
- setCacheable:(BOOL)flag
{
return self;
}
- (BOOL)isCacheable
{
return YES;
}
/* Getting and setting the bounds. */
- getBounds:(NXRect *)theRect
{
*theRect = bounds;
return self;
}
- setBounds:(const NXRect *)aRect
{
bounds = *aRect;
return self;
}
- (NXRect *)getExtendedBounds:(NXRect *)theRect
/*
* Returns, by reference, the rectangle which encloses the Graphic
* AND ITS KNOBBIES and its increased line width (if appropriate).
*/
{
if (bounds.size.width < 0.0) {
theRect->origin.x = bounds.origin.x + bounds.size.width;
theRect->size.width = - bounds.size.width;
} else {
theRect->origin.x = bounds.origin.x;
theRect->size.width = bounds.size.width;
}
if (bounds.size.height < 0.0) {
theRect->origin.y = bounds.origin.y + bounds.size.height;
theRect->size.height = - bounds.size.height;
} else {
theRect->origin.y = bounds.origin.y;
theRect->size.height = bounds.size.height;
}
theRect->size.width = MAX(1.0, theRect->size.width);
theRect->size.height = MAX(1.0, theRect->size.height);
NXInsetRect(theRect, - ((KNOB_WIDTH - 1.0) + linewidth + 1.0),
- ((KNOB_HEIGHT - 1.0) + linewidth + 1.0));
if (gFlags.arrow) {
if (linewidth) {
NXInsetRect(theRect, - linewidth * 2.5, - linewidth * 2.5);
} else {
NXInsetRect(theRect, - 13.0, - 13.0);
}
}
NXIntegralRect(theRect);
return theRect;
}
- (int)knobHit:(const NXPoint *)p
/*
* Returns 0 if point is in bounds, and Graphic isOpaque, and no knobHit.
* Returns -1 if outside bounds or not opaque or not active.
* Returns corner number if there is a hit on a corner.
* We have to be careful when the bounds are off an odd size since the
* knobs on the sides are one pixel larger.
*/
{
NXRect eb;
NXRect knob;
NXCoord dx, dy;
BOOL oddx, oddy;
int cornerMask = [self cornerMask];
[self getExtendedBounds:&eb];
if (!gFlags.active) {
return -1;
} else if (!gFlags.selected) {
return (NXMouseInRect(p, &bounds, NO) && [self isOpaque]) ? 0 : -1;
} else {
if (!NXMouseInRect(p, &eb, NO)) return -1;
}
knob = bounds;
dx = knobRU4e.width / 2.0;
dy = knob.size.height / 2.0;
oddx = (floor(dx) != dx);
oddy = (floor(dy) != dy);
knob.size.width = KNOB_WIDTH;
knob.size.height = KNOB_HEIGHT;
knob.origin.x -= ((KNOB_WIDTH - 1.0) / 2.0);
knob.origin.y -= ((KNOB_HEIGHT - 1.0) / 2.0);
if ((cornerMask & LOWER_LEFT_MASK) && NXMouseInRect(p, &knob, NO))
return(LOWER_LEFT);
knob.origin.y += dy;
if (oddy) knob.origin.y -= 0.5;
if ((cornerMask & LEFT_SIDE_MASK) && NXMouseInRect(p, &knob, NO))
return(LEFT_SIDE);
knob.origin.y += dy;
if (oddy) knob.origin.y += 0.5;
if ((cornerMask & UPPER_LEFT_MASK) && NXMouseInRect(p, &knob, NO))
return(UPPER_LEFT);
knob.origin.x += dx;
if (oddx) knob.origin.x -= 0.5;
if ((cornerMask & TOP_SIDE_MASK) && NXMouseInRect(p, &knob, NO))
return(TOP_SIDE);
knob.origin.x += dx;
if (oddx) knob.origin.x += 0.5;
if ((cornerMask & UPPER_RIGHT_MASK) && NXMouseInRect(p, &knob, NO))
return(UPPER_RIGHT);
knob.origin.y -= dy;
if (oddy) knob.origin.y -= 0.5;
if ((cornerMask & RIGHT_SIDE_MASK) && NXMouseInRect(p, &knob, NO))
return(RIGHT_SIDE);
knob.origin.y -= dy;
if (oddy) knob.origin.y += 0.5;
if ((cornerMask & LOWER_RIGHT_MASK) && NXMouseInRect(p, &knob, NO))
return(LOWER_RIGHT);
knob.origin.x -= dx;
if (oddx) knob.origin.x += 0.5;
if ((cornerMask & BOTTOM_SIDE_MASK) && NXMouseInRect(p, &knob, NO))
return(BOTTOM_SIDE);
return NXMouseInRect(p, &bounds, NO) ? ([self isOpaque] ? 0 : -1) : -1;
}
/* This method is analogous to display (not drawSelf::) in View. */
- draw:(const NXRect *)rect
/*
* Draws the graphic inside rect. If rect is NULL, then it draws the
* entire Graphic. If the Graphic is not intersected by rect, then it
* is not drawn at all. If the Graphic is selected, it is drawn with
* its knobbies. This method is not intended to be overridden. It
* calls the overrideable method "draw" which doesn't have to worry
* about drawing the knobbies.
*
* Note the showFastKnobFills optimization here. If this Graphic is
* opaque then there is a possibility that it might obscure knobbies
* of Graphics underneath it, so we must emit the cached rectfills
* before drawing this Graphic.
*/
{
NXRect r;
[self getExtendedBounds:&r];
if (gFlags.active && (!rect || NXIntersectsRect(recRU5r))) {
if ([self isOpaque]) [Graphic showFastKnobFills];
[self setGraphicsState]; /* does a gsave */
[self draw];
PSgrestore(); /* so we need a grestore here */
if (NXDrawingStatus == NX_DRAWING) {
if (gFlags.selected) {
r.origin.x = floor(bounds.origin.x);
r.origin.y = floor(bounds.origin.y);
r.size.width = floor(bounds.origin.x + bounds.size.width + 0.99) - r.origin.x;
r.size.height = floor(bounds.origin.y + bounds.size.height + 0.99) - r.origin.y;
r.origin.x += 1.0;
r.origin.y -= 1.0;
drawKnobs(&r, [self cornerMask], YES); /* shadows */
r.origin.x = floor(bounds.origin.x);
r.origin.y = floor(bounds.origin.y);
r.size.width = floor(bounds.origin.x + bounds.size.width + 0.99) - r.origin.x;
r.size.height = floor(bounds.origin.y + bounds.size.height + 0.99) - r.origin.y;
drawKnobs(&r, [self cornerMask], NO); /* knobs */
}
}
return self;
}
return nil;
}
/*
* Returns whether this Graphic can emit, all by itself, fully
* encapsulated PostScript (or fully conforming TIFF) representing
* itself. This is an optimization for copy/paste.
*/
- (BOOL)canEmitEPS
{
return NO;
}
- (BOOL)canEmitTIFF
{
return NO;
}
/* Sizing, aligning and moving. */
- moveLeftEdgeTo:(const NXCoord *)x
{
bounds.origin.x = *x;
return self;
}
- moveRightEdgeTo:(const NXCoord *)x
{
bounds.origin.x = *x - bounds.size.width;
return self;
}
- moveTopEdgeTo:(const NXCoord *)y
{
bounds.origin.y = *y - bounds.size.height;
return self;
}
- moveBottomEdgeTo:(const NXCoord *)y
{
bounds.origin.y = *y;
return self;
}
- moveHorizontalCenterTo:(const NXCoord *)x
{
bounds.origin.x = *x - floor(bounds.size.width / 2.0);
return self;
}
- moveVerticalCenterTo:(const NXCoord *)y
{
bounds.origin.y = *y - floor(bounds.size.height / 2.0);
return self;
}
- (NXCoord)baseline
{
return 0.0;
}
- moveBaselineTo:(const NXCoord *)y
{
return self;
}
- moveBy:(const NXPoint *)offset
{
bounds.origin.x += floor(offset->x);
bounds.origin.y += floor(offset->y);
return self;
}
- moveTo:(const NXPoint *)p
{
bounds.origin.x = floor(p->x);
bounds.origin.y = floor(p->y);
return self;
}
- centerAt:(const NXPoint *)p
{
bounds.origin.x = floor(p->x - bounds.size.width / 2.0);
bounds.origin.y = floor(p->y - bounds.size.heRU6 / 2.0);
return self;
}
- sizeTo:(const NXSize *)size
{
bounds.size.width = floor(size->width);
bounds.size.height = floor(size->height);
return self;
}
- sizeToNaturalAspectRatio
{
return [self constrainCorner:UPPER_RIGHT toAspectRatio:[self naturalAspectRatio]];
}
- sizeToGrid:(GraphicView *)graphicView
{
NXPoint p;
[graphicView grid:&bounds.origin];
p.x = bounds.origin.x + bounds.size.width;
p.y = bounds.origin.y + bounds.size.height;
[graphicView grid:&p];
bounds.size.width = p.x - bounds.origin.x;
bounds.size.height = p.y - bounds.origin.y;
return self;
}
- alignToGrid:(GraphicView *)graphicView
{
[graphicView grid:&bounds.origin];
return self;
}
/* Compatibility method for old PSGraphic and Tiff classes. */
- replaceWithImage
{
return self;
}
/* Public routines. */
- setLineWidth:(const float *)value
/*
* This is called with value indirected so that it can be called via
* a perform:with: method. Kind of screwy, but ...
*/
{
if (value) linewidth = *value;
return self;
}
- (float)lineWidth
{
return linewidth;
}
- setLineColor:(const NXColor *)color
{
if (color) {
if (NXEqualColor(*color, NX_COLORBLACK)) {
NX_FREE(lineColor);
lineColor = NULL;
gFlags.nooutline = NO;
} else {
if (!lineColor) NX_ZONEMALLOC([self zone], lineColor, NXColor, 1);
*lineColor = *color;
gFlags.nooutline = NO;
}
}
return self;
}
- (NXColor)lineColor
{
return lineColor ? *lineColor : NX_COLORBLACK;
}
- setFillColor:(const NXColor *)color
{
if (color) {
if (!fillColor) NX_ZONEMALLOC([self zone], fillColor, NXColor, 1);
*fillColor = *color;
if (![self fill]) [self setFill:FILL_NZWR];
}
return self;
}
- (NXColor)fillColor
{
return fillColor ? *fillColor : NX_COLORWHITE;
}
- (Graphic *)colorAcceptorAt:(const NXPoint *)point
/*
* This method supports dragging and dropping colors on Graphics.
* Whatever object is returned from this may well be sent
* setFillColor: if the color actually gets dropped on it.
* See gvDrag.m's acceptsColor:atPoint: method.
*/
{
return nil;
}
- changeFont:sender
{
return self;
}
- font
{
return nil;
}
- setGray:(const float *)value
/*
* This is called with value indirected so that it can be called via
* a perform:with: method. RU7d of screwy, but ...
* Now that we have converted to using NXColor's, we'll interpret this
* method as a request to set the lineColor.
*/
{
NXColor color;
if (value) {
color = NXConvertGrayToColor(*value);
[self setLineColor:&color];
}
return self;
}
- (float)gray
{
float retval;
if (lineColor) {
NXConvertColorToGray(*lineColor, &retval);
} else {
retval = NX_BLACK;
}
return retval;
}
- setFill:(int)mode
{
switch (mode) {
case FILL_NONE: gFlags.eofill = gFlags.fill = NO; break;
case FILL_EO: gFlags.eofill = YES; gFlags.fill = NO; break;
case FILL_NZWR: gFlags.eofill = NO; gFlags.fill = YES; break;
}
return self;
}
- (int)fill
{
if (gFlags.eofill) {
return FILL_EO;
} else if (gFlags.fill) {
return FILL_NZWR;
} else {
return FILL_NONE;
}
}
- setOutlined:(BOOL)outlinedFlag
{
gFlags.nooutline = outlinedFlag ? NO : YES;
return self;
}
- (BOOL)isOutlined
{
return gFlags.nooutline ? NO : YES;
}
- setLineCap:(int)capValue
{
if (capValue >= 0 && capValue <= 2) {
gFlags.linecap = capValue;
}
return self;
}
- (int)lineCap
{
return gFlags.linecap;
}
- setLineArrow:(int)arrowValue
{
if (arrowValue >= 0 && arrowValue <= 3) {
gFlags.arrow = arrowValue;
}
return self;
}
- (int)lineArrow
{
return gFlags.arrow;
}
- setLineJoin:(int)joinValue
{
if (joinValue >= 0 && joinValue <= 2) {
gFlags.linejoin = joinValue;
}
return self;
}
- (int)lineJoin
{
return gFlags.linejoin;
}
/* Archiver-related methods. */
- write:(NXTypedStream *)stream
/*
* Since a typical document has many Graphics, we want to try and make
* the archived document small, so we don't write out the linewidth and
* gray values if they are the most common 0 and NX_BLACK. To accomplish
* this, we note that we haven't written them out by setting the
* bits in gFlags.
*/
{
[super write:stream];
gFlags.linewidthSet = (linewidth != 0.0);
gFlags.lineColorSet = lineColor ? YES : NO;
gFlags.fillColorSet = fillColor ? YES : NO;
NXWriteTypes(stream, "ffffii", &bounds.origin.x, &bounds.origin.y,
&bounds.size.width, &bounds.size.height, &gFlags, &identifier);
if (gFlags.linewidthSet) NXWriteTypes(stream, "f", &linewidth);
if (gFlags.lineColorSet) NXWriteColor(stream, *lineColor);
ifRU8lags.fillColorSet) NXWriteColor(stream, *fillColor);
return self;
}
- read:(NXTypedStream *)stream
{
int version;
float gray = NX_BLACK;
[super read:stream];
version = NXTypedStreamClassVersion(stream, "Graphic");
if (version > 2) {
NXReadTypes(stream, "ffffii", &bounds.origin.x, &bounds.origin.y,
&bounds.size.width, &bounds.size.height, &gFlags, &identifier);
} else if (version > 1) {
NXReadTypes(stream, "ffffsi", &bounds.origin.x, &bounds.origin.y,
&bounds.size.width, &bounds.size.height, &gFlags, &identifier);
} else {
NXReadTypes(stream, "ffffs", &bounds.origin.x, &bounds.origin.y,
&bounds.size.width, &bounds.size.height, &gFlags);
identifier = currentGraphicIdentifier++;
}
if (version > 1 && identifier >= currentGraphicIdentifier) currentGraphicIdentifier = identifier+1;
if (gFlags.linewidthSet) NXReadTypes(stream, "f", &linewidth);
if (version < 1) {
if (gFlags.lineColorSet) NXReadTypes(stream, "f", &gray);
if (gFlags.fillColorSet && (gFlags.eofill | gFlags.fill)) {
NX_ZONEMALLOC([self zone], lineColor, NXColor, 1);
*lineColor = NXConvertGrayToColor(NX_BLACK);
NX_ZONEMALLOC([self zone], fillColor, NXColor, 1);
*fillColor = NXConvertGrayToColor(gray);
} else if (gFlags.eofill | gFlags.fill) {
NX_ZONEMALLOC([self zone], fillColor, NXColor, 1);
*fillColor = NXConvertGrayToColor(gray);
[self setOutlined:NO];
} else {
NX_ZONEMALLOC([self zone], lineColor, NXColor, 1);
*lineColor = NXConvertGrayToColor(gray);
}
} else {
if (gFlags.lineColorSet) {
NX_ZONEMALLOC([self zone], lineColor, NXColor, 1);
*lineColor = NXReadColor(stream);
if (NXEqualColor(*lineColor, NX_COLORCLEAR)) {
free(lineColor);
lineColor = NULL;
[self setOutlined:NO];
}
}
if (gFlags.fillColorSet) {
NX_ZONEMALLOC([self zone], fillColor, NXColor, 1);
*fillColor = NXReadColor(stream);
if (NXEqualColor(*fillColor, NX_COLORCLEAR) || (NXAlphaComponent(*fillColor) == 0.0)) {
free(fillColor);
fillColor = NULL;
[self setFill:FILL_NONE];
// } else if (!gFlags.eofill && !gFlags.fill) { // why did I add this code here?
// gFlags.fill = YES;
}
}
}
return self;
}
/* Routines which may need subclassing for different Graphic types. */
- (BOOL)constrainByDefault
{
RU9urn NO;
}
- constrainCorner:(int)corner toAspectRatio:(float)aspect
/*
* Modifies the bounds rectangle by moving the specified corner so that
* the Graphic maintains the specified aspect ratio. This is used during
* constrained resizing. Can be overridden if the aspect ratio is not
* sufficient to constrain resizing.
*/
{
int newcorner;
float actualAspect;
if (!bounds.size.height || !bounds.size.width || !aspect) return self;
actualAspect = bounds.size.width / bounds.size.height;
if (actualAspect == aspect) return self;
switch (corner) {
case LEFT_SIDE:
bounds.origin.x -= bounds.size.height * aspect-bounds.size.width;
case RIGHT_SIDE:
bounds.size.width = bounds.size.height * aspect;
if (bounds.size.width) NXIntegralRect(&bounds);
return self;
case BOTTOM_SIDE:
bounds.origin.y -= bounds.size.width / aspect-bounds.size.height;
case TOP_SIDE:
bounds.size.height = bounds.size.width / aspect;
if (bounds.size.height) NXIntegralRect(&bounds);
return self;
case LOWER_LEFT:
corner = 0;
case 0:
case UPPER_RIGHT:
case UPPER_LEFT:
case LOWER_RIGHT:
if (actualAspect > aspect) {
newcorner = ((corner|KNOB_DY_ONCE)&(~(KNOB_DY_TWICE)));
} else {
newcorner = ((corner|KNOB_DX_ONCE)&(~(KNOB_DX_TWICE)));
}
return [self constrainCorner:newcorner toAspectRatio:aspect];
default:
return self;
}
}
#define RESIZE_MASK (NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK|NX_TIMERMASK)
- resize:(NXEvent *)event by:(int)corner in:(GraphicView *)view
/*
* Resizes the graphic by the specified corner. If corner == CREATE,
* then it is resized by the UPPER_RIGHT corner, but the initial size
* is reset to 1 by 1.
*/
{
NXPoint p, last;
float aspect = 0.0;
Window *window = [view window];
BOOL constrain, canScroll;
DrawStatusType oldDrawStatus;
NXTrackingTimer *timer = NULL;
NXRect eb, starteb, oldeb, visibleRect;
if (!gFlags.active || !gFlags.selected || !corner) return self;
constrain = ((event->flags & NX_ALTERNATEMASK) &&
((bounds.size.width && bounds.size.height) || corner == CREATE));
if ([self constrainByDefault]) constrain = !constrain;
if (constrain) aspect = bounds.size.width / bounds.size.height;
if (corner == CREATE) {
bounds.size.width = bounds.size.height = 1.0;
corner = UPPER_RIGHT;
RU@ gFlags.selected = NO;
[self getExtendedBounds:&eb];
[view lockFocus];
gFlags.active = NO;
[view cache:&eb andUpdateLinks:NO];
gFlags.active = YES;
starteb = eb;
[self draw:NULL];
[window flushWindow];
oldDrawStatus = DrawStatus;
DrawStatus = Resizing;
[view getVisibleRect:&visibleRect];
canScroll = !NXEqualRect(&visibleRect, &bounds);
if (canScroll && !timer) timer = NXBeginTimer(NULL, 0.1, 0.1);
while (event->type != NX_MOUSEUP) {
p = event->location;
event = [NXApp getNextEvent:RESIZE_MASK];
if (event->type == NX_TIMER) event->location = p;
p = event->location;
[view convertPoint:&p fromView:nil];
[view grid:&p];
if (p.x != last.x || p.y != last.y) {
corner = [self moveCorner:corner to:&p constrain:constrain];
if (constrain) [self constrainCorner:corner toAspectRatio:aspect];
oldeb = eb;
[self getExtendedBounds:&eb];
[window disableFlushWindow];
[view drawSelf:&oldeb :1];
if (canScroll) {
[view scrollPointToVisible:&p]; // actually we want to keep the "edges" of the
// Graphic being resized that were visible when
// the resize started visible throughout the
// resizing time (this will be difficult if those
// edges flip from being the left edge to the
// right edge in the middle of the resize!).
}
[self draw:NULL];
[view tryToPerform:@selector(updateRulers:) with:(void *)&bounds];
[window reenableFlushWindow];
[window flushWindow];
last = p;
NXPing();
}
}
if (canScroll && timer) {
NXEndTimer(timer);
timer = NULL;
}
gFlags.selected = YES;
DrawStatus = oldDrawStatus;
[view cache:&eb andUpdateLinks:NO]; // redraw after resizing a Graphic
NXUnionRect(&eb, &starteb);
[view updateTrackedLinks:&starteb];
[view tryToPerform:@selector(updateRulers:) with:nil];
[window flushWindow];
[view unlockFocus];
return self;
}
- (BOOL)create:(NXEvent *)event in:(GraphicView *)view
/*
* This method rarely needs to be subclassed.
* It sets up an initial bounds, and calls resize:by:in:.
*/
{
BOOL valid;
NXCoord gridSpacing;
bounds.origin = event->location;
[view convertPoint:&bounds.origin fromView:nil];
[view grid:&bounds.origin];
gridSpacing = (NXCoord)[view gridSpacing];
RUAounds.size.height = gridSpacing;
bounds.size.width = gridSpacing * [self naturalAspectRatio];
[self resize:event by:CREATE in:view];
valid = [self isValid];
if (valid) {
gFlags.selected = YES;
gFlags.active = YES;
} else {
gFlags.selected = NO;
gFlags.active = NO;
[view display];
}
return valid;
}
- (BOOL)hit:(const NXPoint *)p
{
return (!gFlags.locked && gFlags.active && NXMouseInRect(p, &bounds, NO));
}
- (BOOL)isOpaque
{
return [self fill] ? YES : NO;
}
- (BOOL)isValid
/*
* Called after a Graphic is created to see if it is valid (this usually
* means "is it big enough?").
*/
{
return (bounds.size.width > MINSIZE && bounds.size.height > MINSIZE);
}
- (float)naturalAspectRatio
/*
* A natural aspect ratio of zero means it doesn't have a natural aspect ratio.
*/
{
return 0.0;
}
- (int)moveCorner:(int)corner to:(const NXPoint *)p constrain:(BOOL)flag
/*
* Moves the specified corner to the specified point.
* Returns the position of the corner after it was moved.
*/
{
int newcorner = corner;
if ((corner & KNOB_DX_ONCE) && (corner & KNOB_DX_TWICE)) {
bounds.size.width += p->x - (bounds.origin.x + bounds.size.width);
if (bounds.size.width <= 0.0) {
newcorner &= ~ (KNOB_DX_ONCE | KNOB_DX_TWICE);
bounds.origin.x += bounds.size.width;
bounds.size.width = - bounds.size.width;
}
} else if (!(corner & KNOB_DX_ONCE)) {
bounds.size.width += bounds.origin.x - p->x;
bounds.origin.x = p->x;
if (bounds.size.width <= 0.0) {
newcorner |= KNOB_DX_ONCE | KNOB_DX_TWICE;
bounds.origin.x += bounds.size.width;
bounds.size.width = - bounds.size.width;
}
}
if ((corner & KNOB_DY_ONCE) && (corner & KNOB_DY_TWICE)) {
bounds.size.height += p->y - (bounds.origin.y + bounds.size.height);
if (bounds.size.height <= 0.0) {
newcorner &= ~ (KNOB_DY_ONCE | KNOB_DY_TWICE);
bounds.origin.y += bounds.size.height;
bounds.size.height = - bounds.size.height;
}
} else if (!(corner & KNOB_DY_ONCE)) {
bounds.size.height += bounds.origin.y - p->y;
bounds.origin.y = p->y;
if (bounds.size.height <= 0.0) {
newcorner |= KNOB_DY_ONCE | KNOB_DY_TWICE;
bounds.origin.y += bounds.size.height;
bounds.size.height = - bounds.size.height;
}
}
if (newcorner != LOWER_LEFT) newcorner &= 0xf;
if (RUBcorner) newcorner = LOWER_LEFT;
return newcorner;
}
- unitDraw
/*
* If a Graphic just wants to draw itself in the bounding box of
* {{0.0,0.0},{1.0,1.0}}, it can simply override this method.
* Everything else will work fine.
*/
{
return self;
}
- draw
/*
* Almost all Graphics need to override this method.
* It does the Graphic-specific drawing.
* By default, it scales the coordinate system and calls unitDraw.
*/
{
if (bounds.size.width >= 1.0 && bounds.size.height >= 1.0) {
PStranslate(bounds.origin.x, bounds.origin.y);
PSscale(bounds.size.width, bounds.size.height);
[self unitDraw];
}
return self;
}
- (BOOL)edit:(NXEvent *)event in:(View *)view
/*
* Any Graphic which has editable text should override this method
* to edit that text. TextGraphic is an example.
*/
{
return NO;
}
@end