#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, s32*, 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<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, s32 *posn, int max, int orient) { s32 top, bot, amount; int 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, "", 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 and * (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, "", 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.xmax.x && (bp->min.xmax.x || (rp->max.x == b.min.x && rp->min.x == b.min.x)) && rp->min.ymax.y && bp->min.ymax.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 = ""; 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, s32 ody, s32 *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, };