1 /* DBWfeedback.c -
2 *
3 * This file provides a standard set of procedures for Magic
4 * commands to use to provide feedback to users. Feedback
5 * consists of areas of the screen that are highlighted, along
6 * with text describing why those particular areas are important.
7 * Feedback is used for things like displaying CIF, and for errors
8 * in CIF-generation and routing.
9 *
10 * *********************************************************************
11 * * Copyright (C) 1985, 1990 Regents of the University of California. *
12 * * Permission to use, copy, modify, and distribute this *
13 * * software and its documentation for any purpose and without *
14 * * fee is hereby granted, provided that the above copyright *
15 * * notice appear in all copies. The University of California *
16 * * makes no representations about the suitability of this *
17 * * software for any purpose. It is provided "as is" without *
18 * * express or implied warranty. Export of this software outside *
19 * * of the United States of America may require an export license. *
20 * *********************************************************************
21 */
22
23 #ifndef lint
24 static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/dbwind/DBWfdback.c,v 1.2 2010/06/24 12:37:16 tim Exp $";
25 #endif /* not lint */
26
27 #include <stdio.h>
28 #include <string.h>
29
30 #include "utils/magic.h"
31 #include "utils/geometry.h"
32 #include "tiles/tile.h"
33 #include "utils/hash.h"
34 #include "database/database.h"
35 #include "windows/windows.h"
36 #include "graphics/graphics.h"
37 #include "dbwind/dbwind.h"
38 #include "utils/utils.h"
39 #include "utils/styles.h"
40 #include "utils/malloc.h"
41 #include "utils/signals.h"
42
43 /* Use a reference-counted character structure for feedback info */
44
45 typedef struct rcstring
46 {
47 int refcount;
48 char *string;
49 } RCString;
50
51
52 /* Each feedback area is stored in a record that looks like this: */
53
54 typedef struct feedback
55 {
56 Rect fb_area; /* The area to be highlighted, in coords of
57 * fb_rootDef, but prior to scaling by
58 * fb_scale.
59 */
60 Rect fb_rootArea; /* The area of the feedback, in Magic coords.
61 * of fb_rootDef, scaled up to the next
62 * integral Magic unit.
63 */
64 RCString *fb_text; /* Text explanation for the feedback. */
65 CellDef *fb_rootDef; /* Root definition of windows in which to
66 * display this feedback.
67 */
68 int fb_scale; /* Scale factor to use in redisplaying
69 * (fb_scale units in fb_area correspond
70 * to one unit in fb_rootDef).
71 */
72 int fb_style; /* Display style to use for this feedback. */
73 } Feedback;
74
75 /* The following stuff describes all the feedback information we know
76 * about. The feedback is stored in a big array that grows whenever
77 * necessary.
78 */
79
80 static Feedback *dbwfbArray = NULL; /* Array holding all feedback info. */
81 global int DBWFeedbackCount = 0; /* Number of active entries in
82 * dbwfbArray.
83 */
84 static int dbwfbSize = 0; /* Size of dbwfbArray, in entries. */
85 static int dbwfbNextToShow = 0; /* Index of first feedback area that
86 * hasn't been displayed yet. Used by
87 * DBWFBShow.
88 */
89 static CellDef *dbwfbRootDef; /* To pass root cell definition from
90 * dbwfbGetTransform back up to
91 * DBWFeedbackAdd.
92 */
93
94
95 /*
96 * ----------------------------------------------------------------------------
97 *
98 * DBWFeedbackRedraw --
99 *
100 * This procedure is called by the highlight manager to redisplay
101 * feedback highlights. The window is locked before entry.
102 *
103 * Results:
104 * None.
105 *
106 * Side effects:
107 * Any feedback information that overlaps a non-space tile in
108 * plane is redrawn.
109 *
110 * Tricky stuff:
111 * Redisplay is numerically difficult, particularly when feedbacks
112 * have a large internal scale factor: the tendency is to get
113 * integer overflow and get everything goofed up. Be careful
114 * when making changes to the code below.
115 *
116 * ----------------------------------------------------------------------------
117 */
118
119 void
DBWFeedbackRedraw(window,plane)120 DBWFeedbackRedraw(window, plane)
121 MagWindow *window; /* Window in which to redraw. */
122 Plane *plane; /* Non-space tiles on this plane mark what
123 * needs to be redrawn.
124 */
125 {
126 dlong x, y;
127 int i, halfScale, curStyle, newStyle, curScale;
128 CellDef *windowRoot;
129 Rect worldArea, screenArea, tmp;
130 Feedback *fb;
131 extern int dbwFeedbackAlways1(); /* Forward reference. */
132
133 if (DBWFeedbackCount == 0) return;
134
135 windowRoot = ((CellUse *) (window->w_surfaceID))->cu_def;
136 curStyle = -1;
137 curScale = -1;
138 for (i = 0, fb = dbwfbArray; i < DBWFeedbackCount; i++, fb++)
139 {
140 /* We expect that most of the feedbacks will have the same
141 * style and scale, so compute information with the current
142 * values, and recompute only when the values change.
143 */
144 if (fb->fb_scale != curScale)
145 {
146 curScale = fb->fb_scale;
147 halfScale = curScale / 2;
148 worldArea.r_xbot = window->w_surfaceArea.r_xbot * curScale;
149 worldArea.r_xtop = window->w_surfaceArea.r_xtop * curScale;
150 worldArea.r_ybot = window->w_surfaceArea.r_ybot * curScale;
151 worldArea.r_ytop = window->w_surfaceArea.r_ytop * curScale;
152 }
153
154 /*
155 * Check to make sure this feedback area is relevant.
156 * Clip it to TiPlaneRect as a sanity check against
157 * callers who provide a feedback area that extends
158 * out somewhere in never-never land.
159 */
160 if (fb->fb_rootDef != windowRoot) continue;
161 tmp = fb->fb_rootArea;
162 GeoClip(&tmp, &TiPlaneRect);
163 if (!DBSrPaintArea((Tile *) NULL, plane, &tmp,
164 &DBAllButSpaceBits, dbwFeedbackAlways1, (ClientData) NULL))
165 continue;
166
167 /* Transform the feedback area to screen coords. This is
168 * very similar to the code in WindSurfaceToScreen, except
169 * that there's additional scaling involved.
170 */
171
172 tmp = fb->fb_area;
173
174 /* Don't clip non-Manhattan tiles, or else the diagonals are */
175 /* clipped to an incorrect position. */
176
177 if (!(fb->fb_style & TT_DIAGONAL))
178 GeoClip(&tmp, &worldArea);
179
180 /*
181 * Ignore areas which have been clipped out of existence
182 */
183 if (tmp.r_xtop < tmp.r_xbot || tmp.r_ytop < tmp.r_ybot)
184 continue;
185
186 x = ((dlong)(tmp.r_xbot - worldArea.r_xbot) * window->w_scale) + halfScale;
187 screenArea.r_xbot = (int)((x/curScale + window->w_origin.p_x) >> SUBPIXELBITS);
188 x = ((dlong)(tmp.r_xtop - worldArea.r_xbot) * window->w_scale) + halfScale;
189 screenArea.r_xtop = (int)((x/curScale + window->w_origin.p_x) >> SUBPIXELBITS);
190 y = ((dlong)(tmp.r_ybot - worldArea.r_ybot) * window->w_scale) + halfScale;
191 screenArea.r_ybot = (int)((y/curScale + window->w_origin.p_y) >> SUBPIXELBITS);
192 y = ((dlong)(tmp.r_ytop - worldArea.r_ybot) * window->w_scale) + halfScale;
193 screenArea.r_ytop = (int)((y/curScale + window->w_origin.p_y) >> SUBPIXELBITS);
194
195 /*
196 * Sanity check on integer bounds
197 */
198 if (screenArea.r_xtop < screenArea.r_xbot ||
199 screenArea.r_ytop < screenArea.r_ybot)
200 {
201 TxError("Internal error: Feedback area exceeds integer bounds "
202 "on screen rectangle!\n");
203 continue;
204 }
205
206 newStyle = fb->fb_style & (TT_LEFTMASK | TT_RIGHTMASK);
207
208 /* Another little trick: when the feedback area is very small ("very
209 * small" is a hand-tuned constant), change all stippled styles to
210 * solid.
211 * (The usefulness of this trick is questionable, as it generally
212 * creates very bothersome output for the "cif see" function and
213 * other places where the feedback is generated from tiles.)
214 */
215 /*
216 if (((GEO_WIDTH(&screenArea) < 18) || (GEO_HEIGHT(&screenArea) < 18))
217 && ((newStyle == STYLE_MEDIUMHIGHLIGHTS)
218 || (newStyle == STYLE_PALEHIGHLIGHTS)))
219 newStyle = STYLE_SOLIDHIGHLIGHTS;
220 */
221 if (newStyle != curStyle)
222 {
223 curStyle = newStyle;
224 GrSetStuff(curStyle);
225 }
226
227 if (fb->fb_style & TT_DIAGONAL)
228 GrDiagonal(&screenArea, fb->fb_style);
229
230 /* Special case (i.e., "hack")---to get a diagonal line */
231 /* instead of a triangle, set TT_SIDE but not */
232 /* TT_DIAGONAL. TT_DIRECTION still indicates / or \ */
233
234 else if (fb->fb_style & TT_SIDE)
235 {
236 if (fb->fb_style & TT_DIRECTION)
237 GrClipLine(screenArea.r_xbot, screenArea.r_ytop,
238 screenArea.r_xtop, screenArea.r_ybot);
239 else
240 GrClipLine(screenArea.r_xbot, screenArea.r_ybot,
241 screenArea.r_xtop, screenArea.r_ytop);
242 }
243 else
244 GrFastBox(&screenArea);
245 }
246 }
247
248 int
dbwFeedbackAlways1()249 dbwFeedbackAlways1()
250 {
251 return 1;
252 }
253
254 /*
255 * ----------------------------------------------------------------------------
256 *
257 * DBWFeedbackClear --
258 *
259 * This procedure clears all existing feedback information from
260 * the screen.
261 *
262 * Results:
263 * None.
264 *
265 * Side effects:
266 * Any existing feedback information is cleared from the screen
267 * and from our database.
268 *
269 * ----------------------------------------------------------------------------
270 */
271
272 void
DBWFeedbackClear(text)273 DBWFeedbackClear(text)
274 char *text;
275 {
276 int i, oldCount;
277 Feedback *fb, *fl, *fe;
278 Rect area;
279 CellDef *currentRoot;
280 RCString *quickptr = NULL;
281
282 /* Clear out the feedback array and recycle string storage. Whenever
283 * the root cell changes, make a call to erase from the screen.
284 */
285
286 currentRoot = (CellDef *) NULL;
287 oldCount = DBWFeedbackCount;
288 DBWFeedbackCount = 0;
289 for (fb = dbwfbArray; fb < dbwfbArray + oldCount; fb++)
290 {
291 if ((text == NULL) ||
292 ((quickptr != NULL) && (fb->fb_text == quickptr)) ||
293 (strstr(fb->fb_text->string, text) != NULL))
294 {
295 // Track the last refcount string that matched the
296 // text so we can run through lists of matching text
297 // without having to call strstr()
298
299 if (text != NULL) quickptr = fb->fb_text;
300
301 if (currentRoot != fb->fb_rootDef)
302 {
303 if (currentRoot != (CellDef *) NULL)
304 DBWHLRedraw(currentRoot, &area, TRUE);
305 area = GeoNullRect;
306 }
307 (void) GeoInclude(&fb->fb_rootArea, &area);
308 currentRoot = fb->fb_rootDef;
309
310 // Decrement refcount and free if zero.
311 fb->fb_text->refcount--;
312 if (fb->fb_text->refcount == 0)
313 {
314 freeMagic(fb->fb_text->string);
315 freeMagic(fb->fb_text);
316 }
317 fb->fb_text = NULL;
318 }
319 }
320 if (currentRoot != NULL)
321 DBWHLRedraw(currentRoot, &area, TRUE);
322 dbwfbNextToShow = 0;
323
324 if (text != NULL)
325 {
326 /* Close up feedback list around removed text entries */
327
328 fl = dbwfbArray;
329 fe = fl + oldCount;
330 for (fb = dbwfbArray; fb < fe; fb++)
331 {
332 while ((fb->fb_text == NULL) && (fb < fe)) fb++;
333 if (fb < fe) *fl++ = *fb;
334 }
335 DBWFeedbackCount = (int)(fl - dbwfbArray);
336
337 for (fb = fl; fb < dbwfbArray + oldCount; fb++)
338 fb->fb_text = NULL;
339 }
340
341 /* Free up feedback memory whenever we have removed all feedback entries */
342 if (DBWFeedbackCount == 0)
343 {
344 if (dbwfbArray != NULL) {
345 freeMagic((char *) dbwfbArray);
346 dbwfbArray = NULL;
347 }
348 dbwfbSize = 0;
349 return;
350 }
351 }
352
353 /*
354 * ----------------------------------------------------------------------------
355 *
356 * DBWFeedbackAdd --
357 *
358 * Adds a new piece of feedback information to the list we have
359 * already.
360 *
361 * Results:
362 * None.
363 *
364 * Side effects:
365 * CellDef's ancestors are searched until its first root definition
366 * is found, and the coordinates of area are transformed into the
367 * root. Then the feedback area is added to our current list, using
368 * the style and scalefactor given. This stuff will be displayed on
369 * the screen at the end of the current command.
370 * ----------------------------------------------------------------------------
371 */
372
373 void
DBWFeedbackAdd(area,text,cellDef,scaleFactor,style)374 DBWFeedbackAdd(area, text, cellDef, scaleFactor, style)
375 Rect *area; /* The area to be highlighted. */
376 char *text; /* Text associated with the area. */
377 CellDef *cellDef; /* The cellDef in whose coordinates area
378 * is given.
379 */
380 int scaleFactor; /* The coordinates provided for feedback
381 * areas are divided by this to produce
382 * coordinates in Magic database units.
383 * This will probably be 1 most of the time.
384 * By making it bigger, say 10, and scaling
385 * other coordinates appropriately, it's
386 * possible to draw narrow lines on the
387 * screen, or to handle CIF, which isn't in
388 * exactly the same coordinates as other Magic
389 * stuff.
390 */
391 int style; /* A display style to use for the feedback.
392 * Use one of:
393 * STYLE_OUTLINEHIGHLIGHTS: solid outlines
394 * STYLE_DOTTEDHIGHLIGHTS: dotted outlines
395 * STYLE_SOLIDHIGHLIGHTS: solid fill
396 * STYLE_MEDIUMHIGHLIGHTS: medium stipple
397 * STYLE_PALEHIGHLIGHTS: pald stipple
398 * At very coarse viewing scales, the last
399 * two styles are hard to see, so they are
400 * turned into STYLE_SOLIDHIGHLIGHTS.
401 */
402 {
403 Rect tmp, tmp2, tmp3;
404 Transform transform;
405 int i;
406 Feedback *fb, *fblast;
407 extern int dbwfbGetTransform(); /* Forward declaration. */
408
409 /* Find a transform from this cell to the root, and use it to
410 * transform the area. If the root isn't an ancestor, just
411 * return.
412 */
413
414 if (!DBSrRoots(cellDef, &GeoIdentityTransform,
415 dbwfbGetTransform, (ClientData) &transform)) return;
416
417 /* SigInterruptPending screws up DBSrRoots */
418 if (SigInterruptPending)
419 return;
420
421 /* Don't get fooled like I did. The translations for
422 * this transform are in Magic coordinates, not feedback
423 * coordinates. Scale them into feedback coordinates.
424 */
425
426 transform.t_c *= scaleFactor;
427 transform.t_f *= scaleFactor;
428 GeoTransRect(&transform, area, &tmp2);
429 area = &tmp2;
430
431 /* Make sure there's enough space in the current array. If
432 * not, make a new array, copy the old to the new, then delete
433 * the old array. Use memcpy() to make sure this happens very fast.
434 */
435
436 if (DBWFeedbackCount == dbwfbSize)
437 {
438 Feedback *new;
439
440 if (dbwfbSize == 0) dbwfbSize = 32;
441 else dbwfbSize <<= 1;
442
443 new = (Feedback *) mallocMagic(dbwfbSize * sizeof(Feedback));
444 memcpy((char *)new, (char *)dbwfbArray, DBWFeedbackCount * sizeof(Feedback));
445
446 for (i = DBWFeedbackCount; i < dbwfbSize; i++) new[i].fb_text = NULL;
447
448 if (dbwfbArray != NULL) freeMagic((char *) dbwfbArray);
449 dbwfbArray = new;
450 }
451 fb = &dbwfbArray[DBWFeedbackCount];
452 fb->fb_area = *area;
453 fblast = (DBWFeedbackCount == 0) ? NULL : &dbwfbArray[DBWFeedbackCount - 1];
454
455 if (fblast && (!strcmp(fblast->fb_text->string, text)))
456 {
457 fb->fb_text = fblast->fb_text;
458 fb->fb_text->refcount++;
459 }
460 else
461 {
462 fb->fb_text = (RCString *)mallocMagic(sizeof(RCString));
463 fb->fb_text->refcount = 1;
464 fb->fb_text->string = StrDup(NULL, text);
465 }
466
467 fb->fb_rootDef = dbwfbRootDef;
468 fb->fb_scale = scaleFactor;
469 fb->fb_style = style;
470 DBWFeedbackCount++;
471
472 /* Round the area up into Magic coords, and save it too. */
473 if (area->r_xtop > 0)
474 tmp.r_xtop = (area->r_xtop + scaleFactor - 1)/scaleFactor;
475 else tmp.r_xtop = area->r_xtop/scaleFactor;
476 if (area->r_ytop > 0)
477 tmp.r_ytop = (area->r_ytop + scaleFactor - 1)/scaleFactor;
478 else tmp.r_ytop = area->r_ytop/scaleFactor;
479 if (area->r_xbot > 0) tmp.r_xbot = area->r_xbot/scaleFactor;
480 else tmp.r_xbot = (area->r_xbot - scaleFactor + 1)/scaleFactor;
481 if (area->r_ybot > 0) tmp.r_ybot = area->r_ybot/scaleFactor;
482 else tmp.r_ybot = (area->r_ybot - scaleFactor + 1)/scaleFactor;
483
484 /* Clip to ensure well within TiPlaneRect */
485 tmp3.r_xbot = TiPlaneRect.r_xbot + 10;
486 tmp3.r_ybot = TiPlaneRect.r_ybot + 10;
487 tmp3.r_xtop = TiPlaneRect.r_xtop - 10;
488 tmp3.r_ytop = TiPlaneRect.r_ytop - 10;
489 GeoClip(&tmp, &tmp3);
490
491 fb->fb_rootArea = tmp;
492 }
493
494 /* This utility procedure is invoked by DBSrRoots. Save the root definition
495 * in dbwfbRootDef, save the transform in the argument, and abort the search.
496 * Make sure that the root we pick is actually displayed in a window
497 * someplace (there could be root cells that are no longer displayed
498 * anywhere).
499 */
500
501 int
dbwfbGetTransform(use,transform,cdarg)502 dbwfbGetTransform(use, transform, cdarg)
503 CellUse *use; /* A root use that is an ancestor
504 * of cellDef in DBWFeedbackAdd.
505 */
506 Transform *transform; /* Transform up from cellDef to use. */
507 Transform *cdarg; /* Place to store transform from
508 * cellDef to its root def.
509 */
510 {
511 extern int dbwfbWindFunc();
512 if (use->cu_def->cd_flags & CDINTERNAL) return 0;
513 if (!WindSearch((ClientData) DBWclientID, (ClientData) use,
514 (Rect *) NULL, dbwfbWindFunc, (ClientData) NULL)) return 0;
515 if (SigInterruptPending)
516 return 0;
517 dbwfbRootDef = use->cu_def;
518 *cdarg = *transform;
519 return 1;
520 }
521
522 /* This procedure is called if a window is found for the cell in
523 * dbwfbGetTransform above. It returns 1 to abort the search and
524 * notify dbwfbGetTransform that there was a window for that root
525 * cell.
526 */
527
528 int
dbwfbWindFunc()529 dbwfbWindFunc()
530 {
531 return 1;
532 }
533
534 /*
535 * ----------------------------------------------------------------------------
536 *
537 * Client initialization
538 *
539 * ----------------------------------------------------------------------------
540 */
541
542 void
dbwFeedbackInit()543 dbwFeedbackInit()
544 {
545 DBWHLAddClient(DBWFeedbackRedraw);
546 }
547
548
549 /*
550 * ----------------------------------------------------------------------------
551 *
552 * DBWFeedbackShow --
553 *
554 * Causes new feedback information actually to be displayed on
555 * the screen.
556 *
557 * Results:
558 * None.
559 *
560 * Side effects:
561 * All new feedback information that has been created since the
562 * last call to this procedure is added to the display.
563 *
564 * ----------------------------------------------------------------------------
565 */
566
567 void
DBWFeedbackShow()568 DBWFeedbackShow()
569 {
570 Rect area;
571 CellDef *currentRoot;
572 Feedback *fb;
573 int i;
574
575 /* Scan through all of the feedback areas starting with dbwfbNextToShow.
576 * Save up the total bounding box until the root definition changes,
577 * then redisplay what's been saved up so far.
578 */
579
580 currentRoot = NULL;
581 for (i = dbwfbNextToShow, fb = &(dbwfbArray[dbwfbNextToShow]);
582 i < DBWFeedbackCount; i++, fb++)
583 {
584 if (currentRoot != fb->fb_rootDef)
585 {
586 if (currentRoot != NULL)
587 DBWHLRedraw(currentRoot, &area, FALSE);
588 area = GeoNullRect;
589 }
590 (void) GeoInclude(&fb->fb_rootArea, &area);
591 currentRoot = fb->fb_rootDef;
592 }
593 if (currentRoot != NULL)
594 DBWHLRedraw(currentRoot, &area, FALSE);
595 dbwfbNextToShow = DBWFeedbackCount;
596 }
597
598 /*
599 * ----------------------------------------------------------------------------
600 *
601 * DBWFeedbackNth --
602 *
603 * Provides the area and text associated with a particular
604 * feedback area.
605 *
606 * Results:
607 * Returns a pointer to the text associated with the Nth feedback
608 * entry.
609 *
610 * Side effects:
611 * The parameter "area" is filled with the area of the nth
612 * feedback, and the text of that feedback is returned. *pRootDef
613 * is filled in with rootDef for window of feedback area. *pStyle
614 * is filled in with the display style for the feedback area. If
615 * the particular area doesn't exist (nth >= DBWFeedbackCount),
616 * area and *pRootDef and *pStyle are untouched and NULL is
617 * returned. NULL may also be returned if there simply wasn't
618 * any text associated with the selected feedback.
619 *
620 * ----------------------------------------------------------------------------
621 */
622
623 char *
DBWFeedbackNth(nth,area,pRootDef,pStyle)624 DBWFeedbackNth(nth, area, pRootDef, pStyle)
625 int nth; /* Selects which feedback area to return
626 * stuff from. (0 <= nth < DBWFeedbackCount)
627 */
628 Rect *area; /* To be filled in with area of feedback, in
629 * rounded-outward Magic coordinates.
630 */
631 CellDef **pRootDef; /* *pRootDef gets filled in with root def for
632 * this feedback area. If pRootDef is NULL,
633 * nothing is touched.
634 */
635 int *pStyle; /* *pStyle gets filled in with the display
636 * style for this feedback area. If NULL,
637 * nothing is touched.
638 */
639 {
640 if (nth >= DBWFeedbackCount) return NULL;
641 *area = dbwfbArray[nth].fb_rootArea;
642 if (pRootDef != NULL) *pRootDef = dbwfbArray[nth].fb_rootDef;
643 if (pStyle != NULL) *pStyle = dbwfbArray[nth].fb_style;
644 return dbwfbArray[nth].fb_text->string;
645 }
646