|
|
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.