|
|
1.1 root 1: #import "draw.h"
2:
3: /*
4: * This line is just a stub to get genstrings to generate
5: * a .strings file entry for the name of this type of Graphic.
6: * The name is used in the Undo New <Whatever> menu item.
7: *
8: * NXLocalString("Scribble", NULL, "Name of the tool that draws scribbles, i.e., the %s of the New %s operation.")
9: */
10:
11: @implementation Scribble : Graphic
12:
13: static NXPoint lastPoint; /* used in creating only */
14:
15: + initialize
16: /*
17: * This bumps the class version so that we can compatibly read
18: * old Graphic objects out of an archive.
19: */
20: {
21: [Scribble setVersion:1];
22: return self;
23: }
24:
25: + cursor
26: /*
27: * A Scribble uses a pencil as its cursor.
28: */
29: {
30: NXPoint spot;
31: static NXCursor *cursor = nil;
32:
33: if (!cursor) {
34: cursor = [NXCursor newFromImage:[NXImage newFromSection:"Pencil.tiff"]];
35: spot.x = 0.0; spot.y = 15.0;
36: [cursor setHotSpot:&spot];
37: }
38:
39: return cursor ? cursor : [super cursor];
40: }
41:
42: - free
43: {
44: NX_FREE(points);
45: NX_FREE(userPathOps);
46: return [super free];
47: }
48:
49: - allocateChunk
50: /*
51: * The Scribble's storage is allocated in chunks.
52: * This allocates another chunk.
53: */
54: {
55: int i, newSize;
56:
57: newSize = length + CHUNK_SIZE;
58: if (points) {
59: NX_ZONEREALLOC([self zone], points, float, newSize << 1);
60: NX_ZONEREALLOC([self zone], userPathOps, char, newSize);
61: } else {
62: NX_ZONEMALLOC([self zone], points, float, newSize << 1);
63: NX_ZONEMALLOC([self zone], userPathOps, char, newSize);
64: }
65: for (i = newSize - 1; i >= length; i--) {
66: userPathOps[i] = dps_rlineto;
67: }
68:
69: return self;
70: }
71:
72: - (float)naturalAspectRatio
73: /*
74: * The Scribble's natural aspect ratio is the one it was created with.
75: */
76: {
77: return (gFlags.initialized ? ((bbox[2]-bbox[0])/(bbox[3]-bbox[1])) : 0.0);
78: }
79:
80: - (int)moveCorner:(int)corner to:(const NXPoint *)point constrain:(BOOL)flag
81: /*
82: * After the Scribble is created (gFlags.initialized == YES), this method
83: * just returns super's implementation. During creation, every time the
84: * "corner" is moved, a new line segment is added to the Scribble and
85: * the bounding box is expanded if necessary.
86: */
87: {
88: float *p;
89:
90: if (gFlags.initialized) {
91: return [super moveCorner:corner to:point constrain:flag];
92: }
93:
94: if (!(point->x - lastPoint.x || point->y - lastPoint.y)) return corner;
95:
96: length++;
97:
98: if (!(length % CHUNK_SIZE)) [self allocateChunk];
99:
100: p = points + (length << 1);
101: *p++ = point->x - lastPoint.x;
102: *p = point->y - lastPoint.y;
103: lastPoint = *point;
104:
105: bbox[2] = MAX(point->x, bbox[2]);
106: bbox[0] = MIN(point->x, bbox[0]);
107: bbox[3] = MAX(point->y, bbox[3]);
108: bbox[1] = MIN(point->y, bbox[1]);
109:
110: bounds.origin.x = bbox[0];
111: bounds.origin.y = bbox[1];
112: bounds.size.width = bbox[2] - bbox[0];
113: bounds.size.height = bbox[3] - bbox[1];
114:
115: return corner;
116: }
117:
118:
119: - (BOOL)create:(NXEvent *)event in:view
120: /*
121: * Before creating, an initial chunk is initialized, and the userPathOps
122: * are initialized. The lastPoint is also remembered as the start point.
123: * After the Scribble is created, the initialized flag is set.
124: */
125: {
126: NXPoint p;
127:
128: [self allocateChunk];
129: userPathOps[0] = dps_moveto;
130: p = event->location;
131: [view convertPoint:&p fromView:nil];
132: [view grid:&p];
133: points[0] = p.x;
134: points[1] = p.y;
135: lastPoint = p;
136: bbox[0] = bbox[2] = p.x;
137: bbox[1] = bbox[3] = p.y;
138: bounds.origin = p;
139: bounds.size.width = bounds.size.height = 0.0;
140:
141: if ([super create:event in:view]) {
142: gFlags.initialized = YES;
143: return YES;
144: }
145:
146: return NO;
147: }
148:
149:
150: - draw
151: /*
152: * The Scribble is drawn simply by scaling appropriately from its
153: * initial bounding box and drawing the user path.
154: */
155: {
156: NXCoord x, y;
157: NXPoint p1, p2;
158: int i, count, coords;
159: float angle, sx, sy, tx, ty;
160:
161: if (bounds.size.width < 1.0 || bounds.size.height < 1.0) return self;
162:
163: if (length && (bbox[2] - bbox[0]) && (bbox[3] - bbox[1])) {
164: sx = bounds.size.width / (bbox[2] - bbox[0]);
165: sy = bounds.size.height / (bbox[3] - bbox[1]);
166: tx = (bounds.origin.x +
167: ((points[0]-bbox[0]) / (bbox[2]-bbox[0]) * bounds.size.width)) -
168: points[0] * sx;
169: ty = (bounds.origin.y +
170: ((points[1]-bbox[1]) / (bbox[3]-bbox[1]) * bounds.size.height)) -
171: points[1] * sy;
172: if (gFlags.arrow && ![self fill] && (sx != 1.0 || sy != 1.0 || tx || ty)) {
173: PSgsave();
174: }
175: if ([self fill]) {
176: PSgsave();
177: PStranslate(tx, ty);
178: PSscale(sx, sy);
179: [self setFillColor];
180: DPSDoUserPath(points, (length + 1) << 1, dps_float,
181: userPathOps, length + 1, bbox,
182: gFlags.eofill ? dps_ueofill : dps_ufill);
183: PSgrestore();
184: }
185: if (!gFlags.nooutline) {
186: PStranslate(tx, ty);
187: PSscale(sx, sy);
188: [self setLineColor];
189: DPSDoUserPath(points, (length + 1) << 1, dps_float,
190: userPathOps, length + 1, bbox, dps_ustroke);
191: }
192: if (gFlags.arrow && ![self fill]) {
193: if (sx != 1.0 || sy != 1.0 || tx || ty) {
194: PSgrestore();
195: [self setLineColor];
196: }
197: if (gFlags.arrow != ARROW_AT_END) {
198: i = 0;
199: p1.x = points[i++];
200: p1.y = points[i++];
201: p2 = p1;
202: p2.x += points[i++];
203: p2.y += points[i++];
204: count = length - 1;
205: while (hypot((p1.x-p2.x)*sx,(p1.y-p2.y)*sy) < 7.0 && count--) {
206: p2.x += points[i++];
207: p2.y += points[i++];
208: }
209: angle = atan2((p1.y-p2.y)*sy, (p1.x-p2.x)*sx);
210: angle = (angle / 3.1415) * 180.0;
211: x = bounds.origin.x + (p1.x - bbox[0]) * sx;
212: y = bounds.origin.y + (p1.y - bbox[1]) * sy;
213: PSArrow(x, y, angle);
214: }
215: if (gFlags.arrow != ARROW_AT_START) {
216: i = 0;
217: coords = (length + 1) << 1;
218: p1.x = points[i++];
219: p1.y = points[i++];
220: while (i < coords) {
221: p1.x += points[i++];
222: p1.y += points[i++];
223: }
224: p2 = p1;
225: i = coords;
226: p2.y -= points[--i];
227: p2.x -= points[--i];
228: count = length - 1;
229: while (hypot((p2.x-p1.x)*sx,(p2.y-p1.y)*sy) < 7.0 && count--) {
230: p2.y -= points[--i];
231: p2.x -= points[--i];
232: }
233: angle = atan2((p1.y-p2.y)*sy, (p1.x-p2.x)*sx);
234: angle = (angle / 3.1415) * 180.0;
235: x = bounds.origin.x + (p1.x - bbox[0]) * sx;
236: y = bounds.origin.y + (p1.y - bbox[1]) * sy;
237: PSArrow(x, y, angle);
238: }
239: }
240: }
241:
242: return self;
243: }
244:
245: - write:(NXTypedStream *)stream
246: /*
247: * The Scribble is written out by writing its length (in segments), its
248: * bounding box, and all the points.
249: */
250: {
251: int i, numFloats;
252:
253: [super write:stream];
254:
255: NXWriteTypes(stream,"iffff",&length,&bbox[0],&bbox[1],&bbox[2],&bbox[3]);
256:
257: numFloats = (length + 1) << 1;
258: for (i = 0; i < numFloats; i++) {
259: NXWriteTypes(stream, "f", &points[i]);
260: }
261:
262: return self;
263: }
264:
265: - read:(NXTypedStream *)stream
266: {
267: int i;
268: float *p;
269:
270: [super read:stream];
271:
272: NXReadTypes(stream,"iffff",&length,&bbox[0],&bbox[1],&bbox[2],&bbox[3]);
273:
274: NX_ZONEMALLOC([self zone], points, float, (length + 1) << 1);
275: NX_ZONEMALLOC([self zone], userPathOps, char, length + 1);
276:
277: p = points;
278: for (i = 0; i <= length; i++) {
279: NXReadTypes(stream, "f", p++);
280: NXReadTypes(stream, "f", p++);
281: userPathOps[i] = dps_rlineto;
282: }
283: userPathOps[0] = dps_moveto;
284:
285: return self;
286: }
287:
288: @end
289:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.