|
|
1.1 ! root 1: #import "draw.h" ! 2: ! 3: @implementation Line : Graphic ! 4: /* ! 5: * Drawing a line is simple except that we have to keep track of whether ! 6: * the line goes from the upper left to the lower right of the bounds or ! 7: * from the lower left to the upper right. This can easily be determined ! 8: * every time a corner is moved to a different corner. Therefore, all ! 9: * that is needed is to override moveCorner:to:constrain: to keep track ! 10: * of that. It is an efficiency hack to have the downhill flag kept ! 11: * in our superclass's flags. ! 12: * ! 13: * This line is just a stub to get genstrings to generate ! 14: * a .strings file entry for the name of this type of Graphic. ! 15: * The name is used in the Undo New <Whatever> menu item. ! 16: * ! 17: * NXLocalString("Line", NULL, "Name of the tool that draws lines, i.e., the %s of the New %s operation.") ! 18: */ ! 19: ! 20: #define HIT_TOLERANCE 6.0 ! 21: ! 22: + initialize ! 23: { ! 24: [Line setVersion:1]; ! 25: return self; ! 26: } ! 27: ! 28: - init ! 29: { ! 30: [super init]; ! 31: startCorner = LOWER_LEFT; ! 32: return self; ! 33: } ! 34: ! 35: - (BOOL)isValid ! 36: /* ! 37: * A line is validly created if EITHER of the dimensions is big enough. ! 38: */ ! 39: { ! 40: return(bounds.size.width >= 5.0 || bounds.size.height >= 5.0); ! 41: } ! 42: ! 43: static int oppositeCorner(int corner) ! 44: { ! 45: switch (corner) { ! 46: case UPPER_RIGHT: return LOWER_LEFT; ! 47: case LOWER_LEFT: return UPPER_RIGHT; ! 48: case UPPER_LEFT: return LOWER_RIGHT; ! 49: case LOWER_RIGHT: return UPPER_LEFT; ! 50: } ! 51: ! 52: return corner; ! 53: } ! 54: ! 55: - (int)moveCorner:(int)corner to:(const NXPoint *)point constrain:(BOOL)flag ! 56: /* ! 57: * Moves the corner to the specified point keeping track of whether the ! 58: * line is going uphill or downhill and where the start corner has moved to. ! 59: */ ! 60: { ! 61: int newcorner; ! 62: ! 63: newcorner = [super moveCorner:corner to:point constrain:flag]; ! 64: ! 65: if (newcorner != corner) { ! 66: if ((newcorner == UPPER_RIGHT && corner == LOWER_LEFT) || ! 67: (newcorner == UPPER_LEFT && corner == LOWER_RIGHT) || ! 68: (newcorner == LOWER_RIGHT && corner == UPPER_LEFT) || ! 69: (newcorner == LOWER_LEFT && corner == UPPER_RIGHT)) { ! 70: } else { ! 71: gFlags.downhill = !gFlags.downhill; ! 72: } ! 73: if (startCorner == corner) { ! 74: startCorner = newcorner; ! 75: } else { ! 76: startCorner = oppositeCorner(newcorner); ! 77: } ! 78: } ! 79: ! 80: return newcorner; ! 81: } ! 82: ! 83: - constrainCorner:(int)corner toAspectRatio:(float)ratio ! 84: /* ! 85: * Constrains the corner to the nearest 15 degree angle. Ignores ratio. ! 86: */ ! 87: { ! 88: NXCoord width, height; ! 89: double angle, distance; ! 90: ! 91: distance = hypot(bounds.size.width, bounds.size.height); ! 92: angle = atan2(bounds.size.height, bounds.size.width); ! 93: angle = (angle / 3.1415) * 180.0; ! 94: angle = floor(angle / 15.0 + 0.5) * 15.0; ! 95: angle = (angle / 180.0) * 3.1415; ! 96: width = floor(cos(angle) * distance + 0.5); ! 97: height = floor(sin(angle) * distance + 0.5); ! 98: ! 99: switch (corner) { ! 100: case LOWER_LEFT: ! 101: bounds.origin.x -= width - bounds.size.width; ! 102: bounds.origin.y -= height - bounds.size.height; ! 103: break; ! 104: case UPPER_LEFT: ! 105: bounds.origin.x -= width - bounds.size.width; ! 106: break; ! 107: case LOWER_RIGHT: ! 108: bounds.origin.y -= height - bounds.size.height; ! 109: break; ! 110: } ! 111: ! 112: bounds.size.width = width; ! 113: bounds.size.height = height; ! 114: ! 115: return self; ! 116: } ! 117: ! 118: - (int)cornerMask ! 119: /* ! 120: * Only put corner knobs at the start and end of the line. ! 121: */ ! 122: { ! 123: if (gFlags.downhill) { ! 124: return(UPPER_LEFT_MASK|LOWER_RIGHT_MASK); ! 125: } else { ! 126: return(LOWER_LEFT_MASK|UPPER_RIGHT_MASK); ! 127: } ! 128: } ! 129: ! 130: - draw ! 131: /* ! 132: * Calls drawLine to draw the line, then draws the arrows if any. ! 133: */ ! 134: { ! 135: if (bounds.size.width < 1.0 && bounds.size.height < 1.0) return self; ! 136: ! 137: [self setLineColor]; ! 138: [self drawLine]; ! 139: ! 140: if (gFlags.arrow) { ! 141: if (gFlags.downhill) { ! 142: if (((gFlags.arrow != ARROW_AT_START) && ! 143: (startCorner == LOWER_RIGHT)) || ! 144: ((gFlags.arrow != ARROW_AT_END) && ! 145: (startCorner == UPPER_LEFT))) { ! 146: PSArrow(bounds.origin.x, ! 147: bounds.origin.y + bounds.size.height, ! 148: [self arrowAngle:UPPER_LEFT]); ! 149: } ! 150: if (((gFlags.arrow != ARROW_AT_START) && ! 151: (startCorner == UPPER_LEFT)) || ! 152: ((gFlags.arrow != ARROW_AT_END) && ! 153: (startCorner == LOWER_RIGHT))) { ! 154: PSArrow(bounds.origin.x + bounds.size.width, ! 155: bounds.origin.y, ! 156: [self arrowAngle:LOWER_RIGHT]); ! 157: } ! 158: } else { ! 159: if (((gFlags.arrow != ARROW_AT_START) && ! 160: (startCorner == LOWER_LEFT)) || ! 161: ((gFlags.arrow != ARROW_AT_END) && ! 162: (startCorner == UPPER_RIGHT))) { ! 163: PSArrow(bounds.origin.x + bounds.size.width, ! 164: bounds.origin.y + bounds.size.height, ! 165: [self arrowAngle:UPPER_RIGHT]); ! 166: } ! 167: if (((gFlags.arrow != ARROW_AT_START) && ! 168: (startCorner == UPPER_RIGHT)) || ! 169: ((gFlags.arrow != ARROW_AT_END) && ! 170: (startCorner == LOWER_LEFT))) { ! 171: PSArrow(bounds.origin.x, ! 172: bounds.origin.y, ! 173: [self arrowAngle:LOWER_LEFT]); ! 174: } ! 175: } ! 176: } ! 177: ! 178: return self; ! 179: } ! 180: ! 181: - (BOOL)hit:(const NXPoint *)point ! 182: /* ! 183: * Gets a hit if the point is within HIT_TOLERANCE of the line. ! 184: */ ! 185: { ! 186: NXRect r; ! 187: NXPoint p; ! 188: float lineangle, pointangle, distance; ! 189: float tolerance = HIT_TOLERANCE + linewidth; ! 190: ! 191: if (gFlags.locked || !gFlags.active) return NO; ! 192: ! 193: r = bounds; ! 194: if (r.size.width < tolerance) { ! 195: r.size.width += tolerance * 2.0; ! 196: r.origin.x -= tolerance; ! 197: } ! 198: if (r.size.height < tolerance) { ! 199: r.size.height += tolerance * 2.0; ! 200: r.origin.y -= tolerance; ! 201: } ! 202: ! 203: if (!NXMouseInRect(point, &r, NO)) return NO; ! 204: ! 205: p.x = point->x - bounds.origin.x; ! 206: p.y = point->y - bounds.origin.y; ! 207: if (gFlags.downhill) p.y = bounds.size.height - p.y; ! 208: if (p.x && bounds.size.width) { ! 209: lineangle = atan(bounds.size.height/bounds.size.width); ! 210: pointangle = atan(p.y/p.x); ! 211: distance = sqrt(p.x*p.x+p.y*p.y)*sin(fabs(lineangle-pointangle)); ! 212: } else { ! 213: distance = fabs(point->x - bounds.origin.x); ! 214: } ! 215: ! 216: return((distance - tolerance) <= linewidth); ! 217: } ! 218: ! 219: /* Methods intended to be subclassed */ ! 220: ! 221: - (float)arrowAngle:(int)corner ! 222: /* ! 223: * Returns the angle which the arrow should be drawn at. ! 224: */ ! 225: { ! 226: float angle; ! 227: angle = atan2(bounds.size.height, bounds.size.width); ! 228: angle = (angle / 3.1415) * 180.0; ! 229: switch (corner) { ! 230: case UPPER_RIGHT: return angle; ! 231: case LOWER_LEFT: return angle + 180.0; ! 232: case UPPER_LEFT: return 180.0 - angle; ! 233: case LOWER_RIGHT: return - angle; ! 234: } ! 235: return angle; ! 236: } ! 237: ! 238: - drawLine ! 239: /* ! 240: * The actual line drawing is done here so that it can be subclassed. ! 241: */ ! 242: { ! 243: if (gFlags.downhill) { ! 244: PSLine(bounds.origin.x, bounds.origin.y + bounds.size.height, ! 245: bounds.size.width, - bounds.size.height); ! 246: } else { ! 247: PSLine(bounds.origin.x, bounds.origin.y, ! 248: bounds.size.width, bounds.size.height); ! 249: } ! 250: return self; ! 251: } ! 252: ! 253: /* Archiving methods */ ! 254: ! 255: - write:(NXTypedStream *)stream ! 256: { ! 257: [super write:stream]; ! 258: NXWriteType(stream, "i", &startCorner); ! 259: return self; ! 260: } ! 261: ! 262: - read:(NXTypedStream *)stream ! 263: { ! 264: [super read:stream]; ! 265: if (NXTypedStreamClassVersion(stream, "Line") > 0) { ! 266: NXReadType(stream, "i", &startCorner); ! 267: } else { ! 268: startCorner = LOWER_LEFT; ! 269: } ! 270: return self; ! 271: } ! 272: ! 273: @end
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.