magic/dbwind/DBWfdback.c

655 lines
20 KiB
C
Raw Permalink 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(
Tile *tile, /* (unused) */
TileType dinfo, /* (unused) */
ClientData clientdata) /* (unused) */
{
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.
*/
/*ARGSUSED*/
int
Add ClientData parameter to indirect-call callbacks for WASM WASM call_indirect enforces an exact type match between the caller and the callee. Many Magic callbacks had K&R-style () forward declarations and a single-argument definition, but were passed to iterators that always push a trailing ClientData argument. Native builds tolerated the mismatch via loose prototypes; WASM traps with "indirect call signature mismatch". Added the missing ClientData (or, where the concrete type is known, FindRegion *) parameter to: * calma/CalmaRead.c, calma/CalmaWrite.c, calma/CalmaWriteZ.c — calmaWriteInitFunc * cif/CIFwrite.c — cifWriteInitFunc * commands/CmdSubrs.c — cmdWindSet * database/DBtimestmp.c — dbStampFunc * dbwind/DBWelement.c — dbwElementAlways1 * dbwind/DBWfdback.c — dbwfbWindFunc * dbwind/DBWhlights.c — DBWHLRedrawWind * ext2spice/ext2hier.c — spcnodeHierVisit * extract/ExtBasic.c — extSDTileFunc, extTransPerimFunc, extAnnularTileFunc, extResistorTileFunc * extract/ExtMain.c — extDefInitFunc * extract/ExtTimes.c — extTimesInitFunc Also adjusted commands/CmdE.c and commands/CmdTZ.c: SelectExpand was being called with four arguments (the legacy surroundFlag), but its real signature has been three arguments for years (the surround mode is encoded in the expandType bit). The fourth argument was redundant (DB_EXPAND_SURROUND in arg 2 is the source of truth) and rejected by WASM. Native behavior is unchanged. The added parameters are unused in the function bodies; they exist only to satisfy the indirect-call signature.
2026-05-04 13:29:11 +02:00
dbwfbWindFunc(w, clientData)
MagWindow *w; /* UNUSED */
ClientData clientData; /* UNUSED */
{
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;
}