2 |
- |
1 |
#include <u.h>
|
|
|
2 |
#include <libc.h>
|
|
|
3 |
#include <ctype.h>
|
|
|
4 |
#include <draw.h>
|
|
|
5 |
#include <event.h>
|
|
|
6 |
#include <cursor.h>
|
|
|
7 |
#include <stdio.h>
|
|
|
8 |
|
|
|
9 |
#define Never 0xffffffff /* Maximum ulong */
|
|
|
10 |
#define LOG2 0.301029995664
|
|
|
11 |
#define Button_bit(b) (1 << ((b)-1))
|
|
|
12 |
|
|
|
13 |
enum {
|
|
|
14 |
But1 = Button_bit(1),/* mouse buttons for events */
|
|
|
15 |
But2 = Button_bit(2),
|
|
|
16 |
But3 = Button_bit(3),
|
|
|
17 |
};
|
|
|
18 |
int cantmv = 1; /* disallow rotate and move? 0..1 */
|
|
|
19 |
int plotdots; /* plot dots instead of lines */
|
|
|
20 |
int top_border, bot_border, lft_border, rt_border;
|
|
|
21 |
int lft_border0; /* lft_border for y-axis labels >0 */
|
|
|
22 |
int top_left, top_right; /* edges of top line free space */
|
|
|
23 |
int Mv_delay = 400; /* msec for button click vs. button hold down */
|
|
|
24 |
int Dotrad = 2; /* dot radius in pixels */
|
|
|
25 |
int framewd=1; /* line thickness for frame (pixels) */
|
|
|
26 |
int framesep=1; /* distance between frame and surrounding text */
|
|
|
27 |
int outersep=1; /* distance: surrounding text to screen edge */
|
|
|
28 |
Point sdigit; /* size of a digit in the font */
|
|
|
29 |
Point smaxch; /* assume any character in font fits in this */
|
|
|
30 |
double underscan = .05; /* fraction of frame initially unused per side */
|
|
|
31 |
double fuzz = 6; /* selection tolerance in pixels */
|
|
|
32 |
int tick_len = 15; /* length of axis label tick mark in pixels */
|
|
|
33 |
FILE* logfil = 0; /* dump selected points here if nonzero */
|
|
|
34 |
|
|
|
35 |
#define labdigs 3 /* allow this many sig digits in axis labels */
|
|
|
36 |
#define digs10pow 1000 /* pow(10,labdigs) */
|
|
|
37 |
#define axis_color clr_im(DLtblue)
|
|
|
38 |
|
|
|
39 |
|
|
|
40 |
|
|
|
41 |
|
|
|
42 |
/********************************* Utilities *********************************/
|
|
|
43 |
|
|
|
44 |
/* Prepend string s to null-terminated string in n-byte buffer buf[], truncating if
|
|
|
45 |
necessary and using a space to separate s from the rest of buf[].
|
|
|
46 |
*/
|
|
|
47 |
char* str_insert(char* buf, char* s, int n)
|
|
|
48 |
{
|
|
|
49 |
int blen, slen = strlen(s) + 1;
|
|
|
50 |
if (slen >= n)
|
|
|
51 |
{strncpy(buf,s,n); buf[n-1]='\0'; return buf;}
|
|
|
52 |
blen = strlen(buf);
|
|
|
53 |
if (blen >= n-slen)
|
|
|
54 |
buf[blen=n-slen-1] = '\0';
|
|
|
55 |
memmove(buf+slen, buf, slen+blen+1);
|
|
|
56 |
memcpy(buf, s, slen-1);
|
|
|
57 |
buf[slen-1] = ' ';
|
|
|
58 |
return buf;
|
|
|
59 |
}
|
|
|
60 |
|
|
|
61 |
/* Alter string smain (without lengthening it) so as to remove the first occurrence of
|
|
|
62 |
ssub, assuming ssub is ASCII. Return nonzero (true) if string smain had to be changed.
|
|
|
63 |
In spite of the ASCII-centric appearance, I think this can handle UTF in smain.
|
|
|
64 |
*/
|
|
|
65 |
int remove_substr(char* smain, char* ssub)
|
|
|
66 |
{
|
|
|
67 |
char *ss, *s = strstr(smain, ssub);
|
|
|
68 |
int n = strlen(ssub);
|
|
|
69 |
if (s==0)
|
|
|
70 |
return 0;
|
|
|
71 |
if (islower(s[n]))
|
|
|
72 |
s[0] ^= 32; /* probably tolower(s[0]) or toupper(s[0]) */
|
|
|
73 |
else {
|
|
|
74 |
for (ss=s+n; *ss!=0; s++, ss++)
|
|
|
75 |
*s = *ss;
|
|
|
76 |
*s = '\0';
|
|
|
77 |
}
|
|
|
78 |
return 1;
|
|
|
79 |
}
|
|
|
80 |
|
|
|
81 |
void adjust_border(Font* f)
|
|
|
82 |
{
|
|
|
83 |
int sep = framesep + outersep;
|
|
|
84 |
sdigit = stringsize(f, "8");
|
|
|
85 |
smaxch = stringsize(f, "MMMg");
|
|
|
86 |
smaxch.x = (smaxch.x + 3)/4;
|
|
|
87 |
lft_border0 = (1+labdigs)*sdigit.x + framewd + sep;
|
|
|
88 |
rt_border = (lft_border0 - sep)/2 + outersep;
|
|
|
89 |
bot_border = sdigit.y + framewd + sep;
|
|
|
90 |
top_border = smaxch.y + framewd + sep;
|
|
|
91 |
lft_border = lft_border0; /* this gets reset later */
|
|
|
92 |
}
|
|
|
93 |
|
|
|
94 |
|
|
|
95 |
int is_off_screen(Point p)
|
|
|
96 |
{
|
|
|
97 |
const Rectangle* r = &(screen->r);
|
|
|
98 |
return p.x-r->min.x<lft_border || r->max.x-p.x<rt_border
|
|
|
99 |
|| p.y-r->min.y<=top_border || r->max.y-p.y<=bot_border;
|
|
|
100 |
}
|
|
|
101 |
|
|
|
102 |
|
|
|
103 |
Cursor bullseye =
|
|
|
104 |
{
|
|
|
105 |
{-7, -7},
|
|
|
106 |
{
|
|
|
107 |
0x1F, 0xF8, 0x3F, 0xFC, 0x7F, 0xFE, 0xFB, 0xDF,
|
|
|
108 |
0xF3, 0xCF, 0xE3, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
|
109 |
0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xC7, 0xF3, 0xCF,
|
|
|
110 |
0x7B, 0xDF, 0x7F, 0xFE, 0x3F, 0xFC, 0x1F, 0xF8,
|
|
|
111 |
},
|
|
|
112 |
{
|
|
|
113 |
0x00, 0x00, 0x0F, 0xF0, 0x31, 0x8C, 0x21, 0x84,
|
|
|
114 |
0x41, 0x82, 0x41, 0x82, 0x41, 0x82, 0x7F, 0xFE,
|
|
|
115 |
0x7F, 0xFE, 0x41, 0x82, 0x41, 0x82, 0x41, 0x82,
|
|
|
116 |
0x21, 0x84, 0x31, 0x8C, 0x0F, 0xF0, 0x00, 0x00,
|
|
|
117 |
}
|
|
|
118 |
};
|
|
|
119 |
|
|
|
120 |
/* Wait for a mouse click and return 0 for failue if not button but (curs can be 0) */
|
|
|
121 |
int get_1click(int but, Mouse* m, Cursor* curs)
|
|
|
122 |
{
|
|
|
123 |
if (curs)
|
|
|
124 |
esetcursor(curs);
|
|
|
125 |
while (m->buttons==0)
|
|
|
126 |
*m = emouse();
|
|
|
127 |
if (curs)
|
|
|
128 |
esetcursor(0);
|
|
|
129 |
return (m->buttons==Button_bit(but));
|
|
|
130 |
}
|
|
|
131 |
|
|
|
132 |
|
|
|
133 |
/* Wait for a mouse click or keyboard event from the string of expected characters. Return
|
|
|
134 |
the character code or -1 for a button-but mouse event or 0 for wrong button.
|
|
|
135 |
*/
|
|
|
136 |
int get_click_or_kbd(int but, Mouse* m, const char* expected)
|
|
|
137 |
{
|
|
|
138 |
Event ev;
|
|
|
139 |
ulong expbits[4], ty;
|
|
|
140 |
expbits[0] = expbits[1] = expbits[2] = expbits[3];
|
|
|
141 |
for (; *expected!=0; expected++)
|
|
|
142 |
expbits[((*expected)>>5)&3] |= 1 << (*expected&31);
|
|
|
143 |
do ty = eread(Emouse|Ekeyboard, &ev);
|
|
|
144 |
while ((ty&Emouse) ? ev.mouse.buttons==0
|
|
|
145 |
: (ev.kbdc&~127) || !(expbits[(ev.kbdc>>5)&3] & (1<<(ev.kbdc&31))) );
|
|
|
146 |
if (ty&Ekeyboard)
|
|
|
147 |
return ev.kbdc;
|
|
|
148 |
*m = ev.mouse;
|
|
|
149 |
return (ev.mouse.buttons==Button_bit(but)) ? -1 : 0;
|
|
|
150 |
}
|
|
|
151 |
|
|
|
152 |
|
|
|
153 |
/* Wait until but goes up or until a mouse event's msec passes tlimit.
|
|
|
154 |
Return a boolean result that tells whether the button went up.
|
|
|
155 |
*/
|
|
|
156 |
int lift_button(int but, Mouse* m, int tlimit)
|
|
|
157 |
{
|
|
|
158 |
do { *m = emouse();
|
|
|
159 |
if (m->msec >= tlimit)
|
|
|
160 |
return 0;
|
|
|
161 |
} while (m->buttons & Button_bit(but));
|
|
|
162 |
return 1;
|
|
|
163 |
}
|
|
|
164 |
|
|
|
165 |
|
|
|
166 |
/* Set *m to the last pending mouse event, or the first one where but is up.
|
|
|
167 |
If no mouse events are pending, wait for the next one.
|
|
|
168 |
*/
|
|
|
169 |
void latest_mouse(int but, Mouse* m)
|
|
|
170 |
{
|
|
|
171 |
int bbit = Button_bit(but);
|
|
|
172 |
do { *m = emouse();
|
|
|
173 |
} while ((m->buttons & bbit) && ecanmouse());
|
|
|
174 |
}
|
|
|
175 |
|
|
|
176 |
|
|
|
177 |
|
|
|
178 |
/*********************************** Colors ***********************************/
|
|
|
179 |
|
|
|
180 |
enum { DOrange=0xffaa00FF, Dgray=0xbbbbbbFF, DDkgreen=0x009900FF,
|
|
|
181 |
DDkred=0xcc0000FF, DViolet=0x990099FF, DDkyellow=0xaaaa00FF,
|
|
|
182 |
DLtblue=0xaaaaffFF, DPink=0xffaaaaFF,
|
|
|
183 |
/* ndraw.h sets DBlack, DBlue, DRed, DYellow, DGreen,
|
|
|
184 |
DCyan, DMagenta, DWhite */
|
|
|
185 |
};
|
|
|
186 |
|
|
|
187 |
|
|
|
188 |
typedef struct thick_color {
|
|
|
189 |
int thick; /* use 1+2*thick pixel wide lines */
|
|
|
190 |
Image* clr; /* Color to use when drawing this */
|
|
|
191 |
} thick_color;
|
|
|
192 |
|
|
|
193 |
|
|
|
194 |
typedef struct color_ref {
|
|
|
195 |
ulong c; /* RGBA pixel color */
|
|
|
196 |
char* nam; /* ASCII name (matched to input, used in output)*/
|
|
|
197 |
int nam1; /* single-letter version of color name */
|
|
|
198 |
Image* im; /* replicated solid-color image */
|
|
|
199 |
} color_ref;
|
|
|
200 |
|
|
|
201 |
color_ref clrtab[] = {
|
|
|
202 |
DRed, "Red", 'R', 0,
|
|
|
203 |
DPink, "Pink", 'P', 0,
|
|
|
204 |
DDkred, "Dkred", 'r', 0,
|
|
|
205 |
DOrange, "Orange", 'O', 0,
|
|
|
206 |
DYellow, "Yellow", 'Y', 0,
|
|
|
207 |
DDkyellow, "Dkyellow", 'y', 0,
|
|
|
208 |
DGreen, "Green", 'G', 0,
|
|
|
209 |
DDkgreen, "Dkgreen", 'g', 0,
|
|
|
210 |
DCyan, "Cyan", 'C', 0,
|
|
|
211 |
DBlue, "Blue", 'B', 0,
|
|
|
212 |
DLtblue, "Ltblue", 'b', 0,
|
|
|
213 |
DMagenta, "Magenta", 'M', 0,
|
|
|
214 |
DViolet, "Violet", 'V', 0,
|
|
|
215 |
Dgray, "Gray", 'A', 0,
|
|
|
216 |
DBlack, "Black", 'K', 0,
|
|
|
217 |
DWhite, "White", 'W', 0,
|
|
|
218 |
DNofill, 0, 0, 0 /* DNofill means "end of data" */
|
|
|
219 |
};
|
|
|
220 |
|
|
|
221 |
short nam1_idx[128]; /* the clrtab[] index for each nam1, else -1 */
|
|
|
222 |
|
|
|
223 |
|
|
|
224 |
void init_clrtab(void)
|
|
|
225 |
{
|
|
|
226 |
int i;
|
|
|
227 |
Rectangle r = Rect(0,0,1,1);
|
|
|
228 |
memset(&nam1_idx[0], -1, sizeof(nam1_idx));
|
|
|
229 |
for (i=0; clrtab[i].c!=DNofill; i++) {
|
|
|
230 |
clrtab[i].im = allocimage(display, r, CMAP8, 1, clrtab[i].c);
|
|
|
231 |
/* should check for 0 result? */
|
|
|
232 |
nam1_idx[clrtab[i].nam1] = i;
|
|
|
233 |
}
|
|
|
234 |
}
|
|
|
235 |
|
|
|
236 |
|
|
|
237 |
int clrim_id(Image* clr)
|
|
|
238 |
{
|
|
|
239 |
int i;
|
|
|
240 |
for (i=0; clrtab[i].im!=clr; i++)
|
|
|
241 |
if (clrtab[i].c==DNofill)
|
|
|
242 |
exits("bad image color");
|
|
|
243 |
return i;
|
|
|
244 |
}
|
|
|
245 |
|
|
|
246 |
int clr_id(int clr)
|
|
|
247 |
{
|
|
|
248 |
int i;
|
|
|
249 |
for (i=0; clrtab[i].c!=clr; i++)
|
|
|
250 |
if (clrtab[i].c==DNofill)
|
|
|
251 |
exits("bad color");
|
|
|
252 |
return i;
|
|
|
253 |
}
|
|
|
254 |
|
|
|
255 |
|
|
|
256 |
#define clr_im(clr) clrtab[clr_id(clr)].im
|
|
|
257 |
#define is_Multi -2 /* dummy clrtab[] less than -1 */
|
|
|
258 |
|
|
|
259 |
|
|
|
260 |
thick_color* tc_default(thick_color *buf)
|
|
|
261 |
{
|
|
|
262 |
buf[0].thick = 1;
|
|
|
263 |
buf[1].clr = clr_im(DBlack);
|
|
|
264 |
buf[1].thick = 0;
|
|
|
265 |
return buf;
|
|
|
266 |
}
|
|
|
267 |
|
|
|
268 |
|
|
|
269 |
/* Return an allocated array that describes the color letters (clrtab[].nam1 values,
|
|
|
270 |
optionally prefixed by 'T') in the string that starts at c0 and ends just before
|
|
|
271 |
fin. The first entry is a dummy whose thick field tells how many data entries follow.
|
|
|
272 |
If buf!=0, it should point to an array of length 2 that is to hold the output
|
|
|
273 |
(truncated to a dummy and one data entry). The error indication is 1 data entry
|
|
|
274 |
of default color and thickness; e.g., "Multi(xxbadxx)" in a label prevents gview
|
|
|
275 |
from recognizing anything that follows.
|
|
|
276 |
*/
|
|
|
277 |
thick_color* parse_color_chars(const char* c0, const char* fin, thick_color *buf)
|
|
|
278 |
{
|
|
|
279 |
thick_color* tc; /* Pending return value */
|
|
|
280 |
int i, j, n=fin-c0; /* n is an upper bound on how many data members */
|
|
|
281 |
const char* c;
|
|
|
282 |
for (c=c0; c<fin-1; c++)
|
|
|
283 |
if (*c=='T')
|
|
|
284 |
n--;
|
|
|
285 |
if (buf==0)
|
|
|
286 |
tc = (thick_color*) malloc((n+1)*sizeof(thick_color));
|
|
|
287 |
else {tc=buf; n=1;}
|
|
|
288 |
i = 0;
|
|
|
289 |
for (c=c0; c<fin && i<n; c++) {
|
|
|
290 |
tc[++i].thick = 0;
|
|
|
291 |
if (*c=='T')
|
|
|
292 |
if (++c==fin)
|
|
|
293 |
return tc_default(tc);
|
|
|
294 |
else tc[i].thick=1;
|
|
|
295 |
j = (*c&~127) ? -1 : nam1_idx[*c];
|
|
|
296 |
if (j < 0)
|
|
|
297 |
return tc_default(tc);
|
|
|
298 |
tc[i].clr = clrtab[j].im;
|
|
|
299 |
}
|
|
|
300 |
tc[0].thick = i;
|
|
|
301 |
return tc;
|
|
|
302 |
}
|
|
|
303 |
|
|
|
304 |
|
|
|
305 |
/* Decide what color and thickness to use for a polyline based on the label it has in the
|
|
|
306 |
input file. The winner is whichever color name comes first, or otherwise black; and
|
|
|
307 |
thickness is determined by the presence of "Thick" in the string. Place the result
|
|
|
308 |
in *r1 and return 0 unless a Multi(...) spec is found, in which case the result is
|
|
|
309 |
an allocated array of alternative color and thickness values. A nonzero idxdest
|
|
|
310 |
requests the clrtab[] index in *idxdest and no allocated array.
|
|
|
311 |
*/
|
|
|
312 |
thick_color* nam2thclr(const char* nam, thick_color *r1, int *idxdest)
|
|
|
313 |
{
|
|
|
314 |
char *c, *cbest=0, *rp=0;
|
|
|
315 |
int i, ibest=-1;
|
|
|
316 |
thick_color* tc = 0;
|
|
|
317 |
thick_color buf[2];
|
|
|
318 |
if (*nam!=0) {
|
|
|
319 |
c = strstr(nam, "Multi(");
|
|
|
320 |
if (c!=0 && (rp=strchr(c+6,')'))!=0)
|
|
|
321 |
{ibest=is_Multi; cbest=c;}
|
|
|
322 |
for (i=0; clrtab[i].nam!=0; i++) {
|
|
|
323 |
c = strstr(nam,clrtab[i].nam);
|
|
|
324 |
if (c!=0 && (ibest==-1 || c<cbest))
|
|
|
325 |
{ibest=i; cbest=c;}
|
|
|
326 |
}
|
|
|
327 |
}
|
|
|
328 |
if (ibest==is_Multi) {
|
|
|
329 |
tc = parse_color_chars(cbest+6, rp, (idxdest==0 ? 0 : &buf[0]));
|
|
|
330 |
ibest = clrim_id(tc[1].clr);
|
|
|
331 |
}
|
|
|
332 |
if (idxdest!=0)
|
|
|
333 |
*idxdest = (ibest<0) ? clr_id(DBlack) : ibest;
|
|
|
334 |
r1->clr = (ibest<0) ? clr_im(DBlack) : clrtab[ibest].im;
|
|
|
335 |
r1->thick = (tc!=0) ? tc[1].thick : (strstr(nam,"Thick")==0 ? 0 : 1);
|
|
|
336 |
return tc;
|
|
|
337 |
}
|
|
|
338 |
|
|
|
339 |
|
|
|
340 |
/* Alter string nam so that nam2thick() and nam2clr() agree with *tc, using
|
|
|
341 |
buf[] (a buffer of length bufn) to store the result if it differs from nam.
|
|
|
342 |
We go to great pains to perform this alteration in a manner that will seem natural
|
|
|
343 |
to the user, i.e., we try removing a suitably isolated color name before inserting
|
|
|
344 |
a new one.
|
|
|
345 |
*/
|
|
|
346 |
char* nam_with_thclr(char* nam, const thick_color *tc, char* buf, int bufn)
|
|
|
347 |
{
|
|
|
348 |
thick_color c0;
|
|
|
349 |
int clr0i;
|
|
|
350 |
nam2thclr(nam, &c0, &clr0i);
|
|
|
351 |
char *clr0s;
|
|
|
352 |
if (c0.thick==tc->thick && c0.clr==tc->clr)
|
|
|
353 |
return nam;
|
|
|
354 |
clr0s = clrtab[clr0i].nam;
|
|
|
355 |
if (strlen(nam)<bufn) strcpy(buf,nam);
|
|
|
356 |
else {strncpy(buf,nam,bufn); buf[bufn-1]='\0';}
|
|
|
357 |
if (c0.clr != tc->clr)
|
|
|
358 |
remove_substr(buf, clr0s);
|
|
|
359 |
if (c0.thick > tc->thick)
|
|
|
360 |
while (remove_substr(buf, "Thick"))
|
|
|
361 |
/* do nothing */;
|
|
|
362 |
nam2thclr(nam, &c0, &clr0i);
|
|
|
363 |
if (c0.clr != tc->clr)
|
|
|
364 |
str_insert(buf, clrtab[clrim_id(tc->clr)].nam, bufn);
|
|
|
365 |
if (c0.thick < tc->thick)
|
|
|
366 |
str_insert(buf, "Thick", bufn);
|
|
|
367 |
return buf;
|
|
|
368 |
}
|
|
|
369 |
|
|
|
370 |
|
|
|
371 |
|
|
|
372 |
/****************************** Data structures ******************************/
|
|
|
373 |
|
|
|
374 |
Image* mv_bkgd; /* Background image (usually 0) */
|
|
|
375 |
|
|
|
376 |
typedef struct fpoint {
|
|
|
377 |
double x, y;
|
|
|
378 |
} fpoint;
|
|
|
379 |
|
|
|
380 |
typedef struct frectangle {
|
|
|
381 |
fpoint min, max;
|
|
|
382 |
} frectangle;
|
|
|
383 |
|
|
|
384 |
frectangle empty_frect = {1e30, 1e30, -1e30, -1e30};
|
|
|
385 |
|
|
|
386 |
|
|
|
387 |
/* When *r2 is transformed by y=y-x*slant, might it intersect *r1 ?
|
|
|
388 |
*/
|
|
|
389 |
int fintersects(const frectangle* r1, const frectangle* r2, double slant)
|
|
|
390 |
{
|
|
|
391 |
double x2min=r2->min.x, x2max=r2->max.x;
|
|
|
392 |
if (r1->max.x <= x2min || x2max <= r1->min.x)
|
|
|
393 |
return 0;
|
|
|
394 |
if (slant >=0)
|
|
|
395 |
{x2min*=slant; x2max*=slant;}
|
|
|
396 |
else {double t=x2min*slant; x2min=x2max*slant; x2max=t;}
|
|
|
397 |
return r1->max.y > r2->min.y-x2max && r2->max.y-x2min > r1->min.y;
|
|
|
398 |
}
|
|
|
399 |
|
|
|
400 |
int fcontains(const frectangle* r, fpoint p)
|
|
|
401 |
{
|
|
|
402 |
return r->min.x <=p.x && p.x<= r->max.x && r->min.y <=p.y && p.y<= r->max.y;
|
|
|
403 |
}
|
|
|
404 |
|
|
|
405 |
|
|
|
406 |
void grow_bb(frectangle* dest, const frectangle* r)
|
|
|
407 |
{
|
|
|
408 |
if (r->min.x < dest->min.x) dest->min.x=r->min.x;
|
|
|
409 |
if (r->min.y < dest->min.y) dest->min.y=r->min.y;
|
|
|
410 |
if (r->max.x > dest->max.x) dest->max.x=r->max.x;
|
|
|
411 |
if (r->max.y > dest->max.y) dest->max.y=r->max.y;
|
|
|
412 |
}
|
|
|
413 |
|
|
|
414 |
|
|
|
415 |
void slant_frect(frectangle *r, double sl)
|
|
|
416 |
{
|
|
|
417 |
r->min.y += sl*r->min.x;
|
|
|
418 |
r->max.y += sl*r->max.x;
|
|
|
419 |
}
|
|
|
420 |
|
|
|
421 |
|
|
|
422 |
fpoint fcenter(const frectangle* r)
|
|
|
423 |
{
|
|
|
424 |
fpoint c;
|
|
|
425 |
c.x = .5*(r->max.x + r->min.x);
|
|
|
426 |
c.y = .5*(r->max.y + r->min.y);
|
|
|
427 |
return c;
|
|
|
428 |
}
|
|
|
429 |
|
|
|
430 |
|
|
|
431 |
typedef struct fpolygon {
|
|
|
432 |
fpoint* p; /* a malloc'ed array */
|
|
|
433 |
int n; /* p[] has n elements: p[0..n] */
|
|
|
434 |
frectangle bb; /* bounding box */
|
|
|
435 |
char* nam; /* name of this polygon (malloc'ed) */
|
|
|
436 |
thick_color c; /* the current color and line thickness */
|
|
|
437 |
thick_color* ct; /* 0 or malloc'ed color schemes, ct[1..ct->thick] */
|
|
|
438 |
struct fpolygon* link;
|
|
|
439 |
} fpolygon;
|
|
|
440 |
|
|
|
441 |
typedef struct fpolygons {
|
|
|
442 |
fpolygon* p; /* the head of a linked list */
|
|
|
443 |
frectangle bb; /* overall bounding box */
|
|
|
444 |
frectangle disp; /* part being mapped onto screen->r */
|
|
|
445 |
double slant_ht; /* controls how disp is slanted */
|
|
|
446 |
} fpolygons;
|
|
|
447 |
|
|
|
448 |
|
|
|
449 |
fpolygons univ = { /* everything there is to display */
|
|
|
450 |
0,
|
|
|
451 |
1e30, 1e30, -1e30, -1e30,
|
|
|
452 |
0, 0, 0, 0,
|
|
|
453 |
2*1e30
|
|
|
454 |
};
|
|
|
455 |
|
|
|
456 |
|
|
|
457 |
void free_fp_etc(fpolygon* fp)
|
|
|
458 |
{
|
|
|
459 |
if (fp->ct != 0)
|
|
|
460 |
free(fp->ct);
|
|
|
461 |
free(fp->p);
|
|
|
462 |
free(fp);
|
|
|
463 |
}
|
|
|
464 |
|
|
|
465 |
|
|
|
466 |
void set_default_clrs(fpolygons* fps, fpolygon* fpstop)
|
|
|
467 |
{
|
|
|
468 |
fpolygon* fp;
|
|
|
469 |
for (fp=fps->p; fp!=0 && fp!=fpstop; fp=fp->link)
|
|
|
470 |
fp->ct = nam2thclr(fp->nam, &fp->c, 0);
|
|
|
471 |
}
|
|
|
472 |
|
|
|
473 |
|
|
|
474 |
void fps_invert(fpolygons* fps)
|
|
|
475 |
{
|
|
|
476 |
fpolygon *p, *r=0;
|
|
|
477 |
for (p=fps->p; p!=0;) {
|
|
|
478 |
fpolygon* q = p;
|
|
|
479 |
p = p->link;
|
|
|
480 |
q->link = r;
|
|
|
481 |
r = q;
|
|
|
482 |
}
|
|
|
483 |
fps->p = r;
|
|
|
484 |
}
|
|
|
485 |
|
|
|
486 |
|
|
|
487 |
void fp_remove(fpolygons* fps, fpolygon* fp)
|
|
|
488 |
{
|
|
|
489 |
fpolygon *q, **p = &fps->p;
|
|
|
490 |
while (*p!=fp)
|
|
|
491 |
if (*p==0)
|
|
|
492 |
return;
|
|
|
493 |
else p = &(*p)->link;
|
|
|
494 |
*p = fp->link;
|
|
|
495 |
fps->bb = empty_frect;
|
|
|
496 |
for (q=fps->p; q!=0; q=q->link)
|
|
|
497 |
grow_bb(&fps->bb, &q->bb);
|
|
|
498 |
}
|
|
|
499 |
|
|
|
500 |
|
|
|
501 |
/* The transform maps abstract fpoint coordinates (the ones used in the input)
|
|
|
502 |
to the current screen coordinates. The do_untransform() macros reverses this.
|
|
|
503 |
If univ.slant_ht is not the height of univ.disp, the actual region in the
|
|
|
504 |
abstract coordinates is a parallelogram inscribed in univ.disp with two
|
|
|
505 |
vertical edges and two slanted slanted edges: slant_ht>0 means that the
|
|
|
506 |
vertical edges have height slant_ht and the parallelogram touches the lower
|
|
|
507 |
left and upper right corners of univ.disp; slant_ht<0 refers to a parallelogram
|
|
|
508 |
of height -slant_ht that touches the other two corners of univ.disp.
|
|
|
509 |
NOTE: the ytransform macro assumes that tr->sl times the x coordinate has
|
|
|
510 |
already been subtracted from yy.
|
|
|
511 |
*/
|
|
|
512 |
typedef struct transform {
|
|
|
513 |
double sl;
|
|
|
514 |
fpoint o, sc; /* (x,y):->(o.x+sc.x*x, o.y+sc.y*y+sl*x) */
|
|
|
515 |
} transform;
|
|
|
516 |
|
|
|
517 |
#define do_transform(d,tr,s) ((d)->x = (tr)->o.x + (tr)->sc.x*(s)->x, \
|
|
|
518 |
(d)->y = (tr)->o.y + (tr)->sc.y*(s)->y \
|
|
|
519 |
+ (tr)->sl*(s)->x)
|
|
|
520 |
#define do_untransform(d,tr,s) ((d)->x = (.5+(s)->x-(tr)->o.x)/(tr)->sc.x, \
|
|
|
521 |
(d)->y = (.5+(s)->y-(tr)->sl*(d)->x-(tr)->o.y) \
|
|
|
522 |
/(tr)->sc.y)
|
|
|
523 |
#define xtransform(tr,xx) ((tr)->o.x + (tr)->sc.x*(xx))
|
|
|
524 |
#define ytransform(tr,yy) ((tr)->o.y + (tr)->sc.y*(yy))
|
|
|
525 |
#define dxuntransform(tr,xx) ((xx)/(tr)->sc.x)
|
|
|
526 |
#define dyuntransform(tr,yy) ((yy)/(tr)->sc.y)
|
|
|
527 |
|
|
|
528 |
|
|
|
529 |
transform cur_trans(void)
|
|
|
530 |
{
|
|
|
531 |
transform t;
|
|
|
532 |
Rectangle d = screen->r;
|
|
|
533 |
const frectangle* s = &univ.disp;
|
|
|
534 |
double sh = univ.slant_ht;
|
|
|
535 |
d.min.x += lft_border;
|
|
|
536 |
d.min.y += top_border;
|
|
|
537 |
d.max.x -= rt_border;
|
|
|
538 |
d.max.y -= bot_border;
|
|
|
539 |
t.sc.x = (d.max.x - d.min.x)/(s->max.x - s->min.x);
|
|
|
540 |
t.sc.y = -(d.max.y - d.min.y)/fabs(sh);
|
|
|
541 |
if (sh > 0) {
|
|
|
542 |
t.sl = -t.sc.y*(s->max.y-s->min.y-sh)/(s->max.x - s->min.x);
|
|
|
543 |
t.o.y = d.min.y - t.sc.y*s->max.y - t.sl*s->max.x;
|
|
|
544 |
} else {
|
|
|
545 |
t.sl = t.sc.y*(s->max.y-s->min.y+sh)/(s->max.x - s->min.x);
|
|
|
546 |
t.o.y = d.min.y - t.sc.y*s->max.y - t.sl*s->min.x;
|
|
|
547 |
}
|
|
|
548 |
t.o.x = d.min.x - t.sc.x*s->min.x;
|
|
|
549 |
return t;
|
|
|
550 |
}
|
|
|
551 |
|
|
|
552 |
|
|
|
553 |
double u_slant_amt(fpolygons *u)
|
|
|
554 |
{
|
|
|
555 |
double sh=u->slant_ht, dy=u->disp.max.y - u->disp.min.y;
|
|
|
556 |
double dx = u->disp.max.x - u->disp.min.x;
|
|
|
557 |
return (sh>0) ? (dy-sh)/dx : -(dy+sh)/dx;
|
|
|
558 |
}
|
|
|
559 |
|
|
|
560 |
|
|
|
561 |
/* Set *y0 and *y1 to the lower and upper bounds of the set of y-sl*x values that
|
|
|
562 |
*u says to display, where sl is the amount of slant.
|
|
|
563 |
*/
|
|
|
564 |
double set_unslanted_y(fpolygons *u, double *y0, double *y1)
|
|
|
565 |
{
|
|
|
566 |
double yy1, sl=u_slant_amt(u);
|
|
|
567 |
if (u->slant_ht > 0) {
|
|
|
568 |
*y0 = u->disp.min.y - sl*u->disp.min.x;
|
|
|
569 |
yy1 = *y0 + u->slant_ht;
|
|
|
570 |
} else {
|
|
|
571 |
yy1 = u->disp.max.y - sl*u->disp.min.x;
|
|
|
572 |
*y0 = yy1 + u->slant_ht;
|
|
|
573 |
}
|
|
|
574 |
if (y1 != 0)
|
|
|
575 |
*y1 = yy1;
|
|
|
576 |
return sl;
|
|
|
577 |
}
|
|
|
578 |
|
|
|
579 |
|
|
|
580 |
|
|
|
581 |
|
|
|
582 |
/*************************** The region to display ****************************/
|
|
|
583 |
|
|
|
584 |
void nontrivial_interval(double *lo, double *hi)
|
|
|
585 |
{
|
|
|
586 |
if (*lo >= *hi) {
|
|
|
587 |
double mid = .5*(*lo + *hi);
|
|
|
588 |
double tweak = 1e-6 + 1e-6*fabs(mid);
|
|
|
589 |
*lo = mid - tweak;
|
|
|
590 |
*hi = mid + tweak;
|
|
|
591 |
}
|
|
|
592 |
}
|
|
|
593 |
|
|
|
594 |
|
|
|
595 |
void init_disp(void)
|
|
|
596 |
{
|
|
|
597 |
double dw = (univ.bb.max.x - univ.bb.min.x)*underscan;
|
|
|
598 |
double dh = (univ.bb.max.y - univ.bb.min.y)*underscan;
|
|
|
599 |
univ.disp.min.x = univ.bb.min.x - dw;
|
|
|
600 |
univ.disp.min.y = univ.bb.min.y - dh;
|
|
|
601 |
univ.disp.max.x = univ.bb.max.x + dw;
|
|
|
602 |
univ.disp.max.y = univ.bb.max.y + dh;
|
|
|
603 |
nontrivial_interval(&univ.disp.min.x, &univ.disp.max.x);
|
|
|
604 |
nontrivial_interval(&univ.disp.min.y, &univ.disp.max.y);
|
|
|
605 |
univ.slant_ht = univ.disp.max.y - univ.disp.min.y; /* means no slant */
|
|
|
606 |
}
|
|
|
607 |
|
|
|
608 |
|
|
|
609 |
void recenter_disp(Point c)
|
|
|
610 |
{
|
|
|
611 |
transform tr = cur_trans();
|
|
|
612 |
fpoint cc, off;
|
|
|
613 |
do_untransform(&cc, &tr, &c);
|
|
|
614 |
off.x = cc.x - .5*(univ.disp.min.x + univ.disp.max.x);
|
|
|
615 |
off.y = cc.y - .5*(univ.disp.min.y + univ.disp.max.y);
|
|
|
616 |
univ.disp.min.x += off.x;
|
|
|
617 |
univ.disp.min.y += off.y;
|
|
|
618 |
univ.disp.max.x += off.x;
|
|
|
619 |
univ.disp.max.y += off.y;
|
|
|
620 |
}
|
|
|
621 |
|
|
|
622 |
|
|
|
623 |
/* Find the upper-left and lower-right corners of the bounding box of the
|
|
|
624 |
parallelogram formed by untransforming the rectangle rminx, rminy, ... (given
|
|
|
625 |
in screen coordinates), and return the height of the parallelogram (negated
|
|
|
626 |
if it slopes downward).
|
|
|
627 |
*/
|
|
|
628 |
double untransform_corners(double rminx, double rminy, double rmaxx, double rmaxy,
|
|
|
629 |
fpoint *ul, fpoint *lr)
|
|
|
630 |
{
|
|
|
631 |
fpoint r_ur, r_ul, r_ll, r_lr; /* corners of the given recangle */
|
|
|
632 |
fpoint ur, ll; /* untransformed versions of r_ur, r_ll */
|
|
|
633 |
transform tr = cur_trans();
|
|
|
634 |
double ht;
|
|
|
635 |
r_ur.x=rmaxx; r_ur.y=rminy;
|
|
|
636 |
r_ul.x=rminx; r_ul.y=rminy;
|
|
|
637 |
r_ll.x=rminx; r_ll.y=rmaxy;
|
|
|
638 |
r_lr.x=rmaxx; r_lr.y=rmaxy;
|
|
|
639 |
do_untransform(ul, &tr, &r_ul);
|
|
|
640 |
do_untransform(lr, &tr, &r_lr);
|
|
|
641 |
do_untransform(&ur, &tr, &r_ur);
|
|
|
642 |
do_untransform(&ll, &tr, &r_ll);
|
|
|
643 |
ht = ur.y - lr->y;
|
|
|
644 |
if (ll.x < ul->x)
|
|
|
645 |
ul->x = ll.x;
|
|
|
646 |
if (ur.y > ul->y)
|
|
|
647 |
ul->y = ur.y;
|
|
|
648 |
else ht = -ht;
|
|
|
649 |
if (ur.x > lr->x)
|
|
|
650 |
lr->x = ur.x;
|
|
|
651 |
if (ll.y < lr->y)
|
|
|
652 |
lr->y = ll.y;
|
|
|
653 |
return ht;
|
|
|
654 |
}
|
|
|
655 |
|
|
|
656 |
|
|
|
657 |
void disp_dozoom(double rminx, double rminy, double rmaxx, double rmaxy)
|
|
|
658 |
{
|
|
|
659 |
fpoint ul, lr;
|
|
|
660 |
double sh = untransform_corners(rminx, rminy, rmaxx, rmaxy, &ul, &lr);
|
|
|
661 |
if (ul.x==lr.x || ul.y==lr.y)
|
|
|
662 |
return;
|
|
|
663 |
univ.slant_ht = sh;
|
|
|
664 |
univ.disp.min.x = ul.x;
|
|
|
665 |
univ.disp.max.y = ul.y;
|
|
|
666 |
univ.disp.max.x = lr.x;
|
|
|
667 |
univ.disp.min.y = lr.y;
|
|
|
668 |
nontrivial_interval(&univ.disp.min.x, &univ.disp.max.x);
|
|
|
669 |
nontrivial_interval(&univ.disp.min.y, &univ.disp.max.y);
|
|
|
670 |
}
|
|
|
671 |
|
|
|
672 |
|
|
|
673 |
void disp_zoomin(Rectangle r)
|
|
|
674 |
{
|
|
|
675 |
disp_dozoom(r.min.x, r.min.y, r.max.x, r.max.y);
|
|
|
676 |
}
|
|
|
677 |
|
|
|
678 |
|
|
|
679 |
void disp_zoomout(Rectangle r)
|
|
|
680 |
{
|
|
|
681 |
double qminx, qminy, qmaxx, qmaxy;
|
|
|
682 |
double scx, scy;
|
|
|
683 |
Rectangle s = screen->r;
|
|
|
684 |
if (r.min.x==r.max.x || r.min.y==r.max.y)
|
|
|
685 |
return;
|
|
|
686 |
s.min.x += lft_border;
|
|
|
687 |
s.min.y += top_border;
|
|
|
688 |
s.max.x -= rt_border;
|
|
|
689 |
s.max.y -= bot_border;
|
|
|
690 |
scx = (s.max.x - s.min.x)/(r.max.x - r.min.x);
|
|
|
691 |
scy = (s.max.y - s.min.y)/(r.max.y - r.min.y);
|
|
|
692 |
qminx = s.min.x + scx*(s.min.x - r.min.x);
|
|
|
693 |
qmaxx = s.max.x + scx*(s.max.x - r.max.x);
|
|
|
694 |
qminy = s.min.y + scy*(s.min.y - r.min.y);
|
|
|
695 |
qmaxy = s.max.y + scy*(s.max.y - r.max.y);
|
|
|
696 |
disp_dozoom(qminx, qminy, qmaxx, qmaxy);
|
|
|
697 |
}
|
|
|
698 |
|
|
|
699 |
|
|
|
700 |
void expand2(double* a, double* b, double f)
|
|
|
701 |
{
|
|
|
702 |
double mid = .5*(*a + *b);
|
|
|
703 |
*a = mid + f*(*a - mid);
|
|
|
704 |
*b = mid + f*(*b - mid);
|
|
|
705 |
}
|
|
|
706 |
|
|
|
707 |
void disp_squareup(void)
|
|
|
708 |
{
|
|
|
709 |
double dx = univ.disp.max.x - univ.disp.min.x;
|
|
|
710 |
double dy = univ.disp.max.y - univ.disp.min.y;
|
|
|
711 |
dx /= screen->r.max.x - lft_border - screen->r.min.x - rt_border;
|
|
|
712 |
dy /= screen->r.max.y - bot_border - screen->r.min.y - top_border;
|
|
|
713 |
if (dx > dy)
|
|
|
714 |
expand2(&univ.disp.min.y, &univ.disp.max.y, dx/dy);
|
|
|
715 |
else expand2(&univ.disp.min.x, &univ.disp.max.x, dy/dx);
|
|
|
716 |
univ.slant_ht = univ.disp.max.y - univ.disp.min.y;
|
|
|
717 |
}
|
|
|
718 |
|
|
|
719 |
|
|
|
720 |
/* Slant so that p and q appear at the same height on the screen and the
|
|
|
721 |
screen contains the smallest possible superset of what its previous contents.
|
|
|
722 |
*/
|
|
|
723 |
void slant_disp(fpoint p, fpoint q)
|
|
|
724 |
{
|
|
|
725 |
double yll, ylr, yul, yur; /* corner y coords of displayed parallelogram */
|
|
|
726 |
double sh, dy;
|
|
|
727 |
if (p.x == q.x)
|
|
|
728 |
return;
|
|
|
729 |
sh = univ.slant_ht;
|
|
|
730 |
if (sh > 0) {
|
|
|
731 |
yll=yul=univ.disp.min.y; yul+=sh;
|
|
|
732 |
ylr=yur=univ.disp.max.y; ylr-=sh;
|
|
|
733 |
} else {
|
|
|
734 |
yll=yul=univ.disp.max.y; yll+=sh;
|
|
|
735 |
ylr=yur=univ.disp.min.y; yur-=sh;
|
|
|
736 |
}
|
|
|
737 |
dy = (univ.disp.max.x-univ.disp.min.x)*(q.y - p.y)/(q.x - p.x);
|
|
|
738 |
dy -= ylr - yll;
|
|
|
739 |
if (dy > 0)
|
|
|
740 |
{yll-=dy; yur+=dy;}
|
|
|
741 |
else {yul-=dy; ylr+=dy;}
|
|
|
742 |
if (ylr > yll) {
|
|
|
743 |
univ.disp.min.y = yll;
|
|
|
744 |
univ.disp.max.y = yur;
|
|
|
745 |
univ.slant_ht = yur - ylr;
|
|
|
746 |
} else {
|
|
|
747 |
univ.disp.max.y = yul;
|
|
|
748 |
univ.disp.min.y = ylr;
|
|
|
749 |
univ.slant_ht = ylr - yur;
|
|
|
750 |
}
|
|
|
751 |
}
|
|
|
752 |
|
|
|
753 |
|
|
|
754 |
|
|
|
755 |
|
|
|
756 |
/******************************** Ascii input ********************************/
|
|
|
757 |
|
|
|
758 |
void set_fbb(fpolygon* fp)
|
|
|
759 |
{
|
|
|
760 |
fpoint lo=fp->p[0], hi=fp->p[0];
|
|
|
761 |
const fpoint *q, *qtop;
|
|
|
762 |
for (qtop=(q=fp->p)+fp->n; ++q<=qtop;) {
|
|
|
763 |
if (q->x < lo.x) lo.x=q->x;
|
|
|
764 |
if (q->y < lo.y) lo.y=q->y;
|
|
|
765 |
if (q->x > hi.x) hi.x=q->x;
|
|
|
766 |
if (q->y > hi.y) hi.y=q->y;
|
|
|
767 |
}
|
|
|
768 |
fp->bb.min = lo;
|
|
|
769 |
fp->bb.max = hi;
|
|
|
770 |
}
|
|
|
771 |
|
|
|
772 |
char* mystrdup(char* s)
|
|
|
773 |
{
|
|
|
774 |
char *r, *t = strrchr(s,'"');
|
|
|
775 |
if (t==0) {
|
|
|
776 |
t = s + strlen(s);
|
|
|
777 |
while (t>s && (t[-1]=='\n' || t[-1]=='\r'))
|
|
|
778 |
t--;
|
|
|
779 |
}
|
|
|
780 |
r = malloc(1+(t-s));
|
|
|
781 |
memcpy(r, s, t-s);
|
|
|
782 |
r[t-s] = 0;
|
|
|
783 |
return r;
|
|
|
784 |
}
|
|
|
785 |
|
|
|
786 |
int is_valid_label(char* lab)
|
|
|
787 |
{
|
|
|
788 |
char* t;
|
|
|
789 |
if (lab[0]=='"')
|
|
|
790 |
return (t=strrchr(lab,'"'))!=0 && t!=lab && strspn(t+1," \t\r\n")==strlen(t+1);
|
|
|
791 |
return strcspn(lab," \t")==strlen(lab);
|
|
|
792 |
}
|
|
|
793 |
|
|
|
794 |
/* Read a polyline and update the number of lines read. A zero result indicates bad
|
|
|
795 |
syntax if *lineno increases; otherwise it indicates end of file.
|
|
|
796 |
*/
|
|
|
797 |
fpolygon* rd_fpoly(FILE* fin, int *lineno)
|
|
|
798 |
{
|
|
|
799 |
char buf[4096], junk[2];
|
|
|
800 |
fpoint q;
|
|
|
801 |
fpolygon* fp;
|
|
|
802 |
int allocn;
|
|
|
803 |
if (!fgets(buf,4096,fin))
|
|
|
804 |
return 0;
|
|
|
805 |
(*lineno)++;
|
|
|
806 |
if (sscanf(buf,"%lg%lg%1s",&q.x,&q.y,junk) != 2)
|
|
|
807 |
return 0;
|
|
|
808 |
fp = malloc(sizeof(fpolygon));
|
|
|
809 |
allocn = 4;
|
|
|
810 |
fp->p = malloc(allocn*sizeof(fpoint));
|
|
|
811 |
fp->p[0] = q;
|
|
|
812 |
fp->n = 0;
|
|
|
813 |
fp->nam = "";
|
|
|
814 |
fp->c.thick = 0;
|
|
|
815 |
fp->c.clr = clr_im(DBlack);
|
|
|
816 |
fp->ct = 0;
|
|
|
817 |
while (fgets(buf,4096,fin)) {
|
|
|
818 |
(*lineno)++;
|
|
|
819 |
if (sscanf(buf,"%lg%lg%1s",&q.x,&q.y,junk) != 2) {
|
|
|
820 |
if (!is_valid_label(buf))
|
|
|
821 |
{free_fp_etc(fp); return 0;}
|
|
|
822 |
fp->nam = (buf[0]=='"') ? buf+1 : buf;
|
|
|
823 |
break;
|
|
|
824 |
}
|
|
|
825 |
if (++(fp->n) == allocn)
|
|
|
826 |
fp->p = realloc(fp->p, (allocn<<=1)*sizeof(fpoint));
|
|
|
827 |
fp->p[fp->n] = q;
|
|
|
828 |
}
|
|
|
829 |
fp->nam = mystrdup(fp->nam);
|
|
|
830 |
set_fbb(fp);
|
|
|
831 |
fp->link = 0;
|
|
|
832 |
return fp;
|
|
|
833 |
}
|
|
|
834 |
|
|
|
835 |
|
|
|
836 |
/* Read input into *fps and return 0 or a line number where there's a syntax error */
|
|
|
837 |
int rd_fpolys(FILE* fin, fpolygons* fps)
|
|
|
838 |
{
|
|
|
839 |
fpolygon *fp, *fp0=fps->p;
|
|
|
840 |
int lineno=0, ok_upto=0;
|
|
|
841 |
while ((fp=rd_fpoly(fin,&lineno)) != 0) {
|
|
|
842 |
ok_upto = lineno;
|
|
|
843 |
fp->link = fps->p;
|
|
|
844 |
fps->p = fp;
|
|
|
845 |
grow_bb(&fps->bb, &fp->bb);
|
|
|
846 |
}
|
|
|
847 |
set_default_clrs(fps, fp0);
|
|
|
848 |
return (ok_upto==lineno) ? 0 : lineno;
|
|
|
849 |
}
|
|
|
850 |
|
|
|
851 |
|
|
|
852 |
/* Read input from file fnam and return an error line no., -1 for "can't open"
|
|
|
853 |
or 0 for success.
|
|
|
854 |
*/
|
|
|
855 |
int doinput(char* fnam)
|
|
|
856 |
{
|
|
|
857 |
FILE* fin = strcmp(fnam,"-")==0 ? stdin : fopen(fnam, "r");
|
|
|
858 |
int errline_or0;
|
|
|
859 |
if (fin==0)
|
|
|
860 |
return -1;
|
|
|
861 |
errline_or0 = rd_fpolys(fin, &univ);
|
|
|
862 |
fclose(fin);
|
|
|
863 |
return errline_or0;
|
|
|
864 |
}
|
|
|
865 |
|
|
|
866 |
|
|
|
867 |
|
|
|
868 |
/******************************** Ascii output ********************************/
|
|
|
869 |
|
|
|
870 |
fpolygon* fp_reverse(fpolygon* fp)
|
|
|
871 |
{
|
|
|
872 |
fpolygon* r = 0;
|
|
|
873 |
while (fp!=0) {
|
|
|
874 |
fpolygon* q = fp->link;
|
|
|
875 |
fp->link = r;
|
|
|
876 |
r = fp;
|
|
|
877 |
fp = q;
|
|
|
878 |
}
|
|
|
879 |
return r;
|
|
|
880 |
}
|
|
|
881 |
|
|
|
882 |
void wr_fpoly(FILE* fout, const fpolygon* fp)
|
|
|
883 |
{
|
|
|
884 |
char buf[256];
|
|
|
885 |
int i;
|
|
|
886 |
for (i=0; i<=fp->n; i++)
|
|
|
887 |
fprintf(fout,"%.12g\t%.12g\n", fp->p[i].x, fp->p[i].y);
|
|
|
888 |
fprintf(fout,"\"%s\"\n", nam_with_thclr(fp->nam, &fp->c, buf, 256));
|
|
|
889 |
}
|
|
|
890 |
|
|
|
891 |
void wr_fpolys(FILE* fout, fpolygons* fps)
|
|
|
892 |
{
|
|
|
893 |
fpolygon* fp;
|
|
|
894 |
fps->p = fp_reverse(fps->p);
|
|
|
895 |
for (fp=fps->p; fp!=0; fp=fp->link)
|
|
|
896 |
wr_fpoly(fout, fp);
|
|
|
897 |
fps->p = fp_reverse(fps->p);
|
|
|
898 |
}
|
|
|
899 |
|
|
|
900 |
|
|
|
901 |
int dooutput(char* fnam)
|
|
|
902 |
{
|
|
|
903 |
FILE* fout = fopen(fnam, "w");
|
|
|
904 |
if (fout==0)
|
|
|
905 |
return 0;
|
|
|
906 |
wr_fpolys(fout, &univ);
|
|
|
907 |
fclose(fout);
|
|
|
908 |
return 1;
|
|
|
909 |
}
|
|
|
910 |
|
|
|
911 |
|
|
|
912 |
|
|
|
913 |
|
|
|
914 |
/************************ Clipping to screen rectangle ************************/
|
|
|
915 |
|
|
|
916 |
/* Find the t values, 0<=t<=1 for which x0+t*(x1-x0) is between xlo and xhi,
|
|
|
917 |
or return 0 to indicate no such t values exist. If returning 1, set *t0 and
|
|
|
918 |
*t1 to delimit the t interval.
|
|
|
919 |
*/
|
|
|
920 |
int do_xory(double x0, double x1, double xlo, double xhi, double* t0, double* t1)
|
|
|
921 |
{
|
|
|
922 |
*t1 = 1.0;
|
|
|
923 |
if (x0<xlo) {
|
|
|
924 |
if (x1<xlo) return 0;
|
|
|
925 |
*t0 = (xlo-x0)/(x1-x0);
|
|
|
926 |
if (x1>xhi) *t1 = (xhi-x0)/(x1-x0);
|
|
|
927 |
} else if (x0>xhi) {
|
|
|
928 |
if (x1>xhi) return 0;
|
|
|
929 |
*t0 = (xhi-x0)/(x1-x0);
|
|
|
930 |
if (x1<xlo) *t1 = (xlo-x0)/(x1-x0);
|
|
|
931 |
} else {
|
|
|
932 |
*t0 = 0.0;
|
|
|
933 |
if (x1>xhi) *t1 = (xhi-x0)/(x1-x0);
|
|
|
934 |
else if (x1<xlo) *t1 = (xlo-x0)/(x1-x0);
|
|
|
935 |
else *t1 = 1.0;
|
|
|
936 |
}
|
|
|
937 |
return 1;
|
|
|
938 |
}
|
|
|
939 |
|
|
|
940 |
|
|
|
941 |
/* After mapping y to y-slope*x, what initial fraction of the *p to *q edge is
|
|
|
942 |
outside of *r? Note that the edge could start outside *r, pass through *r,
|
|
|
943 |
and wind up outside again.
|
|
|
944 |
*/
|
|
|
945 |
double frac_outside(const fpoint* p, const fpoint* q, const frectangle* r,
|
|
|
946 |
double slope)
|
|
|
947 |
{
|
|
|
948 |
double t0, t1, tt0, tt1;
|
|
|
949 |
double px=p->x, qx=q->x;
|
|
|
950 |
if (!do_xory(px, qx, r->min.x, r->max.x, &t0, &t1))
|
|
|
951 |
return 1;
|
|
|
952 |
if (!do_xory(p->y-slope*px, q->y-slope*qx, r->min.y, r->max.y, &tt0, &tt1))
|
|
|
953 |
return 1;
|
|
|
954 |
if (tt0 > t0)
|
|
|
955 |
t0 = tt0;
|
|
|
956 |
if (t1<=t0 || tt1<=t0)
|
|
|
957 |
return 1;
|
|
|
958 |
return t0;
|
|
|
959 |
}
|
|
|
960 |
|
|
|
961 |
|
|
|
962 |
/* Think of p0..pn as piecewise-linear function F(t) for t=0..pn-p0, and find
|
|
|
963 |
the maximum tt such that F(0..tt) is all inside of r, assuming p0 is inside.
|
|
|
964 |
Coordinates are transformed by y=y-x*slope before testing against r.
|
|
|
965 |
*/
|
|
|
966 |
double in_length(const fpoint* p0, const fpoint* pn, frectangle r, double slope)
|
|
|
967 |
{
|
|
|
968 |
const fpoint* p = p0;
|
|
|
969 |
double px, py;
|
|
|
970 |
do if (++p > pn)
|
|
|
971 |
return pn - p0;
|
|
|
972 |
while (r.min.x<=(px=p->x) && px<=r.max.x
|
|
|
973 |
&& r.min.y<=(py=p->y-slope*px) && py<=r.max.y);
|
|
|
974 |
return (p - p0) - frac_outside(p, p-1, &r, slope);
|
|
|
975 |
}
|
|
|
976 |
|
|
|
977 |
|
|
|
978 |
/* Think of p0..pn as piecewise-linear function F(t) for t=0..pn-p0, and find
|
|
|
979 |
the maximum tt such that F(0..tt) is all outside of *r. Coordinates are
|
|
|
980 |
transformed by y=y-x*slope before testing against r.
|
|
|
981 |
*/
|
|
|
982 |
double out_length(const fpoint* p0, const fpoint* pn, frectangle r, double slope)
|
|
|
983 |
{
|
|
|
984 |
const fpoint* p = p0;
|
|
|
985 |
double fr;
|
|
|
986 |
do { if (p->x < r.min.x)
|
|
|
987 |
do if (++p>pn) return pn-p0;
|
|
|
988 |
while (p->x <= r.min.x);
|
|
|
989 |
else if (p->x > r.max.x)
|
|
|
990 |
do if (++p>pn) return pn-p0;
|
|
|
991 |
while (p->x >= r.max.x);
|
|
|
992 |
else if (p->y-slope*p->x < r.min.y)
|
|
|
993 |
do if (++p>pn) return pn-p0;
|
|
|
994 |
while (p->y-slope*p->x <= r.min.y);
|
|
|
995 |
else if (p->y-slope*p->x > r.max.y)
|
|
|
996 |
do if (++p>pn) return pn-p0;
|
|
|
997 |
while (p->y-slope*p->x >= r.max.y);
|
|
|
998 |
else return p - p0;
|
|
|
999 |
} while ((fr=frac_outside(p-1,p,&r,slope)) == 1);
|
|
|
1000 |
return (p - p0) + fr-1;
|
|
|
1001 |
}
|
|
|
1002 |
|
|
|
1003 |
|
|
|
1004 |
|
|
|
1005 |
/*********************** Drawing frame and axis labels ***********************/
|
|
|
1006 |
|
|
|
1007 |
#define Nthous 7
|
|
|
1008 |
#define Len_thous 30 /* bound on strlen(thous_nam[i]) */
|
|
|
1009 |
char* thous_nam[Nthous] = {
|
|
|
1010 |
"one", "thousand", "million", "billion",
|
|
|
1011 |
"trillion", "quadrillion", "quintillion",
|
|
|
1012 |
};
|
|
|
1013 |
|
|
|
1014 |
|
|
|
1015 |
typedef struct lab_interval {
|
|
|
1016 |
double sep; /* separation between tick marks */
|
|
|
1017 |
double unit; /* power of 1000 divisor */
|
|
|
1018 |
int logunit; /* log base 1000 of of this divisor */
|
|
|
1019 |
double off; /* offset to subtract before dividing */
|
|
|
1020 |
} lab_interval;
|
|
|
1021 |
|
|
|
1022 |
|
|
|
1023 |
char* abbrev_num(double x, const lab_interval* iv)
|
|
|
1024 |
{
|
|
|
1025 |
static char buf[16];
|
|
|
1026 |
double dx = x - iv->off;
|
|
|
1027 |
dx = iv->sep * floor(dx/iv->sep + .5);
|
|
|
1028 |
sprintf(buf,"%g", dx/iv->unit);
|
|
|
1029 |
return buf;
|
|
|
1030 |
}
|
|
|
1031 |
|
|
|
1032 |
|
|
|
1033 |
double lead_digits(double n, double r) /* n truncated to power of 10 above r */
|
|
|
1034 |
{
|
|
|
1035 |
double rr = pow(10, ceil(log10(r)));
|
|
|
1036 |
double nn = (n<rr) ? 0.0 : rr*floor(n/rr);
|
|
|
1037 |
if (n+r-nn >= digs10pow) {
|
|
|
1038 |
rr /= 10;
|
|
|
1039 |
nn = (n<rr) ? 0.0 : rr*floor(n/rr);
|
|
|
1040 |
}
|
|
|
1041 |
return nn;
|
|
|
1042 |
}
|
|
|
1043 |
|
|
|
1044 |
|
|
|
1045 |
lab_interval next_larger(double s0, double xlo, double xhi)
|
|
|
1046 |
{
|
|
|
1047 |
double nlo, nhi;
|
|
|
1048 |
lab_interval r;
|
|
|
1049 |
r.logunit = (int) floor(log10(s0) + LOG2);
|
|
|
1050 |
r.unit = pow(10, r.logunit);
|
|
|
1051 |
nlo = xlo/r.unit;
|
|
|
1052 |
nhi = xhi/r.unit;
|
|
|
1053 |
if (nhi >= digs10pow)
|
|
|
1054 |
r.off = r.unit*lead_digits(nlo, nhi-nlo);
|
|
|
1055 |
else if (nlo <= -digs10pow)
|
|
|
1056 |
r.off = -r.unit*lead_digits(-nhi, nhi-nlo);
|
|
|
1057 |
else r.off = 0;
|
|
|
1058 |
r.sep = (s0<=r.unit) ? r.unit : (s0<2*r.unit ? 2*r.unit : 5*r.unit);
|
|
|
1059 |
switch (r.logunit%3) {
|
|
|
1060 |
case 1: r.unit*=.1; r.logunit--;
|
|
|
1061 |
break;
|
|
|
1062 |
case -1: case 2:
|
|
|
1063 |
r.unit*=10; r.logunit++;
|
|
|
1064 |
break;
|
|
|
1065 |
case -2: r.unit*=100; r.logunit+=2;
|
|
|
1066 |
}
|
|
|
1067 |
r.logunit /= 3;
|
|
|
1068 |
return r;
|
|
|
1069 |
}
|
|
|
1070 |
|
|
|
1071 |
|
|
|
1072 |
double min_hsep(const transform* tr)
|
|
|
1073 |
{
|
|
|
1074 |
double s = (2+labdigs)*sdigit.x;
|
|
|
1075 |
double ss = (univ.disp.min.x<0) ? s+sdigit.x : s;
|
|
|
1076 |
return dxuntransform(tr, ss);
|
|
|
1077 |
}
|
|
|
1078 |
|
|
|
1079 |
|
|
|
1080 |
lab_interval mark_x_axis(const transform* tr)
|
|
|
1081 |
{
|
|
|
1082 |
fpoint p = univ.disp.min;
|
|
|
1083 |
Point q, qtop, qbot, tmp;
|
|
|
1084 |
double x0=univ.disp.min.x, x1=univ.disp.max.x;
|
|
|
1085 |
double seps0, nseps, seps;
|
|
|
1086 |
lab_interval iv = next_larger(min_hsep(tr), x0, x1);
|
|
|
1087 |
set_unslanted_y(&univ, &p.y, 0);
|
|
|
1088 |
q.y = ytransform(tr, p.y) + .5;
|
|
|
1089 |
qtop.y = q.y - tick_len;
|
|
|
1090 |
qbot.y = q.y + framewd + framesep;
|
|
|
1091 |
seps0 = ceil(x0/iv.sep);
|
|
|
1092 |
for (seps=0, nseps=floor(x1/iv.sep)-seps0; seps<=nseps; seps+=1) {
|
|
|
1093 |
char* num = abbrev_num((p.x=iv.sep*(seps0+seps)), &iv);
|
|
|
1094 |
Font* f = display->defaultfont;
|
|
|
1095 |
q.x = qtop.x = qbot.x = xtransform(tr, p.x);
|
|
|
1096 |
line(screen, qtop, q, Enddisc, Enddisc, 0, axis_color, q);
|
|
|
1097 |
tmp = stringsize(f, num);
|
|
|
1098 |
qbot.x -= tmp.x/2;
|
|
|
1099 |
string(screen, qbot, display->black, qbot, f, num);
|
|
|
1100 |
}
|
|
|
1101 |
return iv;
|
|
|
1102 |
}
|
|
|
1103 |
|
|
|
1104 |
|
|
|
1105 |
lab_interval mark_y_axis(const transform* tr)
|
|
|
1106 |
{
|
|
|
1107 |
Font* f = display->defaultfont;
|
|
|
1108 |
fpoint p = univ.disp.min;
|
|
|
1109 |
Point q, qrt, qlft;
|
|
|
1110 |
double y0, y1, seps0, nseps, seps;
|
|
|
1111 |
lab_interval iv;
|
|
|
1112 |
set_unslanted_y(&univ, &y0, &y1);
|
|
|
1113 |
iv = next_larger(dyuntransform(tr,-f->height), y0, y1);
|
|
|
1114 |
q.x = xtransform(tr, p.x) - .5;
|
|
|
1115 |
qrt.x = q.x + tick_len;
|
|
|
1116 |
qlft.x = q.x - (framewd + framesep);
|
|
|
1117 |
seps0 = ceil(y0/iv.sep);
|
|
|
1118 |
for (seps=0, nseps=floor(y1/iv.sep)-seps0; seps<=nseps; seps+=1) {
|
|
|
1119 |
char* num = abbrev_num((p.y=iv.sep*(seps0+seps)), &iv);
|
|
|
1120 |
Point qq = stringsize(f, num);
|
|
|
1121 |
q.y = qrt.y = qlft.y = ytransform(tr, p.y);
|
|
|
1122 |
line(screen, qrt, q, Enddisc, Enddisc, 0, axis_color, q);
|
|
|
1123 |
qq.x = qlft.x - qq.x;
|
|
|
1124 |
qq.y = qlft.y - qq.y/2;
|
|
|
1125 |
string(screen, qq, display->black, qq, f, num);
|
|
|
1126 |
}
|
|
|
1127 |
return iv;
|
|
|
1128 |
}
|
|
|
1129 |
|
|
|
1130 |
|
|
|
1131 |
void lab_iv_info(const lab_interval *iv, double slant, char* buf, int *n)
|
|
|
1132 |
{
|
|
|
1133 |
if (iv->off > 0)
|
|
|
1134 |
(*n) += sprintf(buf+*n,"-%.12g",iv->off);
|
|
|
1135 |
else if (iv->off < 0)
|
|
|
1136 |
(*n) += sprintf(buf+*n,"+%.12g",-iv->off);
|
|
|
1137 |
if (slant>0)
|
|
|
1138 |
(*n) += sprintf(buf+*n,"-%.6gx", slant);
|
|
|
1139 |
else if (slant<0)
|
|
|
1140 |
(*n) += sprintf(buf+*n,"+%.6gx", -slant);
|
|
|
1141 |
if (abs(iv->logunit) >= Nthous)
|
|
|
1142 |
(*n) += sprintf(buf+*n," in 1e%d units", 3*iv->logunit);
|
|
|
1143 |
else if (iv->logunit > 0)
|
|
|
1144 |
(*n) += sprintf(buf+*n," in %ss", thous_nam[iv->logunit]);
|
|
|
1145 |
else if (iv->logunit < 0)
|
|
|
1146 |
(*n) += sprintf(buf+*n," in %sths", thous_nam[-iv->logunit]);
|
|
|
1147 |
}
|
|
|
1148 |
|
|
|
1149 |
|
|
|
1150 |
void draw_xy_ranges(const lab_interval *xiv, const lab_interval *yiv)
|
|
|
1151 |
{
|
|
|
1152 |
Point p;
|
|
|
1153 |
char buf[2*(19+Len_thous+8)+50];
|
|
|
1154 |
int bufn = 0;
|
|
|
1155 |
buf[bufn++] = 'x';
|
|
|
1156 |
lab_iv_info(xiv, 0, buf, &bufn);
|
|
|
1157 |
bufn += sprintf(buf+bufn, "; y");
|
|
|
1158 |
lab_iv_info(yiv, u_slant_amt(&univ), buf, &bufn);
|
|
|
1159 |
buf[bufn] = '\0';
|
|
|
1160 |
p = stringsize(display->defaultfont, buf);
|
|
|
1161 |
top_left = screen->r.min.x + lft_border;
|
|
|
1162 |
p.x = top_right = screen->r.max.x - rt_border - p.x;
|
|
|
1163 |
p.y = screen->r.min.y + outersep;
|
|
|
1164 |
string(screen, p, display->black, p, display->defaultfont, buf);
|
|
|
1165 |
}
|
|
|
1166 |
|
|
|
1167 |
|
|
|
1168 |
transform draw_frame(void)
|
|
|
1169 |
{
|
|
|
1170 |
lab_interval x_iv, y_iv;
|
|
|
1171 |
transform tr;
|
|
|
1172 |
Rectangle r = screen->r;
|
|
|
1173 |
lft_border = (univ.disp.min.y<0) ? lft_border0+sdigit.x : lft_border0;
|
|
|
1174 |
tr = cur_trans();
|
|
|
1175 |
r.min.x += lft_border;
|
|
|
1176 |
r.min.y += top_border;
|
|
|
1177 |
r.max.x -= rt_border;
|
|
|
1178 |
r.max.y -= bot_border;
|
|
|
1179 |
border(screen, r, -framewd, axis_color, r.min);
|
|
|
1180 |
x_iv = mark_x_axis(&tr);
|
|
|
1181 |
y_iv = mark_y_axis(&tr);
|
|
|
1182 |
draw_xy_ranges(&x_iv, &y_iv);
|
|
|
1183 |
return tr;
|
|
|
1184 |
}
|
|
|
1185 |
|
|
|
1186 |
|
|
|
1187 |
|
|
|
1188 |
/*************************** Finding the selection ***************************/
|
|
|
1189 |
|
|
|
1190 |
typedef struct pt_on_fpoly {
|
|
|
1191 |
fpoint p; /* the point */
|
|
|
1192 |
fpolygon* fp; /* the fpolygon it lies on */
|
|
|
1193 |
double t; /* how many knots from the beginning */
|
|
|
1194 |
} pt_on_fpoly;
|
|
|
1195 |
|
|
|
1196 |
|
|
|
1197 |
static double myx, myy;
|
|
|
1198 |
#define mydist(p,o,sl,xwt,ywt) (myx=(p).x-(o).x, myy=(p).y-sl*(p).x-(o).y, \
|
|
|
1199 |
xwt*myx*myx + ywt*myy*myy)
|
|
|
1200 |
|
|
|
1201 |
/* At what fraction of the way from p0[0] to p0[1] is mydist(p,ctr,slant,xwt,ywt)
|
|
|
1202 |
minimized?
|
|
|
1203 |
*/
|
|
|
1204 |
double closest_time(const fpoint* p0, const fpoint* ctr, double slant,
|
|
|
1205 |
double xwt, double ywt)
|
|
|
1206 |
{
|
|
|
1207 |
double p00y=p0[0].y-slant*p0[0].x, p01y=p0[1].y-slant*p0[1].x;
|
|
|
1208 |
double dx=p0[1].x-p0[0].x, dy=p01y-p00y;
|
|
|
1209 |
double x0=p0[0].x-ctr->x, y0=p00y-ctr->y;
|
|
|
1210 |
double bot = xwt*dx*dx + ywt*dy*dy;
|
|
|
1211 |
if (bot==0)
|
|
|
1212 |
return 0;
|
|
|
1213 |
return -(xwt*x0*dx + ywt*y0*dy)/bot;
|
|
|
1214 |
}
|
|
|
1215 |
|
|
|
1216 |
|
|
|
1217 |
/* Scan the polygonal path of length len knots starting at p0, and find the
|
|
|
1218 |
point that the transformation y=y-x*slant makes closest to the center of *r,
|
|
|
1219 |
where *r itself defines the distance metric. Knots get higher priority than
|
|
|
1220 |
points between knots. If psel->t is negative, always update *psel; otherwise
|
|
|
1221 |
update *psel only if the scan can improve it. Return a boolean that says
|
|
|
1222 |
whether *psel was updated.
|
|
|
1223 |
Note that *r is a very tiny rectangle (tiny when converted screen pixels)
|
|
|
1224 |
such that anything in *r is considered close enough to match the mouse click.
|
|
|
1225 |
The purpose of this routine is to be careful in case there is a lot of hidden
|
|
|
1226 |
detail in the tiny rectangle *r.
|
|
|
1227 |
*/
|
|
|
1228 |
int improve_pt(fpoint* p0, double len, const frectangle* r, double slant,
|
|
|
1229 |
pt_on_fpoly* psel)
|
|
|
1230 |
{
|
|
|
1231 |
fpoint ctr = fcenter(r);
|
|
|
1232 |
double x_wt=2/(r->max.x-r->min.x), y_wt=2/(r->max.y-r->min.y);
|
|
|
1233 |
double xwt=x_wt*x_wt, ywt=y_wt*y_wt;
|
|
|
1234 |
double d, dbest = (psel->t <0) ? 1e30 : mydist(psel->p,ctr,slant,xwt,ywt);
|
|
|
1235 |
double tt, dbest0 = dbest;
|
|
|
1236 |
fpoint pp;
|
|
|
1237 |
int ilen = (int) len;
|
|
|
1238 |
if (len==0 || ilen>0) {
|
|
|
1239 |
int i;
|
|
|
1240 |
for (i=(len==0 ? 0 : 1); i<=ilen; i++) {
|
|
|
1241 |
d = mydist(p0[i], ctr, slant, xwt, ywt);
|
|
|
1242 |
if (d < dbest)
|
|
|
1243 |
{psel->p=p0[i]; psel->t=i; dbest=d;}
|
|
|
1244 |
}
|
|
|
1245 |
return (dbest < dbest0);
|
|
|
1246 |
}
|
|
|
1247 |
tt = closest_time(p0, &ctr, slant, xwt, ywt);
|
|
|
1248 |
if (tt > len)
|
|
|
1249 |
tt = len;
|
|
|
1250 |
pp.x = p0[0].x + tt*(p0[1].x - p0[0].x);
|
|
|
1251 |
pp.y = p0[0].y + tt*(p0[1].y - p0[0].y);
|
|
|
1252 |
if (mydist(pp, ctr, slant, xwt, ywt) < dbest) {
|
|
|
1253 |
psel->p = pp;
|
|
|
1254 |
psel->t = tt;
|
|
|
1255 |
return 1;
|
|
|
1256 |
}
|
|
|
1257 |
return 0;
|
|
|
1258 |
}
|
|
|
1259 |
|
|
|
1260 |
|
|
|
1261 |
/* Test *fp against *r after transforming by y=y-x*slope, and set *psel accordingly.
|
|
|
1262 |
*/
|
|
|
1263 |
void select_in_fpoly(fpolygon* fp, const frectangle* r, double slant,
|
|
|
1264 |
pt_on_fpoly* psel)
|
|
|
1265 |
{
|
|
|
1266 |
fpoint *p0=fp->p, *pn=fp->p+fp->n;
|
|
|
1267 |
double l1, l2;
|
|
|
1268 |
if (p0==pn)
|
|
|
1269 |
{improve_pt(p0, 0, r, slant, psel); psel->fp=fp; return;}
|
|
|
1270 |
while ((l1=out_length(p0,pn,*r,slant)) < pn-p0) {
|
|
|
1271 |
fpoint p0sav;
|
|
|
1272 |
int i1 = (int) l1;
|
|
|
1273 |
p0+=i1; l1-=i1;
|
|
|
1274 |
p0sav = *p0;
|
|
|
1275 |
p0[0].x += l1*(p0[1].x - p0[0].x);
|
|
|
1276 |
p0[0].y += l1*(p0[1].y - p0[0].y);
|
|
|
1277 |
l2 = in_length(p0, pn, *r, slant);
|
|
|
1278 |
if (improve_pt(p0, l2, r, slant, psel)) {
|
|
|
1279 |
if (l1==0 && psel->t!=((int) psel->t)) {
|
|
|
1280 |
psel->t = 0;
|
|
|
1281 |
psel->p = *p0;
|
|
|
1282 |
} else if (psel->t < 1)
|
|
|
1283 |
psel->t += l1*(1 - psel->t);
|
|
|
1284 |
psel->t += p0 - fp->p;
|
|
|
1285 |
psel->fp = fp;
|
|
|
1286 |
}
|
|
|
1287 |
*p0 = p0sav;
|
|
|
1288 |
p0 += (l2>0) ? ((int) ceil(l2)) : 1;
|
|
|
1289 |
}
|
|
|
1290 |
}
|
|
|
1291 |
|
|
|
1292 |
|
|
|
1293 |
/* Test all the fpolygons against *r after transforming by y=y-x*slope, and return
|
|
|
1294 |
the resulting selection, if any.
|
|
|
1295 |
*/
|
|
|
1296 |
pt_on_fpoly* select_in_univ(const frectangle* r, double slant)
|
|
|
1297 |
{
|
|
|
1298 |
static pt_on_fpoly answ;
|
|
|
1299 |
fpolygon* fp;
|
|
|
1300 |
answ.t = -1;
|
|
|
1301 |
for (fp=univ.p; fp!=0; fp=fp->link)
|
|
|
1302 |
if (fintersects(r, &fp->bb, slant))
|
|
|
1303 |
select_in_fpoly(fp, r, slant, &answ);
|
|
|
1304 |
if (answ.t < 0)
|
|
|
1305 |
return 0;
|
|
|
1306 |
return &answ;
|
|
|
1307 |
}
|
|
|
1308 |
|
|
|
1309 |
|
|
|
1310 |
|
|
|
1311 |
/**************************** Using the selection ****************************/
|
|
|
1312 |
|
|
|
1313 |
pt_on_fpoly cur_sel; /* current selection if cur_sel.t>=0 */
|
|
|
1314 |
pt_on_fpoly prev_sel; /* previous selection if prev_sel.t>=0 (for slant) */
|
|
|
1315 |
Image* sel_bkg = 0; /* what's behind the red dot */
|
|
|
1316 |
|
|
|
1317 |
|
|
|
1318 |
void clear_txt(void)
|
|
|
1319 |
{
|
|
|
1320 |
Rectangle r;
|
|
|
1321 |
r.min = screen->r.min;
|
|
|
1322 |
r.min.x += lft_border;
|
|
|
1323 |
r.min.y += outersep;
|
|
|
1324 |
r.max.x = top_left;
|
|
|
1325 |
r.max.y = r.min.y + smaxch.y;
|
|
|
1326 |
draw(screen, r, display->white, display->opaque, r.min);
|
|
|
1327 |
top_left = r.min.x;
|
|
|
1328 |
}
|
|
|
1329 |
|
|
|
1330 |
|
|
|
1331 |
Rectangle sel_dot_box(const transform* tr)
|
|
|
1332 |
{
|
|
|
1333 |
Point ctr;
|
|
|
1334 |
Rectangle r;
|
|
|
1335 |
if (tr==0)
|
|
|
1336 |
ctr.x = ctr.y = Dotrad;
|
|
|
1337 |
else do_transform(&ctr, tr, &cur_sel.p);
|
|
|
1338 |
r.min.x=ctr.x-Dotrad; r.max.x=ctr.x+Dotrad+1;
|
|
|
1339 |
r.min.y=ctr.y-Dotrad; r.max.y=ctr.y+Dotrad+1;
|
|
|
1340 |
return r;
|
|
|
1341 |
}
|
|
|
1342 |
|
|
|
1343 |
|
|
|
1344 |
void unselect(const transform* tr)
|
|
|
1345 |
{
|
|
|
1346 |
transform tra;
|
|
|
1347 |
if (sel_bkg==0)
|
|
|
1348 |
sel_bkg = allocimage(display, sel_dot_box(0), CMAP8, 0, DWhite);
|
|
|
1349 |
clear_txt();
|
|
|
1350 |
if (cur_sel.t < 0)
|
|
|
1351 |
return;
|
|
|
1352 |
prev_sel = cur_sel;
|
|
|
1353 |
if (tr==0)
|
|
|
1354 |
{tra=cur_trans(); tr=&tra;}
|
|
|
1355 |
draw(screen, sel_dot_box(tr), sel_bkg, display->opaque, ZP);
|
|
|
1356 |
cur_sel.t = -1;
|
|
|
1357 |
}
|
|
|
1358 |
|
|
|
1359 |
|
|
|
1360 |
/* Text at top right is written first and this low-level routine clobbers it if
|
|
|
1361 |
the new top-left text would overwrite it. However, users of this routine should
|
|
|
1362 |
try to keep the new text short enough to avoid this.
|
|
|
1363 |
*/
|
|
|
1364 |
void show_mytext(char* msg)
|
|
|
1365 |
{
|
|
|
1366 |
Point tmp, pt = screen->r.min;
|
|
|
1367 |
int siz;
|
|
|
1368 |
tmp = stringsize(display->defaultfont, msg);
|
|
|
1369 |
siz = tmp.x;
|
|
|
1370 |
pt.x=top_left; pt.y+=outersep;
|
|
|
1371 |
if (top_left+siz > top_right) {
|
|
|
1372 |
Rectangle r;
|
|
|
1373 |
r.min.y = pt.y;
|
|
|
1374 |
r.min.x = top_right;
|
|
|
1375 |
r.max.y = r.min.y + smaxch.y;
|
|
|
1376 |
r.max.x = top_left+siz;
|
|
|
1377 |
draw(screen, r, display->white, display->opaque, r.min);
|
|
|
1378 |
top_right = top_left+siz;
|
|
|
1379 |
}
|
|
|
1380 |
string(screen, pt, display->black, ZP, display->defaultfont, msg);
|
|
|
1381 |
top_left += siz;
|
|
|
1382 |
}
|
|
|
1383 |
|
|
|
1384 |
|
|
|
1385 |
double rnd(double x, double tol) /* round to enough digits for accuracy tol */
|
|
|
1386 |
{
|
|
|
1387 |
double t = pow(10, floor(log10(tol)));
|
|
|
1388 |
return t * floor(x/t + .5);
|
|
|
1389 |
}
|
|
|
1390 |
|
|
|
1391 |
double t_tol(double xtol, double ytol)
|
|
|
1392 |
{
|
|
|
1393 |
int t = (int) floor(cur_sel.t);
|
|
|
1394 |
fpoint* p = cur_sel.fp->p;
|
|
|
1395 |
double dx, dy;
|
|
|
1396 |
if (t==cur_sel.t)
|
|
|
1397 |
return 1;
|
|
|
1398 |
dx = fabs(p[t+1].x - p[t].x);
|
|
|
1399 |
dy = fabs(p[t+1].y - p[t].y);
|
|
|
1400 |
xtol /= (xtol>dx) ? xtol : dx;
|
|
|
1401 |
ytol /= (ytol>dy) ? ytol : dy;
|
|
|
1402 |
return (xtol<ytol) ? xtol : ytol;
|
|
|
1403 |
}
|
|
|
1404 |
|
|
|
1405 |
void say_where(const transform* tr)
|
|
|
1406 |
{
|
|
|
1407 |
double xtol=dxuntransform(tr,1), ytol=dyuntransform(tr,-1);
|
|
|
1408 |
char buf[100];
|
|
|
1409 |
int n, nmax = (top_right - top_left)/smaxch.x;
|
|
|
1410 |
if (nmax >= 100)
|
|
|
1411 |
nmax = 100-1;
|
|
|
1412 |
n = sprintf(buf,"(%.14g,%.14g) at t=%.14g",
|
|
|
1413 |
rnd(cur_sel.p.x,xtol), rnd(cur_sel.p.y,ytol),
|
|
|
1414 |
rnd(cur_sel.t, t_tol(xtol,ytol)));
|
|
|
1415 |
if (cur_sel.fp->nam[0] != 0)
|
|
|
1416 |
sprintf(buf+n," %.*s", nmax-n-1, cur_sel.fp->nam);
|
|
|
1417 |
show_mytext(buf);
|
|
|
1418 |
}
|
|
|
1419 |
|
|
|
1420 |
|
|
|
1421 |
void reselect(const transform* tr) /* uselect(); set cur_sel; call this */
|
|
|
1422 |
{
|
|
|
1423 |
Point pt2, pt3;
|
|
|
1424 |
fpoint p2;
|
|
|
1425 |
transform tra;
|
|
|
1426 |
if (cur_sel.t < 0)
|
|
|
1427 |
return;
|
|
|
1428 |
if (tr==0)
|
|
|
1429 |
{tra=cur_trans(); tr=&tra;}
|
|
|
1430 |
do_transform(&p2, tr, &cur_sel.p);
|
|
|
1431 |
if (fabs(p2.x)+fabs(p2.y)>1e8 || (pt2.x=p2.x, pt2.y=p2.y, is_off_screen(pt2)))
|
|
|
1432 |
{cur_sel.t= -1; return;}
|
|
|
1433 |
pt3.x=pt2.x-Dotrad; pt3.y=pt2.y-Dotrad;
|
|
|
1434 |
draw(sel_bkg, sel_dot_box(0), screen, display->opaque, pt3);
|
|
|
1435 |
fillellipse(screen, pt2, Dotrad, Dotrad, clr_im(DRed), pt2);
|
|
|
1436 |
say_where(tr);
|
|
|
1437 |
}
|
|
|
1438 |
|
|
|
1439 |
|
|
|
1440 |
void do_select(Point pt)
|
|
|
1441 |
{
|
|
|
1442 |
transform tr = cur_trans();
|
|
|
1443 |
fpoint pt1, pt2, ctr;
|
|
|
1444 |
frectangle r;
|
|
|
1445 |
double slant;
|
|
|
1446 |
pt_on_fpoly* psel;
|
|
|
1447 |
unselect(&tr);
|
|
|
1448 |
do_untransform(&ctr, &tr, &pt);
|
|
|
1449 |
pt1.x=pt.x-fuzz; pt1.y=pt.y+fuzz;
|
|
|
1450 |
pt2.x=pt.x+fuzz; pt2.y=pt.y-fuzz;
|
|
|
1451 |
do_untransform(&r.min, &tr, &pt1);
|
|
|
1452 |
do_untransform(&r.max, &tr, &pt2);
|
|
|
1453 |
slant = u_slant_amt(&univ);
|
|
|
1454 |
slant_frect(&r, -slant);
|
|
|
1455 |
psel = select_in_univ(&r, slant);
|
|
|
1456 |
if (psel==0)
|
|
|
1457 |
return;
|
|
|
1458 |
if (logfil!=0) {
|
|
|
1459 |
fprintf(logfil,"%.14g\t%.14g\n", psel->p.x, psel->p.y);
|
|
|
1460 |
fflush(logfil);
|
|
|
1461 |
}
|
|
|
1462 |
cur_sel = *psel;
|
|
|
1463 |
reselect(&tr);
|
|
|
1464 |
}
|
|
|
1465 |
|
|
|
1466 |
|
|
|
1467 |
/***************************** Prompting for text *****************************/
|
|
|
1468 |
|
|
|
1469 |
void unshow_mytext(char* msg)
|
|
|
1470 |
{
|
|
|
1471 |
Rectangle r;
|
|
|
1472 |
Point siz = stringsize(display->defaultfont, msg);
|
|
|
1473 |
top_left -= siz.x;
|
|
|
1474 |
r.min.y = screen->r.min.y + outersep;
|
|
|
1475 |
r.min.x = top_left;
|
|
|
1476 |
r.max.y = r.min.y + siz.y;
|
|
|
1477 |
r.max.x = r.min.x + siz.x;
|
|
|
1478 |
draw(screen, r, display->white, display->opaque, r.min);
|
|
|
1479 |
}
|
|
|
1480 |
|
|
|
1481 |
|
|
|
1482 |
/* Show the given prompt and read a line of user input. The text appears at the
|
|
|
1483 |
top left. If it runs into the top right text, we stop echoing but let the user
|
|
|
1484 |
continue typing blind if he wants to.
|
|
|
1485 |
*/
|
|
|
1486 |
char* prompt_text(char* prompt)
|
|
|
1487 |
{
|
|
|
1488 |
static char buf[200];
|
|
|
1489 |
int n0, n=0, nshown=0;
|
|
|
1490 |
Rune c;
|
|
|
1491 |
unselect(0);
|
|
|
1492 |
show_mytext(prompt);
|
|
|
1493 |
while (n<200-1-UTFmax && (c=ekbd())!='\n') {
|
|
|
1494 |
if (c=='\b') {
|
|
|
1495 |
buf[n] = 0;
|
|
|
1496 |
if (n > 0)
|
|
|
1497 |
do n--;
|
|
|
1498 |
while (n>0 && (buf[n-1]&0xc0)==0x80);
|
|
|
1499 |
if (n < nshown)
|
|
|
1500 |
{unshow_mytext(buf+n); nshown=n;}
|
|
|
1501 |
} else {
|
|
|
1502 |
n0 = n;
|
|
|
1503 |
n += runetochar(buf+n, &c);
|
|
|
1504 |
buf[n] = 0;
|
|
|
1505 |
if (nshown==n0 && top_right-top_left >= smaxch.x)
|
|
|
1506 |
{show_mytext(buf+n0); nshown=n;}
|
|
|
1507 |
}
|
|
|
1508 |
}
|
|
|
1509 |
buf[n] = 0;
|
|
|
1510 |
while (ecanmouse())
|
|
|
1511 |
emouse();
|
|
|
1512 |
return buf;
|
|
|
1513 |
}
|
|
|
1514 |
|
|
|
1515 |
|
|
|
1516 |
/**************************** Redrawing the screen ****************************/
|
|
|
1517 |
|
|
|
1518 |
/* Let p0 and its successors define a piecewise-linear function of a paramter t,
|
|
|
1519 |
and draw the 0<=t<=n1 portion using transform *tr.
|
|
|
1520 |
*/
|
|
|
1521 |
void draw_fpts(const fpoint* p0, double n1, const transform* tr, int thick,
|
|
|
1522 |
Image* clr)
|
|
|
1523 |
{
|
|
|
1524 |
int n = (int) n1;
|
|
|
1525 |
const fpoint* p = p0 + n;
|
|
|
1526 |
fpoint pp;
|
|
|
1527 |
Point qq, q;
|
|
|
1528 |
if (n1 > n) {
|
|
|
1529 |
pp.x = p[0].x + (n1-n)*(p[1].x - p[0].x);
|
|
|
1530 |
pp.y = p[0].y + (n1-n)*(p[1].y - p[0].y);
|
|
|
1531 |
} else pp = *p--;
|
|
|
1532 |
do_transform(&qq, tr, &pp);
|
|
|
1533 |
if (n1==0)
|
|
|
1534 |
fillellipse(screen, qq, 1+thick, 1+thick, clr, qq);
|
|
|
1535 |
for (; p>=p0; p--) {
|
|
|
1536 |
do_transform(&q, tr, p);
|
|
|
1537 |
if(plotdots)
|
|
|
1538 |
fillellipse(screen, q, Dotrad, Dotrad, clr, q);
|
|
|
1539 |
else
|
|
|
1540 |
line(screen, qq, q, Enddisc, Enddisc, thick, clr, qq);
|
|
|
1541 |
qq = q;
|
|
|
1542 |
}
|
|
|
1543 |
}
|
|
|
1544 |
|
|
|
1545 |
void draw_1fpoly(const fpolygon* fp, const transform* tr, Image* clr,
|
|
|
1546 |
const frectangle *udisp, double slant)
|
|
|
1547 |
{
|
|
|
1548 |
fpoint *p0=fp->p, *pn=fp->p+fp->n;
|
|
|
1549 |
double l1, l2;
|
|
|
1550 |
if (p0==pn && fcontains(udisp,*p0))
|
|
|
1551 |
{draw_fpts(p0, 0, tr, fp->c.thick, clr); return;}
|
|
|
1552 |
while ((l1=out_length(p0,pn,*udisp,slant)) < pn-p0) {
|
|
|
1553 |
fpoint p0sav;
|
|
|
1554 |
int i1 = (int) l1;
|
|
|
1555 |
p0+=i1; l1-=i1;
|
|
|
1556 |
p0sav = *p0;
|
|
|
1557 |
p0[0].x += l1*(p0[1].x - p0[0].x);
|
|
|
1558 |
p0[0].y += l1*(p0[1].y - p0[0].y);
|
|
|
1559 |
l2 = in_length(p0, pn, *udisp, slant);
|
|
|
1560 |
draw_fpts(p0, l2, tr, fp->c.thick, clr);
|
|
|
1561 |
*p0 = p0sav;
|
|
|
1562 |
p0 += (l2>0) ? ((int) ceil(l2)) : 1;
|
|
|
1563 |
}
|
|
|
1564 |
}
|
|
|
1565 |
|
|
|
1566 |
|
|
|
1567 |
double get_clip_data(const fpolygons *u, frectangle *r)
|
|
|
1568 |
{
|
|
|
1569 |
double slant = set_unslanted_y(u, &r->min.y, &r->max.y);
|
|
|
1570 |
r->min.x = u->disp.min.x;
|
|
|
1571 |
r->max.x = u->disp.max.x;
|
|
|
1572 |
return slant;
|
|
|
1573 |
}
|
|
|
1574 |
|
|
|
1575 |
|
|
|
1576 |
void draw_fpoly(const fpolygon* fp, const transform* tr, Image* clr)
|
|
|
1577 |
{
|
|
|
1578 |
frectangle r;
|
|
|
1579 |
double slant = get_clip_data(&univ, &r);
|
|
|
1580 |
draw_1fpoly(fp, tr, clr, &r, slant);
|
|
|
1581 |
}
|
|
|
1582 |
|
|
|
1583 |
|
|
|
1584 |
void eresized(int new)
|
|
|
1585 |
{
|
|
|
1586 |
transform tr;
|
|
|
1587 |
fpolygon* fp;
|
|
|
1588 |
frectangle clipr;
|
|
|
1589 |
double slant;
|
|
|
1590 |
if(new && getwindow(display, Refmesg) < 0) {
|
|
|
1591 |
fprintf(stderr,"can't reattach to window\n");
|
|
|
1592 |
exits("reshap");
|
|
|
1593 |
}
|
|
|
1594 |
draw(screen, screen->r, display->white, display->opaque, screen->r.min);
|
|
|
1595 |
tr = draw_frame();
|
|
|
1596 |
slant = get_clip_data(&univ, &clipr);
|
|
|
1597 |
for (fp=univ.p; fp!=0; fp=fp->link)
|
|
|
1598 |
if (fintersects(&clipr, &fp->bb, slant))
|
|
|
1599 |
draw_1fpoly(fp, &tr, fp->c.clr, &clipr, slant);
|
|
|
1600 |
reselect(0);
|
|
|
1601 |
if (mv_bkgd!=0 && mv_bkgd->repl==0) {
|
|
|
1602 |
freeimage(mv_bkgd);
|
|
|
1603 |
mv_bkgd = display->white;
|
|
|
1604 |
}
|
|
|
1605 |
flushimage(display, 1);
|
|
|
1606 |
}
|
|
|
1607 |
|
|
|
1608 |
|
|
|
1609 |
|
|
|
1610 |
|
|
|
1611 |
/********************************* Recoloring *********************************/
|
|
|
1612 |
|
|
|
1613 |
int draw_palette(int n) /* n is number of colors; returns patch dy */
|
|
|
1614 |
{
|
|
|
1615 |
int y0 = screen->r.min.y + top_border;
|
|
|
1616 |
int dy = (screen->r.max.y - bot_border - y0)/n;
|
|
|
1617 |
Rectangle r;
|
|
|
1618 |
int i;
|
|
|
1619 |
r.min.y = y0;
|
|
|
1620 |
r.min.x = screen->r.max.x - rt_border + framewd;
|
|
|
1621 |
r.max.y = y0 + dy;
|
|
|
1622 |
r.max.x = screen->r.max.x;
|
|
|
1623 |
for (i=0; i<n; i++) {
|
|
|
1624 |
draw(screen, r, clrtab[i].im, display->opaque, r.min);
|
|
|
1625 |
r.min.y = r.max.y;
|
|
|
1626 |
r.max.y += dy;
|
|
|
1627 |
}
|
|
|
1628 |
return dy;
|
|
|
1629 |
}
|
|
|
1630 |
|
|
|
1631 |
|
|
|
1632 |
Image* palette_color(Point pt, int dy, int n)
|
|
|
1633 |
{ /* mouse at pt, patch size dy, n colors */
|
|
|
1634 |
int yy;
|
|
|
1635 |
if (screen->r.max.x - pt.x > rt_border - framewd)
|
|
|
1636 |
return 0;
|
|
|
1637 |
yy = pt.y - (screen->r.min.y + top_border);
|
|
|
1638 |
if (yy<0 || yy>=n*dy)
|
|
|
1639 |
return 0;
|
|
|
1640 |
return clrtab[yy/dy].im;
|
|
|
1641 |
}
|
|
|
1642 |
|
|
|
1643 |
|
|
|
1644 |
void all_set_clr(fpolygons* fps, Image* clr)
|
|
|
1645 |
{
|
|
|
1646 |
fpolygon* p;
|
|
|
1647 |
for (p=fps->p; p!=0; p=p->link)
|
|
|
1648 |
p->c.clr = clr;
|
|
|
1649 |
}
|
|
|
1650 |
|
|
|
1651 |
|
|
|
1652 |
void all_set_scheme(fpolygons* fps, int scheme)
|
|
|
1653 |
{
|
|
|
1654 |
fpolygon* p;
|
|
|
1655 |
for (p=fps->p; p!=0; p=p->link)
|
|
|
1656 |
if (p->ct!=0 && scheme <= p->ct[0].thick)
|
|
|
1657 |
p->c = p->ct[scheme];
|
|
|
1658 |
}
|
|
|
1659 |
|
|
|
1660 |
|
|
|
1661 |
void do_recolor(int but, Mouse* m, int alluniv)
|
|
|
1662 |
{
|
|
|
1663 |
int sel, clkk, nclr = clr_id(DWhite);
|
|
|
1664 |
int dy = draw_palette(nclr);
|
|
|
1665 |
Image* clr;
|
|
|
1666 |
clkk = get_click_or_kbd(but, m, "123456789abcdefghijklmnopqrstuvwxyz");
|
|
|
1667 |
if (clkk < 0) {
|
|
|
1668 |
clr = palette_color(m->xy, dy, nclr);
|
|
|
1669 |
if (clr != 0) {
|
|
|
1670 |
if (alluniv)
|
|
|
1671 |
all_set_clr(&univ, clr);
|
|
|
1672 |
else cur_sel.fp->c.clr = clr;
|
|
|
1673 |
}
|
|
|
1674 |
eresized(0);
|
|
|
1675 |
lift_button(but, m, Never);
|
|
|
1676 |
} else if (clkk > 0) {
|
|
|
1677 |
sel = ('0'<clkk&&clkk<='9') ? 0 : 10+(clkk-'a')*10;
|
|
|
1678 |
while (!('0'<=clkk&&clkk<='9'))
|
|
|
1679 |
clkk = ekbd();
|
|
|
1680 |
sel += clkk-'0';
|
|
|
1681 |
if (alluniv)
|
|
|
1682 |
all_set_scheme(&univ, sel);
|
|
|
1683 |
else if (sel <= cur_sel.fp->ct[0].thick)
|
|
|
1684 |
cur_sel.fp->c = cur_sel.fp->ct[sel];
|
|
|
1685 |
}
|
|
|
1686 |
eresized(0);
|
|
|
1687 |
}
|
|
|
1688 |
|
|
|
1689 |
|
|
|
1690 |
/****************************** Move and rotate ******************************/
|
|
|
1691 |
|
|
|
1692 |
void prepare_mv(const fpolygon* fp)
|
|
|
1693 |
{
|
|
|
1694 |
Rectangle r = screen->r;
|
|
|
1695 |
Image* scr0;
|
|
|
1696 |
int dt = 1 + fp->c.thick;
|
|
|
1697 |
r.min.x+=lft_border-dt; r.min.y+=top_border-dt;
|
|
|
1698 |
r.max.x-=rt_border-dt; r.max.y-=bot_border-dt;
|
|
|
1699 |
if (mv_bkgd!=0 && mv_bkgd->repl==0)
|
|
|
1700 |
freeimage(mv_bkgd);
|
|
|
1701 |
mv_bkgd = allocimage(display, r, CMAP8, 0, DNofill);
|
|
|
1702 |
if (mv_bkgd==0)
|
|
|
1703 |
mv_bkgd = display->white;
|
|
|
1704 |
else { transform tr = cur_trans();
|
|
|
1705 |
draw(mv_bkgd, r, screen, display->opaque, r.min);
|
|
|
1706 |
draw(mv_bkgd, sel_dot_box(&tr), sel_bkg, display->opaque, ZP);
|
|
|
1707 |
scr0 = screen;
|
|
|
1708 |
screen = mv_bkgd;
|
|
|
1709 |
draw_fpoly(fp, &tr, display->white);
|
|
|
1710 |
screen = scr0;
|
|
|
1711 |
}
|
|
|
1712 |
}
|
|
|
1713 |
|
|
|
1714 |
|
|
|
1715 |
void move_fp(fpolygon* fp, double dx, double dy)
|
|
|
1716 |
{
|
|
|
1717 |
fpoint *p, *pn=fp->p+fp->n;
|
|
|
1718 |
for (p=fp->p; p<=pn; p++) {
|
|
|
1719 |
(p->x) += dx;
|
|
|
1720 |
(p->y) += dy;
|
|
|
1721 |
}
|
|
|
1722 |
(fp->bb.min.x)+=dx; (fp->bb.min.y)+=dy;
|
|
|
1723 |
(fp->bb.max.x)+=dx; (fp->bb.max.y)+=dy;
|
|
|
1724 |
}
|
|
|
1725 |
|
|
|
1726 |
|
|
|
1727 |
void rotate_fp(fpolygon* fp, fpoint o, double theta)
|
|
|
1728 |
{
|
|
|
1729 |
double s=sin(theta), c=cos(theta);
|
|
|
1730 |
fpoint *p, *pn=fp->p+fp->n;
|
|
|
1731 |
for (p=fp->p; p<=pn; p++) {
|
|
|
1732 |
double x=p->x-o.x, y=p->y-o.y;
|
|
|
1733 |
(p->x) = o.x + c*x - s*y;
|
|
|
1734 |
(p->y) = o.y + s*x + c*y;
|
|
|
1735 |
}
|
|
|
1736 |
set_fbb(fp);
|
|
|
1737 |
}
|
|
|
1738 |
|
|
|
1739 |
|
|
|
1740 |
/* Move the selected fpolygon so the selected point tracks the mouse, and return
|
|
|
1741 |
the total amount of movement. Button but has already been held down for at
|
|
|
1742 |
least Mv_delay milliseconds and the mouse might have moved some distance.
|
|
|
1743 |
*/
|
|
|
1744 |
fpoint do_move(int but, Mouse* m)
|
|
|
1745 |
{
|
|
|
1746 |
transform tr = cur_trans();
|
|
|
1747 |
int bbit = Button_bit(but);
|
|
|
1748 |
fpolygon* fp = cur_sel.fp;
|
|
|
1749 |
fpoint loc, loc0=cur_sel.p;
|
|
|
1750 |
double tsav = cur_sel.t;
|
|
|
1751 |
unselect(&tr);
|
|
|
1752 |
do { latest_mouse(but, m);
|
|
|
1753 |
(fp->c.thick)++; /* line() DISAGREES WITH ITSELF */
|
|
|
1754 |
draw_fpoly(fp, &tr, mv_bkgd);
|
|
|
1755 |
(fp->c.thick)--;
|
|
|
1756 |
do_untransform(&loc, &tr, &m->xy);
|
|
|
1757 |
move_fp(fp, loc.x-cur_sel.p.x, loc.y-cur_sel.p.y);
|
|
|
1758 |
cur_sel.p = loc;
|
|
|
1759 |
draw_fpoly(fp, &tr, fp->c.clr);
|
|
|
1760 |
} while (m->buttons & bbit);
|
|
|
1761 |
cur_sel.t = tsav;
|
|
|
1762 |
reselect(&tr);
|
|
|
1763 |
loc.x -= loc0.x;
|
|
|
1764 |
loc.y -= loc0.y;
|
|
|
1765 |
return loc;
|
|
|
1766 |
}
|
|
|
1767 |
|
|
|
1768 |
|
|
|
1769 |
double dir_angle(const Point* pt, const transform* tr)
|
|
|
1770 |
{
|
|
|
1771 |
fpoint p;
|
|
|
1772 |
double dy, dx;
|
|
|
1773 |
do_untransform(&p, tr, pt);
|
|
|
1774 |
dy=p.y-cur_sel.p.y; dx=p.x-cur_sel.p.x;
|
|
|
1775 |
return (dx==0 && dy==0) ? 0.0 : atan2(dy, dx);
|
|
|
1776 |
}
|
|
|
1777 |
|
|
|
1778 |
|
|
|
1779 |
/* Rotate the selected fpolygon around the selection point so as to track the
|
|
|
1780 |
direction angle from the selected point to m->xy. Stop when button but goes
|
|
|
1781 |
up and return the total amount of rotation in radians.
|
|
|
1782 |
*/
|
|
|
1783 |
double do_rotate(int but, Mouse* m)
|
|
|
1784 |
{
|
|
|
1785 |
transform tr = cur_trans();
|
|
|
1786 |
int bbit = Button_bit(but);
|
|
|
1787 |
fpolygon* fp = cur_sel.fp;
|
|
|
1788 |
double theta0 = dir_angle(&m->xy, &tr);
|
|
|
1789 |
double th, theta = theta0;
|
|
|
1790 |
do { latest_mouse(but, m);
|
|
|
1791 |
(fp->c.thick)++; /* line() DISAGREES WITH ITSELF */
|
|
|
1792 |
draw_fpoly(fp, &tr, mv_bkgd);
|
|
|
1793 |
(fp->c.thick)--;
|
|
|
1794 |
th = dir_angle(&m->xy, &tr);
|
|
|
1795 |
rotate_fp(fp, cur_sel.p, th-theta);
|
|
|
1796 |
theta = th;
|
|
|
1797 |
draw_fpoly(fp, &tr, fp->c.clr);
|
|
|
1798 |
} while (m->buttons & bbit);
|
|
|
1799 |
unselect(&tr);
|
|
|
1800 |
cur_sel = prev_sel;
|
|
|
1801 |
reselect(&tr);
|
|
|
1802 |
return theta - theta0;
|
|
|
1803 |
}
|
|
|
1804 |
|
|
|
1805 |
|
|
|
1806 |
|
|
|
1807 |
/********************************* Edit menu *********************************/
|
|
|
1808 |
|
|
|
1809 |
typedef enum e_index {
|
|
|
1810 |
Erecolor, Ethick, Edelete, Eundo, Erotate, Eoptions,
|
|
|
1811 |
Emove
|
|
|
1812 |
} e_index;
|
|
|
1813 |
|
|
|
1814 |
char* e_items[Eoptions+1];
|
|
|
1815 |
|
|
|
1816 |
Menu e_menu = {e_items, 0, 0};
|
|
|
1817 |
|
|
|
1818 |
|
|
|
1819 |
typedef struct e_action {
|
|
|
1820 |
e_index typ; /* What type of action */
|
|
|
1821 |
fpolygon* fp; /* fpolygon the action applies to */
|
|
|
1822 |
Image* clr; /* color to use if typ==Erecolor */
|
|
|
1823 |
double amt; /* rotation angle or line thickness */
|
|
|
1824 |
fpoint pt; /* movement vector or rotation center */
|
|
|
1825 |
struct e_action* link; /* next in a stack */
|
|
|
1826 |
} e_action;
|
|
|
1827 |
|
|
|
1828 |
e_action* unact = 0; /* heads a linked list of actions */
|
|
|
1829 |
e_action* do_undo(e_action*); /* pop off an e_action and (un)do it */
|
|
|
1830 |
e_action* save_act(e_action*,e_index); /* append new e_action for status quo */
|
|
|
1831 |
|
|
|
1832 |
|
|
|
1833 |
void save_mv(fpoint movement)
|
|
|
1834 |
{
|
|
|
1835 |
unact = save_act(unact, Emove);
|
|
|
1836 |
unact->pt = movement;
|
|
|
1837 |
}
|
|
|
1838 |
|
|
|
1839 |
|
|
|
1840 |
void init_e_menu(void)
|
|
|
1841 |
{
|
|
|
1842 |
char* u = "can't undo";
|
|
|
1843 |
e_items[Erecolor] = "recolor";
|
|
|
1844 |
e_items[Edelete] = "delete";
|
|
|
1845 |
e_items[Erotate] = "rotate";
|
|
|
1846 |
e_items[Eoptions-cantmv] = 0;
|
|
|
1847 |
e_items[Ethick] = (cur_sel.fp->c.thick >0) ? "thin" : "thick";
|
|
|
1848 |
if (unact!=0)
|
|
|
1849 |
switch (unact->typ) {
|
|
|
1850 |
case Erecolor: u="uncolor"; break;
|
|
|
1851 |
case Ethick: u=(unact->fp->c.thick==0) ? "unthin" : "unthicken";
|
|
|
1852 |
break;
|
|
|
1853 |
case Edelete: u="undelete"; break;
|
|
|
1854 |
case Emove: u="unmove"; break;
|
|
|
1855 |
case Erotate: u="unrotate"; break;
|
|
|
1856 |
}
|
|
|
1857 |
e_items[Eundo] = u;
|
|
|
1858 |
}
|
|
|
1859 |
|
|
|
1860 |
|
|
|
1861 |
void do_emenu(int but, Mouse* m)
|
|
|
1862 |
{
|
|
|
1863 |
int h;
|
|
|
1864 |
if (cur_sel.t < 0)
|
|
|
1865 |
return;
|
|
|
1866 |
init_e_menu();
|
|
|
1867 |
h = emenuhit(but, m, &e_menu);
|
|
|
1868 |
switch(h) {
|
|
|
1869 |
case Ethick: unact = save_act(unact, h);
|
|
|
1870 |
cur_sel.fp->c.thick ^= 1;
|
|
|
1871 |
eresized(0);
|
|
|
1872 |
break;
|
|
|
1873 |
case Edelete: unact = save_act(unact, h);
|
|
|
1874 |
fp_remove(&univ, cur_sel.fp);
|
|
|
1875 |
unselect(0);
|
|
|
1876 |
eresized(0);
|
|
|
1877 |
break;
|
|
|
1878 |
case Erecolor: unact = save_act(unact, h);
|
|
|
1879 |
do_recolor(but, m, 0);
|
|
|
1880 |
break;
|
|
|
1881 |
case Erotate: unact = save_act(unact, h);
|
|
|
1882 |
prepare_mv(cur_sel.fp);
|
|
|
1883 |
if (get_1click(but, m, 0)) {
|
|
|
1884 |
unact->pt = cur_sel.p;
|
|
|
1885 |
unact->amt = do_rotate(but, m);
|
|
|
1886 |
}
|
|
|
1887 |
break;
|
|
|
1888 |
case Eundo: unact = do_undo(unact);
|
|
|
1889 |
break;
|
|
|
1890 |
}
|
|
|
1891 |
}
|
|
|
1892 |
|
|
|
1893 |
|
|
|
1894 |
|
|
|
1895 |
/******************************* Undoing edits *******************************/
|
|
|
1896 |
|
|
|
1897 |
e_action* save_act(e_action* a0, e_index typ)
|
|
|
1898 |
{ /* append new e_action for status quo */
|
|
|
1899 |
e_action* a = malloc(sizeof(e_action));
|
|
|
1900 |
a->link = a0;
|
|
|
1901 |
a->pt.x = a->pt.y = 0.0;
|
|
|
1902 |
a->amt = cur_sel.fp->c.thick;
|
|
|
1903 |
a->clr = cur_sel.fp->c.clr;
|
|
|
1904 |
a->fp = cur_sel.fp;
|
|
|
1905 |
a->typ = typ;
|
|
|
1906 |
return a;
|
|
|
1907 |
}
|
|
|
1908 |
|
|
|
1909 |
|
|
|
1910 |
/* This would be trivial except it's nice to preserve the selection in order to make
|
|
|
1911 |
it easy to undo a series of moves. (There's no do_unrotate() because it's harder
|
|
|
1912 |
and less important to preserve the selection in that case.)
|
|
|
1913 |
*/
|
|
|
1914 |
void do_unmove(e_action* a)
|
|
|
1915 |
{
|
|
|
1916 |
double tsav = cur_sel.t;
|
|
|
1917 |
unselect(0);
|
|
|
1918 |
move_fp(a->fp, -a->pt.x, -a->pt.y);
|
|
|
1919 |
if (a->fp == cur_sel.fp) {
|
|
|
1920 |
cur_sel.p.x -= a->pt.x;
|
|
|
1921 |
cur_sel.p.y -= a->pt.y;
|
|
|
1922 |
}
|
|
|
1923 |
cur_sel.t = tsav;
|
|
|
1924 |
reselect(0);
|
|
|
1925 |
}
|
|
|
1926 |
|
|
|
1927 |
|
|
|
1928 |
e_action* do_undo(e_action* a0) /* pop off an e_action and (un)do it */
|
|
|
1929 |
{
|
|
|
1930 |
e_action* a = a0;
|
|
|
1931 |
if (a==0)
|
|
|
1932 |
return 0;
|
|
|
1933 |
switch(a->typ) {
|
|
|
1934 |
case Ethick: a->fp->c.thick = a->amt;
|
|
|
1935 |
eresized(0);
|
|
|
1936 |
break;
|
|
|
1937 |
case Erecolor: a->fp->c.clr = a->clr;
|
|
|
1938 |
eresized(0);
|
|
|
1939 |
break;
|
|
|
1940 |
case Edelete:
|
|
|
1941 |
a->fp->link = univ.p;
|
|
|
1942 |
univ.p = a->fp;
|
|
|
1943 |
grow_bb(&univ.bb, &a->fp->bb);
|
|
|
1944 |
eresized(0);
|
|
|
1945 |
break;
|
|
|
1946 |
case Emove:
|
|
|
1947 |
do_unmove(a);
|
|
|
1948 |
eresized(0);
|
|
|
1949 |
break;
|
|
|
1950 |
case Erotate:
|
|
|
1951 |
unselect(0);
|
|
|
1952 |
rotate_fp(a->fp, a->pt, -a->amt);
|
|
|
1953 |
eresized(0);
|
|
|
1954 |
break;
|
|
|
1955 |
}
|
|
|
1956 |
a0 = a->link;
|
|
|
1957 |
free(a);
|
|
|
1958 |
return a0;
|
|
|
1959 |
}
|
|
|
1960 |
|
|
|
1961 |
|
|
|
1962 |
|
|
|
1963 |
/********************************* Main menu *********************************/
|
|
|
1964 |
|
|
|
1965 |
enum m_index { Mzoom_in, Mzoom_out, Munzoom, Mslant, Munslant,
|
|
|
1966 |
Msquare_up, Mrecenter, Mrecolor, Mrestack, Mread,
|
|
|
1967 |
Mwrite, Mexit};
|
|
|
1968 |
char* m_items[] = {"zoom in", "zoom out", "unzoom", "slant", "unslant",
|
|
|
1969 |
"square up", "recenter", "recolor", "restack", "read",
|
|
|
1970 |
"write", "exit", 0};
|
|
|
1971 |
|
|
|
1972 |
Menu m_menu = {m_items, 0, 0};
|
|
|
1973 |
|
|
|
1974 |
|
|
|
1975 |
void do_mmenu(int but, Mouse* m)
|
|
|
1976 |
{
|
|
|
1977 |
int e, h = emenuhit(but, m, &m_menu);
|
|
|
1978 |
switch (h) {
|
|
|
1979 |
case Mzoom_in:
|
|
|
1980 |
disp_zoomin(egetrect(but,m));
|
|
|
1981 |
eresized(0);
|
|
|
1982 |
break;
|
|
|
1983 |
case Mzoom_out:
|
|
|
1984 |
disp_zoomout(egetrect(but,m));
|
|
|
1985 |
eresized(0);
|
|
|
1986 |
break;
|
|
|
1987 |
case Msquare_up:
|
|
|
1988 |
disp_squareup();
|
|
|
1989 |
eresized(0);
|
|
|
1990 |
break;
|
|
|
1991 |
case Munzoom:
|
|
|
1992 |
init_disp();
|
|
|
1993 |
eresized(0);
|
|
|
1994 |
break;
|
|
|
1995 |
case Mrecenter:
|
|
|
1996 |
if (get_1click(but, m, &bullseye)) {
|
|
|
1997 |
recenter_disp(m->xy);
|
|
|
1998 |
eresized(0);
|
|
|
1999 |
lift_button(but, m, Never);
|
|
|
2000 |
}
|
|
|
2001 |
break;
|
|
|
2002 |
case Mslant:
|
|
|
2003 |
if (cur_sel.t>=0 && prev_sel.t>=0) {
|
|
|
2004 |
slant_disp(prev_sel.p, cur_sel.p);
|
|
|
2005 |
eresized(0);
|
|
|
2006 |
}
|
|
|
2007 |
break;
|
|
|
2008 |
case Munslant:
|
|
|
2009 |
univ.slant_ht = univ.disp.max.y - univ.disp.min.y;
|
|
|
2010 |
eresized(0);
|
|
|
2011 |
break;
|
|
|
2012 |
case Mrecolor:
|
|
|
2013 |
do_recolor(but, m, 1);
|
|
|
2014 |
break;
|
|
|
2015 |
case Mrestack:
|
|
|
2016 |
fps_invert(&univ);
|
|
|
2017 |
eresized(0);
|
|
|
2018 |
break;
|
|
|
2019 |
case Mread:
|
|
|
2020 |
e = doinput(prompt_text("File:"));
|
|
|
2021 |
if (e==0)
|
|
|
2022 |
eresized(0);
|
|
|
2023 |
else if (e<0)
|
|
|
2024 |
show_mytext(" - can't read");
|
|
|
2025 |
else {
|
|
|
2026 |
char ebuf[80];
|
|
|
2027 |
snprintf(ebuf, 80, " - error line %d", e);
|
|
|
2028 |
show_mytext(ebuf);
|
|
|
2029 |
}
|
|
|
2030 |
break;
|
|
|
2031 |
case Mwrite:
|
|
|
2032 |
if (!dooutput(prompt_text("File:")))
|
|
|
2033 |
show_mytext(" - can't write");
|
|
|
2034 |
break;
|
|
|
2035 |
case Mexit:
|
|
|
2036 |
exits("");
|
|
|
2037 |
}
|
|
|
2038 |
}
|
|
|
2039 |
|
|
|
2040 |
|
|
|
2041 |
|
|
|
2042 |
/****************************** Handling events ******************************/
|
|
|
2043 |
|
|
|
2044 |
void doevent(void)
|
|
|
2045 |
{
|
|
|
2046 |
ulong etype;
|
|
|
2047 |
int mobile;
|
|
|
2048 |
ulong mvtime;
|
|
|
2049 |
Event ev;
|
|
|
2050 |
|
|
|
2051 |
etype = eread(Emouse|Ekeyboard, &ev);
|
|
|
2052 |
if(etype & Emouse) {
|
|
|
2053 |
if (ev.mouse.buttons & But1) {
|
|
|
2054 |
do_select(ev.mouse.xy);
|
|
|
2055 |
mvtime = Never;
|
|
|
2056 |
mobile = !cantmv && cur_sel.t>=0;
|
|
|
2057 |
if (mobile) {
|
|
|
2058 |
mvtime = ev.mouse.msec + Mv_delay;
|
|
|
2059 |
prepare_mv(cur_sel.fp);
|
|
|
2060 |
}
|
|
|
2061 |
if (!lift_button(1, &ev.mouse, mvtime) && mobile)
|
|
|
2062 |
save_mv(do_move(1, &ev.mouse));
|
|
|
2063 |
} else if (ev.mouse.buttons & But2)
|
|
|
2064 |
do_emenu(2, &ev.mouse);
|
|
|
2065 |
else if (ev.mouse.buttons & But3)
|
|
|
2066 |
do_mmenu(3, &ev.mouse);
|
|
|
2067 |
} else if (etype & Ekeyboard) {
|
|
|
2068 |
if (ev.kbdc=='\n' && cur_sel.t>=0 && logfil!=0) {
|
|
|
2069 |
fprintf(logfil,"%s\n", cur_sel.fp->nam);
|
|
|
2070 |
fflush(logfil);
|
|
|
2071 |
}
|
|
|
2072 |
}
|
|
|
2073 |
}
|
|
|
2074 |
|
|
|
2075 |
|
|
|
2076 |
|
|
|
2077 |
/******************************** Main program ********************************/
|
|
|
2078 |
|
|
|
2079 |
extern char* argv0;
|
|
|
2080 |
|
|
|
2081 |
void usage(void)
|
|
|
2082 |
{
|
|
|
2083 |
int i;
|
|
|
2084 |
fprintf(stderr,"Usage %s [options] [infile]\n", argv0);
|
|
|
2085 |
fprintf(stderr,
|
|
|
2086 |
"option ::= -l logfile | -m | -p\n"
|
|
|
2087 |
"\n"
|
|
|
2088 |
"Read a polygonal line graph in an ASCII format (one x y pair per line, delimited\n"
|
|
|
2089 |
"by spaces with a label after each polyline), and view it interactively. Use\n"
|
|
|
2090 |
"standard input if no infile is specified.\n"
|
|
|
2091 |
"Option -l specifies a file in which to log the coordinates of each point selected.\n"
|
|
|
2092 |
"(Clicking a point with button one selects it and displays its coordinates and\n"
|
|
|
2093 |
"the label of its polylone.) Option -m allows polylines to be moved and rotated.\n"
|
|
|
2094 |
"The -p option plots only the vertices of the polygons.\n"
|
|
|
2095 |
"The polyline labels can use the following color names:"
|
|
|
2096 |
);
|
|
|
2097 |
for (i=0; clrtab[i].c!=DNofill; i++)
|
|
|
2098 |
fprintf(stderr,"%s%8s", (i%8==0 ? "\n" : " "), clrtab[i].nam);
|
|
|
2099 |
fputc('\n', stderr);
|
|
|
2100 |
exits("usage");
|
|
|
2101 |
}
|
|
|
2102 |
|
|
|
2103 |
void main(int argc, char *argv[])
|
|
|
2104 |
{
|
|
|
2105 |
int e;
|
|
|
2106 |
char err[ERRMAX];
|
|
|
2107 |
|
|
|
2108 |
ARGBEGIN {
|
|
|
2109 |
case 'm':
|
|
|
2110 |
cantmv=0;
|
|
|
2111 |
break;
|
|
|
2112 |
case 'l':
|
|
|
2113 |
logfil = fopen(ARGF(),"w");
|
|
|
2114 |
break;
|
|
|
2115 |
case 'p':
|
|
|
2116 |
plotdots++;
|
|
|
2117 |
break;
|
|
|
2118 |
default:
|
|
|
2119 |
usage();
|
|
|
2120 |
} ARGEND;
|
|
|
2121 |
|
|
|
2122 |
if(initdraw(0, 0, "gview") < 0)
|
|
|
2123 |
exits("initdraw");
|
|
|
2124 |
einit(Emouse|Ekeyboard);
|
|
|
2125 |
|
|
|
2126 |
do {
|
|
|
2127 |
e = doinput(*argv ? *argv : "-");
|
|
|
2128 |
if (e < 0) {
|
|
|
2129 |
rerrstr(err, sizeof err);
|
|
|
2130 |
fprintf(stderr, "%s: cannot read %s: %s\n",
|
|
|
2131 |
argv0, *argv, err);
|
|
|
2132 |
exits("no valid input file");
|
|
|
2133 |
} else if (e > 0) {
|
|
|
2134 |
fprintf(stderr, "%s: %s:%d: bad data syntax\n",
|
|
|
2135 |
argv0, (*argv ? *argv : "-"), e);
|
|
|
2136 |
exits("bad syntax in input");
|
|
|
2137 |
}
|
|
|
2138 |
} while (*argv && *++argv);
|
|
|
2139 |
init_disp();
|
|
|
2140 |
init_clrtab();
|
|
|
2141 |
set_default_clrs(&univ, 0);
|
|
|
2142 |
adjust_border(display->defaultfont);
|
|
|
2143 |
cur_sel.t = prev_sel.t = -1;
|
|
|
2144 |
eresized(0);
|
|
|
2145 |
for(;;)
|
|
|
2146 |
doevent();
|
|
|
2147 |
}
|