magic/dbwind/DBWfdback.c

649 lines
19 KiB
C
Raw Normal View History

/* DBWfeedback.c -
*
* This file provides a standard set of procedures for Magic
* commands to use to provide feedback to users. Feedback
* consists of areas of the screen that are highlighted, along
* with text describing why those particular areas are important.
* Feedback is used for things like displaying CIF, and for errors
* in CIF-generation and routing.
*
* *********************************************************************
* * Copyright (C) 1985, 1990 Regents of the University of California. *
* * Permission to use, copy, modify, and distribute this *
* * software and its documentation for any purpose and without *
* * fee is hereby granted, provided that the above copyright *
* * notice appear in all copies. The University of California *
* * makes no representations about the suitability of this *
* * software for any purpose. It is provided "as is" without *
* * express or implied warranty. Export of this software outside *
* * of the United States of America may require an export license. *
* *********************************************************************
*/
#ifndef lint
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 $";
#endif /* not lint */
#include <stdio.h>
#include <string.h>
#include "utils/magic.h"
#include "utils/geometry.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "database/database.h"
#include "windows/windows.h"
#include "graphics/graphics.h"
#include "dbwind/dbwind.h"
#include "utils/utils.h"
#include "utils/styles.h"
#include "utils/malloc.h"
#include "utils/signals.h"
/* C99 compat */
#include "textio/textio.h"
/* Use a reference-counted character structure for feedback info */
typedef struct rcstring
{
int refcount;
char *string;
} RCString;
/* Each feedback area is stored in a record that looks like this: */
typedef struct feedback
{
Rect fb_area; /* The area to be highlighted, in coords of
* fb_rootDef, but prior to scaling by
* fb_scale.
*/
Rect fb_rootArea; /* The area of the feedback, in Magic coords.
* of fb_rootDef, scaled up to the next
* integral Magic unit.
*/
RCString *fb_text; /* Text explanation for the feedback. */
CellDef *fb_rootDef; /* Root definition of windows in which to
* display this feedback.
*/
int fb_scale; /* Scale factor to use in redisplaying
* (fb_scale units in fb_area correspond
* to one unit in fb_rootDef).
*/
int fb_style; /* Display style to use for this feedback. */
} Feedback;
/* The following stuff describes all the feedback information we know
* about. The feedback is stored in a big array that grows whenever
* necessary.
*/
static Feedback *dbwfbArray = NULL; /* Array holding all feedback info. */
global int DBWFeedbackCount = 0; /* Number of active entries in
* dbwfbArray.
*/
static int dbwfbSize = 0; /* Size of dbwfbArray, in entries. */
static int dbwfbNextToShow = 0; /* Index of first feedback area that
* hasn't been displayed yet. Used by
* DBWFBShow.
*/
static CellDef *dbwfbRootDef; /* To pass root cell definition from
* dbwfbGetTransform back up to
* DBWFeedbackAdd.
*/
/*
* ----------------------------------------------------------------------------
*
* DBWFeedbackRedraw --
*
* This procedure is called by the highlight manager to redisplay
* feedback highlights. The window is locked before entry.
*
* Results:
* None.
*
* Side effects:
* Any feedback information that overlaps a non-space tile in
* plane is redrawn.
*
* Tricky stuff:
* Redisplay is numerically difficult, particularly when feedbacks
* have a large internal scale factor: the tendency is to get
* integer overflow and get everything goofed up. Be careful
* when making changes to the code below.
*
* ----------------------------------------------------------------------------
*/
void
DBWFeedbackRedraw(window, plane)
MagWindow *window; /* Window in which to redraw. */
Plane *plane; /* Non-space tiles on this plane mark what
* needs to be redrawn.
*/
{
dlong x, y;
int i, halfScale, curStyle, newStyle, curScale;
CellDef *windowRoot;
Rect worldArea, screenArea, tmp;
Feedback *fb;
extern int dbwFeedbackAlways1(); /* Forward reference. */
if (DBWFeedbackCount == 0) return;
windowRoot = ((CellUse *) (window->w_surfaceID))->cu_def;
curStyle = -1;
curScale = -1;
for (i = 0, fb = dbwfbArray; i < DBWFeedbackCount; i++, fb++)
{
/* We expect that most of the feedbacks will have the same
* style and scale, so compute information with the current
* values, and recompute only when the values change.
*/
if (fb->fb_scale != curScale)
{
curScale = fb->fb_scale;
halfScale = curScale / 2;
worldArea.r_xbot = window->w_surfaceArea.r_xbot * curScale;
worldArea.r_xtop = window->w_surfaceArea.r_xtop * curScale;
worldArea.r_ybot = window->w_surfaceArea.r_ybot * curScale;
worldArea.r_ytop = window->w_surfaceArea.r_ytop * curScale;
}
/*
* Check to make sure this feedback area is relevant.
* Clip it to TiPlaneRect as a sanity check against
* callers who provide a feedback area that extends
* out somewhere in never-never land.
*/
if (fb->fb_rootDef != windowRoot) continue;
tmp = fb->fb_rootArea;
GeoClip(&tmp, &TiPlaneRect);
if (!DBSrPaintArea((Tile *) NULL, plane, &tmp,
&DBAllButSpaceBits, dbwFeedbackAlways1, (ClientData) NULL))
continue;
/* Transform the feedback area to screen coords. This is
* very similar to the code in WindSurfaceToScreen, except
* that there's additional scaling involved.
*/
tmp = fb->fb_area;
/* Don't clip non-Manhattan tiles, or else the diagonals are */
/* clipped to an incorrect position. */
if (!(fb->fb_style & TT_DIAGONAL))
GeoClip(&tmp, &worldArea);
/*
* Ignore areas which have been clipped out of existence
*/
if (tmp.r_xtop < tmp.r_xbot || tmp.r_ytop < tmp.r_ybot)
continue;
x = ((dlong)(tmp.r_xbot - worldArea.r_xbot) * window->w_scale) + halfScale;
screenArea.r_xbot = (int)((x/curScale + window->w_origin.p_x) >> SUBPIXELBITS);
x = ((dlong)(tmp.r_xtop - worldArea.r_xbot) * window->w_scale) + halfScale;
screenArea.r_xtop = (int)((x/curScale + window->w_origin.p_x) >> SUBPIXELBITS);
y = ((dlong)(tmp.r_ybot - worldArea.r_ybot) * window->w_scale) + halfScale;
screenArea.r_ybot = (int)((y/curScale + window->w_origin.p_y) >> SUBPIXELBITS);
y = ((dlong)(tmp.r_ytop - worldArea.r_ybot) * window->w_scale) + halfScale;
screenArea.r_ytop = (int)((y/curScale + window->w_origin.p_y) >> SUBPIXELBITS);
/*
* Sanity check on integer bounds
*/
if (screenArea.r_xtop < screenArea.r_xbot ||
screenArea.r_ytop < screenArea.r_ybot)
{
TxError("Internal error: Feedback area exceeds integer bounds "
"on screen rectangle!\n");
continue;
}
newStyle = fb->fb_style & (TT_LEFTMASK | TT_RIGHTMASK);
/* Another little trick: when the feedback area is very small ("very
* small" is a hand-tuned constant), change all stippled styles to
* solid.
* (The usefulness of this trick is questionable, as it generally
* creates very bothersome output for the "cif see" function and
* other places where the feedback is generated from tiles.)
*/
/*
if (((GEO_WIDTH(&screenArea) < 18) || (GEO_HEIGHT(&screenArea) < 18))
&& ((newStyle == STYLE_MEDIUMHIGHLIGHTS)
|| (newStyle == STYLE_PALEHIGHLIGHTS)))
newStyle = STYLE_SOLIDHIGHLIGHTS;
*/
if (newStyle != curStyle)
{
curStyle = newStyle;
GrSetStuff(curStyle);
}
if (fb->fb_style & TT_DIAGONAL)
GrDiagonal(&screenArea, fb->fb_style);
/* Special case (i.e., "hack")---to get a diagonal line */
/* instead of a triangle, set TT_SIDE but not */
/* TT_DIAGONAL. TT_DIRECTION still indicates / or \ */
else if (fb->fb_style & TT_SIDE)
{
if (fb->fb_style & TT_DIRECTION)
GrClipLine(screenArea.r_xbot, screenArea.r_ytop,
screenArea.r_xtop, screenArea.r_ybot);
else
GrClipLine(screenArea.r_xbot, screenArea.r_ybot,
screenArea.r_xtop, screenArea.r_ytop);
}
else
GrFastBox(&screenArea);
}
}
int
dbwFeedbackAlways1()
{
return 1;
}
/*
* ----------------------------------------------------------------------------
*
* DBWFeedbackClear --
*
* This procedure clears all existing feedback information from
* the screen.
*
* Results:
* None.
*
* Side effects:
* Any existing feedback information is cleared from the screen
* and from our database.
*
* ----------------------------------------------------------------------------
*/
void
DBWFeedbackClear(text)
char *text;
{
int i, oldCount;
Feedback *fb, *fl, *fe;
Rect area;
CellDef *currentRoot;
RCString *quickptr = NULL;
/* Clear out the feedback array and recycle string storage. Whenever
* the root cell changes, make a call to erase from the screen.
*/
currentRoot = (CellDef *) NULL;
oldCount = DBWFeedbackCount;
DBWFeedbackCount = 0;
for (fb = dbwfbArray; fb < dbwfbArray + oldCount; fb++)
{
if ((text == NULL) ||
((quickptr != NULL) && (fb->fb_text == quickptr)) ||
(strstr(fb->fb_text->string, text) != NULL))
{
// Track the last refcount string that matched the
// text so we can run through lists of matching text
// without having to call strstr()
if (text != NULL) quickptr = fb->fb_text;
if (currentRoot != fb->fb_rootDef)
{
if (currentRoot != (CellDef *) NULL)
DBWHLRedraw(currentRoot, &area, TRUE);
area = GeoNullRect;
}
(void) GeoInclude(&fb->fb_rootArea, &area);
currentRoot = fb->fb_rootDef;
// Decrement refcount and free if zero.
fb->fb_text->refcount--;
if (fb->fb_text->refcount == 0)
{
freeMagic(fb->fb_text->string);
freeMagic(fb->fb_text);
}
fb->fb_text = NULL;
}
}
if (currentRoot != NULL)
DBWHLRedraw(currentRoot, &area, TRUE);
dbwfbNextToShow = 0;
if (text != NULL)
{
/* Close up feedback list around removed text entries */
fl = dbwfbArray;
fe = fl + oldCount;
for (fb = dbwfbArray; fb < fe; fb++)
{
while ((fb->fb_text == NULL) && (fb < fe)) fb++;
if (fb < fe) *fl++ = *fb;
}
DBWFeedbackCount = (int)(fl - dbwfbArray);
for (fb = fl; fb < dbwfbArray + oldCount; fb++)
fb->fb_text = NULL;
}
/* Free up feedback memory whenever we have removed all feedback entries */
if (DBWFeedbackCount == 0)
{
if (dbwfbArray != NULL) {
freeMagic((char *) dbwfbArray);
dbwfbArray = NULL;
}
dbwfbSize = 0;
return;
}
}
/*
* ----------------------------------------------------------------------------
*
* DBWFeedbackAdd --
*
* Adds a new piece of feedback information to the list we have
* already.
*
* Results:
* None.
*
* Side effects:
* CellDef's ancestors are searched until its first root definition
* is found, and the coordinates of area are transformed into the
* root. Then the feedback area is added to our current list, using
* the style and scalefactor given. This stuff will be displayed on
* the screen at the end of the current command.
* ----------------------------------------------------------------------------
*/
void
DBWFeedbackAdd(area, text, cellDef, scaleFactor, style)
Rect *area; /* The area to be highlighted. */
char *text; /* Text associated with the area. */
CellDef *cellDef; /* The cellDef in whose coordinates area
* is given.
*/
int scaleFactor; /* The coordinates provided for feedback
* areas are divided by this to produce
* coordinates in Magic database units.
* This will probably be 1 most of the time.
* By making it bigger, say 10, and scaling
* other coordinates appropriately, it's
* possible to draw narrow lines on the
* screen, or to handle CIF, which isn't in
* exactly the same coordinates as other Magic
* stuff.
*/
int style; /* A display style to use for the feedback.
* Use one of:
* STYLE_OUTLINEHIGHLIGHTS: solid outlines
* STYLE_DOTTEDHIGHLIGHTS: dotted outlines
* STYLE_SOLIDHIGHLIGHTS: solid fill
* STYLE_MEDIUMHIGHLIGHTS: medium stipple
* STYLE_PALEHIGHLIGHTS: pald stipple
* At very coarse viewing scales, the last
* two styles are hard to see, so they are
* turned into STYLE_SOLIDHIGHLIGHTS.
*/
{
Rect tmp, tmp2, tmp3;
Transform transform;
int i;
Feedback *fb, *fblast;
extern int dbwfbGetTransform(); /* Forward declaration. */
/* Find a transform from this cell to the root, and use it to
* transform the area. If the root isn't an ancestor, just
* return.
*/
if (!DBSrRoots(cellDef, &GeoIdentityTransform,
dbwfbGetTransform, (ClientData) &transform)) return;
/* SigInterruptPending screws up DBSrRoots */
if (SigInterruptPending)
return;
/* Don't get fooled like I did. The translations for
* this transform are in Magic coordinates, not feedback
* coordinates. Scale them into feedback coordinates.
*/
transform.t_c *= scaleFactor;
transform.t_f *= scaleFactor;
GeoTransRect(&transform, area, &tmp2);
area = &tmp2;
/* Make sure there's enough space in the current array. If
* not, make a new array, copy the old to the new, then delete
* the old array. Use memcpy() to make sure this happens very fast.
*/
if (DBWFeedbackCount == dbwfbSize)
{
Feedback *new;
if (dbwfbSize == 0) dbwfbSize = 32;
else dbwfbSize <<= 1;
new = (Feedback *) mallocMagic(dbwfbSize * sizeof(Feedback));
memcpy((char *)new, (char *)dbwfbArray, DBWFeedbackCount * sizeof(Feedback));
for (i = DBWFeedbackCount; i < dbwfbSize; i++) new[i].fb_text = NULL;
if (dbwfbArray != NULL) freeMagic((char *) dbwfbArray);
dbwfbArray = new;
}
fb = &dbwfbArray[DBWFeedbackCount];
fb->fb_area = *area;
fblast = (DBWFeedbackCount == 0) ? NULL : &dbwfbArray[DBWFeedbackCount - 1];
if (fblast && (!strcmp(fblast->fb_text->string, text)))
{
fb->fb_text = fblast->fb_text;
fb->fb_text->refcount++;
}
else
{
fb->fb_text = (RCString *)mallocMagic(sizeof(RCString));
fb->fb_text->refcount = 1;
fb->fb_text->string = StrDup(NULL, text);
}
fb->fb_rootDef = dbwfbRootDef;
fb->fb_scale = scaleFactor;
fb->fb_style = style;
DBWFeedbackCount++;
/* Round the area up into Magic coords, and save it too. */
if (area->r_xtop > 0)
tmp.r_xtop = (area->r_xtop + scaleFactor - 1)/scaleFactor;
else tmp.r_xtop = area->r_xtop/scaleFactor;
if (area->r_ytop > 0)
tmp.r_ytop = (area->r_ytop + scaleFactor - 1)/scaleFactor;
else tmp.r_ytop = area->r_ytop/scaleFactor;
if (area->r_xbot > 0) tmp.r_xbot = area->r_xbot/scaleFactor;
else tmp.r_xbot = (area->r_xbot - scaleFactor + 1)/scaleFactor;
if (area->r_ybot > 0) tmp.r_ybot = area->r_ybot/scaleFactor;
else tmp.r_ybot = (area->r_ybot - scaleFactor + 1)/scaleFactor;
/* Clip to ensure well within TiPlaneRect */
tmp3.r_xbot = TiPlaneRect.r_xbot + 10;
tmp3.r_ybot = TiPlaneRect.r_ybot + 10;
tmp3.r_xtop = TiPlaneRect.r_xtop - 10;
tmp3.r_ytop = TiPlaneRect.r_ytop - 10;
GeoClip(&tmp, &tmp3);
fb->fb_rootArea = tmp;
}
/* This utility procedure is invoked by DBSrRoots. Save the root definition
* in dbwfbRootDef, save the transform in the argument, and abort the search.
* Make sure that the root we pick is actually displayed in a window
* someplace (there could be root cells that are no longer displayed
* anywhere).
*/
int
dbwfbGetTransform(use, transform, cdarg)
CellUse *use; /* A root use that is an ancestor
* of cellDef in DBWFeedbackAdd.
*/
Transform *transform; /* Transform up from cellDef to use. */
Transform *cdarg; /* Place to store transform from
* cellDef to its root def.
*/
{
extern int dbwfbWindFunc();
if (use->cu_def->cd_flags & CDINTERNAL) return 0;
if (!WindSearch((ClientData) DBWclientID, (ClientData) use,
(Rect *) NULL, dbwfbWindFunc, (ClientData) NULL)) return 0;
if (SigInterruptPending)
return 0;
dbwfbRootDef = use->cu_def;
*cdarg = *transform;
return 1;
}
/* This procedure is called if a window is found for the cell in
* dbwfbGetTransform above. It returns 1 to abort the search and
* notify dbwfbGetTransform that there was a window for that root
* cell.
*/
int
dbwfbWindFunc()
{
return 1;
}
/*
* ----------------------------------------------------------------------------
*
* Client initialization
*
* ----------------------------------------------------------------------------
*/
void
dbwFeedbackInit()
{
DBWHLAddClient(DBWFeedbackRedraw);
}
/*
* ----------------------------------------------------------------------------
*
* DBWFeedbackShow --
*
* Causes new feedback information actually to be displayed on
* the screen.
*
* Results:
* None.
*
* Side effects:
* All new feedback information that has been created since the
* last call to this procedure is added to the display.
*
* ----------------------------------------------------------------------------
*/
void
DBWFeedbackShow()
{
Rect area;
CellDef *currentRoot;
Feedback *fb;
int i;
/* Scan through all of the feedback areas starting with dbwfbNextToShow.
* Save up the total bounding box until the root definition changes,
* then redisplay what's been saved up so far.
*/
currentRoot = NULL;
for (i = dbwfbNextToShow, fb = &(dbwfbArray[dbwfbNextToShow]);
i < DBWFeedbackCount; i++, fb++)
{
if (currentRoot != fb->fb_rootDef)
{
if (currentRoot != NULL)
DBWHLRedraw(currentRoot, &area, FALSE);
area = GeoNullRect;
}
(void) GeoInclude(&fb->fb_rootArea, &area);
currentRoot = fb->fb_rootDef;
}
if (currentRoot != NULL)
DBWHLRedraw(currentRoot, &area, FALSE);
dbwfbNextToShow = DBWFeedbackCount;
}
/*
* ----------------------------------------------------------------------------
*
* DBWFeedbackNth --
*
* Provides the area and text associated with a particular
* feedback area.
*
* Results:
* Returns a pointer to the text associated with the Nth feedback
* entry.
*
* Side effects:
* The parameter "area" is filled with the area of the nth
* feedback, and the text of that feedback is returned. *pRootDef
* is filled in with rootDef for window of feedback area. *pStyle
* is filled in with the display style for the feedback area. If
* the particular area doesn't exist (nth >= DBWFeedbackCount),
* area and *pRootDef and *pStyle are untouched and NULL is
* returned. NULL may also be returned if there simply wasn't
* any text associated with the selected feedback.
*
* ----------------------------------------------------------------------------
*/
char *
DBWFeedbackNth(nth, area, pRootDef, pStyle)
int nth; /* Selects which feedback area to return
* stuff from. (0 <= nth < DBWFeedbackCount)
*/
Rect *area; /* To be filled in with area of feedback, in
* rounded-outward Magic coordinates.
*/
CellDef **pRootDef; /* *pRootDef gets filled in with root def for
* this feedback area. If pRootDef is NULL,
* nothing is touched.
*/
int *pStyle; /* *pStyle gets filled in with the display
* style for this feedback area. If NULL,
* nothing is touched.
*/
{
if (nth >= DBWFeedbackCount) return NULL;
*area = dbwfbArray[nth].fb_rootArea;
if (pRootDef != NULL) *pRootDef = dbwfbArray[nth].fb_rootDef;
if (pStyle != NULL) *pStyle = dbwfbArray[nth].fb_style;
return dbwfbArray[nth].fb_text->string;
}