This is Graphic.m in view mode; [Download] [Up]
#import "draw.h" #import <math.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 */ /* Object Versions */ #define NS30_VERSION 3 #define ROTATED_VERSION 4 #define CURRENT_VERSION ROTATED_VERSION #define PI 3.1415926535 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); } dkgrayRectList[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:CURRENT_VERSION]; 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. * Since Draw has been converted to subproject in Hippo.bundle, * we have to find the tiff files. -- pfkeb */ { NXBundle *bundle; NXImage *image; NXPoint spot; char buffer[MAXPATHLEN+1]; if (!CrossCursor) { bundle = [NXBundle bundleForClass:[self class]]; if ( [bundle getPath:buffer forResource:"cross" ofType:"tiff"] ) { image = [[NXImage allocFromZone:[self zone]] initFromFile:buffer]; CrossCursor = [NXCursor newFromImage:image]; 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 = currentGraphicIdentifier++; 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]; } static void unrotRect( NXRect *u, NXRect *b, NXPoint *p0, int angle ); /* 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.rtf and 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]]; } } return 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 { 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 = 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) && 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; NXRect boundSave; [self getExtendedBounds:&r]; if (gFlags.active && (!rect || NXIntersectsRect(rect, &r))) { if ([self isOpaque]) [Graphic showFastKnobFills]; [self setGraphicsState];/* does a gsave */ if (rotAngle != 0.0) { boundSave = bounds; unrotRect( &bounds, &boundSave, &unrotOrigin, rotAngle); PStranslate(bounds.origin.x, bounds.origin.y); PSrotate(rotAngle); PStranslate(-bounds.origin.x, -bounds.origin.y); } [self draw]; if (rotAngle != 0.0) bounds = boundSave; 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.height / 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; } /* don't call this function with b == u */ static void unrotRect( NXRect *u, NXRect *b, NXPoint *p0, int angle ) { double a = angle * PI / 180.0; u->origin.x = p0->x * b->size.width; u->origin.y = p0->y * b->size.height; if (angle == 0 || angle == 180 || angle == -180) { u->size.width = b->size.width; u->size.height = b->size.height; } else if (angle == 90 || angle == -90) { u->size.width = b->size.height; u->size.height = b->size.width; } else if (angle > 0 && angle < 90) { u->size.width = (b->size.width - u->origin.x)/cos(a); u->size.height = u->origin.x / sin(a); } else if (angle > 90 && angle < 180) { u->size.width = (b->size.height - u->origin.y)/sin(a); u->size.height = -u->origin.y / cos(a); } else if (angle > -90 && angle < 0) { u->size.width = -u->origin.y / sin(a); u->size.height = (b->size.height - u->origin.y)/cos(a); } else { u->size.width = -u->origin.x / cos(a); u->size.height = (-b->size.width + u->origin.x)/sin(a); } u->origin.x += b->origin.x; u->origin.y += b->origin.y; } - rotateTo:(const int *)angle { NXRect temp; int a; if (*angle == rotAngle) return self; /* * make sure angle is in the useful range */ a = *angle; while (a > 180) a -= 360; while (a < -180) a += 360; if (rotAngle == 0) temp = bounds; else unrotRect( &temp, &bounds, &unrotOrigin, rotAngle); rotAngle = a; if (rotAngle == 0) bounds = temp; else { float ang = rotAngle * PI/180.0; float x2 = temp.size.width * cos(ang); float y2 = temp.size.width * sin(ang); float x3 = x2 - temp.size.height * sin(ang); float y3 = y2 + temp.size.height * cos(ang); float x4 = - temp.size.height * sin(ang); float y4 = temp.size.height * cos(ang); bounds.origin.x = MIN(0.0, MIN(x2, MIN(x3, x4))); bounds.origin.y = MIN(0.0, MIN(y2, MIN(y3, y4))); bounds.size.width = MAX(0.0, MAX(x2, MAX(x3, x4))) - bounds.origin.x; bounds.size.height = MAX(0.0, MAX(y2, MAX(y3, y4))) - bounds.origin.y; bounds.origin.x += temp.origin.x; bounds.origin.y += temp.origin.y; } unrotOrigin.x = (temp.origin.x - bounds.origin.x) / bounds.size.width; unrotOrigin.y = (temp.origin.y - bounds.origin.y) / bounds.size.height; return self; } - rotateBy:(const int *) angle { int t = rotAngle+*angle; return [self rotateTo: &t]; } - (int) rotation { return rotAngle; } /* 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. Kind 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); if (gFlags.fillColorSet) NXWriteColor(stream, *fillColor); // new with ROTATED_VERSION NXWriteTypes(stream, "f", &rotAngle); NXWriteTypes(stream, "ff", &unrotOrigin.x, &unrotOrigin.y ); 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; } } } if (version >= ROTATED_VERSION) { NXReadTypes(stream, "f", &rotAngle); NXReadTypes(stream, "ff", &unrotOrigin.x, &unrotOrigin.y ); } return self; } /* Routines which may need subclassing for different Graphic types. */ - (BOOL)constrainByDefault { return 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; } 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]; bounds.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 (!newcorner) 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; } /* Methods added to support Hippo drawing */ - forward:(SEL)aSelector :(marg_list)argFrame { return self; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.