ref: 9f76a7f6819ac04552b4fb6588156f3e4089d1d7
dir: /libtk/textw.c/
#include "lib9.h"
#include "draw.h"
#include "keyboard.h"
#include "tk.h"
#include "textw.h"
/*
* useful text widget info to be found at:
* :/coordinate.systems - what coord. systems are in use
* textu.c:/assumed.invariants - some invariants that must be preserved
*/
#define istring u.string
#define iwin u.win
#define imark u.mark
#define iline u.line
#define FLUSH() flushimage(tk->env->top->display, 1)
#define O(t, e) ((long)(&((t*)0)->e))
/* Layout constants */
enum {
Textpadx = 2,
Textpady = 0,
};
typedef struct Interval {
int lo;
int hi;
} Interval;
typedef struct Mprint Mprint;
struct Mprint
{
char* buf;
int ptr;
int len;
};
typedef struct TkDump TkDump;
struct TkDump
{
int sgml;
int metrics;
};
static
TkOption dumpopts[] =
{
"sgml", OPTbool, O(TkDump, sgml), nil,
"metrics", OPTbool, O(TkDump, metrics), nil,
nil
};
static
TkStab tkcompare[] =
{
"<", TkLt,
"<=", TkLte,
"==", TkEq,
">=", TkGte,
">", TkGt,
"!=", TkNeq,
nil
};
static
TkOption textopts[] =
{
"wrap", OPTstab, O(TkText, opts[TkTwrap]), tkwrap,
"spacing1", OPTnndist, O(TkText, opts[TkTspacing1]), (void *)O(Tk, env),
"spacing2", OPTnndist, O(TkText, opts[TkTspacing2]), (void *)O(Tk, env),
"spacing3", OPTnndist, O(TkText, opts[TkTspacing3]), (void *)O(Tk, env),
"tabs", OPTtabs, O(TkText, tabs), (void *)O(Tk, env),
"xscrollcommand", OPTtext, O(TkText, xscroll), nil,
"yscrollcommand", OPTtext, O(TkText, yscroll), nil,
"insertwidth", OPTnndist, O(TkText, inswidth), nil,
"tagshare", OPTwinp, O(TkText, tagshare), nil,
"propagate", OPTstab, O(TkText, propagate), tkbool,
"selectborderwidth", OPTnndist, O(TkText, sborderwidth), nil,
nil
};
#define CNTL(c) ((c)&0x1f)
#define DEL 0x7f
static TkEbind tktbinds[] = {
{TkButton1P, "%W tkTextButton1 %X %Y"},
{TkButton1P|TkMotion, "%W tkTextSelectTo %X %Y"},
{TkButton1P|TkDouble, "%W tkTextSelectTo %X %Y double"},
{TkButton1R, "%W tkTextButton1R"},
{TkButton2P, "%W scan mark %x %y"},
{TkButton2P|TkMotion, "%W scan dragto %x %y"},
{TkKey, "%W tkTextInsert {%A}"},
{TkKey|CNTL('a'), "%W tkTextSetCursor {insert linestart}"},
{TkKey|Home, "%W tkTextSetCursor {insert linestart}"},
{TkKey|CNTL('<'), "%W tkTextSetCursor {insert linestart}"},
{TkKey|CNTL('b'), "%W tkTextSetCursor insert-1c"},
{TkKey|Left, "%W tkTextSetCursor insert-1c"},
{TkKey|CNTL('d'), "%W tkTextDelIns"},
{TkKey|CNTL('e'), "%W tkTextSetCursor {insert lineend}"},
{TkKey|End, "%W tkTextSetCursor {insert lineend}"},
{TkKey|CNTL('>'), "%W tkTextSetCursor {insert lineend}"},
{TkKey|CNTL('f'), "%W tkTextSetCursor insert+1c"},
{TkKey|Right, "%W tkTextSetCursor insert+1c"},
{TkKey|CNTL('h'), "%W tkTextDelIns -c"},
{TkKey|DEL, "%W tkTextDelIns"},
{TkKey|CNTL('k'), "%W tkTextDelIns +l"},
{TkKey|CNTL('n'), "%W tkTextSetCursor {insert+1l}"},
{TkKey|Down, "%W tkTextSetCursor {insert+1l}"},
{TkKey|CNTL('o'), "%W tkTextInsert {\n}; %W mark set insert insert-1c"},
{TkKey|CNTL('p'), "%W tkTextSetCursor {insert-1l}"},
{TkKey|Up, "%W tkTextSetCursor {insert-1l}"},
{TkKey|CNTL('u'), "%W tkTextDelIns -l"},
{TkKey|CNTL('v'), "%W yview scroll 0.75 page"},
{TkKey|Pgdown, "%W yview scroll 0.75 page"},
{TkKey|CNTL('w'), "%W tkTextDelIns -w"},
{TkKey|Pgup, "%W yview scroll -0.75 page"},
{TkButton4P, "%W yview scroll -0.2 page"},
{TkButton5P, "%W yview scroll 0.2 page"},
{TkFocusout, "%W tkTextCursor delete"},
{TkKey|APP|'\t', ""},
{TkKey|BackTab, ""},
};
static int tktclickmatch(TkText *, int, int, int, TkTindex *);
static void tktdoubleclick(TkText *, TkTindex *, TkTindex *);
static char* tktdrawline(Image*, Tk*, TkTline*, Point);
static void tktextcursordraw(Tk *, int);
static char* tktsetscroll(Tk*, int);
static void tktsetclip(Tk *);
static char* tktview(Tk*, char*, char**, int, int*, int, int);
static Interval tkttranslate(Tk*, Interval, int);
static void tktfixscroll(Tk*, Point);
static void tktnotdrawn(Tk*, int, int, int);
static void tktdrawbg(Tk*, int, int, int);
static int tktwidbetween(Tk*, int, TkTindex*, TkTindex*);
static int tktpostspace(Tk*, TkTline*);
static int tktprespace(Tk*, TkTline*);
static void tktsee(Tk*, TkTindex*, int);
static Point tktrelpos(Tk*);
static void autoselect(Tk*, void*, int);
static void blinkreset(Tk*);
/* debugging */
extern int tktdbg;
extern void tktprinttext(TkText*);
extern void tktprintindex(TkTindex*);
extern void tktprintitem(TkTitem*);
extern void tktprintline(TkTline*);
extern void tktcheck(TkText*, char*);
extern int tktutfpos(char *, int);
char*
tktext(TkTop *t, char* arg, char **ret)
{
Tk *tk;
char *e;
TkEnv *ev;
TkTline *l;
TkTitem *it = nil;
TkName *names = nil;
TkTtaginfo *ti = nil;
TkOptab tko[3];
TkTmarkinfo *mi = nil;
TkText *tkt, *tktshare;
tk = tknewobj(t, TKtext, sizeof(Tk)+sizeof(TkText));
if(tk == nil)
return TkNomem;
tkt = TKobj(TkText, tk);
tk->relief = TKsunken;
tk->borderwidth = 1;
tk->ipad.x = Textpadx * 2;
tk->ipad.y = Textpady * 2;
tk->flag |= Tktakefocus;
tkt->sborderwidth = 0;
tkt->inswidth = 2;
tkt->cur_flag = 0; /* text cursor doesn't show up initially */
tkt->opts[TkTwrap] = Tkwrapchar;
tkt->opts[TkTrelief] = TKflat;
tkt->opts[TkTjustify] = Tkleft;
tkt->propagate = BoolX;
tko[0].ptr = tk;
tko[0].optab = tkgeneric;
tko[1].ptr = tkt;
tko[1].optab = textopts;
tko[2].ptr = nil;
tk->req.width = tk->env->wzero*Textwidth;
tk->req.height = tk->env->font->height*Textheight;
names = nil;
e = tkparse(t, arg, tko, &names);
if(e != nil)
goto err;
tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd));
if(names == nil) {
/* tkerr(t, arg); XXX */
e = TkBadwp;
goto err;
}
if(tkt->tagshare != nil) {
tkputenv(tk->env);
tk->env = tkt->tagshare->env;
tk->env->ref++;
}
if(tk->flag&Tkdisabled)
tkt->inswidth = 0;
if(tkt->tabs == nil) {
tkt->tabs = malloc(sizeof(TkTtabstop));
if(tkt->tabs == nil)
goto err;
tkt->tabs->pos = 8*tk->env->wzero;
tkt->tabs->justify = Tkleft;
tkt->tabs->next = nil;
}
if(tkt->tagshare != nil) {
tktshare = TKobj(TkText, tkt->tagshare);
tkt->tags = tktshare->tags;
tkt->nexttag = tktshare->nexttag;
}
else {
/* Note: sel should have id == TkTselid == 0 */
e = tktaddtaginfo(tk, "sel", &ti);
if(e != nil)
goto err;
tkputenv(ti->env);
ti->env = tknewenv(t);
if(ti->env == nil)
goto err;
ev = ti->env;
ev->colors[TkCbackgnd] = tk->env->colors[TkCselectbgnd];
ev->colors[TkCbackgndlght] = tk->env->colors[TkCselectbgndlght];
ev->colors[TkCbackgnddark] = tk->env->colors[TkCselectbgnddark];
ev->colors[TkCforegnd] = tk->env->colors[TkCselectfgnd];
ev->set = (1<<TkCbackgnd)|(1<<TkCbackgndlght)|
(1<<TkCbackgnddark)|(1<<TkCforegnd);
ti->opts[TkTborderwidth] = tkt->sborderwidth;
if(tkt->sborderwidth > 0)
ti->opts[TkTrelief] = TKraised;
}
e = tktaddmarkinfo(tkt, "current", &mi);
if(e != nil)
goto err;
e = tktaddmarkinfo(tkt, "insert", &mi);
if(e != nil)
goto err;
tkt->start.flags = TkTfirst|TkTlast;
tkt->end.flags = TkTlast;
e = tktnewitem(TkTnewline, 0, &it);
if(e != nil)
goto err;
e = tktnewline(TkTfirst|TkTlast, it, &tkt->start, &tkt->end, &l);
if(e != nil)
goto err;
e = tktnewitem(TkTmark, 0, &it);
if(e != nil)
goto err;
it->next = l->items;
l->items = it;
it->imark = mi;
mi->cur = it;
tkt->nlines = 1;
tkt->scrolltop[Tkvertical] = -1;
tkt->scrolltop[Tkhorizontal] = -1;
tkt->scrollbot[Tkvertical] = -1;
tkt->scrollbot[Tkhorizontal] = -1;
if(tkt->tagshare != nil)
tk->binds = tkt->tagshare->binds;
else {
e = tkbindings(t, tk, tktbinds, nelem(tktbinds));
if(e != nil)
goto err;
}
if (tkt->propagate == BoolT) {
if ((tk->flag & Tksetwidth) == 0)
tk->req.width = tktmaxwid(tkt->start.next);
if ((tk->flag & Tksetheight) == 0)
tk->req.height = tkt->end.orig.y;
}
e = tkaddchild(t, tk, &names);
tkfreename(names);
if(e != nil)
goto err;
tk->name->link = nil;
return tkvalue(ret, "%s", tk->name->name);
err:
/* XXX it's possible there's a memory leak here */
tkfreeobj(tk);
return e;
}
/*
* There are four coordinate systems of interest:
* S - screen coordinate system (i.e. top left corner of
* inferno screen is (0,0) in S space.)
* I - image coordinate system (i.e. top left corner of
* tkimageof(this widget) is (0,0) in I space.)
* T - text coordinate system (i.e., top left of first line
* is at (0,0) in T space.)
* V - view coordinate system (i.e., top left of visible
* portion of widget is at (0,0) in V space.)
*
* A point P in the four systems (Ps, Pi, Pt, Pv) satisfies:
* Pt = Ps - deltast
* Pv = Ps - deltasv
* Pv = Pi - deltaiv
* (where deltast is vector from S origin to T origin;
* deltasv is vector from S origin to V origin;
* deltaiv is vector from I origin to V origin)
*
* We keep deltatv, deltasv, and deltaiv in tkt.
* Deltatv is updated by scrolling.
* Deltasv is updated by geom changes:
* tkposn(tk)+ipad/2
* Deltaiv is affected by geom changes and the call to the draw function:
* tk->act+orig+ipad/2+(bw,bw) (orig is the parameter to tkdrawtext),
*
* We can derive
* Ps = Pt + deltast
* = Pt + deltasv - deltatv
*
* Pv = Pt - deltatv
*
* Here are various coordinates in the text widget according
* to which coordinate system they use:
*
* S - Mouse coordinates (coming in to tktextevent);
* the deltasv parameter to tkdrawtext;
* coords in tkt->image, where drawing is done to
* (to get same bit-alignment as screen, for fast transfer)
* T - orig in TkTlines
* V - %x,%y delivered via binds to TkText or its tags
* Note deltasv changes underneath us, so is calculated on the fly
* when it needs to be (in tktextevent).
*
*/
static void
tktsetdeltas(Tk *tk, Point orig)
{
TkText *tkt = TKobj(TkText, tk);
tkt->deltaiv.x = orig.x + tk->act.x + tk->ipad.x/2 + tk->borderwidth;
tkt->deltaiv.y = orig.y + tk->act.y + tk->ipad.y/2 + tk->borderwidth;
}
static Point
tktrelpos(Tk *sub)
{
Tk *tk;
TkTindex ix;
Rectangle r;
Point ans;
tk = sub->parent;
if(tk == nil)
return ZP;
if(tktfindsubitem(sub, &ix)) {
r = tktbbox(tk, &ix);
ans.x = r.min.x;
ans.y = r.min.y;
return r.min;
}
return ZP;
}
static void
tktreplclipr(Image *dst, Rectangle r)
{
int locked;
locked = lockdisplay(dst->display);
replclipr(dst, 0, r);
if(locked)
unlockdisplay(dst->display);
}
char*
tkdrawtext(Tk *tk, Point orig)
{
int vh;
Image *dst;
TkText *tkt;
TkTline *l, *lend;
Point p, deltait;
Rectangle oclipr;
int reldone = 1;
char *e;
tkt = TKobj(TkText, tk);
dst = tkimageof(tk);
if (dst == nil)
return nil;
tkt->image = dst;
tktsetdeltas(tk, orig);
tkt->tflag |= TkTdrawn|TkTdlocked;
oclipr = dst->clipr;
tktsetclip(tk);
if(tk->flag&Tkrefresh) {
reldone = 0;
tktnotdrawn(tk, 0, tkt->end.orig.y, 1);
}
tk->flag &= ~Tkrefresh;
deltait = subpt(tkt->deltaiv, tkt->deltatv);
vh = tk->act.height - tk->ipad.y/2;
lend = &tkt->end;
for(l = tkt->start.next; l != lend; l = l->next) {
if(l->orig.y+l->height < tkt->deltatv.y)
continue;
if(l->orig.y > tkt->deltatv.y + vh)
break;
if(!(l->flags&TkTdrawn)) {
e = tktdrawline(dst, tk, l, deltait);
if(e != nil)
return e;
}
}
tktreplclipr(dst, oclipr);
if(!reldone) {
p.x = orig.x + tk->act.x;
p.y = orig.y + tk->act.y;
tkdrawrelief(dst, tk, p, TkCbackgnd, tk->relief);
}
tkt->tflag &= ~TkTdlocked;
return nil;
}
/*
* Set the clipping rectangle of the destination image to the
* intersection of the current clipping rectangle and the area inside
* the text widget that needs to be redrawn.
* The caller should save the old one and restore it later.
*/
static void
tktsetclip(Tk *tk)
{
Rectangle r;
Image *dst;
TkText *tkt = TKobj(TkText, tk);
dst = tkt->image;
r.min = tkt->deltaiv;
r.max.x = r.min.x + tk->act.width - tk->ipad.x / 2;
r.max.y = r.min.y + tk->act.height - tk->ipad.y / 2;
if(!rectclip(&r, dst->clipr))
r.max = r.min;
tktreplclipr(dst, r);
}
static char*
tktdrawline(Image *i, Tk *tk, TkTline *l, Point deltait)
{
Tk *sub;
Font *f;
Image *bg;
Point p, q;
Rectangle r;
TkText *tkt;
TkTitem *it, *z;
int bevtop, bevbot;
TkEnv *e, *et, *env;
int *opts;
int o, bd, ul, ov, h, w, la, lh, cursorx, join;
char *err;
env = mallocz(sizeof(TkEnv), 0);
if(env == nil)
return TkNomem;
opts = mallocz(TkTnumopts*sizeof(int), 0);
if(opts == nil) {
free(env);
return TkNomem;
}
tkt = TKobj(TkText, tk);
e = tk->env;
et = env;
et->top = e->top;
f = e->font;
/* l->orig is in T space, p is in I space */
la = l->ascent;
lh = l->height;
p = addpt(l->orig, deltait);
p.y += la;
/* if(tktdbg){print("drawline, p=(%d,%d), f->a=%d, f->h=%d\n", p.x, p.y, f->ascent, f->height); tktprintline(l);} */
cursorx = -1000;
join = 0;
for(it = l->items; it != nil; it = it->next) {
bg = tkgc(e, TkCbackgnd);
if(tktanytags(it)) {
tkttagopts(tk, it, opts, env, nil, 1);
if(e->colors[TkCbackgnd] != et->colors[TkCbackgnd]) {
bg = tkgc(et, TkCbackgnd);
r.min = p;
r.min.y -= la;
r.max.x = r.min.x + it->width;
r.max.y = r.min.y + lh;
draw(i, r, bg, nil, ZP);
}
o = opts[TkTrelief];
bd = opts[TkTborderwidth];
if((o == TKsunken || o == TKraised) && bd > 0) {
/* fit relief inside item bounding box */
q.x = p.x;
q.y = p.y - la;
if(it->width < 2*bd)
bd = it->width / 2;
if(lh < 2*bd)
bd = lh / 2;
w = it->width - 2*bd;
h = lh - 2*bd;
if(o == TKraised) {
bevtop = TkLightshade;
bevbot = TkDarkshade;
}
else {
bevtop = TkDarkshade;
bevbot = TkLightshade;
}
tkbevel(i, q, w, h, bd,
tkgc(et, TkCbackgnd+bevtop), tkgc(et, TkCbackgnd+bevbot));
/* join relief between adjacent items if tags match */
if(join) {
r.min.x = q.x;
r.max.x = q.x + bd;
r.min.y = q.y + bd;
r.max.y = r.min.y + h;
draw(i, r, bg, nil, ZP);
r.min.y = r.max.y;
r.max.y = r.min.y + bd;
draw(i, r, tkgc(et, TkCbackgnd+bevbot), nil, ZP);
}
for(z = it->next; z != nil && z->kind == TkTmark; )
z = z->next;
if(z != nil && tktsametags(z, it)) {
r.min.x = q.x + bd + w;
r.max.x = r.min.x + bd;
r.min.y = q.y;
r.max.y = q.y + bd;
draw(i, r, tkgc(et, TkCbackgnd+bevtop), nil, ZP);
r.min.y = r.max.y;
r.max.y = r.min.y + h;
draw(i, r, bg, nil, ZP);
join = 1;
}
else
join = 0;
}
o = opts[TkToffset];
ul = opts[TkTunderline];
ov = opts[TkToverstrike];
}
else {
et->font = f;
et->colors[TkCforegnd] = e->colors[TkCforegnd];
o = 0;
ul = 0;
ov = 0;
}
switch(it->kind) {
case TkTascii:
case TkTrune:
q.x = p.x;
q.y = p.y - env->font->ascent - o;
/*if(tktdbg)print("q=(%d,%d)\n", q.x, q.y);*/
string(i, q, tkgc(et, TkCforegnd), q, env->font, it->istring);
if(ov == BoolT) {
r.min.x = q.x;
r.max.x = r.min.x + it->width;
r.min.y = q.y + 2*env->font->ascent/3;
r.max.y = r.min.y + 2;
draw(i, r, tkgc(et, TkCforegnd), nil, ZP);
}
if(ul == BoolT) {
r.min.x = q.x;
r.max.x = r.min.x + it->width;
r.max.y = p.y - la + lh;
r.min.y = r.max.y - 2;
draw(i, r, tkgc(et, TkCforegnd), nil, ZP);
}
break;
case TkTmark:
if((it->imark != nil)
&& strcmp(it->imark->name, "insert") == 0) {
cursorx = p.x - 1;
}
break;
case TkTwin:
sub = it->iwin->sub;
if(sub != nil) {
int dirty;
sub->flag |= Tkrefresh;
sub->dirty = tkrect(sub, 1);
err = tkdrawslaves(sub, p, &dirty);
if(err != nil) {
free(opts);
free(env);
return err;
}
}
break;
}
p.x += it->width;
}
l->flags |= TkTdrawn;
/* do cursor last, so not overwritten by later items */
if(cursorx != -1000 && tkt->inswidth > 0) {
r.min.x = cursorx;
r.min.y = p.y - la;
r.max.x = r.min.x + tkt->inswidth;
r.max.y = r.min.y + lh;
r = rectsubpt(r, deltait);
if (!eqrect(tkt->cur_rec, r))
blinkreset(tk);
tkt->cur_rec = r;
if(tkt->cur_flag)
tktextcursordraw(tk, TkCforegnd);
}
free(opts);
free(env);
return nil;
}
static void
tktextcursordraw(Tk *tk, int color)
{
Rectangle r;
TkText *tkt;
Image *i;
tkt = TKobj(TkText, tk);
r = rectaddpt(tkt->cur_rec, subpt(tkt->deltaiv, tkt->deltatv));
/* check the cursor with widget boundary */
/* do nothing if entire cursor outside widget boundary */
if( ! ( r.max.x < tkt->deltaiv.x ||
r.min.x > tkt->deltaiv.x + tk->act.width ||
r.max.y < tkt->deltaiv.y ||
r.min.y > tkt->deltaiv.y + tk->act.height)) {
/* clip rectangle if extends beyond widget boundary */
if (r.min.x < tkt->deltaiv.x)
r.min.x = tkt->deltaiv.x;
if (r.max.x > tkt->deltaiv.x + tk->act.width)
r.max.x = tkt->deltaiv.x + tk->act.width;
if (r.min.y < tkt->deltaiv.y)
r.min.y = tkt->deltaiv.y;
if (r.max.y > tkt->deltaiv.y + tk->act.height)
r.max.y = tkt->deltaiv.y + tk->act.height;
i = tkimageof(tk);
if (i != nil)
draw(i, r, tkgc(tk->env, color), nil, ZP);
}
}
static void
blinkreset(Tk *tk)
{
TkText *tkt = TKobj(TkText, tk);
if (!tkhaskeyfocus(tk) || tk->flag&Tkdisabled)
return;
tkt->cur_flag = 1;
tkblinkreset(tk);
}
static void
showcaret(Tk *tk, int on)
{
TkText *tkt = TKobj(TkText, tk);
TkTline *l, *lend;
TkTitem *it;
tkt->cur_flag = on;
lend = &tkt->end;
for(l = tkt->start.next; l != lend; l = l->next) {
for (it = l->items; it != nil; it = it->next) {
if (it->kind == TkTmark && it->imark != nil &&
strcmp(it->imark->name, "insert") == 0) {
if (on) {
tktextcursordraw(tk, TkCforegnd);
tk->dirty = tkrect(tk, 1);
} else
tktnotdrawn(tk, l->orig.y, l->orig.y+l->height, 0);
tkdirty(tk);
return;
}
}
}
}
char*
tktextcursor(Tk *tk, char* arg, char **ret)
{
int on = 0;
USED(ret);
if (tk->flag&Tkdisabled)
return nil;
if(strcmp(arg, " insert") == 0) {
tkblink(tk, showcaret);
on = 1;
}
else
tkblink(nil, nil);
showcaret(tk, on);
return nil;
}
/*
* Insert string s just before ins, but don't worry about geometry values.
* Don't worry about doing wrapping correctly, but break long strings
* into pieces to avoid bad behavior in the wrapping code of tktfixgeom.
* If tagit != 0, use its tags, else use the intersection of tags of
* non cont or mark elements just before and just after insertion point.
* (At beginning and end of widget, just use the tags of one adjacent item).
* Keep *ins up-to-date.
*/
char*
tktinsert(Tk *tk, TkTindex *ins, char *s, TkTitem *tagit)
{
int c, n, nextra, nmax, atend, atbeg;
char *e, *p;
Rune r;
TkTindex iprev, inext;
TkTitem *i, *utagit;
TkText *tkt = TKobj(TkText, tk);
e = tktsplititem(ins);
if(e != nil)
return e;
/* if no tags give, use intersection of previous and next char tags */
nextra = 0;
n = tk->env->wzero;
if(n <= 0)
n = 8;
nmax = tk->act.width - tk->ipad.x;
if(nmax <= 0) {
if (tkt->propagate != BoolT || (tk->flag & Tksetwidth))
nmax = tk->req.width;
if(nmax <= 0)
nmax = 60*n;
}
nmax = (nmax + n - 1) / n;
utagit = nil;
if(tagit == nil) {
inext = *ins;
tktadjustind(tkt, TkTbycharstart, &inext);
atend = (inext.item->next == nil && inext.line->next == &tkt->end);
if(atend || tktanytags(inext.item)) {
iprev = *ins;
tktadjustind(tkt, TkTbycharback, &iprev);
atbeg = (iprev.line->prev == &tkt->start && iprev.line->items == iprev.item);
if(atbeg || tktanytags(iprev.item)) {
nextra = 0;
if(!atend)
nextra = inext.item->tagextra;
if(!atbeg && iprev.item->tagextra > nextra)
nextra = iprev.item->tagextra;
e = tktnewitem(TkTascii, nextra, &utagit);
if(e != nil)
return e;
if(!atend) {
tkttagcomb(utagit, inext.item, 1);
if(!atbeg)
tkttagcomb(utagit, iprev.item, 0);
}
else if(!atbeg)
tkttagcomb(utagit, iprev.item, 1);
tagit = utagit;
}
}
}
else
nextra = tagit->tagextra;
while((c = *s) != '\0') {
e = tktnewitem(TkTascii, nextra, &i);
if(e != nil) {
if(utagit != nil)
free(utagit);
return e;
}
if(tagit != nil)
tkttagcomb(i, tagit, 1);
if(c == '\n') {
i->kind = TkTnewline;
tkt->nlines++;
s++;
}
else
if(c == '\t') {
i->kind = TkTtab;
s++;
}
else {
p = s;
n = 0;
i->kind = TkTascii;
while(c != '\0' && c != '\n' && c != '\t' && n < nmax){
s += chartorune(&r, s);
c = *s;
n++;
}
/*
* if more bytes than runes, then it's not all ascii, so create a TkTrune item
*/
if(s - p > n)
i->kind = TkTrune;
n = s - p;
i->istring = malloc(n+1);
if(i->istring == nil) {
tktfreeitems(tkt, i, 1);
if(utagit != nil)
free(utagit);
return TkNomem;
}
memmove(i->istring, p, n);
i->istring[n] = '\0';
}
e = tktiteminsert(tkt, ins, i);
if(e != nil) {
if(utagit != nil)
free(utagit);
tktfreeitems(tkt, i, 1);
return e;
}
}
if(utagit != nil)
free(utagit);
return nil;
}
void
tktextsize(Tk *tk, int dogeom)
{
TkText *tkt;
TkGeom g;
tkt = TKobj(TkText, tk);
if (tkt->propagate == BoolT) {
g = tk->req;
if ((tk->flag & Tksetwidth) == 0)
tk->req.width = tktmaxwid(tkt->start.next);
if ((tk->flag & Tksetheight) == 0)
tk->req.height = tkt->end.orig.y;
if (dogeom)
tkgeomchg(tk, &g, tk->borderwidth);
}
}
static int
maximum(int a, int b)
{
if (a > b)
return a;
return b;
}
/*
* For lines l1->next, ..., l2, fix up the geometry
* elements of constituent TkTlines and TkTitems.
* This involves doing proper line wrapping, and calculating item
* widths and positions.
* Also, merge any adjacent TkTascii/TkTrune items with the same tags.
* Finally, bump the y component of lines l2->next, ... end.
* l2 should not be tkt->end.
*
* if finalwidth is 0, we're trying to work out what the
* width and height should be. if propagation is off,
* it's irrelevant; otherwise it must assume that
* its desired width will be fulfilled, as the packer
* doesn't iterate...
*
* N.B. this function rearranges lines, merges and splits items.
* this means that in general the item and line pointed to
* by any index might have been freed after tktfixgeom
* has been called.
*/
char*
tktfixgeom(Tk *tk, TkTline *l1, TkTline *l2, int finalwidth)
{
int x, y, a, wa, h, w, o, n, j, sp3, xleft, xright, winw, oa, oh, lh;
int wrapmode, just, needsplit;
char *e, *s;
TkText *tkt;
Tk *sub;
TkTitem *i, *it, *ilast, *iprev;
TkTindex ix, ixprev, ixw;
TkTline *l, *lafter;
Interval oldi, hole, rest, newrest;
TkEnv *env;
Font *f;
int *opts;
TkTtabstop *tb;
tkt = TKobj(TkText, tk);
if(tktdbg)
tktcheck(tkt, "tktfixgeom");
if (!finalwidth && tkt->propagate == BoolT) {
if ((tk->flag & Tksetwidth) == 0)
winw = 1000000;
else
winw = tk->req.width;
} else {
winw = tk->act.width - tk->ipad.x;
if(winw <= 0)
winw = tk->req.width;
}
if(winw < 0)
return nil;
/*
* Make lafter be the first line after l2 that comes after a newline
* (so that wrap correction cannot affect it)
*/
lafter = l2->next;
if(tktdbg && lafter == nil) {
print("tktfixgeom: botch 1\n");
return nil;
}
while((lafter->flags & TkTfirst) == 0 && lafter != &tkt->end)
lafter = lafter->next;
y = l1->orig.y + l1->height + tktpostspace(tk, l1);
oldi.lo = y;
oldi.hi = lafter->orig.y;
rest.lo = oldi.hi;
rest.hi = rest.lo + 1000; /* get background after end, too */
opts = mallocz(TkTnumopts*sizeof(int), 0);
if(opts == nil)
return TkNomem;
env = mallocz(sizeof(TkEnv), 0);
if(env == nil) {
free(opts);
return TkNomem;
}
for(l = l1->next; l != lafter; l = l->next) {
if(tktdbg && l == nil) {
print("tktfixgeom: botch 2\n");
free(opts);
free(env);
return nil;
}
l->flags &= ~TkTdrawn;
/* some spacing depends on tags of first non-mark on display line */
iprev = nil;
for(i = l->items; i->kind == TkTmark; ) {
iprev = i;
i = i->next;
}
tkttagopts(tk, i, opts, env, &tb, 1);
if(l->flags&TkTfirst) {
xleft = opts[TkTlmargin1];
y += opts[TkTspacing1];
}
else {
xleft = opts[TkTlmargin2];
y += opts[TkTspacing2];
}
sp3 = opts[TkTspacing3];
just = opts[TkTjustify];
wrapmode = opts[TkTwrap];
f = env->font;
h = f->height;
lh = opts[TkTlineheight];
a = f->ascent;
x = xleft;
xright = winw - opts[TkTrmargin];
if(xright < xleft)
xright = xleft;
/*
* perform line wrapping and calculate h (height) and a (ascent)
* for the current line
*/
for(; i != nil; iprev = i, i = i->next) {
again:
if(i->kind == TkTmark)
continue;
if(i->kind == TkTnewline)
break;
if(i->kind == TkTcontline) {
/*
* See if some of following line fits on this one.
* First, ensure that following line isn't empty.
*/
it = l->next->items;
while(it->kind == TkTmark)
it = it->next;
if(it->kind == TkTnewline || it->kind == TkTcontline) {
/* next line is empty; join it to this one by removing i */
ix.item = i;
ix.line = l;
ix.pos = 0;
tktremitem(tkt, &ix);
it = l->next->items;
if(iprev == nil)
i = l->items;
else
i = iprev->next;
goto again;
}
n = xright - x;
if(n <= 0)
break;
ixprev.line = l;
ixprev.item = i;
ixprev.pos = 0;
ix = ixprev;
tktadjustind(tkt, TkTbychar, &ix);
if(wrapmode == Tkwrapword)
tktadjustind(tkt, TkTbywrapend, &ix);
if(wrapmode != Tkwrapnone && tktwidbetween(tk, x, &ixprev, &ix) > n)
break;
/* move one item up from next line and try again */
it = l->next->items;
if(tktdbg && (it == nil || it->kind == TkTnewline || it->kind == TkTcontline)) {
print("tktfixgeom: botch 3\n");
free(opts);
free(env);
return nil;
}
if(iprev == nil)
l->items = it;
else
iprev->next = it;
l->next->items = it->next;
it->next = i;
i = it;
goto again;
}
oa = a;
oh = h;
if(!tktanytags(i)) {
env->font = tk->env->font;
o = 0;
}
else {
tkttagopts(tk, i, opts, env, nil, 1);
o = opts[TkToffset];
}
if((o != 0 || env->font != f) && i->kind != TkTwin) {
/* check ascent of current item */
n = o+env->font->ascent;
if(n > a) {
a = n;
h += (a - oa);
}
/* check descent of current item */
n = (env->font->height - env->font->ascent) - o;
if(n > h-a)
h = a + n;
}
if(i->kind == TkTwin && i->iwin->sub != nil) {
sub = i->iwin->sub;
n = 2 * i->iwin->pady + sub->act.height +
2 * sub->borderwidth;
switch(i->iwin->align) {
case Tktop:
case Tkbottom:
if(n > h)
h = n;
break;
case Tkcenter:
if(n/2 > a)
a = n/2;
if(n/2 > h-a)
h = a + n/2;
break;
case Tkbaseline:
wa = i->iwin->ascent;
if (wa == -1)
wa = n;
h = maximum(a, wa) + maximum(h - a, n - wa);
a = maximum(a, wa);
break;
}
}
w = tktdispwidth(tk, tb, i, env->font, x, 0, -1);
n = x + w - xright;
if(n > 0 && wrapmode != Tkwrapnone) {
/* find shortest suffix that can be removed to fit item */
j = tktposcount(i) - 1;
while(j > 0 && tktdispwidth(tk, tb, i, env->font, x, j, -1) < n)
j--;
/* put at least one item on a line before splitting */
if(j == 0 && x == xleft) {
if(tktposcount(i) == 1)
goto Nosplit;
j = 1;
}
ix.line = l;
ix.item = i;
ix.pos = j;
if(wrapmode == Tkwrapword) {
/* trim the item at the first word at or before the shortest suffix */
/* TO DO: convert any resulting trailing white space to zero width */
ixw = ix;
if(tktisbreak(tktindrune(&ixw))) {
/* at break character, find end of word preceding it */
while(tktisbreak(tktindrune(&ixw))){
if(!tktadjustind(tkt, TkTbycharback, &ixw) ||
ixw.line != l || ixw.item == l->items && ixw.pos == 0)
goto Wrapchar; /* no suitable point, degrade to char wrap */
}
ix = ixw;
}
/* now find start of word */
tktadjustind(tkt, TkTbywrapstart, &ixw);
if(ixw.line == l && (ixw.item != l->items || ixw.pos > 0)){
/* it will leave something on the line, so reasonable to split here */
ix = ixw;
}
/* otherwise degrade to char wrap */
}
Wrapchar:
if(ix.pos > 0) {
needsplit = 1;
e = tktsplititem(&ix);
if(e != nil) {
free(opts);
free(env);
return e;
}
}
else
needsplit = 0;
e = tktnewitem(TkTcontline, 0, &it);
if(e != nil) {
free(opts);
free(env);
return e;
}
e = tktiteminsert(tkt, &ix, it);
if(e != nil) {
tktfreeitems(tkt, it, 1);
free(opts);
free(env);
return e;
}
l = l->prev; /* work on part of line up to split */
if(needsplit) {
/* have to calculate width of pre-split part */
ixprev = ix;
if(tktadjustind(tkt, TkTbyitemback, &ixprev) &&
tktadjustind(tkt, TkTbyitemback, &ixprev)) {
w = tktdispwidth(tk, tb, ixprev.item, nil, x, 0, -1);
ixprev.item->width = w;
x += w;
}
}
else {
h = oh;
a = oa;
}
break;
}
else {
Nosplit:
i->width =w;
x += w;
}
}
if (a > h)
h = a;
if (lh == 0)
lh = f->height;
if (lh > h) {
a += (lh - h) / 2;
h = lh;
}
/*
* Now line l is broken correctly and has correct item widths/line height/ascent.
* Merge adjacent TkTascii/TkTrune items with same tags.
* Also, set act{x,y} of embedded widgets to offset from
* left of item box at baseline.
*/
for(i = l->items; i->next != nil; i = i->next) {
it = i->next;
if( (i->kind == TkTascii || i->kind == TkTrune)
&&
i->kind == it->kind
&&
tktsametags(i, it)) {
n = strlen(i->istring);
j = strlen(it->istring);
s = realloc(i->istring, n + j + 1);
if(s == nil) {
free(opts);
free(env);
return TkNomem;
}
i->istring = s;
memmove(i->istring+n, it->istring, j+1);
i->width += it->width;
i->next = it->next;
it->next = nil;
tktfreeitems(tkt, it, 1);
}
else if(i->kind == TkTwin && i->iwin->sub != nil) {
sub = i->iwin->sub;
n = sub->act.height + 2 * sub->borderwidth;
o = i->iwin->pady;
sub->act.x = i->iwin->padx;
/*
* sub->act.y is y-origin of widget relative to baseline.
*/
switch(i->iwin->align) {
case Tktop:
sub->act.y = o - a;
break;
case Tkbottom:
sub->act.y = h - (o + n) - a;
break;
case Tkcenter:
sub->act.y = (h - n) / 2 - a;
break;
case Tkbaseline:
wa = i->iwin->ascent;
if (wa == -1)
wa = n;
sub->act.y = -wa;
break;
}
}
}
l->width = x - xleft;
/* justification bug: wrong if line has tabs */
l->orig.x = xleft;
n = xright - x;
if(n > 0) {
if(just == Tkright)
l->orig.x += n;
else
if(just == Tkcenter)
l->orig.x += n/2;
}
/* give newline or contline width up to right margin */
ilast = tktlastitem(l->items);
ilast->width = xright - l->width;
if(ilast->width < 0)
ilast->width = 0;
l->orig.y = y;
l->height = h;
l->ascent = a;
y += h;
if(l->flags&TkTlast)
y += sp3;
}
free(opts);
free(env);
tktdrawbg(tk, oldi.lo, oldi.hi, 0);
y += tktprespace(tk, l);
newrest.lo = y;
newrest.hi = y + rest.hi - rest.lo;
hole = tkttranslate(tk, newrest, rest.lo);
tktdrawbg(tk, hole.lo, hole.hi, 0);
if(l != &tkt->end) {
while(l != &tkt->end) {
oh = l->next->orig.y - l->orig.y;
l->orig.y = y;
if(y + oh > hole.lo && y < hole.hi) {
l->flags &= ~TkTdrawn;
}
y += oh;
l = l->next;
}
}
tkt->end.orig.y = tkt->end.prev->orig.y + tkt->end.prev->height;
if(tkt->deltatv.y > tkt->end.orig.y)
tkt->deltatv.y = tkt->end.prev->orig.y;
e = tktsetscroll(tk, Tkvertical);
if(e != nil)
return e;
e = tktsetscroll(tk, Tkhorizontal);
if(e != nil)
return e;
tk->dirty = tkrect(tk, 1);
if(tktdbg)
tktcheck(tkt, "tktfixgeom end");
return nil;
}
static int
tktpostspace(Tk *tk, TkTline *l)
{
int ans;
TkTitem *i;
TkEnv env;
int *opts;
opts = mallocz(TkTnumopts*sizeof(int), 0);
if(opts == nil)
return 0;
ans = 0;
if(l->items != nil && (l->flags&TkTlast)) {
for(i = l->items; i->kind == TkTmark; )
i = i->next;
tkttagopts(tk, i, opts, &env, nil, 1);
ans = opts[TkTspacing3];
}
free(opts);
return ans;
}
static int
tktprespace(Tk *tk, TkTline *l)
{
int ans;
TkTitem *i;
TkEnv env;
int *opts;
opts = mallocz(TkTnumopts*sizeof(int), 0);
if(opts == nil)
return 0;
ans = 0;
if(l->items != nil) {
for(i = l->items; i->kind == TkTmark; )
i = i->next;
tkttagopts(tk, i, opts, &env, nil, 1);
if(l->flags&TkTfirst)
ans = opts[TkTspacing1];
else
ans = opts[TkTspacing2];
}
free(opts);
return ans;
}
static int
tktwidbetween(Tk *tk, int x, TkTindex *i1, TkTindex *i2)
{
int d, w, n;
TkTindex ix;
TkText *tkt = TKobj(TkText, tk);
w = 0;
ix = *i1;
while(ix.item != i2->item) {
/* probably wrong w.r.t tag tabs */
d = tktdispwidth(tk, nil, ix.item, nil, x, ix.pos, -1);
w += d;
x += d;
if(!tktadjustind(tkt, TkTbyitem, &ix)) {
if(tktdbg)
print("tktwidbetween botch\n");
break;
}
}
n = i2->pos - ix.pos;
if(n > 0)
/* probably wrong w.r.t tag tabs */
w += tktdispwidth(tk, nil, ix.item, nil, x, ix.pos, i2->pos-ix.pos);
return w;
}
static Interval
tktvclip(Interval i, int vh)
{
if(i.lo < 0)
i.lo = 0;
if(i.hi > vh)
i.hi = vh;
return i;
}
/*
* Do translation of any part of interval that appears on screen
* starting at srcy to its new position, dsti.
* Return y-range of the hole left in the image (either because
* the src bits were out of the V window, or because the src bits
* vacated an area of the V window).
* The coordinates passed in and out are in T space.
*/
static Interval
tkttranslate(Tk *tk, Interval dsti, int srcy)
{
int vh, vw, dvty, locked;
TkText *tkt;
Image *i;
Interval hole, vdst, vsrc;
Point src;
Rectangle dst;
Display *d;
hole.hi = 0;
hole.lo = 0;
/*
* If we are embedded in a text widget, we need to come in through
* the tkdrawtext routine, to ensure our clipr is set properly, so we
* just punt in that case.
* XXX is just checking parent good enough. what if we're in
* a frame in a text widget?
* BUG!
* if(tk->parent != nil && tk->parent->type == TKtext) {
* tk->flag |= Tkrefresh;
* return hole;
* }
*/
tkt = TKobj(TkText, tk);
dvty = tkt->deltatv.y;
i = tkt->image;
vw = tk->act.width - tk->ipad.x;
vh = tk->act.height - tk->ipad.y;
/* convert to V space */
vdst.lo = dsti.lo - dvty;
vdst.hi = dsti.hi - dvty;
vsrc.lo = srcy - dvty;
vsrc.hi = vsrc.lo + dsti.hi - dsti.lo;
if(vsrc.lo == vsrc.hi || vsrc.lo == vdst.lo)
return hole;
else if(vsrc.hi <= 0 || vsrc.lo >= vh)
hole = tktvclip(vdst, vh);
else if(vdst.hi <= 0 || vdst.lo >= vh)
hole = tktvclip(vsrc, vh);
else if(i != nil) {
src.x = 0;
src.y = vsrc.lo;
if(vdst.lo > vsrc.lo) { /* see earlier text lines */
if(vsrc.lo < 0) {
src.y = 0;
vdst.lo -= vsrc.lo;
}
if(vdst.hi > vh)
vdst.hi = vh;
hole.lo = src.y;
hole.hi = vdst.lo;
}
else { /* see later text lines */
if(vsrc.hi > vh)
vdst.hi -= (vsrc.hi - vh);
if(vdst.lo < 0){
src.y -= vdst.lo;
vdst.lo = 0;
}
hole.lo = vdst.hi;
hole.hi = src.y + (vdst.hi - vdst.lo);
}
if(vdst.hi > vdst.lo && (tkt->tflag&TkTdrawn)) {
src = addpt(src, tkt->deltaiv);
dst = rectaddpt(Rect(0, vdst.lo, vw, vdst.hi), tkt->deltaiv);
d = tk->env->top->display;
locked = 0;
if(!(tkt->tflag&TkTdlocked))
locked = lockdisplay(d);
i = tkimageof(tk);
tkt->image = i;
if(i != nil)
draw(i, dst, i, nil, src);
if(locked)
unlockdisplay(d);
}
}
hole.lo += dvty;
hole.hi += dvty;
return hole;
}
/*
* mark lines from firsty to lasty as not drawn.
* firsty and lasty are in T space
*/
static void
tktnotdrawn(Tk *tk, int firsty, int lasty, int all)
{
TkTline *lend, *l;
TkText *tkt = TKobj(TkText, tk);
if(firsty >= lasty && !all)
return;
lend = &tkt->end;
for(l = tkt->start.next; l != lend; l = l->next) {
if(l->orig.y+l->height <= firsty)
continue;
if(l->orig.y >= lasty)
break;
l->flags &= ~TkTdrawn;
if (firsty > l->orig.y)
firsty = l->orig.y;
if (lasty < l->orig.y+l->height)
lasty = l->orig.y+l->height;
}
tktdrawbg(tk, firsty, lasty, all);
tk->dirty = tkrect(tk, 1);
}
/*
* firsty and lasty are in T space
*/
static void
tktdrawbg(Tk *tk, int firsty, int lasty, int all)
{
int vw, vh, locked;
Rectangle r;
Image *i;
Display *d;
TkText *tkt = TKobj(TkText, tk);
if(tk->env->top->root->flag & Tksuspended){
tk->flag |= Tkrefresh;
return;
}
/*
* If we are embedded in a text widget, we need to come in through
* the tkdrawtext routine, to ensure our clipr is set properly, so we
* just punt in that case.
* BUG!
* if(tk->parent != nil && tk->parent->type == TKtext) {
* tk->flag |= Tkrefresh;
* return;
* }
*/
vw = tk->act.width - tk->ipad.x;
vh = tk->act.height - tk->ipad.y;
if(all) {
/* whole background is to be drawn, not just until last line */
firsty = 0;
lasty = 100000;
}
if(firsty >= lasty)
return;
firsty -= tkt->deltatv.y;
lasty -= tkt->deltatv.y;
if(firsty < 0)
firsty = 0;
if(lasty > vh)
lasty = vh;
r = rectaddpt(Rect(0, firsty, vw, lasty), tkt->deltaiv);
if(r.min.y < r.max.y && (tkt->tflag&TkTdrawn)) {
d = tk->env->top->display;
locked = 0;
if(!(tkt->tflag&TkTdlocked))
locked = lockdisplay(d);
i = tkimageof(tk);
tkt->image = i;
if(i != nil)
draw(i, r, tkgc(tk->env, TkCbackgnd), nil, ZP);
if(locked)
unlockdisplay(d);
}
}
static void
tktfixscroll(Tk *tk, Point odeltatv)
{
int lasty;
Interval oi, hole;
Rectangle oclipr;
Image *dst;
Point ndeltatv;
TkText *tkt = TKobj(TkText, tk);
ndeltatv = tkt->deltatv;
if(eqpt(odeltatv, ndeltatv))
return;
/* set clipr to avoid spilling outside (in case didn't come in through draw) */
dst = tkimageof(tk);
if(dst != nil) {
tkt->image = dst;
oclipr = dst->clipr;
tktsetclip(tk);
}
lasty = tkt->end.orig.y;
if(odeltatv.x != ndeltatv.x)
tktnotdrawn(tk, ndeltatv.y, lasty, 0);
else {
oi.lo = odeltatv.y;
oi.hi = lasty;
hole = tkttranslate(tk, oi, ndeltatv.y);
tktnotdrawn(tk, hole.lo, hole.hi, 0);
}
if(dst != nil)
tktreplclipr(dst, oclipr);
}
void
tktextgeom(Tk *tk)
{
TkTindex ix;
Rectangle oclipr;
Image *dst;
TkText *tkt = TKobj(TkText, tk);
char buf[20], *p;
tkt->tflag &= ~TkTdrawn;
tktsetdeltas(tk, ZP);
/* find index of current top-left, so can see it again */
tktxyind(tk, 0, 0, &ix);
/* make sure scroll bar is redrawn */
tkt->scrolltop[Tkvertical] = -1;
tkt->scrolltop[Tkhorizontal] = -1;
tkt->scrollbot[Tkvertical] = -1;
tkt->scrollbot[Tkhorizontal] = -1;
/* set clipr to avoid spilling outside (didn't come in through draw) */
dst = tkimageof(tk);
if(dst != nil) {
tkt->image = dst;
oclipr = dst->clipr;
tktsetclip(tk);
}
/*
* have to save index in a reusable format, as
* tktfixgeom can free everything that ix points to.
*/
snprint(buf, sizeof(buf), "%d.%d", tktlinenum(tkt, &ix), tktlinepos(tkt, &ix));
tktfixgeom(tk, &tkt->start, tkt->end.prev, 1);
p = buf;
tktindparse(tk, &p, &ix); /* restore index to something close to original value */
tktsee(tk, &ix, 1);
if(dst != nil)
tktreplclipr(dst, oclipr);
}
static char*
tktsetscroll(Tk *tk, int orient)
{
TkText *tkt;
TkTline *l;
int ntot, nmin, nmax, top, bot, vw, vh;
char *val, *cmd, *v, *e, *s;
tkt = TKobj(TkText, tk);
s = (orient == Tkvertical)? tkt->yscroll : tkt->xscroll;
if(s == nil)
return nil;
vw = tk->act.width - tk->ipad.x;
vh = tk->act.height - tk->ipad.y;
if(orient == Tkvertical) {
l = tkt->end.prev;
ntot = l->orig.y + l->height;
nmin = tkt->deltatv.y;
if(vh <= 0)
nmax = nmin;
else
nmax = nmin + vh;
}
else {
ntot = tktmaxwid(tkt->start.next);
nmin = tkt->deltatv.x;
if(vw <= 0)
nmax = nmin;
else
nmax = nmin + vw;
}
if(ntot == 0) {
top = 0;
bot = TKI2F(1);
}
else {
if(ntot < nmax)
ntot = nmax;
top = TKI2F(nmin)/ntot;
bot = TKI2F(nmax)/ntot;
}
if(tkt->scrolltop[orient] == top && tkt->scrollbot[orient] == bot)
return nil;
tkt->scrolltop[orient] = top;
tkt->scrollbot[orient] = bot;
val = mallocz(Tkminitem, 0);
if(val == nil)
return TkNomem;
cmd = mallocz(Tkmaxitem, 0);
if(cmd == nil) {
free(val);
return TkNomem;
}
v = tkfprint(val, top);
*v++ = ' ';
tkfprint(v, bot);
snprint(cmd, Tkmaxitem, "%s %s", s, val);
e = tkexec(tk->env->top, cmd, nil);
free(cmd);
free(val);
return e;
}
static char*
tktview(Tk *tk, char *arg, char **val, int nl, int *posn, int max, int orient)
{
int top, bot, amount, n;
char buf[Tkminitem], *v, *e;
if(*arg == '\0') {
if ( max == 0 ) {
top = 0;
bot = TKI2F(1);
}
else {
top = TKI2F(*posn)/max;
bot = TKI2F(*posn+nl)/max;
if (bot > TKI2F(1))
bot = TKI2F(1);
}
v = tkfprint(buf, top);
*v++ = ' ';
tkfprint(v, bot);
return tkvalue(val, "%s", buf);
}
arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
if(strcmp(buf, "moveto") == 0) {
e = tkfracword(tk->env->top, &arg, &top, nil);
if (e != nil)
return e;
*posn = TKF2I(top*max);
}
else
if(strcmp(buf, "scroll") == 0) {
e = tkfracword(tk->env->top, &arg, &amount, nil);
if(e != nil)
return e;
arg = tkskip(arg, " \t");
if(*arg == 'p') /* Pages */
amount *= nl;
else /* Lines or Characters */
if(orient == Tkvertical) {
/* XXX needs improvement */
amount *= tk->env->font->height;
}
else
amount *= tk->env->wzero;
amount = TKF2I(amount);
n = *posn + amount;
if(n < 0)
n = 0;
if(n > max)
n = max;
*posn = n;
}
else
return TkBadcm;
bot = max - (nl * 3 / 4);
if(*posn > bot)
*posn = bot;
if(*posn < 0)
*posn = 0;
return nil;
}
static void
tktclearsel(Tk *tk)
{
TkTindex ibeg, iend;
TkText *tkt = TKobj(TkText, tk);
if(tkt->selfirst == nil)
return;
tktitemind(tkt->selfirst, &ibeg);
tktitemind(tkt->sellast, &iend);
tkttagchange(tk, TkTselid, &ibeg, &iend, 0);
}
static int
tktgetsel(Tk *tk, TkTindex *i1, TkTindex *i2)
{
TkText *tkt =TKobj(TkText, tk);
if(tkt->selfirst == nil)
return 0;
tktitemind(tkt->selfirst, i1);
tktitemind(tkt->sellast, i2);
return 1;
}
/*
* Adjust tkt->deltatv so that indexed character is visible.
* - if seetop is true, make indexed char be at top of window
* - if it is already visible, do nothing.
* - if it is > 1/2 screenful off edge of screen, center it
* else put it at bottom or top (whichever is nearer)
* - if first line is visible, put it at top
* - if last line is visible, allow one blank line at bottom
*
* BUG: should handle x visibility too
*/
static void
tktsee(Tk *tk, TkTindex *ixp, int seetop)
{
int ycur, ynext, deltatvy, adjy, h;
Point p, odeltatv;
Rectangle bbox;
TkTline *l, *el;
TkText *tkt = TKobj(TkText, tk);
TkTindex ix;
ix = *ixp;
deltatvy = tkt->deltatv.y;
odeltatv = tkt->deltatv;
h = tk->act.height;
/* find p (in T space): top left of indexed line */
l = ix.line;
p = l->orig;
/* ycur, ynext in V space */
ycur = p.y - deltatvy;
ynext = ycur + l->height;
adjy = 0;
/* quantize h to line boundaries (works if single font) */
if ( l->height )
h -= h%l->height;
if(seetop) {
deltatvy = p.y;
adjy = 1;
}
else
if(ycur < 0 || ynext >= h) {
adjy = 1;
if(ycur < -h/2 || ycur > 3*h/2)
deltatvy = p.y - h/2;
else if(ycur < 0)
deltatvy = p.y;
else
deltatvy = p.y - h + l->height;
el = tkt->end.prev;
if(el != nil && el->orig.y - deltatvy < h)
deltatvy = tkt->end.orig.y - (h * 3 / 4);
if(p.y - deltatvy < 0)
deltatvy = p.y;
if(deltatvy < 0)
deltatvy = 0;
}
if(adjy) {
tkt->deltatv.y = deltatvy;
tktsetscroll(tk, Tkvertical); /* XXX - Tad: err ignored */
tktfixscroll(tk, odeltatv);
}
while (ix.item->kind == TkTmark)
ix.item = ix.item->next;
bbox = tktbbox(tk, &ix);
/* make sure that cursor at the end gets shown */
tksee(tk, bbox, Pt(bbox.min.x, (bbox.min.y + bbox.max.y) / 2));
}
static int
tktcmatch(int c1, int c2, int nocase)
{
if(nocase) {
if(c1 >= 'a' && c1 <= 'z')
c1 -= 'a' - 'A';
if(c2 >= 'a' && c2 <= 'z')
c2 -= 'a' - 'A';
}
return (c1 == c2);
}
/*
* Return 1 if tag with id m1 ends before tag with id m2,
* starting at the item after that indexed in ix (but don't
* modify ix).
*/
static int
tagendsbefore(TkText *tkt, TkTindex *ix, int m1, int m2)
{
int s1, s2;
TkTindex ix1;
TkTitem *i;
ix1 = *ix;
while(tktadjustind(tkt, TkTbyitem, &ix1)) {
i = ix1.item;
if(i->kind == TkTwin || i->kind == TkTcontline || i->kind == TkTmark)
continue;
s1 = tkttagset(i, m1);
s2 = tkttagset(i, m2);
if(!s1)
return s2;
else if(!s2)
return 0;
}
return 0;
}
static int
tktsgmltags(TkText *tkt, Fmt *fmt, TkTitem *iprev, TkTitem *i, TkTindex *ix, int *stack, int *pnstack, int *tmpstack)
{
int nprev, n, m, r, k, j, ii, onstack, nt;
nprev = 0;
if(iprev != nil && (iprev->tags[0] != 0 || iprev->tagextra > 0))
nprev = 32*(iprev->tagextra + 1);
n = 0;
if(i != nil && (i->tags[0] != 0 || i->tagextra > 0))
n = 32*(i->tagextra + 1);
nt = 0;
if(n > 0) {
/* find tags which open here */
for(m = 0; m < n; m++)
if(tkttagset(i, m) && (iprev == nil || !tkttagset(iprev, m)))
tmpstack[nt++] = m;
}
if(nprev > 0) {
/*
* Find lowest tag in stack that ends before any tag beginning here.
* We have to emit end tags all the way down to there, then add
* back the ones that haven't actually ended here, together with ones
* that start here, and sort all of the added ones so that tags that
* end later are lower in the stack.
*/
ii = *pnstack;
for(k = *pnstack - 1; k >=0; k--) {
m = stack[k];
if(i == nil || !tkttagset(i, m))
ii = k;
else
for(j = 0; j < nt; j++)
if(tagendsbefore(tkt, ix, m, tmpstack[j]))
ii = k;
}
for(k = *pnstack - 1; k >= ii; k--) {
m = stack[k];
r = fmtprint(fmt, "</%s>", tkttagname(tkt, m));
if(r < 0)
return r;
/* add m back to starting tags if m didn't actually end here */
if(i != nil && tkttagset(i, m))
tmpstack[nt++] = m;
}
*pnstack = ii;
}
if(nt > 0) {
/* add tags which open or reopen here */
onstack = *pnstack;
k = onstack;
for(j = 0; j < nt; j++)
stack[k++] = tmpstack[j];
*pnstack = k;
if(k - onstack > 1) {
/* sort new stack entries so tags that end later are lower in stack */
for(ii = k-2; ii>= onstack; ii--) {
m = stack[ii];
for(j = ii+1; j < k && tagendsbefore(tkt, ix, m, stack[j]); j++) {
stack[j-1] = stack[j];
}
stack[j-1] = m;
}
}
for(j = onstack; j < k; j++) {
r = fmtprint(fmt, "<%s>", tkttagname(tkt, stack[j]));
if(r < 0)
return r;
}
}
return 0;
}
/*
* In 'sgml' format, just print text (no special treatment of
* special characters, except that < turns into <)
* interspersed with things like <Bold> and </Bold>
* (where Bold is a tag name).
* Make sure that the tag pairs nest properly.
*/
static char*
tktget(TkText *tkt, TkTindex *ix1, TkTindex *ix2, int sgml, char **val)
{
int n, m, i, bychar, nstack;
int *stack, *tmpstack;
char *s;
TkTitem *iprev;
Tk *sub;
Fmt fmt;
char *buf;
if(!tktindbefore(ix1, ix2))
return nil;
stack = nil;
tmpstack = nil;
iprev = nil;
fmtstrinit(&fmt);
buf = mallocz(100, 0);
if(buf == nil)
return TkNomem;
if(sgml) {
stack = malloc((tkt->nexttag+1)*sizeof(int));
tmpstack = malloc((tkt->nexttag+1)*sizeof(int));
if(stack == nil || tmpstack == nil)
goto nomemret;
nstack = 0;
}
for(;;) {
if(ix1->item == ix2->item && ix1->pos == ix2->pos)
break;
s = nil;
bychar = 0;
m = 1;
switch(ix1->item->kind) {
case TkTrune:
s = ix1->item->istring;
s += tktutfpos(s, ix1->pos);
if(ix1->item == ix2->item) {
m = ix2->pos - ix1->pos;
bychar = 1;
}
break;
case TkTascii:
s = ix1->item->istring + ix1->pos;
if(ix1->item == ix2->item) {
m = ix2->pos - ix1->pos;
bychar = 1;
}
else {
m = strlen(s);
if(sgml && memchr(s, '<', m) != nil)
bychar = 1;
}
break;
case TkTtab:
s = "\t";
break;
case TkTnewline:
s = "\n";
break;
case TkTwin:
sub = ix1->item->iwin->sub;
if(sgml && sub != nil && sub->name != nil) {
snprint(buf, 100, "<Window %s>", sub->name->name);
s = buf;
}
}
if(s != nil) {
if(sgml) {
n = tktsgmltags(tkt, &fmt, iprev, ix1->item, ix1, stack, &nstack, tmpstack);
if(n < 0)
goto nomemret;
}
if(bychar) {
if (ix1->item->kind == TkTrune)
n = fmtprint(&fmt, "%.*s", m, s);
else {
n = 0;
for(i = 0; i < m && n >= 0; i++) {
if(s[i] == '<')
n = fmtprint(&fmt, "<");
else
n = fmtprint(&fmt, "%c", s[i]);
}
}
}
else
n = fmtprint(&fmt, "%s", s);
if(n < 0)
goto nomemret;
iprev = ix1->item;
}
if(ix1->item == ix2->item)
break;
if(!tktadjustind(tkt, TkTbyitem, ix1)) {
if(tktdbg)
print("tktextget botch\n");
break;
}
}
if(sgml) {
n = tktsgmltags(tkt, &fmt, iprev, nil, nil, stack, &nstack, tmpstack);
if(n < 0)
goto nomemret;
}
*val = fmtstrflush(&fmt);
free(buf);
return nil;
nomemret:
free(buf);
if(stack != nil)
free(stack);
if(tmpstack != nil)
free(tmpstack);
return TkNomem;
}
/* Widget Commands (+ means implemented)
+bbox
+cget
+compare
+configure
+debug
+delete
+dlineinfo
+dump
+get
+index
+insert
+mark
+scan
+search
+see
+tag
+window
+xview
+yview
*/
static int
tktviewrectclip(Rectangle *r, Rectangle b);
static char*
tktextbbox(Tk *tk, char *arg, char **val)
{
char *e;
int noclip, w, h;
Rectangle r, rview;
TkTindex ix;
TkText *tkt;
char buf[Tkmaxitem];
e = tktindparse(tk, &arg, &ix);
if(e != nil)
return e;
noclip = 0;
if(*arg != '\0') {
/* extension to tk4.0:
* "noclip" means don't clip to viewable area
* "all" means give unclipped bbox of entire contents
*/
arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
if(strcmp(buf, "noclip") == 0)
noclip = 1;
else
if(strcmp(buf, "all") == 0) {
tkt = TKobj(TkText, tk);
w = tktmaxwid(tkt->start.next);
h = tkt->end.orig.y;
return tkvalue(val, "0 0 %d %d", w, h);
}
}
/*
* skip marks; bbox applies to characters only.
* it's not defined what happens when bbox is applied to a newline char,
* so we'll just let the default case sort that out.
*/
while (ix.item->kind == TkTmark)
ix.item = ix.item->next;
r = tktbbox(tk, &ix);
rview.min.x = 0;
rview.min.y = 0;
rview.max.x = tk->act.width - tk->ipad.x;
rview.max.y = tk->act.height - tk->ipad.y;
if(noclip || tktviewrectclip(&r, rview))
return tkvalue(val, "%d %d %d %d", r.min.x, r.min.y,
r.max.x-r.min.x, r.max.y-r.min.y);
return nil;
}
/*
* a supplemented rectclip, as ((0, 1), (0,1)) does not intersect ((0, 0), (5, 5))
* but for our purposes, we want it to. it's a hack.
*/
static int
tktviewrectclip(Rectangle *rp, Rectangle b)
{
Rectangle *bp = &b;
if((rp->min.x<bp->max.x &&
(bp->min.x<rp->max.x || (rp->max.x == b.min.x
&& rp->min.x == b.min.x)) &&
rp->min.y<bp->max.y && bp->min.y<rp->max.y)==0)
return 0;
/* They must overlap */
if(rp->min.x < bp->min.x)
rp->min.x = bp->min.x;
if(rp->min.y < bp->min.y)
rp->min.y = bp->min.y;
if(rp->max.x > bp->max.x)
rp->max.x = bp->max.x;
if(rp->max.y > bp->max.y)
rp->max.y = bp->max.y;
return 1;
}
static Point
scr2local(Tk *tk, Point p)
{
p = subpt(p, tkposn(tk));
p.x -= tk->borderwidth;
p.y -= tk->borderwidth;
return p;
}
static char*
tktextbutton1(Tk *tk, char *arg, char **val)
{
char *e;
Point p;
TkCtxt *c;
TkTindex ix;
TkTmarkinfo *mi;
TkText *tkt = TKobj(TkText, tk);
USED(val);
e = tkxyparse(tk, &arg, &p);
if(e != nil)
return e;
tkt->track = p;
p = scr2local(tk, p);
tktxyind(tk, p.x, p.y, &ix);
tkt->tflag &= ~TkTjustfoc;
c = tk->env->top->ctxt;
if(!(tk->flag&Tkdisabled) && c->tkkeygrab != tk
&& (tk->name != nil) && ix.item->kind != TkTwin) {
tkfocus(tk->env->top, tk->name->name, nil);
tkt->tflag |= TkTjustfoc;
return nil;
}
mi = tktfindmark(tkt->marks, "insert");
if(tktdbg && !mi) {
print("tktextbutton1: botch\n");
return nil;
}
tktmarkmove(tk, mi, &ix);
tktclearsel(tk);
tkrepeat(tk, autoselect, nil, TkRptpause, TkRptinterval);
return nil;
}
static char*
tktextbutton1r(Tk *tk, char *arg, char **val)
{
TkText *tkt;
USED(arg);
USED(val);
tkt = TKobj(TkText, tk);
tkt->tflag &= ~TkTnodrag;
tkcancelrepeat(tk);
return nil;
}
static char*
tktextcget(Tk *tk, char *arg, char **val)
{
TkText *tkt;
TkOptab tko[3];
tkt = TKobj(TkText, tk);
tko[0].ptr = tk;
tko[0].optab = tkgeneric;
tko[1].ptr = tkt;
tko[1].optab = textopts;
tko[2].ptr = nil;
return tkgencget(tko, arg, val, tk->env->top);
}
static char*
tktextcompare(Tk *tk, char *arg, char **val)
{
int op;
char *e;
TkTindex i1, i2;
TkText *tkt;
TkStab *s;
char *buf;
tkt = TKobj(TkText, tk);
e = tktindparse(tk, &arg, &i1);
if(e != nil)
return e;
if(*arg == '\0')
return TkBadcm;
buf = mallocz(Tkmaxitem, 0);
if(buf == nil)
return TkNomem;
arg = tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil);
op = -1;
for(s = tkcompare; s->val; s++)
if(strcmp(s->val, buf) == 0) {
op = s->con;
break;
}
if(op == -1) {
free(buf);
return TkBadcm;
}
e = tktindparse(tk, &arg, &i2);
if(e != nil) {
free(buf);
return e;
}
e = tkvalue(val, tktindcompare(tkt, &i1, op, &i2)? "1" : "0");
free(buf);
return e;
}
static char*
tktextconfigure(Tk *tk, char *arg, char **val)
{
char *e;
TkGeom g;
int bd;
TkText *tkt;
TkOptab tko[3];
tkt = TKobj(TkText, tk);
tko[0].ptr = tk;
tko[0].optab = tkgeneric;
tko[1].ptr = tkt;
tko[1].optab = textopts;
tko[2].ptr = nil;
if(*arg == '\0')
return tkconflist(tko, val);
g = tk->req;
bd = tk->borderwidth;
e = tkparse(tk->env->top, arg, tko, nil);
tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd));
if (tkt->propagate != BoolT) {
if ((tk->flag & Tksetwidth) == 0)
tk->req.width = tk->env->wzero*Textwidth;
if ((tk->flag & Tksetheight) == 0)
tk->req.height = tk->env->font->height*Textheight;
}
/* note: tkgeomchg() may also call tktfixgeom() via tktextgeom() */
tktfixgeom(tk, &tkt->start, tkt->end.prev, 0);
tktextsize(tk, 0);
tkgeomchg(tk, &g, bd);
tktnotdrawn(tk, 0, tkt->end.orig.y, 1);
return e;
}
static char*
tktextdebug(Tk *tk, char *arg, char **val)
{
char buf[Tkmaxitem];
tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
if(*buf == '\0')
return tkvalue(val, "%s", tktdbg? "on" : "off");
else {
tktdbg = (strcmp(buf, "1") == 0 || strcmp(buf, "yes") == 0);
if(tktdbg) {
tktprinttext(TKobj(TkText, tk));
}
return nil;
}
}
static char*
tktextdelete(Tk *tk, char *arg, char **val)
{
int sameit;
char *e;
TkTindex i1, i2, ip, isee;
TkTline *lmin;
TkText *tkt = TKobj(TkText, tk);
char buf[20], *p;
USED(val);
e = tktindparse(tk, &arg, &i1);
if(e != nil)
return e;
tktadjustind(tkt, TkTbycharstart, &i1);
e = tktsplititem(&i1);
if(e != nil)
return e;
if(*arg != '\0') {
e = tktindparse(tk, &arg, &i2);
if(e != nil)
return e;
}
else {
i2 = i1;
tktadjustind(tkt, TkTbychar, &i2);
}
if(tktindcompare(tkt, &i1, TkGte, &i2))
return nil;
sameit = (i1.item == i2.item);
/* save possible fixup see place */
isee.line = nil;
if(i2.line->orig.y + i2.line->height < tkt->deltatv.y) {
/* delete completely precedes view */
tktxyind(tk, 0, 0, &isee);
}
e = tktsplititem(&i2);
if(e != nil)
return e;
if(sameit) {
/* after split, i1 should be in previous item to i2 */
ip = i2;
tktadjustind(tkt, TkTbyitemback, &ip);
i1.item = ip.item;
}
lmin = tktprevwrapline(tk, i1.line);
while(i1.item != i2.item) {
if(i1.item->kind != TkTmark)
tktremitem(tkt, &i1);
/* tktremitem moves i1 to next item */
else {
if(!tktadjustind(tkt, TkTbyitem, &i1)) {
if(tktdbg)
print("tktextdelete botch\n");
break;
}
}
}
/*
* guard against invalidation of index by tktfixgeom
*/
if (isee.line != nil)
snprint(buf, sizeof(buf), "%d.%d", tktlinenum(tkt, &isee), tktlinepos(tkt, &isee));
tktfixgeom(tk, lmin, i1.line, 0);
tktextsize(tk, 1);
if(isee.line != nil) {
p = buf;
tktindparse(tk, &p, &isee);
tktsee(tk, &isee, 1);
}
return nil;
}
static char*
tktextsee(Tk *tk, char *arg, char **val)
{
char *e;
TkTindex ix;
USED(val);
e = tktindparse(tk, &arg, &ix);
if(e != nil)
return e;
tktsee(tk, &ix, 0);
return nil;
}
static char*
tktextdelins(Tk *tk, char *arg, char **val)
{
int m, c, skipping, wordc, n;
TkTindex ix, ix2;
TkText *tkt = TKobj(TkText, tk);
char buf[30];
USED(val);
if(tk->flag&Tkdisabled)
return nil;
if(tktgetsel(tk, &ix, &ix2))
tktextdelete(tk, "sel.first sel.last", nil);
else {
while(*arg == ' ')
arg++;
if(*arg == '-') {
m = arg[1];
if(m == 'c')
n = 1;
else {
/* delete prev word (m=='w') or prev line (m=='l') */
if(!tktmarkind(tk, "insert", &ix))
return nil;
if(!tktadjustind(tkt, TkTbycharback, &ix))
return nil;
n = 1;
/* ^W skips back over nonwordchars, then takes maximal seq of wordchars */
skipping = 1;
for(;;) {
c = tktindrune(&ix);
if(c == '\n') {
/* special case: always delete at least one char */
if(n > 1)
n--;
break;
}
if(m == 'w') {
wordc = tkiswordchar(c);
if(wordc && skipping)
skipping = 0;
else if(!wordc && !skipping) {
n--;
break;
}
}
if(tktadjustind(tkt, TkTbycharback, &ix))
n++;
else
break;
}
}
sprint(buf, "insert-%dc insert", n);
tktextdelete(tk, buf, nil);
}
else if(arg[0] == '+' && arg[1] == 'l')
tktextdelete(tk, "insert {insert lineend}", nil);
else
tktextdelete(tk, "insert", nil);
tktextsee(tk, "insert", nil);
}
return nil;
}
static char*
tktextdlineinfo(Tk *tk, char *arg, char **val)
{
char *e;
TkTindex ix;
TkTline *l;
Point p;
int vh;
TkText *tkt = TKobj(TkText, tk);
e = tktindparse(tk, &arg, &ix);
if(e != nil)
return e;
l = ix.line;
vh = tk->act.height;
/* get p in V space */
p = subpt(l->orig, tkt->deltatv);
if(p.y+l->height < 0 || p.y >= vh)
return nil;
return tkvalue(val, "%d %d %d %d %d",
p.x, p.y, l->width, l->height, l->ascent);
}
static char*
tktextdump(Tk *tk, char *arg, char **val)
{
TkTline *l;
TkTitem *i;
Fmt fmt;
TkText *tkt;
TkDump tkdump;
TkOptab tko[2];
TkTtaginfo *ti;
TkName *names, *n;
char *e, *win, *p;
TkTindex ix1, ix2;
int r, j, numitems;
ulong fg, bg;
tkt = TKobj(TkText, tk);
tkdump.sgml = 0;
tkdump.metrics = 0;
tko[0].ptr = &tkdump;
tko[0].optab = dumpopts;
tko[1].ptr = nil;
names = nil;
e = tkparse(tk->env->top, arg, tko, &names);
if(e != nil)
return e;
if(names != nil) { /* supplied indices */
p = names->name;
e = tktindparse(tk, &p, &ix1);
if(e != nil) {
tkfreename(names);
return e;
}
n = names->link;
if(n != nil) {
p = n->name;
e = tktindparse(tk, &p, &ix2);
if(e != nil) {
tkfreename(names);
return e;
}
}
else {
ix2 = ix1;
tktadjustind(tkt, TkTbychar, &ix2);
}
tkfreename(names);
if(!tktindbefore(&ix1, &ix2))
return nil;
}
else
return TkBadix;
if(tkdump.metrics != 0) {
fmtstrinit(&fmt);
if(fmtprint(&fmt, "%%Fonts\n") < 0)
return TkNomem;
for(ti=tkt->tags; ti != nil; ti=ti->next) {
if(ti->env == nil || ti->env->font == nil)
continue;
if(fmtprint(&fmt, "%d::%s\n", ti->id,ti->env->font->name) < 0)
return TkNomem;
}
if(fmtprint(&fmt, "-1::%s\n%%Colors\n", tk->env->font->name) < 0)
return TkNomem;
for(ti=tkt->tags; ti != nil; ti=ti->next) {
if(ti->env == nil)
continue;
bg = ti->env->colors[TkCbackgnd];
fg = ti->env->colors[TkCforegnd];
if(bg == tk->env->colors[TkCbackgnd] &&
fg == ti->env->colors[TkCforegnd])
continue;
r = fmtprint(&fmt,"%d::#%.8lux\n", ti->id, bg);
if(r < 0)
return TkNomem;
r = fmtprint(&fmt,"%d::#%.8lux\n", ti->id, fg);
if(r < 0)
return TkNomem;
}
if(fmtprint(&fmt, "%%Lines\n") < 0)
return TkNomem;
/*
* In 'metrics' format lines are recorded in the following way:
* xorig yorig wd ht as [data]
* where data is of the form:
* CodeWidth{tags} data
* For Example;
* A200{200000} Hello World!
* denotes an A(scii) contiguous string of 200 pixels with
* bit 20 set in its tags which corresponds to some font.
*
*/
if(ix2.line->items != ix2.item)
ix2.line = ix2.line->next;
for(l = ix1.line; l != ix2.line; l = l->next) {
numitems = 0;
for(i = l->items; i != nil; i = i->next) {
if(i->kind != TkTmark)
numitems++;
}
r = fmtprint(&fmt, "%d %d %d %d %d %d ",
l->orig.x, l->orig.y, l->width, l->height, l->ascent,numitems);
if(r < 0)
return TkNomem;
for(i = l->items; i != nil; i = i->next) {
switch(i->kind) {
case TkTascii:
case TkTrune:
r = i->kind == TkTascii ? 'A' : 'R';
if(fmtprint(&fmt,"[%c%d{", r, i->width) < 0)
return TkNomem;
if(i->tags !=0 || i->tagextra !=0) {
if(fmtprint(&fmt,"%lux", i->tags[0]) < 0)
return TkNomem;
for(j=0; j < i->tagextra; j++)
if(fmtprint(&fmt,"::%lux", i->tags[j+1]) < 0)
return TkNomem;
}
/* XXX string should be quoted to avoid embedded ']'s */
if(fmtprint(&fmt,"}%s]", i->istring) < 0)
return TkNomem;
break;
case TkTnewline:
case TkTcontline:
r = i->kind == TkTnewline ? 'N' : 'C';
if(fmtprint(&fmt, "[%c]", r) < 0)
return TkNomem;
break;
case TkTtab:
if(fmtprint(&fmt,"[T%d]",i->width) < 0)
return TkNomem;
break;
case TkTwin:
win = "<null>";
if(i->iwin->sub != nil)
win = i->iwin->sub->name->name;
if(fmtprint(&fmt,"[W%d %s]",i->width, win) < 0)
return TkNomem;
break;
}
if(fmtprint(&fmt, " ") < 0)
return TkNomem;
}
if(fmtprint(&fmt, "\n") < 0)
return TkNomem;
*val = fmtstrflush(&fmt);
if(*val == nil)
return TkNomem;
}
}
else
return tktget(tkt, &ix1, &ix2, tkdump.sgml, val);
return nil;
}
static char*
tktextget(Tk *tk, char *arg, char **val)
{
char *e;
TkTindex ix1, ix2;
TkText *tkt = TKobj(TkText, tk);
e = tktindparse(tk, &arg, &ix1);
if(e != nil)
return e;
if(*arg != '\0') {
e = tktindparse(tk, &arg, &ix2);
if(e != nil)
return e;
}
else {
ix2 = ix1;
tktadjustind(tkt, TkTbychar, &ix2);
}
return tktget(tkt, &ix1, &ix2, 0, val);
}
static char*
tktextindex(Tk *tk, char *arg, char **val)
{
char *e;
TkTindex ix;
TkText *tkt = TKobj(TkText, tk);
e = tktindparse(tk, &arg, &ix);
if(e != nil)
return e;
return tkvalue(val, "%d.%d", tktlinenum(tkt, &ix), tktlinepos(tkt, &ix));
}
static char*
tktextinsert(Tk *tk, char *arg, char **val)
{
int n;
char *e, *p, *pe;
TkTindex ins, pins;
TkTtaginfo *ti;
TkText *tkt;
TkTline *lmin;
TkTop *top;
TkTitem *tagit;
char *tbuf, *buf;
USED(val);
tkt = TKobj(TkText, tk);
top = tk->env->top;
e = tktindparse(tk, &arg, &ins);
if(e != nil)
return e;
if(ins.item->kind == TkTmark) {
if(ins.item->imark->gravity == Tkleft) {
while(ins.item->kind == TkTmark && ins.item->imark->gravity == Tkleft)
if(!tktadjustind(tkt, TkTbyitem, &ins)) {
if(tktdbg)
print("tktextinsert botch\n");
break;
}
}
else {
for(;;) {
pins = ins;
if(!tktadjustind(tkt, TkTbyitemback, &pins))
break;
if(pins.item->kind == TkTmark && pins.item->imark->gravity == Tkright)
ins = pins;
else
break;
}
}
}
lmin = tktprevwrapline(tk, ins.line);
n = strlen(arg) + 1;
if(n < Tkmaxitem)
n = Tkmaxitem;
tbuf = malloc(n);
if(tbuf == nil)
return TkNomem;
buf = mallocz(Tkmaxitem, 0);
if(buf == nil) {
free(tbuf);
return TkNomem;
}
tagit = nil;
while(*arg != '\0') {
arg = tkword(top, arg, tbuf, tbuf+n, nil);
if(*arg != '\0') {
/* tag list spec -- add some slop to tagextra for added tags */
e = tktnewitem(TkTascii, (tkt->nexttag-1)/32 + 1, &tagit);
if(e != nil) {
free(tbuf);
free(buf);
return e;
}
arg = tkword(top, arg, buf, buf+Tkmaxitem, nil);
p = buf;
while(*p) {
while(*p == ' ') {
p++;
}
if(*p == '\0')
break;
pe = strchr(p, ' ');
if(pe != nil)
*pe = '\0';
ti = tktfindtag(tkt->tags, p);
if(ti == nil) {
e = tktaddtaginfo(tk, p, &ti);
if(e != nil) {
if(tagit != nil)
free(tagit);
free(tbuf);
free(buf);
return e;
}
}
tkttagbit(tagit, ti->id, 1);
if(pe == nil)
break;
else
p = pe+1;
}
}
e = tktinsert(tk, &ins, tbuf, tagit);
if(tagit != nil) {
free(tagit);
tagit = nil;
}
if(e != nil) {
free(tbuf);
free(buf);
return e;
}
}
tktfixgeom(tk, lmin, ins.line, 0);
tktextsize(tk, 1);
free(tbuf);
free(buf);
return nil;
}
static char*
tktextinserti(Tk *tk, char *arg, char **val)
{
int n;
TkTline *lmin;
TkTindex ix, is1, is2;
TkText *tkt = TKobj(TkText, tk);
char *tbuf, *buf;
USED(val);
if(tk->flag&Tkdisabled)
return nil;
buf = mallocz(Tkmaxitem, 0);
if(buf == nil)
return TkNomem;
tbuf = nil;
n = strlen(arg) + 1;
if(n < Tkmaxitem)
tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
else {
tbuf = malloc(n);
if(tbuf == nil) {
free(buf);
return TkNomem;
}
tkword(tk->env->top, arg, tbuf, buf+n, nil);
}
if(*buf == '\0')
goto Ret;
if(!tktmarkind(tk, "insert", &ix)) {
print("tktextinserti: botch\n");
goto Ret;
}
if(tktgetsel(tk, &is1, &is2)) {
if(tktindcompare(tkt, &is1, TkLte, &ix) &&
tktindcompare(tkt, &is2, TkGte, &ix)) {
tktextdelete(tk, "sel.first sel.last", nil);
/* delete might have changed ix item */
tktmarkind(tk, "insert", &ix);
}
}
lmin = tktprevwrapline(tk, ix.line);
tktinsert(tk, &ix, tbuf==nil ? buf : tbuf, 0);
tktfixgeom(tk, lmin, ix.line, 0);
if(tktmarkind(tk, "insert", &ix)) /* index doesn't remain valid after fixgeom */
tktsee(tk, &ix, 0);
tktextsize(tk, 1);
Ret:
if(tbuf != nil)
free(tbuf);
free(buf);
return nil;
}
static char*
tktextmark(Tk *tk, char *arg, char **val)
{
char *buf;
TkCmdtab *cmd;
buf = mallocz(Tkmaxitem, 0);
if(buf == nil)
return TkNomem;
arg = tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil);
for(cmd = tktmarkcmd; cmd->name != nil; cmd++) {
if(strcmp(cmd->name, buf) == 0) {
free(buf);
return cmd->fn(tk, arg, val);
}
}
free(buf);
return TkBadcm;
}
static char*
tktextscan(Tk *tk, char *arg, char **val)
{
char *e;
int mark, x, y, xmax, ymax, vh, vw;
Point p, odeltatv;
char buf[Tkmaxitem];
TkText *tkt = TKobj(TkText, tk);
USED(val);
arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
if(strcmp(buf, "mark") == 0)
mark = 1;
else
if(strcmp(buf, "dragto") == 0)
mark = 0;
else
return TkBadcm;
e = tkxyparse(tk, &arg, &p);
if(e != nil)
return e;
if(mark)
tkt->track = p;
else {
odeltatv = tkt->deltatv;
vw = tk->act.width - tk->ipad.x;
vh = tk->act.height - tk->ipad.y;
ymax = tkt->end.prev->orig.y + tkt->end.prev->height - vh;
y = tkt->deltatv.y -10*(p.y - tkt->track.y);
if(y > ymax)
y = ymax;
if(y < 0)
y = 0;
tkt->deltatv.y = y;
e = tktsetscroll(tk, Tkvertical);
if(e != nil)
return e;
if(tkt->opts[TkTwrap] == Tkwrapnone) {
xmax = tktmaxwid(tkt->start.next) - vw;
x = tkt->deltatv.x - 10*(p.x - tkt->track.x);
if(x > xmax)
x = xmax;
if(x < 0)
x = 0;
tkt->deltatv.x = x;
e = tktsetscroll(tk, Tkhorizontal);
if(e != nil)
return e;
}
tktfixscroll(tk, odeltatv);
tkt->track = p;
}
return nil;
}
static char*
tktextscrollpages(Tk *tk, char *arg, char **val)
{
TkText *tkt = TKobj(TkText, tk);
USED(tkt);
USED(arg);
USED(val);
return nil;
}
static char*
tktextsearch(Tk *tk, char *arg, char **val)
{
int i, n;
Rune r;
char *e, *s;
int wrap, fwd, nocase;
TkText *tkt;
TkTindex ix1, ix2, ixstart, ixend, tx;
char buf[Tkmaxitem];
tkt = TKobj(TkText, tk);
fwd = 1;
nocase = 0;
while(*arg != '\0') {
arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
if(*buf != '-')
break;
if(strcmp(buf, "-backwards") == 0)
fwd = 0;
else if(strcmp(buf, "-nocase") == 0)
nocase = 1;
else if(strcmp(buf, "--") == 0) {
arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
break;
}
}
tktstartind(tkt, &ixstart);
tktadjustind(tkt, TkTbycharstart, &ixstart);
tktendind(tkt, &ixend);
if(*arg == '\0')
return TkOparg;
e = tktindparse(tk, &arg, &ix1);
if(e != nil)
return e;
tktadjustind(tkt, fwd? TkTbycharstart : TkTbycharback, &ix1);
if(*arg != '\0') {
wrap = 0;
e = tktindparse(tk, &arg, &ix2);
if(e != nil)
return e;
if(!fwd)
tktadjustind(tkt, TkTbycharback, &ix2);
}
else {
wrap = 1;
if(fwd) {
if(tktindcompare(tkt, &ix1, TkEq, &ixstart))
ix2 = ixend;
else {
ix2 = ix1;
tktadjustind(tkt, TkTbycharback, &ix2);
}
}
else {
if(tktindcompare(tkt, &ix1, TkEq, &ixend))
ix2 = ixstart;
else {
ix2 = ix1;
tktadjustind(tkt, TkTbychar, &ix2);
}
}
}
tktadjustind(tkt, TkTbycharstart, &ix2);
if(tktindcompare(tkt, &ix1, TkEq, &ix2))
return nil;
if(*buf == '\0')
return tkvalue(val, "%d.%d", tktlinenum(tkt, &ix1), tktlinepos(tkt, &ix1));
while(!(ix1.item == ix2.item && ix1.pos == ix2.pos)) {
tx = ix1;
for(i = 0; buf[i] != '\0'; i++) {
switch(tx.item->kind) {
case TkTascii:
if(!tktcmatch(tx.item->istring[tx.pos], buf[i], nocase))
goto nomatch;
break;
case TkTrune:
s = tx.item->istring;
s += tktutfpos(s, tx.pos);
n = chartorune(&r, s);
if(strncmp(s, buf+i, n) != 0)
goto nomatch;
i += n-1;
break;
case TkTtab:
if(buf[i] != '\t')
goto nomatch;
break;
case TkTnewline:
if(buf[i] != '\n')
goto nomatch;
break;
default:
goto nomatch;
}
tktadjustind(tkt, TkTbychar, &tx);
}
return tkvalue(val, "%d.%d", tktlinenum(tkt, &ix1), tktlinepos(tkt, &ix1));
nomatch:
if(fwd) {
if(!tktadjustind(tkt, TkTbychar, &ix1)) {
if(!wrap)
break;
ix1 = ixstart;
}
}
else {
if(!tktadjustind(tkt, TkTbycharback, &ix1)) {
if(!wrap)
break;
ix1 = ixend;
}
}
}
return nil;
}
char*
tktextselection(Tk *tk, char *arg, char **val)
{
USED(val);
if (strcmp(arg, " clear") == 0) {
tktclearsel(tk);
return nil;
}
else
return TkBadcm;
}
static void
doselectto(Tk *tk, Point p, int dbl)
{
int halfway;
TkTindex cur, insert, first, last;
TkText *tkt = TKobj(TkText, tk);
tktclearsel(tk);
halfway = tktxyind(tk, p.x, p.y, &cur);
if(!dbl) {
if(!tktmarkind(tk, "insert", &insert))
insert = cur;
if(tktindcompare(tkt, &cur, TkLt, &insert)) {
first = cur;
last = insert;
}
else {
first = insert;
last = cur;
if(halfway)
tktadjustind(tkt, TkTbychar, &last);
if(last.line == &tkt->end)
tktadjustind(tkt, TkTbycharback, &last);
if(tktindcompare(tkt, &first, TkGte, &last))
return;
cur = last;
}
tktsee(tk, &cur, 0);
}
else {
first = cur;
last = cur;
tktdoubleclick(tkt, &first, &last);
}
tkttagchange(tk, TkTselid, &first, &last, 1);
}
static void
autoselect(Tk *tk, void *v, int cancelled)
{
TkText *tkt = TKobj(TkText, tk);
Rectangle hitr;
Point p;
USED(v);
if (cancelled)
return;
p = scr2local(tk, tkt->track);
if (tkvisiblerect(tk, &hitr) && ptinrect(p, hitr))
return;
doselectto(tk, p, 0);
tkdirty(tk);
tkupdate(tk->env->top);
}
static char*
tktextselectto(Tk *tk, char *arg, char **val)
{
int dbl;
char *e;
Point p;
Rectangle hitr;
TkText *tkt = TKobj(TkText, tk);
USED(val);
if(tkt->tflag & (TkTjustfoc|TkTnodrag))
return nil;
e = tkxyparse(tk, &arg, &p);
if(e != nil)
return e;
tkt->track = p;
p = scr2local(tk, p);
arg = tkskip(arg, " ");
if(*arg == 'd') {
tkcancelrepeat(tk);
dbl = 1;
tkt->tflag |= TkTnodrag;
} else {
dbl = 0;
if (!tkvisiblerect(tk, &hitr) || !ptinrect(p, hitr))
return nil;
}
doselectto(tk, p, dbl);
return nil;
}
static char tktleft1[] = "{[(<";
static char tktright1[] = "}])>";
static char tktleft2[] = "\n";
static char tktleft3[] = "\'\"`";
static char *tktleft[] = {tktleft1, tktleft2, tktleft3, nil};
static char *tktright[] = {tktright1, tktleft2, tktleft3, nil};
static void
tktdoubleclick(TkText *tkt, TkTindex *first, TkTindex *last)
{
int c, i;
TkTindex ix, ix2;
char *r, *l, *p;
for(i = 0; tktleft[i] != nil; i++) {
ix = *first;
l = tktleft[i];
r = tktright[i];
/* try matching character to left, looking right */
ix2 = ix;
if(!tktadjustind(tkt, TkTbycharback, &ix2))
c = '\n';
else
c = tktindrune(&ix2);
p = strchr(l, c);
if(p != nil) {
if(tktclickmatch(tkt, c, r[p-l], 1, &ix)) {
*last = ix;
if(c != '\n')
tktadjustind(tkt, TkTbycharback, last);
}
return;
}
/* try matching character to right, looking left */
c = tktindrune(&ix);
p = strchr(r, c);
if(p != nil) {
if(tktclickmatch(tkt, c, l[p-r], -1, &ix)) {
*last = *first;
if(c == '\n')
tktadjustind(tkt, TkTbychar, last);
*first = ix;
if(!(c=='\n' && ix.line == tkt->start.next && ix.item == ix.line->items))
tktadjustind(tkt, TkTbychar, first);
}
return;
}
}
/* try filling out word to right */
while(tkiswordchar(tktindrune(last))) {
if(!tktadjustind(tkt, TkTbychar, last))
break;
}
/* try filling out word to left */
for(;;) {
ix = *first;
if(!tktadjustind(tkt, TkTbycharback, &ix))
break;
if(!tkiswordchar(tktindrune(&ix)))
break;
*first = ix;
}
}
static int
tktclickmatch(TkText *tkt, int cl, int cr, int dir, TkTindex *ix)
{
int c, nest, atend;
nest = 1;
atend = 0;
for(;;) {
if(dir > 0) {
if(atend)
break;
c = tktindrune(ix);
atend = !tktadjustind(tkt, TkTbychar, ix);
} else {
if(!tktadjustind(tkt, TkTbycharback, ix))
break;
c = tktindrune(ix);
}
if(c == cr){
if(--nest==0)
return 1;
}else if(c == cl)
nest++;
}
return cl=='\n' && nest==1;
}
/*
* return the line before line l, unless word wrap is on,
* (for the first word of line l), in which case return the last non-empty line before that.
* tktgeom might then combine the end of that line with the start of the insertion
* (unless there is a newline in the way).
*/
TkTline*
tktprevwrapline(Tk *tk, TkTline *l)
{
TkTitem *i;
int *opts, wrapmode;
TkText *tkt = TKobj(TkText, tk);
TkEnv env;
if(l == nil)
return nil;
/* some spacing depends on tags of first non-mark on display line */
for(i = l->items; i != nil; i = i->next)
if(i->kind != TkTmark && i->kind != TkTcontline)
break;
if(i == nil || i->kind == TkTnewline) /* can't use !tkanytags(i) because it doesn't check env */
return l->prev;
opts = mallocz(TkTnumopts*sizeof(int), 0);
if(opts == nil)
return l->prev; /* in worst case gets word wrap wrong */
tkttagopts(tk, i, opts, &env, nil, 1);
wrapmode = opts[TkTwrap];
free(opts);
if(wrapmode != Tkwrapword)
return l->prev;
if(l->prev != &tkt->start)
l = l->prev; /* having been processed by tktgeom, shouldn't have extraneous marks etc */
return l->prev;
}
static char*
tktextsetcursor(Tk *tk, char *arg, char **val)
{
char *e;
TkTindex ix;
TkTmarkinfo *mi;
TkText *tkt = TKobj(TkText, tk);
USED(val);
/* do clearsel here, because it can change indices */
tktclearsel(tk);
e = tktindparse(tk, &arg, &ix);
if(e != nil)
return e;
mi = tktfindmark(tkt->marks, "insert");
if(tktdbg && mi == nil) {
print("tktextsetcursor: botch\n");
return nil;
}
tktmarkmove(tk, mi, &ix);
tktsee(tk, &ix, 0);
return nil;
}
static char*
tktexttag(Tk *tk, char *arg, char **val)
{
char *buf;
TkCmdtab *cmd;
buf = mallocz(Tkmaxitem, 0);
if(buf == nil)
return TkNomem;
arg = tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil);
for(cmd = tkttagcmd; cmd->name != nil; cmd++) {
if(strcmp(cmd->name, buf) == 0) {
free(buf);
return cmd->fn(tk, arg, val);
}
}
free(buf);
return TkBadcm;
}
static char*
tktextwindow(Tk *tk, char *arg, char **val)
{
char buf[Tkmaxitem];
TkCmdtab *cmd;
arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil);
for(cmd = tktwincmd; cmd->name != nil; cmd++) {
if(strcmp(cmd->name, buf) == 0)
return cmd->fn(tk, arg, val);
}
return TkBadcm;
}
static char*
tktextxview(Tk *tk, char *arg, char **val)
{
int ntot, vw;
char *e;
Point odeltatv;
TkText *tkt = TKobj(TkText, tk);
odeltatv = tkt->deltatv;
vw = tk->act.width - tk->ipad.x;
ntot = tktmaxwid(tkt->start.next);
if(ntot < tkt->deltatv.x +vw)
ntot = tkt->deltatv.x + vw;
e = tktview(tk, arg, val, vw, &tkt->deltatv.x, ntot, Tkhorizontal);
if(e == nil) {
e = tktsetscroll(tk, Tkhorizontal);
if(e == nil)
tktfixscroll(tk, odeltatv);
}
return e;
}
static int
istext(TkTline *l)
{
TkTitem *i;
for(i = l->items; i != nil; i = i->next)
if(i->kind == TkTwin || i->kind == TkTmark)
return 0;
return 1;
}
static void
tkadjpage(Tk *tk, int ody, int *dy)
{
int y, a, b, d;
TkTindex ix;
TkTline *l;
d = *dy-ody;
y = d > 0 ? tk->act.height : 0;
tktxyind(tk, 0, y-d, &ix);
if((l = ix.line) != nil && istext(l)){
a = l->orig.y;
b = a+l->height;
/* print("AP: %d %d %d (%d+%d)\n", a, ody+y, b, ody, y); */
if(a+2 < ody+y && ody+y < b-2){ /* partially obscured line */
if(d > 0)
*dy -= ody+y-a;
else
*dy += b-ody;
}
}
}
static char*
tktextyview(Tk *tk, char *arg, char **val)
{
int ntot, vh, d;
char *e;
TkTline *l;
Point odeltatv;
TkTindex ix;
TkText *tkt = TKobj(TkText, tk);
char buf[Tkmaxitem], *v;
if(*arg != '\0') {
v = tkitem(buf, arg);
if(strcmp(buf, "-pickplace") == 0)
return tktextsee(tk,v, val);
if(strcmp(buf, "moveto") != 0 && strcmp(buf, "scroll") != 0) {
e = tktindparse(tk, &arg, &ix);
if(e != nil)
return e;
tktsee(tk, &ix, 1);
return nil;
}
}
odeltatv = tkt->deltatv;
vh = tk->act.height;
l = tkt->end.prev;
ntot = l->orig.y + l->height;
// if(ntot < tkt->deltatv.y + vh)
// ntot = tkt->deltatv.y + vh;
e = tktview(tk, arg, val, vh, &tkt->deltatv.y, ntot, Tkvertical);
d = tkt->deltatv.y-odeltatv.y;
if(d == vh || d == -vh)
tkadjpage(tk, odeltatv.y, &tkt->deltatv.y);
if(e == nil) {
e = tktsetscroll(tk, Tkvertical);
if(e == nil)
tktfixscroll(tk, odeltatv);
}
return e;
}
static void
tktextfocusorder(Tk *tk)
{
TkTindex ix;
TkText *t;
Tk *isub;
t = TKobj(TkText, tk);
tktstartind(t, &ix);
do {
if(ix.item->kind == TkTwin) {
isub = ix.item->iwin->sub;
if(isub != nil)
tkappendfocusorder(isub);
}
} while(tktadjustind(t, TkTbyitem, &ix));
}
TkCmdtab tktextcmd[] =
{
"bbox", tktextbbox,
"cget", tktextcget,
"compare", tktextcompare,
"configure", tktextconfigure,
"debug", tktextdebug,
"delete", tktextdelete,
"dlineinfo", tktextdlineinfo,
"dump", tktextdump,
"get", tktextget,
"index", tktextindex,
"insert", tktextinsert,
"mark", tktextmark,
"scan", tktextscan,
"search", tktextsearch,
"see", tktextsee,
"selection", tktextselection,
"tag", tktexttag,
"window", tktextwindow,
"xview", tktextxview,
"yview", tktextyview,
"tkTextButton1", tktextbutton1,
"tkTextButton1R", tktextbutton1r,
"tkTextDelIns", tktextdelins,
"tkTextInsert", tktextinserti,
"tkTextSelectTo", tktextselectto,
"tkTextSetCursor", tktextsetcursor,
"tkTextScrollPages", tktextscrollpages,
"tkTextCursor", tktextcursor,
nil
};
TkMethod textmethod = {
"text",
tktextcmd,
tkfreetext,
tkdrawtext,
tktextgeom,
nil,
tktextfocusorder,
tktdirty,
tktrelpos,
tktextevent,
nil, /* XXX need to implement textsee */
tktinwindow,
nil,
tktxtforgetsub,
};