Sindbad~EG File Manager
/* M. Beeson, for Mathpert */
/* select_term to select a term from the screen */
/*
1.28.95 original date
3.8.99 last modified
1.13.00 modified to prevent selecting the superscript in f'(x) ( a PR term)
8.28.04 added FUNCTOR(u) != '*' && at line 285
9.5.04 added include pstring
9.18.06 defined POINT, RECT and PtInRect to free this file of Windows dependence
Eliminated the first (hwnd) arg of select_term, it's not even used.
5.8.13 include <stddef.h>
removed static declarations of get_minusspace(), parenspace, get_parenwidth() , get_charspace(); they have externs in display1.h and are defined in display.c
removed redefinition of NULL
made cons() MEXPORT_SYMSOUT
1.14.24 added size arguments to get_parenspace etc.
*/
#include <assert.h>
#include <stddef.h>
#include "terms.h"
#include "display.h" /* needed by display1.h */
#include "display1.h" /* block, needed by lterm.h */
#include "bigrect.h"
#include "lterm.h"
#include "select.h"
#include "probtype.h"
#include "proverdl.h" /* history */
#include "defns.h" /* vaux.h needs it */
#include "vaux.h" /* get_currentline */
#include "pstring.h" /* paren */
static int subset(BIGRECT *, BIGRECT *, int);
static int list_subset(rectlist *q, rectlist *s, int tolerance);
static int list_disjoint(rectlist *q, rectlist *s);
static rectlist *rectlist_cons(BIGRECT *r, int level, rectlist *q);
static rectlist *rectlist_append(rectlist *a, rectlist *b);
#if 0
static coord get_minusspace() = 6; /* must duplicate the value in display.c */
static coord get_parenspace() = 6; /* space after open paren and before close paren */
static coord get_parenwidth() = 10; /* space occupied by one parenthesis */
static coord get_charspace() = 16; /* space occupied by one 'en' space */
#endif
/*________________________________________________________________*/
typedef struct { int x,y;} POINT;
typedef struct { int left, top, right, bottom;} RECT;
static int PtInRect(const RECT *r, POINT p)
{ if(
((r->left <= p.x && p.x < r->right) || (r->right <= p.x && p.x < r->left)) &&
((r->top <= p.y && p.y < r->bottom) || (r->bottom <= p.y && p.y <= r->top))
)
return 1;
return 0;
}
/*_________________________________________________________________*/
pathlist * cons(int x, unsigned short y, pathlist *tail)
/* make a new pathlist with first item x followed by tail */
/* this is also defined in nextline.c in the automode dll,
but rather than export it for use in this one file, we just make a static copy.
*/
{ pathlist *ans = mallocate(sizeof(pathlist));
if(ans == NULL)
nospace();
ans->data = x;
ans->functor = y;
ans->next = tail;
return ans;
}
/*_________________________________________________________________*/
int select_term(lterm *t, BIGRECT *r, rectlist *selection_region, lterm *ans)
/* Determine the largest subterm of *t whose display rectangle
is contained in rectangle *r, in case lterm t is unbroken.
The answer is a selected term, whose 'path' field points to
a linked list giving the path from abstract(t) to the selected
subterm; the path is a linked list of pairs of integers, empty if the
selected term is t itself, otherwise telling which ARG choices
to take going down the expression tree (in the first members) and
what are the functors at each choice (in the second members).
That is, going down the expression tree in the term corresponding
to the lterm t, not in the lterm itself; often these are the same,
but in the case of MATRIX terms representing systems of equations,
or LINEARSYSTEM terms, they are not the same.
Return 0 for success;
1 for failure (in which case *ans and *path are garbage).
This function also allows the selection of 'generalized subterms'
(gensubterms, or subranges), which include selecting (adjacent) args of a
sum, product, AND, or OR of arity more than two. You can't select
non-adjacent args. In such a case the path comes to the
'+' (or '*', AND, or OR), then (if range i to j-1 inclusive of args
are selected) continues with (i,-j) instead of just (i)
as it would if only the i-th arg were selected. In other words a
negative entry in the last position in the path signals
this situation. More precisely, it will be ((i,+),(0,-j)),
since each node on the path has an arity and a functor.
Another situation in which a gensubterm can be selected is
in the case of a negated product, e.g. -5xy, in which the
user can select -5 or -5x even though these are not genuine
subterms of -5xy. (Users will want to divide -5x=10 by -5.)
In this case the path to the selected term will be
(0,=),(-1,-),(0,*) when -5 is selected,
and (0,=),(-1,-),(0,*),(-2,*) when -5x is selected from -5xy=10
That is, the arg number of the minus term will be -1, and rest of the
path will be as for selecting the subterm of the product.
If t is a broken term, this is shown by t->linelist != NULL, and
in that case, selection_region is a list of rectangles determined
by SelectionRegion. Determine the largest subterm of t that
fits in the union of these rectangles.
If the selected term is parenthesized, the block field of *ans reflects that
in its .bra, .leftbra, .rightbra, and .width fields; but the rectangle
corresponds to the unparenthesized term.
FNISH THIS: NOT YET IMPLEMENTED:
In case the term has been clipped, i.e. does not fit
in the window, then the window must scroll to enable display
and selection of the rest of the term, so that you can
select a term that won't fit in the screen all at once.
*/
{ BIGRECT s = t->r;
pathlist *tail;
rectlist templist;
rectlist *q;
static int tolerance;
int level;
coord leftshift,rightshift;
lterm u;
term bt;
term v;
int i,j,k,err;
unsigned short n,f;
if(!tolerance)
tolerance = (int) papyrus_to_ypixel(6); /* compute this only once */
/* set tolerance to 3/8 the height of a character box. This is
the amount your box can go under the actual top of a term and
still select it. This is needed because the term's box may
extend above the visible term, due to internal leading space
*/
f = FUNCTOR(*t);
if(selection_region==NULL && subset(&t->r,r,tolerance))
/* not a broken line, and the whole term is inside the rectangle r */
{ if(f == VECTOR && ARITY(*t) == 1)
{ /* one row of a one-column matrix */
*ans = LARG(0,*t); /* the entry in that one column */
}
else if(INEQUALITY(f) && FUNCTOR(LARG(0,*t)) == ILLEGAL)
{ /* This happens if you select the "< c" part of "a < b < c",
since a < b < c is really AND(a < b, b < c), but is bblocked
with an ILLEGAL term in place of the second b, and that
apparently carries over to the corresponding lterm.
*/
*ans = LARG(1,*t);
}
else
*ans = *t;
ans->path = NULL;
return 0; /* the selection is the entire term */
}
if(selection_region == NULL && disjoint(&t->r,r))
return 1; /* failure */
if(selection_region)
{ /* the whole line is a broken line, but t may be a broken or
unbroken subterm. If unbroken it may have a NULL linelist,
so we need to create a temporary one-node linelist. We
just re-use the node 'temp' which lives on the stack,
rather than callocate and free2 a node
*/
if(t->linelist == NULL)
{ templist.rect = t->r;
templist.next = templist.prev = NULL;
templist.wl = 0;
q = &templist;
}
else
q = t->linelist;
if(list_subset(q, selection_region, tolerance))
{ *ans = *t;
ans->path = NULL;
return 0; /* the selection is the entire term */
}
if(list_disjoint(q, selection_region))
return 1; /* failure */
}
if(ATOMIC(*t))
return 1;
n = ARITY(*t);
for(i=0;i<n;i++)
{ if(f == CONSTANTOFINTEGRATION && i==0)
continue; /* can't select the subscript
in a constant of integration */
if(f == DIFF && i > 0)
continue; /* can't select the variable of differentiation or the
superscript in d/dx or d^n/dx^n */
if(f == PR && i > 0)
continue; /* can't select the superscript in f'' */
if(f == SUM && n == 5)
{ /* a series displayed using ... instead of sigma notation */
lterm aux = LARG(4,*t);
unsigned short m = ARITY(aux);
if(i > 0)
continue; /* you can't select from within lo, hi, or index */
/* when i==0 you're selecting from within the general term */
/* Does this series display the general term? */
if(m < 4)
continue;
if(FUNCTOR(LARG((unsigned short)(m-3),aux)) != PSEUDOATOM)
continue; /* general term is not showing */
}
err = select_term(LARGPTR(*t)+i,r,selection_region,ans);
/* select a subterm of the i-th argument */
if(!err)
{ tail = ans->path;
if(f == MATRIX && lcontainseq(*t) &&
( get_problemtype() != MINMAX || FUNCTOR(history(get_currentline())) == OR)
)
{ /* a subterm of a system of equations or inequalities
bblocked as a one-column matrix. The path must
come out as a path in the AND or OR term, not
the MATRIX-VECTOR lterm. Here 'tail' is the
path from the i-th vector, unless tail is NULL
and the whole row was selected.
*/
int problemtype = get_problemtype();
unsigned short h;
if(problemtype == LINEAR_EQUATIONS ||
problemtype == RELATED_RATES
)
h = AND;
else
h = OR;
if(tail)
tail = tail->next; /* the path from the equation */
ans->path = cons(i,h,tail);
return 0;
}
if(tail != NULL && f == LINEARSYSTEM)
{ /* a subterm of one equation selected */
/* tail is the path from the VECTOR representing the i-th row */
if(tail->data == ARITY(LARG(i,*t)) - 2) /* the '=' sign */
return 1; /* you can't select the '=' sign */
if(tail->data == ARITY(LARG(i,*t))-1) /* the right side of the eqn */
{ ans->path = cons(i,AND,cons(1,'=',tail->next));
return 0;
}
else /* a subterm or subterms of the left-hand side selected */
{ int count =0;
/* count the empty columns before the tail->data'th one */
assert(tail->data >= 0);
/* You can't select a subrange of a VECTOR */
for(j=0;j<tail->data;j++)
{ if(FUNCTOR(LARG(j,LARG(i,*t))) == ILLEGAL)
++count;
}
tail->data -= count;
if(tail->data == 0)
{ /* check if the summand containing the
selected term is the ONLY nonzero summand */
for(j=count+1; j < ARITY(LARG(i,*t))-2; j++)
{ if(FUNCTOR(LARG(j,LARG(i,*t))) != ILLEGAL)
break;
}
if(j == ARITY(LARG(i,*t))-2)
{ /* this WAS the only nonzero summand */
ans->path = cons(i,AND,cons(0,'=',tail->next));
return 0;
}
}
ans->path = cons(i,AND,cons(0,'=',cons(tail->data,'+',tail->next)));
return 0;
}
}
if(f == '-' && FUNCTOR(LARG(0,*t)) == '*' && /* a negated product*/
tail != NULL && /* not the whole product was selected */
(tail->next == NULL ||
/* one factor of the product has been selected */
(tail->next->next == NULL && tail->next->data < 0)
/* a subrange of the product has been selected */
) &&
t->r.left >= r->left /* the negative sign is included in r */
)
/* example: selecting -5 or -5x from -5xy */
{ u = *ans;
ans->args = (larg *) mallocate(sizeof(lterm));
if(ans->args == NULL)
nospace();
LARGREP(*ans,0,u);
ans->symbol = '-';
ans->arity = 1;
ans->info = 0;
ans->path = cons(-1,f,tail);
ans->block = u.block;
/* Now, are there parentheses around the first part of
the product as in -(a+b)c (selecting -(a+b)) or
not, as in -ab (selecting -a)? We have to make the
selection rectangle big enough to include the minus sign
and the parentheses, both left and right.
*/
if(FUNCTOR(u) != '*' && paren('*',FUNCTOR(u),LEFT))
{ /* there are parentheses */
leftshift = (unsigned short)(get_parenspace(u.block.size)+ get_charspace() + get_minusspace(u.block.size) + get_parenwidth(u.block.size));
/* one get_charspace() for the minus sign, one for the paren */
rightshift =(unsigned short)(get_parenspace(u.block.size) + get_parenwidth(u.block.size) );
}
else
{ leftshift = (unsigned short)(get_charspace() + get_minusspace(u.block.size));
/* for the minus sign and space after it */
rightshift = 0;
}
ans->block.w = (unsigned short)(ans->block.w + leftshift + rightshift);
ans->r = u.r;
ans->r.left -= papyrus_to_xpixel(leftshift);
ans->r.right += papyrus_to_xpixel(rightshift);
return 0;
}
ans->path = cons(i,FUNCTOR(*t),tail);
if(i == n-1 ||
tail != NULL ||
(f != '+' && f != '*' && f != AND && f != OR)
)
return 0;
/* One of the args of a sum (or product, AND, OR, VECTOR, or MATRIX)
fits in r; check if any more args fit in */
for(j=i+1;j<n;j++) /* range of j-values non-empty */
{ u = LARG(j,*t);
if(selection_region)
{ if(u.linelist == NULL)
{ templist.rect = u.r;
templist.next = templist.prev = NULL;
templist.wl = 0;
q = &templist;
}
else
q = u.linelist;
if(!list_subset(q,selection_region,tolerance))
break;
}
else if(!subset(&u.r, r,tolerance))
/* line isn't broken and u doesn't fit in r*/
break;
}
/* j is the first arg that doesn't fit,
or else all the rest fit and j==n */
if(j == i+1)
return 0; /* only the i-th arg fit */
if(i==0 && j==n)
{ /* example: 2a(a+3) with the selection rectangle not including
the last parentheses. The rectangle of (a+3) does NOT include
the parentheses, so all three args will fit in r, even though
the whole product, whose rectangle DOES include the last paren,
will not fit. In such a case, the last arg will NOT be
part of the selected term. */
j = j-1;
if(j == 1)
{ /* example, 2(a+3) with the selection rectangle not
including the last parenthesis. */
return 0; /* as above, only the i-th arg fit */
}
}
/* args i through j-1 fit */
ans->path->data = -j;
ans->path->functor = 0;
ans->path = cons(i,f,ans->path); /* so the path is ((i,f),(-j,0)) */
ans->args = (larg *) callocate(j-i, sizeof(lterm));
if(ans->args == NULL)
nospace();
ans->symbol = f;
ans->arity = (unsigned short)(j-i);
ans->info = t->info;
SetBigRectEmpty(&s);
for(k=i;k<j;k++)
{ u = LARG(k,*t);
LARGREP(*ans,k-i,u);
UnionBigRect(&s,&u.r,&s);
}
/* UnionRect isn't quite sufficient in all cases.
Example: selecting the middle two terms from
(x-1)(x-2)(x-3)(x-4)
The rectangle s is presently set to include x-2)(x-3 but not
the outer parentheses on (x-2)(x-3). Now we'll do something
about that problem. If ARG(i,*t) or ARG(j-1, *t) have
parentheses, we have to enlarge s on the left and/or right,
respectively.
Also, ans->block is still set to the block of the i-th arg.
Rather than directly compute the block from the blocks of the args,
we make sure we get it right by using abstract and bblock
*/
v = abstract(*ans);
bb(v,&bt,(short)ans->block.size,0,NODIR);
/* not bblock(v,&bt) because that presumes TENPOINT size, and
we may be in an exponent or subscript. */
/* Now compute the adjustment for parentheses */
u = LARG(i,*t);
if(u.block.leftbra)
s.left -= papyrus_to_xpixel(save_space(ARG(1,bt)));
u = LARG(j-1,*t);
if(u.block.rightbra)
s.right += papyrus_to_xpixel(save_space(ARG(j-i,bt)));
ans->r = s;
ans->block = GETBLOCK(bt);
destroy_bblocked_term(bt);
if(selection_region)
{ /* we must build ans->linelist which up to now is NULL */
long oldright = 0;
rectlist *marker;
SetBigRectEmpty(&s);
for(k=i;k<j;k++)
{ u = LARG(k,*t);
if(BROKEN(u))
{ UnionBigRect(&s,&u.linelist->rect,&s);
ans->linelist = rectlist_append(ans->linelist,rectlist_cons(&s,u.linelist->wl,NULL));
for(marker=u.linelist->next;marker->next;marker=marker->next)
rectlist_append(ans->linelist, rectlist_cons(&marker->rect,marker->wl,NULL));
/* Now marker points to the last line in u */
s = marker->rect;
level = marker->wl;
oldright = s.right;
}
else if(u.r.left < oldright)
{ /* beginning a new line with no broken subterm */
ans->linelist = rectlist_append(ans->linelist,rectlist_cons(&s,level,NULL));
s = u.r;
oldright = s.right;
level = 0;
}
else
{ /* just another term on this line */
level = (int)( u.r.top + papyrus_to_ypixel(u.block.wl));
oldright = u.r.right;
UnionBigRect(&s,&u.r,&s);
}
}
if(s.right > s.left)
ans->linelist = rectlist_append(ans->linelist,rectlist_cons(&s,level,NULL));
}
return 0;
}
}
return 1;
}
/*_________________________________________________________________*/
static int subset(BIGRECT *r, BIGRECT *s, int tolerance)
/* return 1 if r is a subset of s, 0 if not */
/* Actually, we allow r to stick out of the top of s by a few
pixels, to account for the evil effects of 'internal leading';
you may think you have surrounded a term when you haven't because
its box sticks up above its visible parts. The number of pixels
allowed is passed in 'tolerance'. We also give a little
tolerance on the other three sides.
Assumes s->top <= s->bottom but does not assume s->left <= s->right.
*/
{ int side_tolerance = tolerance/2;
if(s->right < s->left)
return (r->left + side_tolerance >= s->right &&
r->right <= s->left + side_tolerance &&
r->top + tolerance >= s->top &&
r->bottom <= s->bottom + side_tolerance
);
return (r->left + side_tolerance >= s->left &&
r->right <= s->right + side_tolerance &&
r->top + tolerance >= s->top &&
r->bottom <= s->bottom + side_tolerance
);
}
/*_________________________________________________________________*/
int disjoint(BIGRECT *r, BIGRECT *s)
/* return 1 if r and s are disjoint, 0 if not */
/* Two rectangles are disjoint iff they can be separated
by either a horizontal or a vertical line */
/* the right and bottom borders are not 'in' the rectangle */
/* r is assumed normalized. It is assumed that
s->top <= s->bottom but NOT that s->left <= s->right.
*/
{ if(s->left < s->right)
{ if(r->left >= s->right || s->left >= r->right)
return 1; /* separated by a vertical line */
if(s->top >= r->bottom || r->top >= s->bottom)
return 1; /* separated by a horizontal line */
return 0;
}
else
{ if(r->left >= s->left || s->right >= r->right)
return 1; /* separated by a vertical line */
if(s->top >= r->bottom || r->top >= s->bottom)
return 1; /* separated by a horizontal line */
return 0;
}
}
/*_________________________________________________________________*/
static int list_subset(rectlist *q, rectlist *s, int tolerance)
/* Return 1 if the each rectangle of q is a subset of some
rectangle of s, after
expanding all rectangles of s by 'tolerance' at the top and
'tolerance/2' at the other 3 sides. Return 0 otherwise.
*/
{ rectlist *marker1, *marker2;
for(marker1 = q; marker1; marker1=marker1->next)
{ for(marker2 = s; marker2; marker2 = marker2->next)
{ if(subset(&marker1->rect,&marker2->rect, tolerance))
break;
}
if(!marker2)
return 0;
}
return 1;
}
/*_________________________________________________________________*/
static int list_disjoint(rectlist *q, rectlist *s)
/* Return 1 if all rectangles in q are disjoint from all rectangles
in *s. */
{ rectlist *marker1, *marker2;
for(marker1 = q; marker1; marker1=marker1->next)
{ for(marker2 = s; marker2; marker2 = marker2->next)
{ if(!disjoint(&marker1->rect,&marker2->rect))
return 0;
}
}
return 1;
}
/*______________________________________________________________*/
static rectlist *rectlist_cons(BIGRECT *r, int level, rectlist *q)
/* return a new list with a new copy of rectangle r consed onto
the front of q. Does not copy q and does change q->prev. */
{ rectlist *ans;
ans = mallocate(sizeof(rectlist));
if(ans == NULL)
nospace();
ans->prev = NULL;
ans->next = q;
if(q != NULL)
q->prev = ans;
ans->rect = *r;
ans->wl = level;
return ans;
}
/*______________________________________________________________*/
static rectlist *rectlist_append(rectlist *a, rectlist *b)
/* Join the two rectlists putting a in front, returning a (unless a is NULL)
Allocates no new space. */
{ rectlist *marker;
if(a == NULL)
return b;
for(marker = a;marker->next;marker=marker->next)
; /* find the last node of a */
b->prev = marker;
marker->next = b;
return a;
}
/*______________________________________________________________*/
rectlist * SelectionRegion(lterm lt, BIGRECT *r, BIGRECT *activearea)
/* Return a list of rectangles corresponding to the selection
region drawn when rectangle r is chosen with the mouse over the
display of lterm lt. Namely, the right-hand part of the
first row, the left-hand part of the last row, and all of the
rows in between. The water levels are superfluous and not used. */
{ POINT p,q;
BIGRECT rnormal,ss;
RECT s;
rectlist *ans;
rectlist *marker = lt.linelist;
if(marker == NULL) /* Not a broken term */
return rectlist_cons(r,0,NULL); /* list of length 1 */
rnormal = *r;
if(rnormal.top > rnormal.bottom)
{ rnormal.top = r->bottom;
rnormal.bottom = r->top;
}
p.x = (int) rnormal.left; /* upper left */
p.y = (int) rnormal.top;
q.x = (int) rnormal.right; /* lower right */
q.y = (int) rnormal.bottom;
while(marker)
{ s.bottom = (int) marker->rect.bottom;
/* Enlarge s to encompass the spaces above the line */
s.left = (int) activearea->left;
s.right = (int) activearea->right;
if(marker->prev == NULL)
s.top = (int) activearea->top;
else
s.top = (int) marker->prev->rect.bottom;
if(PtInRect(&s,p))
break;
marker=marker->next;
}
if(marker==NULL)
return NULL; /* p isn't in any of the rectangles attached to lt */
/* Now enlarge the rectangle to include the space below the line
as well */
if(marker->next == NULL)
s.bottom = (int) activearea->bottom;
else
s.bottom = (int) marker->next->rect.top;
if(PtInRect(&s,q))
return rectlist_cons(r,0,NULL);
/* Now p is in one line and q in another below. */
/* OK, outline the last part of the line containing p */
ss = marker->rect;
ss.left = p.x;
ss.top = p.y;
ans = rectlist_cons(&ss,0,NULL);
/* Now go on looking for the other corner q in the rectlist of lt */
marker = marker->next;
while(marker)
{ /* Set s to encompass the spaces between the lines,
or above the first line or below the last line */
s.left = (int) activearea->left;
s.right = (int) activearea->right;
if(marker->prev == NULL)
s.top = (int) activearea->top;
else
s.top = (int) marker->prev->rect.bottom;
if(marker->next == NULL)
s.bottom = (int) activearea->bottom;
else
s.bottom = (int) marker->next->rect.top;
if(PtInRect(&s,q))
break;
/* Now the selected rectangle includes the whole next line,
so outline it: */
rectlist_append(ans,rectlist_cons(&marker->rect,0,NULL));
marker= marker->next;
}
if(marker)
{ ss = marker->rect;
ss.right = q.x;
ss.bottom = q.y;
rectlist_append(ans,rectlist_cons(&ss,0,NULL));
}
return ans;
}
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists