1 /*
2  * @(#)db.c	1.2	01/03/85
3  *
4  * This file contains routines for database manipulation for the
5  * SUN Gremlin picture editor.
6  *
7  * Mark Opperman (opcode@monet.BERKELEY)
8  *
9  */
10 
11 #include "gremlin.h"
12 #include <ctype.h>
13 
14 /* imports from undodb */
15 
16 extern UNRembAdd();
17 extern UNRembDelete();
18 extern UNRembMod();
19 
20 /* imports from C */
21 
22 extern char *malloc();
23 
24 /* imports from point.c */
25 
26 extern PTModifyTextPoints();
27 extern POINT *PTMakePoint();
28 
29 /* imports from text.c  */
30 
31 extern TxPutMsg();
32 extern TxMsgOK();
33 
34 /* imports from main */
35 
36 extern SEARCH;			/* Search the path for filename */
37 extern TOOLINSTALLED;
38 
39 /* cset is a pointer to the current set available to the outside world. */
40 
41 ELT *cset;
42 
43 /*
44  * This routine creates a new element with the specified attributes and
45  * links it into database db.
46  */
47 ELT *
48 DBCreateElt(type, pointlist, brush, size, text, db)
49 int type, brush, size;
50 POINT *pointlist;
51 char *text;
52 ELT *(*db);
53 {
54     register ELT *temp;
55 
56     temp = (ELT *) malloc(sizeof(ELT));
57     temp->nextelt = *db;
58     temp->type = type;
59     temp->ptlist = pointlist;
60     temp->brushf = brush;
61     temp->size = size;
62     temp->textpt = text;
63     *db = temp;
64     UNRembAdd(temp, db);
65     return(temp);
66 } /* end DBCreateElt */
67 
68 
69 /*
70  * This routine deletes the specified element by searching the database
71  * for its predecessor and deleting the pointer to the element.
72  * Flag indicates whether or not the element was in the current set
73  * and is passed along for use by the undo routines.
74  */
75 DBDelete(elt, db)
76 register ELT *elt, *(*db);
77 {
78     register ELT *(*temp);
79 
80     temp = db;
81 
82     while (*temp != elt) {
83         if (DBNullelt(*temp)) {
84             error("no such element");
85             return;
86         }
87         temp = &(DBNextElt((*temp)));
88     }
89 
90     UNRembDelete(*temp, db);
91     *temp = DBNextElt(elt);
92 }  /* end DBDelete */
93 
94 
95 #define highval 100000			/* arbitrary value greater than any
96 					 * expected distance */
97 
98 
99 /*
100  * This routine searches the database for the point closest to
101  * (Euclidean distance) point1.  This point is returned as point2
102  * and the element which contained the point is also returned.
103  * The point must be closer than some predefined maximum distance
104  * in order to be gravitated.
105  * If setonly == TRUE the element's "setnext" pointer is used to
106  * find the elements in the list, otherwise "nextelt" is used.
107  */
108 DBGravitate(x1, y1, x2, y2, point, elt, db, setonly)
109 float x1, y1, *x2, *y2;
110 POINT *(*point);
111 ELT *(*elt), *db;
112 int setonly;
113 {
114     register POINT *holdpt;
115     register ELT *temp;
116     register t, t1, t2;
117     register distance = highval;
118 
119     temp = db;
120     *elt = DBInit();
121     *x2 = x1;
122     *y2 = y1;
123     while (!DBNullelt(temp)) {
124         holdpt = temp->ptlist;
125         while (!Nullpoint(holdpt)) {
126 	    /* Calculate the distance between the point in the data
127 	       base and the specified point.  Use Euclidean distance
128 	       except that, since we only need relative distance and
129 	       not an absolute number, it is not necessary to take the
130 	       square root.  The equation for the distance was broken up
131 	       as below in order to allow integer arithmetic wherever
132 	       possible to increase efficiency when it was discovered
133 	       that this routine was too slow. */
134             t1 = holdpt->x - x1;
135             t1 *= t1;
136             t2 = holdpt->y - y1;
137             t2 *= t2;
138             t = t1 + t2;
139 
140             if ((t < distance) && (t < MAXGDIST)) {
141                 distance = t;
142                 *x2 = holdpt->x;
143                 *y2 = holdpt->y;
144     		*point = holdpt;
145                 *elt = temp;
146             }
147 
148             holdpt = holdpt->nextpt;
149         }
150         temp = setonly ? DBNextofSet(temp) : DBNextElt(temp);
151     }
152 }  /* end DBGravitate */
153 
154 
155 /*
156  * This routine returns all storage associated with the element to
157  * free storage.
158  */
159 DBClearElt(elt)
160 register ELT *elt;
161 {
162     register POINT *pt, *pt2;
163 
164     pt = elt->ptlist;
165 
166     while (!Nullpoint(pt)) {
167         pt2 = PTNextPoint(pt);
168         free ((char *) pt);
169         pt = pt2;
170     }
171 
172     free(elt->textpt);
173     free((char *) elt);
174 }  /* end DBClearElt */
175 
176 
177 /*
178  * This routine reads the specified file into a database and
179  * returns a pointer to that database.  Orient and pos are also set
180  * from the file.
181  *
182  * The format of a file written by gremlin is:
183  * the string: "gremlinfile" followed by a carriage return.
184  * the orientation (integer) and the x and y coordinates of a positioning
185  * point (float) followed by another carriage return.
186  * The output of 0 or more elements (see below).
187  * a -1 (integer) indicating end of data.
188  *
189  * The format of each element is:
190  * The element type (integer) followed by a carriage return.
191  * a list of 0 or more pairs of point coordinates (float) each on separate
192  * lines and terminated by the coordinates -1.0 -1.0.
193  * the brush (font) and size (integer) the element was defined with then <cr>
194  * the length (integer) of the string followed by the string terminated with
195  * a carriage return.
196  *
197  * All numbers are printed using standard C output conversion (ascii).
198  *
199  * +++ NEW FORMAT FOR SUN +++
200  *
201  * "sungremlinfile" is keyword in place of "gremlinfile"
202  *
203  * Point lists are terminated by a line containing a single asterik ('*')
204  * to allow the legal point (-1.00 -1.00) in the point list.  All negative
205  * coordinates are now legal.  Element types are indicated by ascii text,
206  * eg, POLYGON, VECTOR, ARC, BOTLEFT, TOPCENT, etc.
207  */
208 ELT *
209 DBRead(filename, orient, pos)
210 char *filename;
211 int *orient;
212 POINT *pos;
213 {
214     FILE *fp, *POpen();
215     ELT *elt, *elist;
216     POINT *plist;
217     char string[128], *txt, *prealname;
218     float x, y;
219     int len, type, i, brush, size, done, lastpoint, sunfile;
220 
221     sunfile = FALSE;
222     elist = DBInit();
223     fp = POpen(filename, &prealname, SEARCH);
224 
225     if (fp == NULL) {
226         (void) sprintf(string, "can't open %s",filename);
227         error(string);
228         return(elist);
229     }
230 
231     if (TOOLINSTALLED)		/* no message if reading startup edit file */
232 	TxPutMsg("reading file...");
233     (void) fscanf(fp, "%s\n", string);
234 
235     if (strcmp(string, "gremlinfile")) {
236 	if (strcmp(string, "sungremlinfile")) {
237 	    error("not gremlin file");
238 	    return(elist);
239 	}
240 	sunfile = TRUE;
241     }
242 
243     (void) fscanf(fp, "%d%f%f\n", orient, &x, &y);
244     pos->x = x;
245     pos->y = y;
246 
247     done = FALSE;
248     while (!done) {
249         if (fscanf(fp,"%s\n", string) == EOF) {		/* element type */
250             error("error in file format");
251 	    fclose(fp);
252             return(elist);
253         }
254 
255         if ((type = DBGetType(string)) < 0) {		/* no more data */
256             done = TRUE;
257         }
258         else {
259             plist = PTInit();
260             (void) fscanf(fp, "%f%f\n", &x, &y);	/* read first point */
261 
262 	    /* Files created on the SUN have point lists terminated
263 	     * by a line containing only an asterik ('*').  Files
264 	     * created on the AED have point lists terminated by the
265 	     * coordinate pair (-1.00 -1.00).
266 	     */
267 	    lastpoint = FALSE;
268 	    do {
269 		(void) PTMakePoint(x, y, &plist);
270 		fgets(string, 127, fp);
271 		if (string[0] == '*') {     /* SUN gremlin file */
272 		    lastpoint = TRUE;
273 		}
274 		else {
275 		    (void) sscanf(string, "%f%f", &x, &y);
276 		    if ((x == -1.00 && y == -1.00) && (!sunfile))
277 			lastpoint = TRUE;
278 		}
279 	    } while (!lastpoint);
280 #ifdef oldway
281             while ((x != -1) && (y != -1)) { /* plist terminated by -1, -1 */
282                 (void) PTMakePoint(x, y, &plist);
283                 (void) fscanf(fp, "%f%f\n", &x, &y);
284             }
285 #endif
286 
287 	    (void) fscanf(fp, "%d%d\n", &brush, &size);
288 	    (void) fscanf(fp, "%d", &len);
289 	    (void) getc(fp);	/* eat blank */
290             txt = malloc((unsigned) len + 1);
291             for (i=0; i<len; ++i)
292                 txt[i] = getc(fp);
293             txt[len] = '\0';
294             elt = DBCreateElt(type, plist, brush, size, txt, &elist);
295 	    if (TEXT(elt->type))	/* recompute text reference points */
296 		PTModifyTextPoints(elt);
297         }
298     }
299 
300     TxMsgOK();
301     fclose(fp);
302     return(elist);
303 } /* end DBRead */
304 
305 
306 /*
307  * Interpret element type in string s.
308  * Old file format consisted of integer element types.
309  * New file format has literal names for element types.
310  */
311 DBGetType(s)
312 register char *s;
313 {
314     if (isdigit(s[0]) || (s[0] == '-'))		/* old element format or EOF */
315 	return(atoi(s));
316 
317     switch (s[0]) {
318 	case 'P':
319 	    return(POLYGON);
320 	case 'V':
321 	    return(VECTOR);
322 	case 'A':
323 	    return(ARC);
324 	case 'C':
325 	    if (s[1] == 'U')
326 		return(CURVE);
327 	    switch (s[4]) {
328 		case 'L':
329 		    return(CENTLEFT);
330 		case 'C':
331 		    return(CENTCENT);
332 		case 'R':
333 		    return(CENTRIGHT);
334 		default:
335 		    error("unknown element type");
336 		    return(-1);
337 	    }
338 	case 'B':
339 	    switch (s[3]) {
340 		case 'L':
341 		    return(BOTLEFT);
342 		case 'C':
343 		    return(BOTCENT);
344 		case 'R':
345 		    return(BOTRIGHT);
346 		default:
347 		    error("unknown element type");
348 		    return(-1);
349 	    }
350 	case 'T':
351 	    switch (s[3]) {
352 		case 'L':
353 		    return(TOPLEFT);
354 		case 'C':
355 		    return(TOPCENT);
356 		case 'R':
357 		    return(TOPRIGHT);
358 		default:
359 		    error("unknown element type");
360 		    return(-1);
361 	    }
362 	default:
363 	    error("unknown element type");
364 	    return(-1);
365     }
366 }  /* end DBGetType */
367 
368 
369 /*
370  * This routine returns true if all points in elt are bounded by
371  * the rectangle who diagonal is formed by (x1, y1) and (x2, y2).
372  */
373 DBBounded(elt, x1, y1, x2, y2)
374 register ELT *elt;
375 register float x1, y1, x2, y2;
376 {
377     register POINT *p1;
378     register float lox, loy, hix, hiy;	/* OK to compare register floats */
379 
380     lox = (x1 < x2) ? x1 : x2;
381     loy = (y1 < y2) ? y1 : y2;
382     hix = (x1 > x2) ? x1 : x2;
383     hiy = (y1 > y2) ? y1 : y2;
384     p1 = elt->ptlist;
385 
386     while (!Nullpoint(p1)) {
387 	if ((p1->x < lox) || (p1->x > hix) || (p1->y < loy) || (p1->y > hiy))
388 	    return(FALSE);
389 	p1 = PTNextPoint(p1);
390     }
391 
392     return(TRUE);
393 }  /* end DBBounded */
394 
395 
396 /*
397  * This routine creates a copy of the the element transformed by
398  * the transformation matrix and adds the new copy to the database.
399  */
400 ELT *
401 DBCopy(elt, transform, db)
402 register ELT *elt;
403 ELT *(*db);
404 float transform[3][2];
405 {
406     register POINT *pt;
407     POINT *newlist;
408     char *newtext;
409 
410     newlist = PTInit();
411     pt = elt->ptlist;
412 
413     while (!Nullpoint(pt)) { /* matrix multiply */
414 	(void) PTMakePoint((((pt->x) * transform[0][0]) +
415 			    ((pt->y) * transform[1][0]) +
416 				       transform[2][0]),
417 			   (((pt->x) * transform[0][1]) +
418 			    ((pt->y) * transform[1][1]) +
419 				       transform[2][1]), &newlist);
420 	pt = pt->nextpt;
421     }
422 
423     newtext = malloc((unsigned) strlen(elt->textpt) + 1);
424     (void) strcpy(newtext, elt->textpt);
425     return( DBCreateElt(elt->type, newlist, elt->brushf,
426 			elt->size, newtext, db) );
427 }  /* end DBCopy */
428 
429 
430 /*
431  * This routine transforms the element by multiplying the
432  * coordinates of each of the points in the element by the
433  * transformation matrix.
434  */
435 DBXform(elt, transform, db)
436 register ELT *elt;
437 float transform[3][2];
438 ELT *(*db);
439 {
440     register POINT *pt;
441     float px, py;
442 
443     UNRembMod(elt, db);
444     pt = elt->ptlist;
445 
446     while (!Nullpoint(pt)) {
447 	px =  ((pt->x) * transform[0][0]) +
448 	      ((pt->y) * transform[1][0]) + transform[2][0];
449 	py =  ((pt->x) * transform[0][1]) +
450 	      ((pt->y) * transform[1][1]) + transform[2][1];
451 	pt->x = px;
452 	pt->y = py;
453 	pt = pt->nextpt;
454     }
455 }  /* end DBXform */
456 
457 
458 /*
459  * This routine changes the brush attribute of the element.
460  */
461 DBChangeBrush(elt, brush, db)
462 ELT *elt, *(*db);
463 int brush;
464 {
465     UNRembMod(elt, db);
466     elt->brushf = brush;
467 }  /* end DBChangeBrush */
468 
469 
470 /*
471  * This routine changes the justify attribute of the element.
472  */
473 DBChangeJustify(elt, justmode, db)
474 ELT *elt, *(*db);
475 int justmode;
476 {
477     register length;
478     register POINT *pos, *point;
479 
480     UNRembMod(elt, db);
481     elt->type = justmode;
482     PTModifyTextPoints(elt);
483 }  /* end DBChangeJustify */
484 
485 
486 /*
487  * This routine changes the font attribute of the given element.
488  */
489 DBChangeFont(elt, font, db)
490 ELT *elt, *(*db);
491 int font;
492 {
493     UNRembMod(elt, db);
494     elt->brushf = font;
495     PTModifyTextPoints(elt);
496 }  /* end DBChangeFont */
497 
498 
499 /*
500  * This routine changes the size attribute of the given element.
501  */
502 DBChangeSize(elt, size, db)
503 ELT *elt, *(*db);
504 int size;
505 {
506     UNRembMod(elt, db);
507     elt->size = size;
508     PTModifyTextPoints(elt);
509 }  /* end DBChangeSize */
510 
511 
512 /*
513  * This routine changes the stipple attribute of the given element.
514  */
515 DBChangeStipple(elt, stipple, db)
516 ELT *elt, *(*db);
517 int stipple;
518 {
519     UNRembMod(elt, db);
520     elt->size = stipple;
521 }  /* end DBChangeStipple */
522 
523 
524 /*
525  * This routine changes the text attribute of the given element.
526  */
527 DBChangeText(elt, text, db)
528 ELT *elt, *(*db);
529 char *text;
530 {
531     char *new;
532 
533     UNRembMod(elt, db);
534     free(elt->textpt);
535     new = malloc((unsigned) strlen(text) + 1);
536     (void) strcpy(new, text);
537     elt->textpt = new;
538     PTModifyTextPoints(elt);
539 }  /* end DBChangeText */
540 
541 
542 /*
543  * This routine changes the type attribute of the given element.
544  */
545 DBChangeType(elt, newtype, db)
546 ELT *elt, *(*db);
547 int newtype;
548 {
549     UNRembMod(elt, db);
550     elt->type = newtype;
551 }  /* end DBChangeType */
552 
553 
554 /*
555  * This routine changes the type and stipple attributes of the given element.
556  */
557 DBChangeTypeStipple(elt, newtype, newstipple, db)
558 ELT *elt, *(*db);
559 int newtype, newstipple;
560 {
561     UNRembMod(elt, db);
562     elt->type = newtype;
563     elt->size = newstipple;
564 }  /* end DBChangeType */
565 
566 
567 /*
568  * This routine changes the type, brush and stipple attributes
569  * of the given element.
570  */
571 DBChangeTypeBrushStipple(elt, newtype, newbrush, newstipple, db)
572 ELT *elt, *(*db);
573 int newtype, newbrush, newstipple;
574 {
575     UNRembMod(elt, db);
576     elt->type = newtype;
577     elt->brushf = newbrush;
578     elt->size = newstipple;
579 }  /* end DBChangeType */
580 
581 
582 /*
583  * This routine adds the element to the current set database.
584  */
585 DBAddSet(elt)
586 register ELT *elt;
587 {
588     register ELT *elist;
589 
590     elist = cset;
591 
592     while (!DBNullelt(elist)) { /* makes sure element not already in list */
593 	if (elist == elt)
594 	    return;
595 	elist = DBNextofSet(elist);
596     }
597 
598     elt->setnext = cset;
599     cset = elt;
600 }  /* end DBAddSet */
601 
602 
603 /*
604  *  Return TRUE if element in current set, else FALSE.
605  */
606 DBInCset(elt)
607 register ELT *elt;
608 {
609     register ELT *elist;
610 
611     elist = cset;
612 
613     while (!DBNullelt(elist)) { /* makes sure element not already in list */
614 	if (elist == elt)
615 	    return(TRUE);
616 	elist = DBNextofSet(elist);
617     }
618     return(FALSE);
619 }  /* end DBInCset */
620 
621 
622 /*
623  * This routine clears the current set by setting the pointer
624  * to a null element.
625  */
626 DBClearSet()
627 {
628     while (!DBNullelt(cset))
629 	cset = DBNextofSet(cset);
630 }  /* end DBClearSet */
631