diff options
| author | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
|---|---|---|
| committer | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
| commit | 37da2899f40661e3e9631e497da8dc59b971cbd0 (patch) | |
| tree | cbc6d4680e347d906f5fa7fca73214418741df72 /libtk | |
| parent | 54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff) | |
20060303a
Diffstat (limited to 'libtk')
| -rw-r--r-- | libtk/NOTICE | 25 | ||||
| -rw-r--r-- | libtk/buton.c | 609 | ||||
| -rw-r--r-- | libtk/canvs.c | 2220 | ||||
| -rw-r--r-- | libtk/canvs.h | 238 | ||||
| -rw-r--r-- | libtk/canvu.c | 506 | ||||
| -rw-r--r-- | libtk/carcs.c | 280 | ||||
| -rw-r--r-- | libtk/cbits.c | 211 | ||||
| -rw-r--r-- | libtk/cimag.c | 211 | ||||
| -rw-r--r-- | libtk/cline.c | 268 | ||||
| -rw-r--r-- | libtk/colrs.c | 90 | ||||
| -rw-r--r-- | libtk/coval.c | 248 | ||||
| -rw-r--r-- | libtk/cpoly.c | 270 | ||||
| -rw-r--r-- | libtk/crect.c | 252 | ||||
| -rw-r--r-- | libtk/ctext.c | 666 | ||||
| -rw-r--r-- | libtk/cwind.c | 410 | ||||
| -rw-r--r-- | libtk/ebind.c | 1033 | ||||
| -rw-r--r-- | libtk/entry.c | 1379 | ||||
| -rw-r--r-- | libtk/extns.c | 37 | ||||
| -rw-r--r-- | libtk/frame.c | 277 | ||||
| -rw-r--r-- | libtk/frame.h | 2 | ||||
| -rw-r--r-- | libtk/grids.c | 1552 | ||||
| -rw-r--r-- | libtk/image.c | 380 | ||||
| -rw-r--r-- | libtk/label.c | 539 | ||||
| -rw-r--r-- | libtk/label.h | 73 | ||||
| -rw-r--r-- | libtk/listb.c | 1065 | ||||
| -rw-r--r-- | libtk/listb.h | 1 | ||||
| -rw-r--r-- | libtk/mail.tk | 224 | ||||
| -rw-r--r-- | libtk/menu.tk | 49 | ||||
| -rw-r--r-- | libtk/menus.c | 1840 | ||||
| -rw-r--r-- | libtk/mkfile | 26 | ||||
| -rw-r--r-- | libtk/mkfile-std | 44 | ||||
| -rw-r--r-- | libtk/packr.c | 689 | ||||
| -rw-r--r-- | libtk/panel.c | 408 | ||||
| -rw-r--r-- | libtk/parse.c | 1165 | ||||
| -rw-r--r-- | libtk/radio.tk | 4 | ||||
| -rw-r--r-- | libtk/scale.c | 958 | ||||
| -rw-r--r-- | libtk/scrol.c | 781 | ||||
| -rw-r--r-- | libtk/textu.c | 1076 | ||||
| -rw-r--r-- | libtk/textw.c | 3693 | ||||
| -rw-r--r-- | libtk/textw.h | 229 | ||||
| -rw-r--r-- | libtk/tindx.c | 609 | ||||
| -rw-r--r-- | libtk/tmark.c | 392 | ||||
| -rw-r--r-- | libtk/ttags.c | 1029 | ||||
| -rw-r--r-- | libtk/twind.c | 396 | ||||
| -rw-r--r-- | libtk/utils.c | 1951 | ||||
| -rw-r--r-- | libtk/windw.c | 716 | ||||
| -rw-r--r-- | libtk/xdata.c | 217 |
47 files changed, 29338 insertions, 0 deletions
diff --git a/libtk/NOTICE b/libtk/NOTICE new file mode 100644 index 00000000..e8c19e7f --- /dev/null +++ b/libtk/NOTICE @@ -0,0 +1,25 @@ +This copyright NOTICE applies to all files in this directory and +subdirectories, unless another copyright notice appears in a given +file or subdirectory. If you take substantial code from this software to use in +other programs, you must somehow include with it an appropriate +copyright notice that includes the copyright notice and the other +notices below. It is fine (and often tidier) to do that in a separate +file such as NOTICE, LICENCE or COPYING. + +Copyright © 1995-1999 Lucent Technologies Inc. +Portions Copyright © 1997-2000 Vita Nuova Limited +Portions Copyright © 2000-2006 Vita Nuova Holdings Limited + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License (`LGPL') as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. diff --git a/libtk/buton.c b/libtk/buton.c new file mode 100644 index 00000000..2f8a20f4 --- /dev/null +++ b/libtk/buton.c @@ -0,0 +1,609 @@ +#include "lib9.h" +#include "draw.h" +#include "tk.h" +#include "label.h" + +#define O(t, e) ((long)(&((t*)0)->e)) + +/* Widget Commands (+ means implemented) + +cget + +configure + +invoke + +select + +deselect + +toggle + */ + +enum { + /* other constants */ + InvokePause = 200, /* delay showing button in down state when invoked */ +}; + +TkOption tkbutopts[] = +{ + "text", OPTtext, O(TkLabel, text), nil, + "label", OPTtext, O(TkLabel, text), nil, + "underline", OPTdist, O(TkLabel, ul), nil, + "justify", OPTstab, O(TkLabel, justify), tkjustify, + "anchor", OPTflag, O(TkLabel, anchor), tkanchor, + "command", OPTtext, O(TkLabel, command), nil, + "bitmap", OPTbmap, O(TkLabel, bitmap), nil, + "image", OPTimag, O(TkLabel, img), nil, + nil +}; + +TkOption tkcbopts[] = +{ + "variable", OPTtext, O(TkLabel, variable), nil, + "indicatoron", OPTstab, O(TkLabel, indicator), tkbool, + "onvalue", OPTtext, O(TkLabel, value), nil, + "offvalue", OPTtext, O(TkLabel, offvalue), nil, + nil, +}; + +TkOption tkradopts[] = +{ + "variable", OPTtext, O(TkLabel, variable), nil, + "value", OPTtext, O(TkLabel, value), nil, + "indicatoron", OPTstab, O(TkLabel, indicator), tkbool, + nil, +}; + +static +TkEbind bb[] = +{ + {TkEnter, "%W configure -state active"}, + {TkLeave, "%W configure -state normal"}, + {TkButton1P, "%W tkButton1P"}, + {TkButton1R, "%W tkButton1R %x %y"}, + {TkMotion|TkButton1P, "" }, + {TkKey, "%W tkButtonKey 0x%K"}, +}; + +static +TkEbind cb[] = +{ + {TkEnter, "%W configure -state active"}, + {TkLeave, "%W configure -state normal"}, + {TkButton1P, "%W invoke"}, + {TkMotion|TkButton1P, "" }, + {TkKey, "%W tkButtonKey 0x%K"}, +}; + + +static char tkselbut[] = "selectedButton"; + +static char* newbutton(TkTop*, int, char*, char**); +static int istransparent(Tk*); +static void tkvarchanged(Tk*, char*, char*); + +char* +tkbutton(TkTop *t, char *arg, char **ret) +{ + return newbutton(t, TKbutton, arg, ret); +} + +char* +tkcheckbutton(TkTop *t, char *arg, char **ret) +{ + return newbutton(t, TKcheckbutton, arg, ret); +} + +char* +tkradiobutton(TkTop *t, char *arg, char **ret) +{ + return newbutton(t, TKradiobutton, arg, ret); +} + +static char* +newbutton(TkTop *t, int btype, char *arg, char **ret) +{ + Tk *tk; + char *e; + TkLabel *tkl; + TkName *names; + TkOptab tko[4]; + TkVar *v; + + tk = tkmkbutton(t, btype); + if(tk == nil) + return TkNomem; + + tkl = TKobj(TkLabel, tk); + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tkl; + tko[1].optab = tkbutopts; + switch(btype){ + case TKcheckbutton: + tko[2].ptr = tkl; + tko[2].optab = tkcbopts; + break; + case TKradiobutton: + tko[2].ptr = tkl; + tko[2].optab = tkradopts; + break; + default: + tk->relief = TKraised; + tk->borderwidth = 2; + tko[2].ptr = nil; + break; + } + tko[3].ptr = nil; + + names = nil; + e = tkparse(t, arg, tko, &names); + if(e != nil) { + tkfreeobj(tk); + return e; + } + + tksettransparent(tk, istransparent(tk)); + tksizebutton(tk); + + e = tkaddchild(t, tk, &names); + tkfreename(names); + if(e != nil) { + tkfreeobj(tk); + return e; + } + tk->name->link = nil; + + if (btype == TKradiobutton && + tkl->variable != nil && + strcmp(tkl->variable, tkselbut) == 0 && + tkl->value == nil && + tk->name != nil) + tkl->value = strdup(tk->name->name); + + if (tkl->variable != nil) { + v = tkmkvar(t, tkl->variable, 0); + if (v == nil){ + if(btype == TKcheckbutton){ + e = tksetvar(t, tkl->variable, tkl->offvalue ? tkl->offvalue : "0"); + if (e != nil) + goto err; + } + } else if(v->type != TkVstring){ + e = TkNotvt; + goto err; + } else + tkvarchanged(tk, tkl->variable, v->value); + } + + return tkvalue(ret, "%s", tk->name->name); + +err: + tkfreeobj(tk); + return e; +} + +Tk* +tkmkbutton(TkTop *t, int btype) +{ + Tk *tk; + TkLabel *tkl; + char *e; + + tk = tknewobj(t, btype, sizeof(Tk)+sizeof(TkLabel)); + if (tk == nil) + return nil; + + e = nil; + tk->relief = TKraised; + tk->borderwidth = 0; + tk->highlightwidth = 1; + tk->flag |= Tktakefocus; + tkl = TKobj(TkLabel, tk); + tkl->ul = -1; + tkl->justify = Tkleft; + if (btype == TKradiobutton) + tkl->variable = strdup(tkselbut); + + switch (btype) { + case TKbutton: + e = tkbindings(t, tk, bb, nelem(bb)); + break; + case TKcheckbutton: + case TKradiobutton: + e = tkbindings(t, tk, cb, nelem(cb)); + break; + } + + if (e != nil) { + print("tkmkbutton internal error: %s\n", e); + tkfreeobj(tk); + return nil; + } + return tk; +} + +void tksizebutton(Tk *tk) +{ + tksizelabel(tk); +} + +/* shame that this is separated from the sizing and rendering code in label.c */ +int +tkbuttonmargin(Tk *tk) +{ + TkLabel *tkl; + tkl = TKobj(TkLabel, tk); + + switch (tk->type) { + case TKbutton: + if (tkl->img != nil || tkl->bitmap != nil) + return 0; + return Textpadx+tk->highlightwidth; + case TKcheckbutton: + case TKradiobutton: + return CheckButton + 2*CheckButtonBW + 2*ButtonBorder; + } + return 0; +} + +static char* +tkbuttoncget(Tk *tk, char *arg, char **val) +{ + TkOptab tko[4]; + TkLabel *tkl = TKobj(TkLabel, tk); + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tkl; + tko[1].optab = tkbutopts; + switch(tk->type){ + case TKcheckbutton: + tko[2].ptr = tkl; + tko[2].optab = tkcbopts; + break; + case TKradiobutton: + tko[2].ptr = tkl; + tko[2].optab = tkradopts; + break; + default: + tko[2].ptr = nil; + break; + } + tko[3].ptr = nil; + return tkgencget(tko, arg, val, tk->env->top); +} + +static char* +tkbuttonconf(Tk *tk, char *arg, char **val) +{ + char *e; + TkGeom g; + int bd; + TkOptab tko[4]; + TkVar *v; + TkLabel *tkl = TKobj(TkLabel, tk); + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tkl; + tko[1].optab = tkbutopts; + switch(tk->type){ + case TKcheckbutton: + tko[2].ptr = tkl; + tko[2].optab = tkcbopts; + break; + case TKradiobutton: + tko[2].ptr = tkl; + tko[2].optab = tkradopts; + break; + default: + tko[2].ptr = nil; + break; + } + tko[3].ptr = nil; + + if(*arg == '\0') + return tkconflist(tko, val); + + g = tk->req; + bd = tk->borderwidth; + e = tkparse(tk->env->top, arg, tko, nil); + tksizebutton(tk); + tkgeomchg(tk, &g, bd); + + tk->dirty = tkrect(tk, 1); + tksettransparent(tk, istransparent(tk)); + /* + * XXX what happens if we're now disabled, but we were in + * active state before? + */ + if (tkl->variable != nil) { + v = tkmkvar(tk->env->top, tkl->variable, 0); + if (v != nil) { + if (v->type != TkVstring) { + e = TkNotvt; + free(tkl->variable); + tkl->variable = nil; + } + else + tkvarchanged(tk, tkl->variable, v->value); + } + } + return e; +} + +static int +istransparent(Tk *tk) +{ + TkEnv *e = tk->env; + return (tkhasalpha(e, TkCbackgnd) || tkhasalpha(e, TkCselectbgnd) || tkhasalpha(e, TkCactivebgnd)); +} + +static void +tkvarchanged(Tk *tk, char *var, char *val) +{ + TkLabel *tkl; + char *sval; + + tkl = TKobj(TkLabel, tk); + if (tkl->variable != nil && strcmp(tkl->variable, var) == 0) { + sval = tkl->value; + if (sval == nil) + sval = tk->type == TKcheckbutton ? "1" : ""; + tkl->check = (strcmp(val, sval) == 0); + tk->dirty = tkrect(tk, 1); + tkdirty(tk); + } +} + +static char* +tkbutton1p(Tk *tk, char *arg, char **val) +{ + USED(arg); + USED(val); + if(tk->flag & Tkdisabled) + return nil; + tk->flag |= Tkactivated; + tk->dirty = tkrect(tk, 1); + tkdirty(tk); + return nil; +} + +static char* +tkbutton1r(Tk *tk, char *arg, char **val) +{ + char *e; + Point p; + Rectangle hitr; + + USED(arg); + + if(tk->flag & Tkdisabled) + return nil; + e = tkxyparse(tk, &arg, &p); + if (e == nil) { + hitr.min = ZP; + hitr.max.x = tk->act.width + tk->borderwidth*2; + hitr.max.y = tk->act.height + tk->borderwidth*2; + if(ptinrect(p, hitr) && (tk->flag & Tkactivated)) + e = tkbuttoninvoke(tk, nil, val); + } + tk->flag &= ~Tkactivated; + tk->dirty = tkrect(tk, 1); + tkdirty(tk); + return e; +} + +static char* +tkbuttonkey(Tk *tk, char *arg, char **val) +{ + int key; + + if(tk->flag & Tkdisabled) + return nil; + + key = atoi(arg); + if (key == '\n' || key ==' ') + return tkbuttoninvoke(tk, nil, val); + return nil; +} + +static char* +tkbuttontoggle(Tk *tk, char *arg, char **val) +{ + char *e; + TkLabel *tkl = TKobj(TkLabel, tk); + char *v; + + USED(arg); + USED(val); + if(tk->flag & Tkdisabled) + return nil; + tkl->check = !tkl->check; + if (tkl->check) + v = tkl->value ? tkl->value : "1"; + else + v = tkl->offvalue ? tkl->offvalue : "0"; + e = tksetvar(tk->env->top, tkl->variable, v); + tk->dirty = tkrect(tk, 0); + return e; +} + +static char* +buttoninvoke(Tk *tk, char **val) +{ + char *e = nil; + TkTop *top; + TkLabel *tkl = TKobj(TkLabel, tk); + + top = tk->env->top; + if (tk->type == TKcheckbutton) + e = tkbuttontoggle(tk, "", val); + else if (tk->type == TKradiobutton) + e = tksetvar(top, tkl->variable, tkl->value); + if(e != nil) + return e; + if(tkl->command != nil) + return tkexec(tk->env->top, tkl->command, val); + return nil; +} + +static void +cancelinvoke(Tk *tk, void *v, int cancelled) +{ + int unset; + USED(cancelled); + USED(v); + + /* if it was active before then leave it active unless cleared since */ + if (v) + unset = 0; + else + unset = Tkactive; + unset &= (tk->flag & Tkactive); + unset |= Tkactivated; + tk->flag &= ~unset; + tksettransparent(tk, istransparent(tk)); + tk->dirty = tkrect(tk, 1); + tkdirty(tk); + tkupdate(tk->env->top); +} + +char* +tkbuttoninvoke(Tk *tk, char *arg, char **val) +{ + char *e; + USED(arg); + + if(tk->flag & Tkdisabled) + return nil; + e = buttoninvoke(tk, val); + if (e == nil && tk->type == TKbutton && !(tk->flag & Tkactivated)) { + tkrepeat(tk, cancelinvoke, (void*)(tk->flag&Tkactive), InvokePause, 0); + tk->flag |= Tkactivated | Tkactive; + tksettransparent(tk, istransparent(tk)); + tk->dirty = tkrect(tk, 1); + tkdirty(tk); + tkupdate(tk->env->top); + } + return e; +} + +static char* +tkbuttonselect(Tk *tk, char *arg, char **val) +{ + char *e, *v; + TkLabel *tkl = TKobj(TkLabel, tk); + + USED(arg); + USED(val); + if (tk->type == TKradiobutton) + v = tkl->value; + else if (tk->type == TKcheckbutton) { + v = tkl->value ? tkl->value : "1"; + tkl->check = 1; + tk->dirty = tkrect(tk, 0); + } else + v = nil; + e = tksetvar(tk->env->top, tkl->variable, v); + if(e != nil) + return e; + return nil; +} + +static char* +tkbuttondeselect(Tk *tk, char *arg, char **val) +{ + char *e, *v; + TkLabel *tkl = TKobj(TkLabel, tk); + + USED(arg); + USED(val); + + if (tk->type == TKcheckbutton) { + v = tkl->offvalue ? tkl->offvalue : "0"; + tkl->check = 0; + tk->dirty = tkrect(tk, 0); + } else + v = nil; + + e = tksetvar(tk->env->top, tkl->variable, v); + if(e != nil) + return e; + return nil; +} + +static +TkCmdtab tkbuttoncmd[] = +{ + "cget", tkbuttoncget, + "configure", tkbuttonconf, + "invoke", tkbuttoninvoke, + "tkButton1P", tkbutton1p, + "tkButton1R", tkbutton1r, + "tkButtonKey", tkbuttonkey, + nil +}; + +static +TkCmdtab tkchkbuttoncmd[] = +{ + "cget", tkbuttoncget, + "configure", tkbuttonconf, + "invoke", tkbuttoninvoke, + "select", tkbuttonselect, + "deselect", tkbuttondeselect, + "toggle", tkbuttontoggle, + "tkButtonKey", tkbuttonkey, + nil +}; + +static +TkCmdtab tkradbuttoncmd[] = +{ + "cget", tkbuttoncget, + "configure", tkbuttonconf, + "invoke", tkbuttoninvoke, + "select", tkbuttonselect, + "deselect", tkbuttondeselect, + "tkButtonKey", tkbuttonkey, + nil +}; + +TkMethod buttonmethod = { + "button", + tkbuttoncmd, + tkfreelabel, + tkdrawlabel, + nil, + tklabelgetimgs +}; + +TkMethod checkbuttonmethod = { + "checkbutton", + tkchkbuttoncmd, + tkfreelabel, + tkdrawlabel, + nil, + tklabelgetimgs, + nil, + nil, + nil, + nil, + nil, + nil, + tkvarchanged +}; + +TkMethod radiobuttonmethod = { + "radiobutton", + tkradbuttoncmd, + tkfreelabel, + tkdrawlabel, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + tkvarchanged +}; diff --git a/libtk/canvs.c b/libtk/canvs.c new file mode 100644 index 00000000..9d7cc4d1 --- /dev/null +++ b/libtk/canvs.c @@ -0,0 +1,2220 @@ +#include <lib9.h> +#include <kernel.h> +#include "draw.h" +#include "tk.h" +#include "canvs.h" + +/* Widget Commands (+ means implemented) + +addtag + except halo and start options of closest spec + +bbox + +bind + +canvasx + +canvasy + +cget + +configure + +coords + +create + +dchars + +delete + +dtag + +find + +focus + +gettags + +icursor + +index + +insert + +itemcget + +itemconfigure + +lower + +move + postscript + +raise + +scale + scan + +select + +type + +xview + +yview +*/ + +static +TkStab tkbuffer[] = { + "visible", TkCbufvisible, + "all", TkCbufall, + "none", TkCbufnone, + "auto", TkCbufauto, + + /* backwards compatibility */ + "1", TkCbufall, + "yes", TkCbufall, + "off", TkCbufall, + "0", TkCbufauto, + "no", TkCbufauto, + "off", TkCbufauto, + nil +}; + +#define O(t, e) ((long)(&((t*)0)->e)) +#define OA(t, e) ((long)(((t*)0)->e)) + +static +TkOption opts[] = +{ + "closeenough", OPTfrac, O(TkCanvas, close), nil, + "confine", OPTfrac, O(TkCanvas, confine), nil, + "scrollregion", OPTfrac, OA(TkCanvas, scrollr), IAUX(4), + "xscrollincrement", OPTfrac, O(TkCanvas, xscrolli), nil, + "yscrollincrement", OPTfrac, O(TkCanvas, yscrolli), nil, + "xscrollcommand", OPTtext, O(TkCanvas, xscroll), nil, + "yscrollcommand", OPTtext, O(TkCanvas, yscroll), nil, + "width", OPTnnfrac, O(TkCanvas, width), nil, + "height", OPTnnfrac, O(TkCanvas, height), nil, + "buffer", OPTstab, O(TkCanvas, buffer), tkbuffer, + "buffered", OPTstab, O(TkCanvas, buffer), tkbool, /* backwards compatibility */ + "selectborderwidth", OPTnndist, O(TkCanvas, sborderwidth), nil, + nil +}; + +int cvslshape[] = { TKI2F(8), TKI2F(10), TKI2F(3) }; +Rectangle bbnil = { 1000000, 1000000, -1000000, -1000000 }; +Rectangle huger = { -1000000, -1000000, 1000000, 1000000 }; + +static void tkcvsgeom(Tk *tk); + + +static void +tkcvsf2i(Tk *tk, TkCanvas *tkc) +{ + Rectangle r; + tk->req.width = TKF2I(tkc->width); + tk->req.height = TKF2I(tkc->height); + + r.min.x = TKF2I(tkc->scrollr[0]); + r.min.y = TKF2I(tkc->scrollr[1]); + r.max.x = TKF2I(tkc->scrollr[2]); + r.max.y = TKF2I(tkc->scrollr[3]); + + /* + * make sure that the region is big enough to hold + * the actually displayed area + */ + if (Dx(r) < tk->act.width) + r.max.x = r.min.x + tk->act.width; + if (Dy(r) < tk->act.height) + r.max.y = r.min.y + tk->act.height; + tkc->region = r; + + /* + * make sure that the view origin is at a valid + * position with respect to the scroll region. + */ + if (tkc->view.x + tk->act.width > r.max.x) + tkc->view.x = r.max.x - tk->act.width; + if (tkc->view.x < r.min.x) + tkc->view.x = r.min.x; + + if (tkc->view.y + tk->act.height > r.max.y) + tkc->view.y = r.max.y - tk->act.height; + if (tkc->view.y < r.min.y) + tkc->view.y = r.min.y; + +} + +char* +tkcanvas(TkTop *t, char *arg, char **ret) +{ + Tk *tk; + char *e; + TkCanvas *tkc; + TkName *names; + TkOptab tko[3]; + + tk = tknewobj(t, TKcanvas, sizeof(Tk)+sizeof(TkCanvas)); + if(tk == nil) + return TkNomem; + + tkc = TKobj(TkCanvas, tk); + tkc->close = TKI2F(1); + tkc->xscrolli = TKI2F(1); + tkc->yscrolli = TKI2F(1); + tkc->width = TKI2F(360); + tkc->height = TKI2F(270); + tkc->actions = 0; + tkc->actlim = Tksweep; + tkc->mask = nil; + tkc->sborderwidth = 1; + + tko[0].ptr = tkc; + tko[0].optab = opts; + tko[1].ptr = tk; + tko[1].optab = tkgeneric; + tko[2].ptr = nil; + + names = nil; + e = tkparse(t, arg, tko, &names); + if(e != nil) + goto err; + if(names == nil) { + /* tkerr(t, arg); XXX */ + e = TkBadwp; + goto err; + } + + tkc->current = tkmkname("current"); + if(tkc->current == nil) { + e = TkNomem; + goto err; + } + + tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd)); + tkcvsf2i(tk, tkc); + + e = tkaddchild(t, tk, &names); + tkfreename(names); + if(e != nil) { + tkfreename(tkc->current); + tkc->current = nil; + goto err; + } + tk->name->link = nil; + + e = tkvalue(ret, "%s", tk->name->name); + if(e == nil) + return nil; + + tkfreename(tkc->current); + return e; +err: + tkfreeobj(tk); + return e; +} + +void +tkcvsdirty(Tk *sub) +{ + TkCanvas *c; + Tk *tk, *parent; + Rectangle r; + Point rel; + + rel = ZP; + for(tk = sub; tk; tk = tk->master) { + rel.x += tk->borderwidth + tk->act.x; + rel.y += tk->borderwidth + tk->act.y; + if (tk->parent != nil) + break; + } + if (tk == nil) + return; + parent = tk->parent; + c = TKobj(TkCanvas, parent); + r = rectaddpt(sub->dirty, rel); + tkbbmax(&c->update, &r); + tkcvssetdirty(parent); +} + +static void +tkcvsfocusorder(Tk *tk) +{ + TkCanvas *tkc = TKobj(TkCanvas, tk); + TkCwind *win; + TkCitem *it; + TkWinfo *inf; + int i, n; + + n = 0; + for (it = tkc->head; it != nil; it = it->next) { + if (it->type == TkCVwindow) { + win = TKobj(TkCwind, it); + if (win->sub != nil) + n++; + } + } + if (n == 0) + return; + + inf = malloc(sizeof(*inf) * n); + if (inf == nil) + return; + + i = 0; + for (it = tkc->head; it != nil; it = it->next) { + if (it->type == TkCVwindow) { + win = TKobj(TkCwind, it); + if (win->sub != nil) { + inf[i].w = win->sub; + inf[i].r = it->p.bb; + i++; + } + } + } + + tksortfocusorder(inf, n); + for (i = 0; i < n; i++) + tkappendfocusorder(inf[i].w); +} + +static char* +tkcvscget(Tk *tk, char *arg, char **val) +{ + TkOptab tko[3]; + TkCanvas *tkc = TKobj(TkCanvas, tk); + + tko[0].ptr = tkc; + tko[0].optab = opts; + tko[1].ptr = tk; + tko[1].optab = tkgeneric; + tko[2].ptr = nil; + + return tkgencget(tko, arg, val, tk->env->top); +} + +static char* +tkcvsconf(Tk *tk, char *arg, char **val) +{ + char *e; + int bd; + TkGeom g; + Rectangle r; + TkOptab tko[3]; + TkCanvas *c = TKobj(TkCanvas, tk); + + tko[0].ptr = c; + tko[0].optab = opts; + tko[1].ptr = tk; + tko[1].optab = tkgeneric; + tko[2].ptr = nil; + + if(*arg == '\0') + return tkconflist(tko, val); + + r.min = c->view; + r.max.x = r.min.x+tk->act.width; + r.max.y = r.min.y+tk->act.height; + tkbbmax(&c->update, &r); + tkbbmax(&c->update, &c->region); + + bd = tk->borderwidth; + g = tk->req; + e = tkparse(tk->env->top, arg, tko, nil); + if(e != nil) + return e; + tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd)); + + tkcvsf2i(tk, c); + + tkcvsgeom(tk); + tkgeomchg(tk, &g, bd); + tkbbmax(&c->update, &c->region); + tk->dirty = tkrect(tk, 1); + return nil; +} + +void +tkcvsfreeitem(TkCitem *i) +{ + int locked; + Display *d; + + d = i->env->top->display; + + locked = lockdisplay(d); + tkcimethod[i->type].free(i); + if(locked) + unlockdisplay(d); + + tkfreepoint(&i->p); + tkputenv(i->env); + free(i); +} + +void +tkfreecanv(Tk *tk) +{ + Display *d; + int j, locked; + TkCanvas *c; + TkName *n, *nn; + TkCtag *t, *tt; + TkCitem *i, *next; + + c = TKobj(TkCanvas, tk); + for(i = c->head; i; i = next) { + next = i->next; + tkcvsfreeitem(i); + } + + if(c->xscroll != nil) + free(c->xscroll); + if(c->yscroll != nil) + free(c->yscroll); + + for(j = 0; j < TkChash; j++) { + for(n = c->thash[j]; n; n = nn) { + nn = n->link; + for(t = n->obj; t; t = tt) { + tt = t->taglist; + free(t); + } + tkfreebind(n->prop.binds); + free(n); + } + } + + free(c->current); + + if((c->ialloc && c->image != nil) || c->mask != nil) { + if (c->ialloc && c->image != nil) + d = c->image->display; + else + d = c->mask->display; + locked = lockdisplay(d); + if (c->image != nil && c->ialloc) + freeimage(c->image); + if (c->mask != nil) + freeimage(c->mask); + if(locked) + unlockdisplay(d); + } +} + +enum {Bufnone = 99}; + +char* +tkdrawcanv(Tk *tk, Point orig) +{ + Image *dst; + TkCitem *i; + Display *d; + TkCanvas *c; + Rectangle r, bufr, oclipr; + int vis, alpha, buffer; + Point rel, p; + TkCimeth *imeth; + + c = TKobj(TkCanvas, tk); + d = tk->env->top->display; + dst = tkimageof(tk); + /* + * translation from local to screen coords + */ + rel.x = orig.x + tk->act.x + tk->borderwidth; + rel.y = orig.y + tk->act.y + tk->borderwidth; + + buffer = c->buffer; + if (buffer == TkCbufauto) + buffer = TkCbufvisible; +/* buffer = (dst == TKobj(TkWin, tk->env->top->root)->image) ? TkCbufvisible : TkCbufnone; */ + + if (buffer == TkCbufnone) { + if(c->image != nil && c->ialloc) + freeimage(c->image); + c->image = dst; + c->ialloc = 0; + + r = tkrect(tk, 0); + bufr = r; + rectclip(&bufr, tk->dirty); + oclipr = dst->clipr; + + replclipr(dst, 0, rectaddpt(bufr, rel)); + draw(dst, rectaddpt(bufr, rel), tkgc(tk->env, TkCbackgnd), nil, ZP); + + p = subpt(rel, c->view); + p.x = TKI2F(p.x); + p.y = TKI2F(p.y); + bufr = rectaddpt(bufr, c->view); + for(i = c->head; i; i = i->next) { + if(rectXrect(i->p.bb, bufr)) { + imeth = &tkcimethod[i->type]; + imeth->coord(i, nil, p.x, p.y); + imeth->draw(dst, i, tk->env); + imeth->coord(i, nil, -p.x, -p.y); + } + } + replclipr(dst, 0, oclipr); + } else { + if (c->buffer == TkCbufall) + bufr = c->region; + else { + bufr.min = c->view; + bufr.max.x = c->view.x + tk->act.width; + bufr.max.y = c->view.y + tk->act.height; + } + alpha = (tk->env->colors[TkCbackgnd] & 0xff) != 0xff; + if(c->image == nil || eqrect(bufr, c->image->r) == 0) { + if(c->image != nil && c->ialloc) + freeimage(c->image); + c->image = allocimage(d, bufr, alpha?RGBA32:d->image->chan, 0, tk->env->colors[TkCbackgnd]); + c->ialloc = 1; + c->update = bufr; + tkcvssetdirty(tk); /* unnecessary? */ + } + + if(c->image == nil) + return nil; + + r = c->update; + if (rectclip(&r, c->image->r)) { + if (alpha) + drawop(c->image, c->update, nil, nil, ZP, Clear); + draw(c->image, c->update, tkgc(tk->env, TkCbackgnd), nil, c->view); + replclipr(c->image, 0, r); + for(i = c->head; i; i = i->next) { + if(rectXrect(i->p.bb, r)) + tkcimethod[i->type].draw(c->image, i, tk->env); + } + replclipr(c->image, 0, c->image->r); + } + /* + * if the visible area of the canvas image doesn't + * fit completely within the dirty rectangle, + * then we'll need to draw the background behind it + */ + r = tkrect(tk, 0); + bufr = rectsubpt(bufr, c->view); + vis = rectclip(&bufr, tkrect(tk, 0)); + + if (!vis || !rectinrect(tk->dirty, bufr)) + draw(dst, rectaddpt(tk->dirty, rel), tkgc(tk->env, TkCbackgnd), nil, c->view); + + if (vis && rectclip(&bufr, tk->dirty)) + draw(dst, rectaddpt(bufr, rel), c->image, nil, addpt(bufr.min, c->view)); + } + + + /* + * if the border is dirty too, then draw that + */ + if (!rectinrect(tk->dirty, bufr)) { + r.min = addpt(r.min, rel); + r.min.x -= tk->borderwidth; + r.min.y -= tk->borderwidth; + tkdrawrelief(dst, tk, r.min, TkCbackgnd, tk->relief); + } + c->update = bbnil; + return nil; +} + +void +tkcvsappend(TkCanvas *c, TkCitem *i) +{ + if(c->head == nil) + c->head = i; + else + c->tail->next = i; + c->tail = i; +} + +void +tkcvssv(Tk *tk) +{ + TkCanvas *c; + int top, bot, height; + char val[Tkminitem], cmd[Tkmaxitem], *v, *e; + + c = TKobj(TkCanvas, tk); + if(c->yscroll == nil) + return; + + top = 0; + bot = TKI2F(1); + + height = Dy(c->region); + if(height != 0) { + top = TKI2F(c->view.y)/height; + bot = TKI2F(c->view.y+tk->act.height)/height; + } + + v = tkfprint(val, top); + *v++ = ' '; + tkfprint(v, bot); + snprint(cmd, sizeof(cmd), "%s %s", c->yscroll, val); + e = tkexec(tk->env->top, cmd, nil); + if ((e != nil) && (tk->name != nil)) + print("tk: yscrollcommand \"%s\": %s\n", tk->name->name, e); +} + +void +tkcvssh(Tk *tk) +{ + int top, bot, width; + TkCanvas *c = TKobj(TkCanvas, tk); + char val[Tkminitem], cmd[Tkmaxitem], *v, *e; + + if(c->xscroll == nil) + return; + + top = 0; + bot = TKI2F(1); + + width = Dx(c->region); + if(width != 0) { + top = TKI2F(c->view.x)/width; + bot = TKI2F(c->view.x+tk->act.width)/width; + } + + v = tkfprint(val, top); + *v++ = ' '; + tkfprint(v, bot); + snprint(cmd, sizeof(cmd), "%s %s", c->xscroll, val); + e = tkexec(tk->env->top, cmd, nil); + if ((e != nil) && (tk->name != nil)) + print("tk: xscrollcommand \"%s\": %s\n", tk->name->name, e); +} + +static void +tkcvsgeom(Tk *tk) +{ + TkCanvas *c; + c = TKobj(TkCanvas, tk); + + tkcvsf2i(tk, c); + tk->dirty = tkrect(tk, 1); + c->update = c->region; + + tkcvssv(tk); + tkcvssh(tk); +} + +char* +tkcvstags(Tk *tk, char *arg, char **val, int af) +{ + TkTop *o; + int x, y; + TkName *f; + TkCtag *t, *tt; + char *fmt; + TkCpoints p; + TkCanvas *c; + TkCitem *i, *b; + int d, dist, dx, dy; + char tag[Tkmaxitem], buf[Tkmaxitem]; + char *e; + + USED(val); + + c = TKobj(TkCanvas, tk); + + o = tk->env->top; + if(af == TkCadd) { + arg = tkword(o, arg, tag, tag+sizeof(tag), nil); + if(tag[0] == '\0' || (tag[0] >= '0' && tag[0] <= '9')) + return TkBadtg; + } + + fmt = "%d"; + arg = tkword(o, arg, buf, buf+sizeof(buf), nil); + if(strcmp(buf, "above") == 0) { + tkword(o, arg, buf, buf+sizeof(buf), nil); + f = tkctaglook(tk, nil, buf); + if(f == nil) + return TkBadtg; + + t = tkclasttag(c->head, f->obj); + if(t == nil) + return TkBadtg; + + for(i = t->item->next; i; i = i->next) { + if(af == TkCadd) { + i->tags = tkmkname(tag); + if(i->tags == nil) + return TkNomem; + tkcaddtag(tk, i, 0); + } + else { + e = tkvalue(val, fmt, i->id); + if(e != nil) + return e; + fmt = " %d"; + } + } + return nil; + } + + if(strcmp(buf, "all") == 0) { + for(i = c->head; i; i = i->next) { + if(af == TkCadd) { + i->tags = tkmkname(tag); + if(i->tags == nil) + return TkNomem; + tkcaddtag(tk, i, 0); + } + else { + e = tkvalue(val, fmt, i->id); + if(e != nil) + return e; + fmt = " %d"; + } + } + return nil; + } + + if(strcmp(buf, "below") == 0) { + tkword(o, arg, buf, buf+sizeof(buf), nil); + f = tkctaglook(tk, nil, buf); + if(f == nil) + return TkBadtg; + tt = f->obj; + for(b = c->head; b; b = b->next) { + for(t = tt; t; t = t->itemlist) + if(t->item == b) + goto found; + } + found: + for(i = c->head; i != b; i = i->next) { + if(af == TkCadd) { + i->tags = tkmkname(tag); + if(i->tags == nil) + return TkNomem; + tkcaddtag(tk, i, 0); + } + else { + e = tkvalue(val, fmt, i->id); + if(e != nil) + return e; + fmt = " %d"; + } + } + return nil; + } + + if(strcmp(buf, "closest") == 0) { + e = tkfracword(o, &arg, &x, nil); + if (e == nil) + e = tkfracword(o, &arg, &y, nil); + if (e != nil) + return e; + if(*arg != '\0') + return "!not implemented"; + + x = TKF2I(x); + y = TKF2I(y); + i = nil; + dist = 0; + for(b = c->head; b != nil; b = b->next) { + dx = x - (b->p.bb.min.x + Dx(b->p.bb)/2); + dy = y - (b->p.bb.min.y + Dy(b->p.bb)/2); + d = dx*dx + dy*dy; + if(d < dist || dist == 0) { + i = b; + dist = d; + } + } + if(i == nil) + return nil; + + if(af == TkCadd) { + i->tags = tkmkname(tag); + if(i->tags == nil) + e = TkNomem; + else + tkcaddtag(tk, i, 0); + } + else + e = tkvalue(val, fmt, i->id); + return e; + } + + if(strcmp(buf, "withtag") == 0) { + tkword(o, arg, buf, buf+sizeof(buf), nil); + f = tkctaglook(tk, nil, buf); + if(f == nil) + return TkBadtg; + for(t = f->obj; t; t = t->taglist) { + i = t->item; + if(af == TkCadd) { + i->tags = tkmkname(tag); + if(i->tags == nil) + return TkNomem; + tkcaddtag(tk, i, 0); + } + else { + e = tkvalue(val, fmt, i->id); + if(e != nil) + return e; + fmt = " %d"; + } + } + return nil; + } + + if(strcmp(buf, "enclosed") == 0) { + e = tkparsepts(o, &p, &arg, 0); + if(e != nil) + goto done; + if(p.npoint != 2) { + e = TkFewpt; + goto done; + } + for(i = c->head; i; i = i->next) { + if(rectinrect(i->p.bb, p.bb)) { + if(af == TkCadd) { + i->tags = tkmkname(tag); + if(i->tags == nil) { + e = TkNomem; + goto done; + } + tkcaddtag(tk, i, 0); + } + else { + e = tkvalue(val, fmt, i->id); + if(e != nil) + goto done; + fmt = " %d"; + } + } + } + goto done; + } + + if(strcmp(buf, "overlapping") == 0) { + e = tkparsepts(o, &p, &arg, 0); + if(e != nil) + goto done; + if(p.npoint != 2) { + e = TkFewpt; + goto done; + } + for(i = c->head; i; i = i->next) { + if(rectXrect(i->p.bb, p.bb)) { + if(af == TkCadd) { + i->tags = tkmkname(tag); + if(i->tags == nil) { + e = TkNomem; + goto done; + } + tkcaddtag(tk, i, 0); + } + else { + e = tkvalue(val, "%d ", i->id); + if(e != nil) + goto done; + } + } + } + goto done; + } + + return TkBadcm; + +done: /* both no error and error do the same thing */ + tkfreepoint(&p); + return e; +} + +static char* +tkcvsaddtag(Tk *tk, char *arg, char **val) +{ + return tkcvstags(tk, arg, val, TkCadd); +} + +static char* +tkcvsfind(Tk *tk, char *arg, char **val) +{ + return tkcvstags(tk, arg, val, TkCfind); +} + +static void +tksweepcanv(Tk *tk) +{ + int j, k; + TkCtag *t, *tt; + TkName **np, *n, *nn; + TkCitem *i; + TkCanvas *c; + TkAction *a; + + c = TKobj(TkCanvas, tk); + + for(j = 0; j < TkChash; j++) + for(n = c->thash[j]; n != nil; n = n->link) + n->ref = 0; + + for(i = c->head; i != nil; i = i->next) + for(t = i->stag; t != nil; t = t->itemlist) + t->name->ref = 1; + + k = 0; + for(j = 0; j < TkChash; j++) { + np = &c->thash[j]; + for(n = *np; n != nil; n = nn) { + nn = n->link; + if(n->ref == 0) { + for(t = n->obj; t != nil; t = tt) { + tt = t->taglist; + free(t); + } + tkfreebind(n->prop.binds); + free(n); + *np = nn; + } else { + np = &n->link; + for(a = n->prop.binds; a != nil; a = a->link) + k++; + } + } + } + + c->actions = k; + k = 3 * k / 2; + if (k < Tksweep) + c->actlim = Tksweep; + else + c->actlim = k; +} + +/* + * extension to tcl/tk: + * grab set tag + * grab release tag + * grab ifunset tag + */ +static char* +tkcvsgrab(Tk *tk, char *arg, char **val) +{ + TkCtag *t; + TkName *f; + TkCanvas *c; + char buf[Tkmaxitem]; + + c = TKobj(TkCanvas, tk); + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + if (strcmp(buf, "status") == 0) { + if (c->grab != nil) + return tkvalue(val, "%d", c->grab->id); + } + else if (strcmp(buf, "release") == 0) { + c->grab = nil; + } + else if (strcmp(buf, "set") == 0 || strcmp(buf, "ifunset") == 0) { + if (buf[0] == 'i' && c->grab != nil) + return nil; + tkword(tk->env->top, arg, buf, buf + sizeof(buf), nil); + + f = tkctaglook(tk, nil, buf); + if(f == nil || f->obj == nil) + return TkBadtg; + + c = TKobj(TkCanvas, tk); + t = tkcfirsttag(c->head, f->obj); + if(t == nil) + return TkBadtg; + c->grab = t->item; + } + else + return TkBadvl; + return nil; +} + +static char* +tkcvsbind(Tk *tk, char *arg, char **val) +{ + Rune r; + TkCtag *t; + TkName *f; + TkAction *a; + TkCanvas *c; + int event, mode; + char *cmd, buf[Tkmaxitem]; + char *e; + + c = TKobj(TkCanvas, tk); + if (c->actions >= c->actlim) + tksweepcanv(tk); + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + + f = tkctaglook(tk, nil, buf); + if(f == nil) { + f = tkctaglook(tk, tkmkname(buf), buf); + if(f == nil) + return TkNomem; + } + + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + if(buf[0] == '<') { + event = tkseqparse(buf+1); + if(event == -1) + return TkBadsq; + } + else { + chartorune(&r, buf); + event = TkKey | r; + } + if(event == 0) + return TkBadsq; + + arg = tkskip(arg, " \t"); + if(*arg == '\0') { + for(t = f->obj; t; t = t->taglist) { + for(a = t->name->prop.binds; a; a = a->link) + if(event == a->event) + return tkvalue(val, "%s", a->arg); + for(a = t->name->prop.binds; a; a = a->link) + if(event & a->event) + return tkvalue(val, "%s", a->arg); + } + return nil; + } + + mode = TkArepl; + if(*arg == '+') { + mode = TkAadd; + arg++; + } + else if(*arg == '-'){ + mode = TkAsub; + arg++; + } + + tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + cmd = strdup(buf); + if(cmd == nil) + return TkNomem; + e = tkaction(&f->prop.binds, event, TkDynamic, cmd, mode); + if(e == nil) + c->actions++; + return e; +} + +static char* +tkcvscreate(Tk *tk, char *arg, char **val) +{ + TkCimeth *m; + char buf[Tkmaxitem]; + + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + for(m = tkcimethod; m->name; m++) + if(strcmp(buf, m->name) == 0) + return m->create(tk, arg, val); + + return TkBadit; +} + +static char* +tkcvsbbox(Tk *tk, char *arg, char **val) +{ + TkName *f; + TkCtag *t; + Rectangle bb; + char buf[Tkmaxitem]; + + bb = bbnil; + for(;;) { + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + if(buf[0] == '\0') + break; + f = tkctaglook(tk, nil, buf); + if(f == nil) + return TkBadtg; + for(t = f->obj; t; t = t->taglist) + tkbbmax(&bb, &t->item->p.bb); + } + return tkvalue(val, "%d %d %d %d", bb.min.x, bb.min.y, bb.max.x, bb.max.y); +} + +static char* +tkcvscanvx(Tk *tk, char *arg, char **val) +{ + int x, s; + TkCanvas *c; + Point p; + char buf[Tkmaxitem]; + + c = TKobj(TkCanvas, tk); + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + if(buf[0] == '\0') + return TkBadvl; + + p = tkposn(tk); + x = atoi(buf) + c->view.x - (p.x + tk->borderwidth); + + if(*arg) { + tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + s = atoi(buf); + if (s) { + if (x>=0) + x = ((x+s/2)/s)*s; + else + x = ((x-s/2)/s)*s; + } + } + return tkvalue(val, "%d", x); +} + +static char* +tkcvscanvy(Tk *tk, char *arg, char **val) +{ + int y, s; + TkCanvas *c; + Point p; + char buf[Tkmaxitem]; + + c = TKobj(TkCanvas, tk); + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + if(buf[0] == '\0') + return TkBadvl; + + p = tkposn(tk); + y = atoi(buf) + c->view.y - (p.y + tk->borderwidth); + + if(*arg) { + tkitem(buf, arg); + s = atoi(buf); + if (s) { + if (y>=0) + y = ((y+s/2)/s)*s; + else + y = ((y-s/2)/s)*s; + } + } + return tkvalue(val, "%d", y); +} + +static char * +tkcvsscreenx(Tk *tk, char *arg, char **val) +{ + int x; + TkCanvas *c; + Point p; + char buf[Tkmaxitem]; + + c = TKobj(TkCanvas, tk); + tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + if(buf[0] == '\0') + return TkBadvl; + p = tkposn(tk); + x = atoi(buf) - c->view.x + (p.x + tk->borderwidth); + return tkvalue(val, "%d", x); +} + +static char * +tkcvsscreeny(Tk *tk, char *arg, char **val) +{ + int y; + TkCanvas *c; + Point p; + char buf[Tkmaxitem]; + + c = TKobj(TkCanvas, tk); + tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + if(buf[0] == '\0') + return TkBadvl; + p = tkposn(tk); + y = atoi(buf) - c->view.y + (p.y + tk->borderwidth); + return tkvalue(val, "%d", y); +} + +static char* +tkcvscoords(Tk *tk, char *arg, char **val) +{ + int i; + Point *p; + TkCtag *t; + TkName *f; + TkCanvas *c; + TkCitem *item; + char *fmt, *e, *v, buf[Tkmaxitem]; + + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + if(buf[0] == '\0') + return TkBadvl; + + f = tkctaglook(tk, nil, buf); + if(f == nil || f->obj == nil) + return TkBadtg; + + c = TKobj(TkCanvas, tk); + + t = tkcfirsttag(c->head, f->obj); + if(t == nil) + return TkBadtg; + + item = t->item; + + if(*arg == '\0') { + fmt = "%s"; + p = item->p.parampt; + for(i = 0; i < item->p.npoint; i++) { + v = tkfprint(buf, p->x); + *v++ = ' '; + tkfprint(v, p->y); + e = tkvalue(val, fmt, buf); + if(e != nil) + return e; + fmt = " %s"; + p++; + } + return nil; + } + + tkbbmax(&c->update, &item->p.bb); + e = tkcimethod[item->type].coord(item, arg, 0, 0); + tkbbmax(&c->update, &item->p.bb); + tkcvssetdirty(tk); + return e; +} + +static char* +tkcvsscale(Tk *tk, char *arg, char **val) +{ + TkName *f; + TkCtag *t; + TkCanvas *c; + TkCpoints pts; + TkCitem *item; + int j; + char *e, buf[Tkmaxitem]; + Point *p, *d, origin, scalef; + + USED(val); + + c = TKobj(TkCanvas, tk); + + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + f = tkctaglook(tk, nil, buf); + if(f == nil || f->obj == nil) + return TkBadtg; + + e = tkparsepts(tk->env->top, &pts, &arg, 0); + if(e != nil) + return e; + if(pts.npoint != 2) { + tkfreepoint(&pts); + return TkFewpt; + } + origin = pts.parampt[0]; + scalef = pts.parampt[1]; + tkfreepoint(&pts); + for(t = f->obj; t; t = t->taglist) { + item = t->item; + p = item->p.parampt; + d = item->p.drawpt; + for(j = 0; j < item->p.npoint; j++) { + p->x -= origin.x; + p->y -= origin.y; + p->x = TKF2I(p->x*scalef.x); + p->y = TKF2I(p->y*scalef.y); + p->x += origin.x; + p->y += origin.y; + d->x = TKF2I(p->x); + d->y = TKF2I(p->y); + d++; + p++; + } + tkbbmax(&c->update, &item->p.bb); + e = tkcimethod[item->type].coord(item, nil, 0, 0); + tkbbmax(&c->update, &item->p.bb); + if(e != nil) + return e; + + tkcvssetdirty(tk); + } + return nil; +} + +static char* +tkcvsdtag(Tk *tk, char *arg, char **val) +{ + TkName *f, *dt; + char buf[Tkmaxitem]; + TkCtag **l, *t, *it, *tf; + + USED(val); + + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + f = tkctaglook(tk, nil, buf); + if(f == nil || f->obj == nil) + return TkBadtg; + +/* + XXX this code doesn't appear to work properly. + fix it later. for the moment, it's just a somewhat more + efficient substitute for the later code, so just use that + instead. + + if(*arg == '\0') { + for(t = f->obj; t; t = tf) { + l = &t->item->stag; + for(it = *l; it; it = it->itemlist) { + if(it->item == t->item) { + *l = it->itemlist; + break; + } + l = &it->itemlist; + } + + tf = t->taglist; + free(t); + } + f->obj = nil; + return nil; + } +*/ + if (*arg == '\0') + dt = f; + else { + tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + dt = tkctaglook(tk, nil, buf); + if(dt == nil || dt->obj == nil) + return TkBadtg; + } + + for(t = f->obj; t; t = t->taglist) { + l = (TkCtag **)&dt->obj; + for(it = dt->obj; it; it = it->taglist) { + if(t->item == it->item) { + *l = it->taglist; + l = &t->item->stag; + for(tf = *l; tf; tf = tf->itemlist) { + if(tf == it) { + *l = tf->itemlist; + break; + } + l = &tf->itemlist; + } + free(it); + break; + } + l = &it->taglist; + } + } + return nil; +} + +static char* +tkcvsdchars(Tk *tk, char *arg, char **val) +{ + TkCtag *t; + TkName *f; + char *e, buf[Tkmaxitem]; + + USED(val); + + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + f = tkctaglook(tk, nil, buf); + if(f == nil || f->obj == nil) + return TkBadtg; + + for(t = f->obj; t; t = t->taglist) { + if(t->item->type == TkCVtext) { + e = tkcvstextdchar(tk, t->item, arg); + if(e != nil) + return e; + } + } + + return nil; +} + +static char* +tkcvsindex(Tk *tk, char *arg, char **val) +{ + TkCtag *t; + TkName *f; + char *e, buf[Tkmaxitem]; + + USED(val); + + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + f = tkctaglook(tk, nil, buf); + if(f == nil || f->obj == nil) + return TkBadtg; + + for(t = f->obj; t; t = t->taglist) { + if(t->item->type == TkCVtext) { + e = tkcvstextindex(tk, t->item, arg, val); + if(e != nil) + return e; + return nil; + } + } + return nil; +} + +static char* +tkcvsicursor(Tk *tk, char *arg, char **val) +{ + TkCtag *t; + TkName *f; + char *e, buf[Tkmaxitem]; + + USED(val); + + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + f = tkctaglook(tk, nil, buf); + if(f == nil || f->obj == nil) + return TkBadtg; + + for(t = f->obj; t; t = t->taglist) { + if(t->item->type == TkCVtext) { + e = tkcvstexticursor(tk, t->item, arg); + if(e != nil) + return e; + return nil; + } + } + return nil; +} + +static char* +tkcvsinsert(Tk *tk, char *arg, char **val) +{ + TkCtag *t; + TkName *f; + char *e, buf[Tkmaxitem]; + + USED(val); + + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + f = tkctaglook(tk, nil, buf); + if(f == nil || f->obj == nil) + return TkBadtg; + + for(t = f->obj; t; t = t->taglist) { + if(t->item->type == TkCVtext) { + e = tkcvstextinsert(tk, t->item, arg); + if(e != nil) + return e; + } + } + + return nil; +} + +static char* +tkcvsselect(Tk *tk, char *arg, char **val) +{ + int op; + TkCtag *t; + TkName *f; + TkCanvas *c; + char buf[Tkmaxitem]; + + c = TKobj(TkCanvas, tk); + + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + if(strcmp(buf, "clear") == 0) { + tkcvstextclr(tk); + return nil; + } + if(strcmp(buf, "item") == 0) { + if(c->selection) + return tkvalue(val, "%d", c->selection->id); + return nil; + } + + if(strcmp(buf, "to") == 0) + op = TkCselto; + else + if(strcmp(buf, "from") == 0) + op = TkCselfrom; + else + if(strcmp(buf, "adjust") == 0) + op = TkCseladjust; + else + return TkBadcm; + + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + f = tkctaglook(tk, nil, buf); + if(f == nil) + return TkBadtg; + + t = tkcfirsttag(c->head, f->obj); + if(t == nil) + return TkBadtg; + + return tkcvstextselect(tk, t->item, arg, op); +} + +static char* +tkcvsitemcget(Tk *tk, char *arg, char **val) +{ + TkName *f; + TkCtag *t; + TkCitem *i; + char buf[Tkmaxitem]; + + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + f = tkctaglook(tk, nil, buf); + if(f == nil || f->obj == nil) + return TkBadtg; + + for(i = TKobj(TkCanvas, tk)->head; i; i = i->next) { + for(t = f->obj; t; t = t->taglist) + if(i == t->item) + return tkcimethod[i->type].cget(i, arg, val); + } + return nil; +} + +static char* +tkcvsitemconf(Tk *tk, char *arg, char **val) +{ + char *e; + TkName *f; + TkCtag *t; + TkCitem *i; + TkCanvas *c; + char buf[Tkmaxitem]; + + USED(val); + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + f = tkctaglook(tk, nil, buf); + if(f == nil || f->obj == nil) + return TkBadtg; + + c = TKobj(TkCanvas, tk); + for(t = f->obj; t; t = t->taglist) { + for(i = c->head; i; i = i->next) { + if(i == t->item) { + tkbbmax(&c->update, &i->p.bb); + e = tkcimethod[i->type].conf(tk, i, arg); + tkbbmax(&c->update, &i->p.bb); + tkcvssetdirty(tk); + if(e != nil) + return e; + } + } + } + return nil; +} + +static void +tkcvsfreename(TkCanvas *c, TkName *n) +{ + ulong h; + char *p, *s; + TkName *f, **l; + + /* just free implicit ones for now */ + if(n == nil) + return; + s = n->name; + if(s == nil || (s[0] < '0' || s[0] > '9')) + return; + h = 0; + for(p = s; *p; p++) + h += 3*h + *p; + l = &c->thash[h%TkChash]; + for(f = *l; f; l = &f->link, f = *l) + if(f == n){ + *l = f->link; + tkfreebind(f->prop.binds); + free(f); + return; + } +} + +static char* +tkcvsdelete(Tk *tk, char *arg, char **val) +{ + TkName *f; + TkCanvas *c; + char buf[Tkmaxitem]; + TkCitem *item, *prev, *i; + TkCtag *t, *inext, **l, *dit, *it; + + USED(val); + + c = TKobj(TkCanvas, tk); + for(;;) { + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + if(buf[0] == '\0') + break; + f = tkctaglook(tk, nil, buf); + if(f == nil || f->obj == nil) + return nil; + while(f->obj) { + t = f->obj; + item = t->item; + for(it = item->stag; it; it = inext) { + inext = it->itemlist; + l = (TkCtag **)&it->name->obj; + for(dit = *l; dit; dit = dit->taglist) { + if(dit->item == item) { + *l = dit->taglist; + if(dit != t){ + tkcvsfreename(c, dit->name); + free(dit); + } + break; + } + l = &dit->taglist; + } + } + tkbbmax(&c->update, &item->p.bb); + tkcvssetdirty(tk); + prev = nil; + for(i = c->head; i; i = i->next) { + if(i == item) + break; + prev = i; + } + if(prev == nil) + c->head = i->next; + else + prev->next = i->next; + if(c->tail == item) + c->tail = prev; + if(c->focus == item) + c->focus = nil; + if(c->mouse == item) + c->mouse = nil; + if(c->selection == item) + c->selection = nil; + if(c->curtag.item == item) + c->current->obj = nil; + if (c->grab == item) + c->grab = nil; + + tkcvsfreeitem(item); + free(t); + } + } + return nil; +} + +static char* +tkcvsfocus(Tk *tk, char *arg, char **val) +{ + TkName *f; + TkCtag *t; + TkCanvas *c; + TkCitem *i, *focus; + char buf[Tkmaxitem]; + + c = TKobj(TkCanvas, tk); + + if(*arg == '\0') { + if(c->focus == nil) + return nil; + return tkvalue(val, "%d", c->focus->id); + } + + tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + if(buf[0] == '\0') + return TkBadvl; + f = tkctaglook(tk, nil, buf); + if(f == nil || f->obj == nil) + return nil; + + focus = c->focus; + if(focus != nil && focus->type == TkCVtext) + tkcvstextfocus(tk, focus, 0); + + for(i = c->head; i; i = i->next) { + if(i->type == TkCVtext || i->type == TkCVwindow) { + for(t = f->obj; t; t = t->taglist) + if(t->item == i) + focus = i; + } + } + + if(focus != nil && focus->type == TkCVtext) + tkcvstextfocus(tk, focus, 1); + + c->focus = focus; + return nil; +} + +static char* +tkcvsgettags(Tk *tk, char *arg, char **val) +{ + TkCtag *t; + TkName *f; + TkCanvas *c; + char *fmt, *e, buf[Tkmaxitem]; + + tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + if(buf[0] == '\0') + return TkBadvl; + + f = tkctaglook(tk, nil, buf); + if(f == nil) + return TkBadtg; + + c = TKobj(TkCanvas, tk); + t = tkclasttag(c->head, f->obj); + if(t == nil) + return TkBadtg; + fmt = "%s"; + t = t->item->stag; + while(t) { + /* XXX when might t->name be legally nil? */ + if (t->name != nil) { + if (strcmp(t->name->name, "all")) { + e = tkvalue(val, fmt, t->name->name); + if(e != nil) + return e; + fmt = " %s"; + } + } + t = t->itemlist; + } + return nil; +} + +static char* +tkcvslower(Tk *tk, char *arg, char **val) +{ + TkCtag *t; + TkCanvas *c; + TkName *f, *b; + char buf[Tkmaxitem]; + TkCitem *it, **l, **below, *items, **itemtail, *prev, *iprev; + + USED(val); + c = TKobj(TkCanvas, tk); + + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + f = tkctaglook(tk, nil, buf); + if(f == nil || f->obj == nil) + return nil; + + below = &c->head; + tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + if(buf[0] != '\0') { + b = tkctaglook(tk, nil, buf); + if(b == nil || f->obj == nil) + return TkBadtg; + for(it = c->head; it; it = it->next) { + for(t = b->obj; t; t = t->taglist) + if(t->item == it) + goto found; + below = &it->next; + } + found:; + } + l = &c->head; + prev = iprev = nil; + itemtail = &items;; + for (it = *l; it != nil; it = *l) { + for (t = f->obj; t; t = t->taglist) { + if(t->item == it) { + if (it == *below || below == &it->next) + below = l; + if (it == c->tail) + c->tail = prev; + *l = it->next; + *itemtail = it; + iprev = it; + itemtail = &it->next; + tkbbmax(&c->update, &it->p.bb); + goto next; + } + } + prev = it; + l = &it->next; +next:; + } + if (prev == nil) + c->tail = iprev; + *itemtail = *below; + *below = items; + tkcvssetdirty(tk); + return nil; +} + +static char* +tkcvsmove(Tk *tk, char *arg, char **val) +{ + TkCtag *t; + int fx, fy; + TkTop *top; + TkCpoints *p; + TkName *tag; + Rectangle *u; + TkCitem *item; + char *e; + char buf[Tkmaxitem]; + + USED(val); + top = tk->env->top; + arg = tkword(top, arg, buf, buf+sizeof(buf), nil); + tag = tkctaglook(tk, nil, buf); + if(tag == nil) + return nil; + + e = tkfracword(top, &arg, &fx, nil); + if (e != nil) + return e; + e = tkfracword(top, &arg, &fy, nil); + if(e != nil) + return e; + + u = &TKobj(TkCanvas, tk)->update; + for(t = tag->obj; t; t = t->taglist) { + item = t->item; + p = &item->p; + tkbbmax(u, &p->bb); + tkcimethod[item->type].coord(item, nil, fx, fy); + tkbbmax(u, &p->bb); + } + tkcvssetdirty(tk); + return nil; +} + +static char* +tkcvsraise(Tk *tk, char *arg, char **val) +{ + TkCtag *t; + TkCanvas *c; + TkName *f, *a; + char buf[Tkmaxitem]; + TkCitem *prev, *it, *above, *items, *itemtail, *next; + + USED(val); + c = TKobj(TkCanvas, tk); + + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + f = tkctaglook(tk, nil, buf); + if(f == nil) + return nil; + + above = c->tail; + tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + if(buf[0] != '\0') { + a = tkctaglook(tk, nil, buf); + if(a == nil) + return TkBadtg; + /* + * find topmost item in the display list matching the "above" tag + */ + for(it = c->head; it != nil; it = it->next) { + for(t = a->obj; t; t = t->taglist) + if(t->item == it) + above = it; + } + } + prev = nil; + items = itemtail = nil; + for (it = c->head; it != nil; it = next) { + next = it->next; + for (t = f->obj; t; t = t->taglist) { + if(t->item == it) { + if (it == above) + above = next; + if (prev) + prev->next = next; + else + c->head = next; + if (itemtail) + itemtail->next = it; + else + items = it; + itemtail = it; + tkbbmax(&c->update, &it->p.bb); + goto next; + } + } + prev = it; +next:; + } + if (items != nil) { + if (above) { + itemtail->next = above->next; + if (above->next == nil) + c->tail = itemtail; + above->next = items; + } else { + if (prev) + prev->next = items; + else + c->head = items; + c->tail = itemtail; + itemtail->next = nil; + } + } + + tkcvssetdirty(tk); + return nil; +} + +static char* +tkcvstype(Tk *tk, char *arg, char **val) +{ + TkCtag *t; + TkName *f; + TkCanvas *c; + char buf[Tkmaxitem]; + + tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + if(buf[0] == '\0') + return TkBadvl; + + f = tkctaglook(tk, nil, buf); + if(f == nil || f->obj == nil) + return nil; + + c = TKobj(TkCanvas, tk); + + t = tkcfirsttag(c->head, f->obj); + if(t == nil) + return nil; + + return tkvalue(val, "%s", tkcimethod[t->item->type].name); +} + +static char* +tkcvsview(Tk *tk, char *arg, char **val, int nl, int *posn, int min, int max, int inc) +{ + TkTop *t; + int top, bot, diff, amount; + char *e; + char buf[Tkmaxitem], *v; + + diff = max-min; + if(*arg == '\0') { + if ( diff == 0 ) + top = bot = 0; + else { + top = TKI2F(*posn-min)/diff; + bot = TKI2F(*posn+nl-min)/diff; + } + v = tkfprint(buf, top); + *v++ = ' '; + tkfprint(v, bot); + return tkvalue(val, "%s", buf); + } + + t = tk->env->top; + arg = tkword(t, arg, buf, buf+sizeof(buf), nil); + if(strcmp(buf, "moveto") == 0) { + e = tkfrac(&arg, &top, nil); + if (e != nil) + return e; + *posn = min + TKF2I((top+1)*diff); + } + else + if(strcmp(buf, "scroll") == 0) { + arg = tkword(t, arg, buf, buf+sizeof(buf), nil); + amount = atoi(buf); + tkword(t, arg, buf, buf+sizeof(buf), nil); + if(buf[0] == 'p') /* Pages */ + amount = amount * nl * 9 /10; + else if (inc > 0) + amount *= inc; + else + amount = amount * nl / 10; + *posn += amount; + } + else + return TkBadcm; + + bot = max - nl; + if(*posn > bot) + *posn = bot; + if(*posn < min) + *posn = min; + + tk->dirty = tkrect(tk, 0); + return nil; +} + +static char* +tkcvsyview(Tk *tk, char *arg, char **val) +{ + int si; + char *e; + TkCanvas *c = TKobj(TkCanvas, tk); + + si = TKF2I(c->yscrolli); + e = tkcvsview(tk, arg, val, tk->act.height, &c->view.y, c->region.min.y, c->region.max.y, si); + tkcvssv(tk); + return e; +} + +static char* +tkcvsxview(Tk *tk, char *arg, char **val) +{ + int si; + char *e; + TkCanvas *c = TKobj(TkCanvas, tk); + + si = TKF2I(c->xscrolli); + e = tkcvsview(tk, arg, val, tk->act.width, &c->view.x, c->region.min.x, c->region.max.x, si); + tkcvssh(tk); + return e; +} + +/* + * return in posn the new view origin such that (preferably) smin and smax + * lie between cmin and cmax (cmin is the current view origin, and cmax the + * other end of the visible area). + * adjust posn (the view origin) so that (preferably) both smin and smax lie + * inside cmin to cmax. if both smin and smax cannot fit, then + * at least make sure that spref (smin<=spref<=smax) is visible. + * return 0 if no adjustment is required (the interval is already visible). + * + * attempt to make an adjustment as small as possible that + * fits these criteria. + */ +static int +tkadjustvis(int *posn, int c0, int c1, int s0, int s1, int spref) +{ + int d, v; + + d = c1 - c0; /* visible width */ + + /* + * if requested range fits inside visible range, + * no adjustment is necessary + */ + if (c0 <= s0 && s1 <= c1) + return 0; + + /* + * if requested range fits, make it fully visible + */ + if (s1 - s0 < d) { + if (s0 < c0) + v = s0; + else + v = s1 - d; + } else { + /* + * choose upper or lower end of requested range, + * depending on which end of requested area is already + * visible (if any). + */ + if (c0 <= s1 && s1 < c1) { /* overlapping left of visible */ + v = s1 - d; + if (v > spref) + v = spref; + } + else + if (c0 <= s0 && s0 < c1) { /* overlapping right of visible */ + v = s0; + if (v + d <= spref) + v = spref - d; + } + else + if (s1 < c0) { /* left of visible */ + v = spref; + if (v + d > s1) + v = s1 - d; + } + else { /* right of visible */ + v = spref - d; + if (v < s0) + v = s0; + } + } + *posn = v; + return 1; +} + +static void +tkcvsseerect(Tk *tk, Rectangle r, Point p) +{ + TkCanvas *c; + int scrollh, scrollv; + + c = TKobj(TkCanvas, tk); + + scrollh = tkadjustvis(&c->view.x, c->view.x, c->view.x + tk->act.width, + r.min.x, r.max.x, p.x); + scrollv = tkadjustvis(&c->view.y, c->view.y, c->view.y + tk->act.height, + r.min.y, r.max.y, p.y); + if (scrollh) + tkcvssh(tk); + if (scrollv) + tkcvssv(tk); + if (scrollh || scrollv) + tk->dirty = tkrect(tk, 0); +} + +static char* +tkcvssee(Tk *tk, char *arg, char **val) +{ + Rectangle r; + int n, coords[4]; + char *e; + + USED(val); + n = 0; + while (n < 4) { + if (*arg == '\0') + break; + e = tkfracword(tk->env->top, &arg, &coords[n++], nil); + if (e != nil) + return e; + } + + if (n != 2 && n != 4) + return TkFewpt; + + r.min.x = TKF2I(coords[0]); + r.min.y = TKF2I(coords[1]); + if (n == 4) { + r.max.x = TKF2I(coords[2]); + r.max.y = TKF2I(coords[3]); + } else + r.max = r.min; + r = canonrect(r); + /* + * XXX should intersect r with scrollregion here, as you shouldn't + * be able to display things outside the scroll region. (??) + */ + + tkcvsseerect(tk, r, r.min); + return nil; +} + +static void +tkcvsseesub(Tk *tk, Rectangle *rr, Point *pp) +{ + Rectangle r; + Point p; + TkCanvas *c; + c = TKobj(TkCanvas, tk); + + r = rectaddpt(*rr, c->view); + p = addpt(*pp, c->view); + + tkcvsseerect(tk, r, p); + + *rr = rectsubpt(r, c->view); + *pp = subpt(p, c->view); +} + +static void +tkcvsgetimgs(Tk* tk, Image **image, Image **mask) +{ + TkCanvas *c; + c = TKobj(TkCanvas, tk); + + *image = c->image; + *mask = c->mask; /* XXX this is wrong - the mask image has nothing to do with the main image */ +} + +TkCimeth tkcimethod[] = +{ + "line", tkcvslinecreat, + tkcvslinedraw, + tkcvslinefree, + tkcvslinecoord, + tkcvslinecget, + tkcvslineconf, + tkcvslinehit, + + "text", tkcvstextcreat, + tkcvstextdraw, + tkcvstextfree, + tkcvstextcoord, + tkcvstextcget, + tkcvstextconf, + nil, + + "rectangle", tkcvsrectcreat, + tkcvsrectdraw, + tkcvsrectfree, + tkcvsrectcoord, + tkcvsrectcget, + tkcvsrectconf, + nil, + + "oval", tkcvsovalcreat, + tkcvsovaldraw, + tkcvsovalfree, + tkcvsovalcoord, + tkcvsovalcget, + tkcvsovalconf, + tkcvsovalhit, + + "bitmap", tkcvsbitcreat, + tkcvsbitdraw, + tkcvsbitfree, + tkcvsbitcoord, + tkcvsbitcget, + tkcvsbitconf, + nil, + + "polygon", tkcvspolycreat, + tkcvspolydraw, + tkcvspolyfree, + tkcvspolycoord, + tkcvspolycget, + tkcvspolyconf, + tkcvspolyhit, + + "window", tkcvswindcreat, + tkcvswinddraw, + tkcvswindfree, + tkcvswindcoord, + tkcvswindcget, + tkcvswindconf, + nil, + + "image", tkcvsimgcreat, + tkcvsimgdraw, + tkcvsimgfree, + tkcvsimgcoord, + tkcvsimgcget, + tkcvsimgconf, + nil, + + "arc", tkcvsarccreat, + tkcvsarcdraw, + tkcvsarcfree, + tkcvsarccoord, + tkcvsarccget, + tkcvsarcconf, + nil, + nil +}; + +static +TkCmdtab tkcanvcmd[] = +{ + "addtag", tkcvsaddtag, + "bbox", tkcvsbbox, + "bind", tkcvsbind, + "cget", tkcvscget, + "configure", tkcvsconf, + "create", tkcvscreate, + "canvasx", tkcvscanvx, + "canvasy", tkcvscanvy, + "coords", tkcvscoords, + "dchars", tkcvsdchars, + "delete", tkcvsdelete, + "dtag", tkcvsdtag, + "find", tkcvsfind, + "focus", tkcvsfocus, + "gettags", tkcvsgettags, + "grab", tkcvsgrab, + "icursor", tkcvsicursor, + "insert", tkcvsinsert, + "index", tkcvsindex, + "itemcget", tkcvsitemcget, + "itemconfigure", tkcvsitemconf, + "lower", tkcvslower, + "move", tkcvsmove, + "raise", tkcvsraise, + "screenx", tkcvsscreenx, + "screeny", tkcvsscreeny, + "see", tkcvssee, + "select", tkcvsselect, + "scale", tkcvsscale, + "type", tkcvstype, + "yview", tkcvsyview, + "xview", tkcvsxview, + nil +}; + +TkMethod canvasmethod = { + "canvas", + tkcanvcmd, + tkfreecanv, + tkdrawcanv, + tkcvsgeom, + tkcvsgetimgs, + tkcvsfocusorder, + tkcvsdirty, + tkcvsrelpos, + tkcvsevent, + tkcvsseesub, + tkcvsinwindow +}; diff --git a/libtk/canvs.h b/libtk/canvs.h new file mode 100644 index 00000000..d82b7eb1 --- /dev/null +++ b/libtk/canvs.h @@ -0,0 +1,238 @@ +typedef struct TkCimeth TkCimeth; +typedef struct TkCitem TkCitem; +typedef struct TkCanvas TkCanvas; +typedef struct TkCline TkCline; +typedef struct TkCtag TkCtag; +typedef struct TkCpoints TkCpoints; +typedef struct TkCwind TkCwind; + +struct TkCline +{ + int arrow; + int shape[3]; + int width; + Image* stipple; + Image* pen; + int arrowf; + int arrowl; + int capstyle; + int smooth; + int steps; +}; + +struct TkCwind +{ + Tk* sub; /* Subwindow of canvas */ + Tk* focus; /* Current Mouse focus */ + int width; /* Requested width */ + int height; /* Requested height */ + int flags; /* possible: Tkanchor|Tksetwidth|Tksetheight */ +}; + +struct TkCpoints +{ + int npoint; /* Number of points */ + Point* parampt; /* Parameters in fixed point */ + Point* drawpt; /* Draw coord in pixels */ + Rectangle bb; /* Bounding box in pixels */ +}; + +struct TkCitem +{ + int id; /* Unique id */ + int type; /* Object type */ + TkCpoints p; /* Points plus bounding box */ + TkEnv* env; /* Colors & fonts */ + TkCitem* next; /* Z order */ + TkName* tags; /* Temporary tag spot */ + TkCtag* stag; /* Real tag structure */ +// char obj[TKSTRUCTALIGN]; +}; + +struct TkCtag +{ + TkCitem* item; /* Link to item */ + TkName* name; /* Text name or id */ + TkCtag* taglist; /* link items with this tag */ + TkCtag* itemlist; /* link tags for this item */ +}; + +enum +{ + /* Item types */ + TkCVline, + TkCVtext, + TkCVrect, + TkCVoval, + TkCVbitmap, + TkCVpoly, + TkCVwindow, + TkCVimage, + TkCVarc, + + TkCselto = 0, + TkCselfrom, + TkCseladjust, + + TkCbufauto = 0, + TkCbufnone, + TkCbufvisible, + TkCbufall, + + TkCadd = 0, + TkCfind, + + TkChash = 32, + + TkCarrowf = (1<<0), + TkCarrowl = (1<<1), + Tknarrow = 6 /* Number of points in arrow */ +}; + +struct TkCanvas +{ + int close; + int confine; + int cleanup; + int scrollr[4]; + Rectangle region; + Rectangle update; /* Area to paint next draw */ + Point view; + TkCitem* selection; + int width; + int height; + int sborderwidth; + int xscrolli; /* Scroll increment */ + int yscrolli; + char* xscroll; /* Scroll commands */ + char* yscroll; + int id; /* Unique id */ + TkCitem* head; /* Items in Z order */ + TkCitem* tail; /* Head is lowest, tail is highest */ + TkCitem* focus; /* Keyboard focus */ + TkCitem* mouse; /* Mouse focus */ + TkCitem* grab; + TkName* current; /* Fake for current tag */ + TkCtag curtag; + Image* image; /* Drawing space */ + int ialloc; /* image was allocated by us? */ + Image* mask; /* mask space (for stippling) */ + TkName* thash[TkChash]; /* Tag hash */ + int actions; + int actlim; + int buffer; +}; + +struct TkCimeth +{ + char* name; + char* (*create)(Tk*, char *arg, char **val); + void (*draw)(Image*, TkCitem*, TkEnv*); + void (*free)(TkCitem*); + char* (*coord)(TkCitem*, char*, int, int); + char* (*cget)(TkCitem*, char*, char**); + char* (*conf)(Tk*, TkCitem*, char*); + int (*hit)(TkCitem*, Point); +}; + +extern TkCimeth tkcimethod[]; +extern int cvslshape[]; +extern Rectangle bbnil; +extern Rectangle huger; + +/* General */ +extern char* tkcaddtag(Tk*, TkCitem*, int); +extern TkCtag* tkcfirsttag(TkCitem*, TkCtag*); +extern TkCtag* tkclasttag(TkCitem*, TkCtag*); +extern void tkcvsappend(TkCanvas*, TkCitem*); +extern TkCitem* tkcnewitem(Tk*, int, int); +extern void tkcvsfreeitem(TkCitem*); +extern Point tkcvsrelpos(Tk*); +extern Tk* tkcvsinwindow(Tk*, Point*); +extern char* tkcvstextdchar(Tk*, TkCitem*, char*); +extern char* tkcvstextindex(Tk*, TkCitem*, char*, char **val); +extern char* tkcvstextinsert(Tk*, TkCitem*, char*); +extern char* tkcvstexticursor(Tk*, TkCitem*, char*); +extern void tkmkpen(Image**, TkEnv*, Image*); +extern void tkcvstextfocus(Tk*, TkCitem*, int); +extern char* tkcvstextselect(Tk*, TkCitem*, char*, int); +extern void tkcvstextclr(Tk*); +extern Tk* tkcvsevent(Tk*, int, void*); +extern Point tkcvsanchor(Point, int, int, int); +extern void tkcvsdirty(Tk*); +extern void tkfreectag(TkCtag*); +extern char* tkparsepts(TkTop*, TkCpoints*, char**, int); +extern void tkfreepoint(TkCpoints*); +extern void tkxlatepts(Point*, int, int, int); +extern void tkpolybound(Point*, int, Rectangle*); +extern TkName* tkctaglook(Tk*, TkName*, char*); +extern void tkbbmax(Rectangle*, Rectangle*); +extern void tkcvssetdirty(Tk*); + +/* Canvas Item methods - required to populate tkcimethod in canvs.c */ +extern char* tkcvslinecreat(Tk*, char *arg, char **val); +extern void tkcvslinedraw(Image*, TkCitem*, TkEnv*); +extern void tkcvslinefree(TkCitem*); +extern char* tkcvslinecoord(TkCitem*, char*, int, int); +extern char* tkcvslinecget(TkCitem*, char*, char**); +extern char* tkcvslineconf(Tk*, TkCitem*, char*); +extern int tkcvslinehit(TkCitem*, Point); + +extern char* tkcvstextcreat(Tk*, char *arg, char **val); +extern void tkcvstextdraw(Image*, TkCitem*, TkEnv*); +extern void tkcvstextfree(TkCitem*); +extern char* tkcvstextcoord(TkCitem*, char*, int, int); +extern char* tkcvstextcget(TkCitem*, char*, char**); +extern char* tkcvstextconf(Tk*, TkCitem*, char*); + +extern char* tkcvsrectcreat(Tk*, char *arg, char **val); +extern void tkcvsrectdraw(Image*, TkCitem*, TkEnv*); +extern void tkcvsrectfree(TkCitem*); +extern char* tkcvsrectcoord(TkCitem*, char*, int, int); +extern char* tkcvsrectcget(TkCitem*, char*, char**); +extern char* tkcvsrectconf(Tk*, TkCitem*, char*); + +extern char* tkcvsovalcreat(Tk*, char *arg, char **val); +extern void tkcvsovaldraw(Image*, TkCitem*, TkEnv*); +extern void tkcvsovalfree(TkCitem*); +extern char* tkcvsovalcoord(TkCitem*, char*, int, int); +extern char* tkcvsovalcget(TkCitem*, char*, char**); +extern char* tkcvsovalconf(Tk*, TkCitem*, char*); +extern int tkcvsovalhit(TkCitem*, Point); + +extern char* tkcvsarccreat(Tk*, char *arg, char **val); +extern void tkcvsarcdraw(Image*, TkCitem*, TkEnv*); +extern void tkcvsarcfree(TkCitem*); +extern char* tkcvsarccoord(TkCitem*, char*, int, int); +extern char* tkcvsarccget(TkCitem*, char*, char**); +extern char* tkcvsarcconf(Tk*, TkCitem*, char*); + +extern char* tkcvsbitcreat(Tk*, char *arg, char **val); +extern void tkcvsbitdraw(Image*, TkCitem*, TkEnv*); +extern void tkcvsbitfree(TkCitem*); +extern char* tkcvsbitcoord(TkCitem*, char*, int, int); +extern char* tkcvsbitcget(TkCitem*, char*, char**); +extern char* tkcvsbitconf(Tk*, TkCitem*, char*); + +extern char* tkcvswindcreat(Tk*, char *arg, char **val); +extern void tkcvswinddraw(Image*, TkCitem*, TkEnv*); +extern void tkcvswindfree(TkCitem*); +extern char* tkcvswindcoord(TkCitem*, char*, int, int); +extern char* tkcvswindcget(TkCitem*, char*, char**); +extern char* tkcvswindconf(Tk*, TkCitem*, char*); + +extern char* tkcvspolycreat(Tk*, char *arg, char **val); +extern void tkcvspolydraw(Image*, TkCitem*, TkEnv*); +extern void tkcvspolyfree(TkCitem*); +extern char* tkcvspolycoord(TkCitem*, char*, int, int); +extern char* tkcvspolycget(TkCitem*, char*, char**); +extern char* tkcvspolyconf(Tk*, TkCitem*, char*); +extern int tkcvspolyhit(TkCitem*, Point); + +extern char* tkcvsimgcreat(Tk*, char *arg, char **val); +extern void tkcvsimgdraw(Image*, TkCitem*, TkEnv*); +extern void tkcvsimgfree(TkCitem*); +extern char* tkcvsimgcoord(TkCitem*, char*, int, int); +extern char* tkcvsimgcget(TkCitem*, char*, char**); +extern char* tkcvsimgconf(Tk*, TkCitem*, char*); + diff --git a/libtk/canvu.c b/libtk/canvu.c new file mode 100644 index 00000000..3894ff20 --- /dev/null +++ b/libtk/canvu.c @@ -0,0 +1,506 @@ +#include <lib9.h> +#include <kernel.h> +#include "draw.h" +#include "tk.h" +#include "canvs.h" + +char* +tkparsepts(TkTop *t, TkCpoints *i, char **arg, int close) +{ + char *s, *e; + Point *p, *d; + int n, npoint; + + i->parampt = nil; + i->drawpt = nil; + i->bb = bbnil; + s = *arg; + npoint = 0; + while(*s) { + s = tkskip(s, " \t"); + if(*s == '-' && (s[1] < '0' || s[1] > '9')) + break; + while(*s && *s != ' ' && *s != '\t') + s++; + npoint++; + } + + i->parampt = mallocz(npoint*sizeof(Point), 0); + if(i->parampt == nil) + return TkNomem; + + s = *arg; + p = i->parampt; + npoint = 0; + while(*s) { + e = tkfracword(t, &s, &p->x, nil); + if(e != nil) + goto Error; + e = tkfracword(t, &s, &p->y, nil); + if(e != nil) + goto Error; + npoint++; + s = tkskip(s, " \t"); + if(*s == '-' && (s[1] < '0' || s[1] > '9')) + break; + p++; + } + *arg = s; + close = (close != 0); + i->drawpt = mallocz((npoint+close)*sizeof(Point), 0); + if(i->drawpt == nil){ + e = TkNomem; + goto Error; + } + + d = i->drawpt; + p = i->parampt; + for(n = 0; n < npoint; n++) { + d->x = TKF2I(p->x); + d->y = TKF2I(p->y); + if(d->x < i->bb.min.x) + i->bb.min.x = d->x; + if(d->x > i->bb.max.x) + i->bb.max.x = d->x; + if(d->y < i->bb.min.y) + i->bb.min.y = d->y; + if(d->y > i->bb.max.y) + i->bb.max.y = d->y; + d++; + p++; + } + if (close) + *d = i->drawpt[0]; + + i->npoint = npoint; + return nil; + +Error: + tkfreepoint(i); + i->parampt = nil; + i->drawpt = nil; + return e; +} + +TkCitem* +tkcnewitem(Tk *tk, int t, int n) +{ + TkCitem *i; + + i = malloc(n); + if(i == nil) + return nil; + memset(i, 0, n); + + i->type = t; + i->env = tk->env; + i->env->ref++; + + return i; +} + +/* + * expand the canvas's dirty rectangle, clipping + * appropriately to its boundaries. + */ +void +tkcvssetdirty(Tk *tk) +{ + TkCanvas *c; + Rectangle r; + c = TKobj(TkCanvas, tk); + + r = tkrect(tk, 0); + if (rectclip(&r, rectsubpt(c->update, c->view))) + combinerect(&tk->dirty, r); +} + +void +tkxlatepts(Point *p, int npoints, int x, int y) +{ + while(npoints--) { + p->x += x; + p->y += y; + p++; + } +} + +void +tkbbmax(Rectangle *bb, Rectangle *r) +{ + if(r->min.x < bb->min.x) + bb->min.x = r->min.x; + if(r->min.y < bb->min.y) + bb->min.y = r->min.y; + if(r->max.x > bb->max.x) + bb->max.x = r->max.x; + if(r->max.y > bb->max.y) + bb->max.y = r->max.y; +} + +void +tkpolybound(Point *p, int n, Rectangle *r) +{ + while(n--) { + if(p->x < r->min.x) + r->min.x = p->x; + if(p->y < r->min.y) + r->min.y = p->y; + if(p->x > r->max.x) + r->max.x = p->x; + if(p->y > r->max.y) + r->max.y = p->y; + p++; + } +} + +/* + * look up a tag for a canvas item. + * if n is non-nil, and the tag isn't found, + * then add it to the canvas's taglist. + * NB if there are no binds done on the + * canvas, these tags never get cleared out, + * even if nothing refers to them. + */ +TkName* +tkctaglook(Tk* tk, TkName *n, char *name) +{ + ulong h; + TkCanvas *c; + char *p, *s; + TkName *f, **l; + + c = TKobj(TkCanvas, tk); + + s = name; + if(s == nil) + s = n->name; + + if(strcmp(s, "current") == 0) + return c->current; + + h = 0; + for(p = s; *p; p++) + h += 3*h + *p; + + l = &c->thash[h%TkChash]; + for(f = *l; f; f = f->link) + if(strcmp(f->name, s) == 0) + return f; + + if(n == nil) + return nil; + n->link = *l; + *l = n; + return n; +} + +char* +tkcaddtag(Tk *tk, TkCitem *i, int new) +{ + TkCtag *t; + TkCanvas *c; + char buf[16]; + TkName *n, *f, *link; + + c = TKobj(TkCanvas, tk); + if(new != 0) { + i->id = ++c->id; + snprint(buf, sizeof(buf), "%d", i->id); + n = tkmkname(buf); + if(n == nil) + return TkNomem; + n->link = i->tags; + i->tags = n; + } + + for(n = i->tags; n; n = link) { + link = n->link; + f = tkctaglook(tk, n, nil); + if(n != f) + free(n); + + for(t = i->stag; t; t = t->itemlist) + if(t->name == f) + break; + if(t == nil) { + t = malloc(sizeof(TkCtag)); + if(t == nil) { + tkfreename(link); + return TkNomem; + } + t->name = f; + t->taglist = f->obj; /* add to head of items with this tag */ + f->obj = t; + t->item = i; + t->itemlist = i->stag; /* add to head of tags for this item */ + i->stag = t; + } + } + i->tags = nil; + + if(new != 0) { + i->tags = tkmkname("all"); + if(i->tags == nil) + return TkNomem; /* XXX - Tad: memory leak? */ + return tkcaddtag(tk, i, 0); + } + + return nil; +} + +void +tkfreepoint(TkCpoints *p) +{ + free(p->drawpt); + free(p->parampt); +} + +/* + * of all the items in ilist tagged with tag, + * return that tag for the first (topmost) item. + */ +TkCtag* +tkclasttag(TkCitem *ilist, TkCtag* tag) +{ + TkCtag *last, *t; + + if (tag == nil || tag->taglist == nil) + return tag; + last = nil; + while(ilist) { + for(t = tag; t; t = t->taglist) { + if(t->item == ilist) { + last = t; + break; + } + } + ilist = ilist->next; + } + return last; +} + +/* + * of all the items in ilist tagged with tag, + * return that tag for the first (bottommost) item. + */ +TkCtag* +tkcfirsttag(TkCitem *ilist, TkCtag* tag) +{ + TkCtag *t; + + if (tag == nil || tag->taglist == nil) + return tag; + for (; ilist != nil; ilist = ilist->next) + for(t = tag; t; t = t->taglist) + if(t->item == ilist) + return t; + return nil; +} + +void +tkmkpen(Image **pen, TkEnv *e, Image *stipple) +{ + int locked; + Display *d; + Image *new, *fill; + + fill = tkgc(e, TkCfill); + + d = e->top->display; + locked = lockdisplay(d); + if(*pen != nil) { + freeimage(*pen); + *pen = nil; + } + if(stipple == nil) { + if(locked) + unlockdisplay(d); + return; + } + + if(fill == nil) + fill = d->black; + new = allocimage(d, stipple->r, RGBA32, 1, DTransparent); /* XXX RGBA32 is excessive sometimes... */ + if (new != nil) + draw(new, stipple->r, fill, stipple, ZP); + else + new = fill; + if(locked) + unlockdisplay(d); + *pen = new; +} + +Point +tkcvsanchor(Point dp, int w, int h, int anchor) +{ + Point o; + + if(anchor & Tknorth) + o.y = dp.y; + else + if(anchor & Tksouth) + o.y = dp.y - h; + else + o.y = dp.y - h/2; + + if(anchor & Tkwest) + o.x = dp.x; + else + if(anchor & Tkeast) + o.x = dp.x - w; + else + o.x = dp.x - w/2; + + return o; +} + +static TkCitem* +tkcvsmousefocus(TkCanvas *c, Point p) +{ + TkCitem *i, *s; + int (*hit)(TkCitem*, Point); + + if (c->grab != nil) + return c->grab; + s = nil; + for(i = c->head; i; i = i->next) + if(ptinrect(p, i->p.bb)) { + if ((hit = tkcimethod[i->type].hit) != nil && !(*hit)(i, p)) + continue; + s = i; + } + + return s; +} + +Tk* +tkcvsinwindow(Tk *tk, Point *p) +{ + TkCanvas *c; + TkCitem *i; + Point q; + TkCwind *w; + + c = TKobj(TkCanvas, tk); + + q = addpt(*p, c->view); + i = tkcvsmousefocus(c, addpt(*p, c->view)); + if (i == nil || i->type != TkCVwindow) + return tk; + w = TKobj(TkCwind, i); + if (w->sub == nil) + return tk; + p->x = q.x - (i->p.bb.min.x + w->sub->borderwidth); + p->y = q.y - (i->p.bb.min.y + w->sub->borderwidth); + return w->sub; +} + +static Tk* +tkcvsmouseinsub(TkCwind *w, TkMouse m) +{ + Point g, mp; + int bd; + + g = tkposn(w->sub); + bd = w->sub->borderwidth; + mp.x = m.x - (g.x + bd); + mp.y = m.y - (g.y + bd); + return tkinwindow(w->sub, mp, 0); +} + +static Tk* +tkcvsdeliver(Tk *tk, TkCitem *i, int event, void *data) +{ + Tk *ftk, *dest; + TkCtag *t; + TkCwind *w; + TkAction *a; + + if(i->type == TkCVwindow) { + dest = nil; + w = TKobj(TkCwind, i); + if(w->sub == nil) + return nil; + + if(!(event & TkKey) && (event & TkEmouse)) { + ftk = tkcvsmouseinsub(w, *(TkMouse*)data); + if(ftk != w->focus) { + tkdeliver(w->focus, TkLeave, data); + tkdeliver(ftk, TkEnter, data); + w->focus = ftk; + } + if(ftk != nil) + dest = tkdeliver(ftk, event, data); + } + else { + if (event & TkLeave) { + tkdeliver(w->focus, TkLeave, data); + w->focus = nil; + } else if (event & TkEnter) { + ftk = tkcvsmouseinsub(w, *(TkMouse*)data); + tkdeliver(ftk, TkEnter, data); + w->focus = ftk; + } else + dest = tkdeliver(w->sub, event, data); + } + return dest; + } + + for(t = i->stag; t != nil; t = t->itemlist) { + a = t->name->prop.binds; + if(a != nil) + tksubdeliver(tk, a, event, data, 0); + } + return nil; +} + +Tk* +tkcvsevent(Tk *tk, int event, void *data) +{ + TkMouse m; + TkCitem *f; + Point mp, g; + TkCanvas *c; + Tk *dest; + + c = TKobj(TkCanvas, tk); + + if(event == TkLeave && c->mouse != nil) { + tkcvsdeliver(tk, c->mouse, TkLeave, data); + c->mouse = nil; + } + + dest = nil; + if(!(event & TkKey) && (event & TkEmouse) || (event & TkEnter)) { + m = *(TkMouse*)data; + g = tkposn(tk); + mp.x = (m.x - g.x - tk->borderwidth) + c->view.x; + mp.y = (m.y - g.y - tk->borderwidth) + c->view.y; + f = tkcvsmousefocus(c, mp); + if(c->mouse != f) { + if(c->mouse != nil) { + tkcvsdeliver(tk, c->mouse, TkLeave, data); + c->current->obj = nil; + } + if(f != nil) { + c->current->obj = &c->curtag; + c->curtag.item = f; + tkcvsdeliver(tk, f, TkEnter, data); + } + c->mouse = f; + } + f = c->mouse; + if(f != nil && (event & TkEnter) == 0) + dest = tkcvsdeliver(tk, f, event, &m); + } + + if(event & TkKey) { + f = c->focus; + if(f != nil) + tkcvsdeliver(tk, f, event, data); + } + if(dest == nil) + tksubdeliver(tk, tk->binds, event, data, 0); + return dest; +} diff --git a/libtk/carcs.c b/libtk/carcs.c new file mode 100644 index 00000000..ded91fd6 --- /dev/null +++ b/libtk/carcs.c @@ -0,0 +1,280 @@ +#include "lib9.h" +#include "draw.h" +#include "tk.h" +#include "canvs.h" + +#define O(t, e) ((long)(&((t*)0)->e)) +typedef void (*Drawfn)(Image*, Point, int, int, Image*, int); + +/* Arc Options (+ means implemented) + +extent + +fill + +outline + outlinestipple + +start + +stipple + +style (+pieslice chord +arc) + +tags + +width +*/ + +typedef struct TkCarc TkCarc; +struct TkCarc +{ + int width; + int start; + int extent; + int style; + Image* stipple; + Image* pen; +}; + +enum Style +{ + Pieslice, + Chord, + Arc +}; + +static +TkStab tkstyle[] = +{ + "pieslice", Pieslice, + "arc", Arc, + "chord", Arc, /* Can't do chords */ + nil +}; + +static +TkOption arcopts[] = +{ + "start", OPTfrac, O(TkCarc, start), nil, + "extent", OPTfrac, O(TkCarc, extent), nil, + "style", OPTstab, O(TkCarc, style), tkstyle, + "width", OPTnnfrac, O(TkCarc, width), nil, + "stipple", OPTbmap, O(TkCarc, stipple), nil, + nil +}; + +static +TkOption itemopts[] = +{ + "tags", OPTctag, O(TkCitem, tags), nil, + "fill", OPTcolr, O(TkCitem, env), IAUX(TkCfill), + "outline", OPTcolr, O(TkCitem, env), IAUX(TkCforegnd), + nil +}; + +void +tkcvsarcsize(TkCitem *i) +{ + int w; + TkCarc *a; + + a = TKobj(TkCarc, i); + w = TKF2I(a->width)*2; + + i->p.bb = bbnil; + tkpolybound(i->p.drawpt, i->p.npoint, &i->p.bb); + i->p.bb = insetrect(i->p.bb, -w); +} + +char* +tkcvsarccreat(Tk* tk, char *arg, char **val) +{ + char *e; + TkCarc *a; + TkCitem *i; + TkCanvas *c; + TkOptab tko[3]; + + c = TKobj(TkCanvas, tk); + + i = tkcnewitem(tk, TkCVarc, sizeof(TkCitem)+sizeof(TkCarc)); + if(i == nil) + return TkNomem; + + a = TKobj(TkCarc, i); + a->width = TKI2F(1); + + e = tkparsepts(tk->env->top, &i->p, &arg, 0); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + if(i->p.npoint != 2) { + tkcvsfreeitem(i); + return TkFewpt; + } + + tko[0].ptr = a; + tko[0].optab = arcopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + e = tkparse(tk->env->top, arg, tko, nil); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + + e = tkcaddtag(tk, i, 1); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + + tkcvsarcsize(i); + tkmkpen(&a->pen, i->env, a->stipple); + + tkcvsappend(c, i); + + tkbbmax(&c->update, &i->p.bb); + tkcvssetdirty(tk); + return tkvalue(val, "%d", i->id); +} + +char* +tkcvsarccget(TkCitem *i, char *arg, char **val) +{ + TkOptab tko[3]; + TkCarc *a = TKobj(TkCarc, i); + + tko[0].ptr = a; + tko[0].optab = arcopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + + return tkgencget(tko, arg, val, i->env->top); +} + +char* +tkcvsarcconf(Tk *tk, TkCitem *i, char *arg) +{ + char *e; + TkOptab tko[3]; + TkCarc *a = TKobj(TkCarc, i); + + tko[0].ptr = a; + tko[0].optab = arcopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + + e = tkparse(tk->env->top, arg, tko, nil); + tkcvsarcsize(i); + tkmkpen(&a->pen, i->env, a->stipple); + + return e; +} + + +void +tkcvsarcfree(TkCitem *i) +{ + TkCarc *a; + + a = TKobj(TkCarc, i); + if(a->stipple) + freeimage(a->stipple); + if(a->pen) + freeimage(a->pen); +} + +void +tkcvsarcdraw(Image *img, TkCitem *i, TkEnv *pe) +{ + TkEnv *e; + TkCarc *a; + Rectangle d; + int w, dx, dy; + int s, ext, s0, s1, e0, e1, l; + Image *pen, *col, *tmp; + Point p0, p1, c; + extern void drawarc(Point,int,int,int,int,int,Image *,Image *,Image *); + + USED(pe); + + d.min = i->p.drawpt[0]; + d.max = i->p.drawpt[1]; + + e = i->env; + a = TKobj(TkCarc, i); + + pen = a->pen; + if(pen == nil && (e->set & (1<<TkCfill))) + pen = tkgc(e, TkCfill); + + w = TKF2I(a->width)/2; + if(w < 0) + return; + + d = canonrect(d); + dx = Dx(d)/2; + dy = Dy(d)/2; + c.x = (d.min.x+d.max.x)/2; + c.y = (d.min.y+d.max.y)/2; + s = TKF2I(a->start); + ext = TKF2I(a->extent); +/* + if(ext == 0) + ext = 90; +*/ + + if(a->style != Arc && pen != nil) + fillarc(img, c, dx, dy, pen, Pt(0,0), s, ext); + col = tkgc(e, TkCforegnd); + arc(img, c, dx, dy, w, col, Pt(0,0), s, ext); + if(a->style == Pieslice){ + /* + * It is difficult to compute the intersection of the lines + * and the ellipse using integers, so let the draw library + * do it for us: use a full ellipse as the source of color + * for drawing the lines. + */ + tmp = allocimage(img->display, d, img->chan, 0, DNofill); + if(tmp == nil) + return; + /* copy dest to tmp so lines don't spill beyond edge of ellipse */ + drawop(tmp, d, img, nil, d.min, S); + fillellipse(tmp, c, dx, dy, col, Pt(0,0)); + icossin(s, &s1, &s0); + icossin(s+ext, &e1, &e0); + if(dx > dy) + l = 2*dx+1; + else + l = 2*dy+1; + p0 = Pt(c.x+l*s1/ICOSSCALE, c.y-l*s0/ICOSSCALE); + p1 = Pt(c.x+l*e1/ICOSSCALE, c.y-l*e0/ICOSSCALE); + line(img, c, p0, Endsquare, Endsquare, w, tmp, c); + line(img, c, p1, Endsquare, Endsquare, w, tmp, c); + freeimage(tmp); + } +} + +char* +tkcvsarccoord(TkCitem *i, char *arg, int x, int y) +{ + char *e; + TkCpoints p; + + if(arg == nil) { + tkxlatepts(i->p.parampt, i->p.npoint, x, y); + tkxlatepts(i->p.drawpt, i->p.npoint, TKF2I(x), TKF2I(y)); + i->p.bb = rectaddpt(i->p.bb, Pt(TKF2I(x), TKF2I(y))); + } + else { + e = tkparsepts(i->env->top, &p, &arg, 0); + if(e != nil) + return e; + if(p.npoint != 2) { + tkfreepoint(&p); + return TkFewpt; + } + tkfreepoint(&i->p); + i->p = p; + tkcvsarcsize(i); + } + return nil; +} diff --git a/libtk/cbits.c b/libtk/cbits.c new file mode 100644 index 00000000..f99fd6d6 --- /dev/null +++ b/libtk/cbits.c @@ -0,0 +1,211 @@ +#include "lib9.h" +#include "draw.h" +#include "tk.h" +#include "canvs.h" + +#define O(t, e) ((long)(&((t*)0)->e)) + +/* Bitmap Options (+ means implemented) + +anchor + +bitmap +*/ + +typedef struct TkCbits TkCbits; +struct TkCbits +{ + int anchor; + Point anchorp; + Image* bitmap; +}; + +static +TkOption bitopts[] = +{ + "anchor", OPTstab, O(TkCbits, anchor), tkanchor, + "bitmap", OPTbmap, O(TkCbits, bitmap), nil, + nil +}; + +static +TkOption itemopts[] = +{ + "tags", OPTctag, O(TkCitem, tags), nil, + "background", OPTcolr, O(TkCitem, env), IAUX(TkCbackgnd), + "foreground", OPTcolr, O(TkCitem, env), IAUX(TkCforegnd), + nil +}; + +void +tkcvsbitsize(TkCitem *i) +{ + Point o; + int dx, dy; + TkCbits *b; + + b = TKobj(TkCbits, i); + i->p.bb = bbnil; + if(b->bitmap == nil) + return; + + dx = Dx(b->bitmap->r); + dy = Dy(b->bitmap->r); + + o = tkcvsanchor(i->p.drawpt[0], dx, dy, b->anchor); + + i->p.bb.min.x = o.x; + i->p.bb.min.y = o.y; + i->p.bb.max.x = o.x + dx; + i->p.bb.max.y = o.y + dy; + b->anchorp = subpt(o, i->p.drawpt[0]); +} + +char* +tkcvsbitcreat(Tk* tk, char *arg, char **val) +{ + char *e; + TkCbits *b; + TkCitem *i; + TkCanvas *c; + TkOptab tko[3]; + + c = TKobj(TkCanvas, tk); + + i = tkcnewitem(tk, TkCVbitmap, sizeof(TkCitem)+sizeof(TkCbits)); + if(i == nil) + return TkNomem; + + b = TKobj(TkCbits, i); + + e = tkparsepts(tk->env->top, &i->p, &arg, 0); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + if(i->p.npoint != 1) { + tkcvsfreeitem(i); + return TkFewpt; + } + + tko[0].ptr = b; + tko[0].optab = bitopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + e = tkparse(tk->env->top, arg, tko, nil); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + + e = tkcaddtag(tk, i, 1); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + + tkcvsbitsize(i); + tkcvsappend(c, i); + + tkbbmax(&c->update, &i->p.bb); + tkcvssetdirty(tk); + return tkvalue(val, "%d", i->id); +} + +char* +tkcvsbitcget(TkCitem *i, char *arg, char **val) +{ + TkOptab tko[3]; + TkCbits *b = TKobj(TkCbits, i); + + tko[0].ptr = b; + tko[0].optab = bitopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + + return tkgencget(tko, arg, val, i->env->top); +} + +char* +tkcvsbitconf(Tk *tk, TkCitem *i, char *arg) +{ + char *e; + TkOptab tko[3]; + TkCbits *b = TKobj(TkCbits, i); + + tko[0].ptr = b; + tko[0].optab = bitopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + + e = tkparse(tk->env->top, arg, tko, nil); + tkcvsbitsize(i); + return e; +} + +void +tkcvsbitfree(TkCitem *i) +{ + TkCbits *b; + + b = TKobj(TkCbits, i); + if(b->bitmap) + freeimage(b->bitmap); +} + +void +tkcvsbitdraw(Image *img, TkCitem *i, TkEnv *pe) +{ + TkEnv *e; + TkCbits *b; + Rectangle r; + Image *bi; + + USED(pe); + + e = i->env; + b = TKobj(TkCbits, i); + + bi = b->bitmap; + if(bi == nil) + return; + + r.min = addpt(b->anchorp, i->p.drawpt[0]); + r.max = r.min; + r.max.x += Dx(bi->r); + r.max.y += Dy(bi->r); + + if(bi->depth != 1) { + draw(img, r, bi, nil, ZP); + return; + } + gendraw(img, r, tkgc(e, TkCbackgnd), r.min, nil, ZP); + draw(img, r, tkgc(e, TkCforegnd), bi, ZP); +} + +char* +tkcvsbitcoord(TkCitem *i, char *arg, int x, int y) +{ + char *e; + TkCpoints p; + + if(arg == nil) { + tkxlatepts(i->p.parampt, i->p.npoint, x, y); + tkxlatepts(i->p.drawpt, i->p.npoint, TKF2I(x), TKF2I(y)); + i->p.bb = rectaddpt(i->p.bb, Pt(TKF2I(x), TKF2I(y))); + } + else { + e = tkparsepts(i->env->top, &p, &arg, 0); + if(e != nil) + return e; + if(p.npoint != 1) { + tkfreepoint(&p); + return TkFewpt; + } + tkfreepoint(&i->p); + i->p = p; + tkcvsbitsize(i); + } + return nil; +} diff --git a/libtk/cimag.c b/libtk/cimag.c new file mode 100644 index 00000000..f01a0bf2 --- /dev/null +++ b/libtk/cimag.c @@ -0,0 +1,211 @@ +#include "lib9.h" +#include "draw.h" +#include "tk.h" +#include "canvs.h" + +#define O(t, e) ((long)(&((t*)0)->e)) + +/* Image Options (+ means implemented) + +anchor + +image +*/ + +typedef struct TkCimag TkCimag; +struct TkCimag +{ + int anchor; + Point anchorp; + TkImg* tki; +}; + +static +TkOption imgopts[] = +{ + "anchor", OPTstab, O(TkCimag, anchor), tkanchor, + "image", OPTimag, O(TkCimag, tki), nil, + nil +}; + +static +TkOption itemopts[] = +{ + "tags", OPTctag, O(TkCitem, tags), nil, + nil +}; + +void +tkcvsimgsize(TkCitem *i) +{ + Point o; + int dx, dy; + TkCimag *t; + + t = TKobj(TkCimag, i); + i->p.bb = bbnil; + if(t->tki == nil) + return; + + dx = t->tki->w; + dy = t->tki->h; + + o = tkcvsanchor(i->p.drawpt[0], dx, dy, t->anchor); + + i->p.bb.min.x = o.x; + i->p.bb.min.y = o.y; + i->p.bb.max.x = o.x + dx; + i->p.bb.max.y = o.y + dy; + t->anchorp = subpt(o, i->p.drawpt[0]); +} + +char* +tkcvsimgcreat(Tk* tk, char *arg, char **val) +{ + char *e; + TkCimag *t; + TkCitem *i; + TkCanvas *c; + TkOptab tko[3]; + + c = TKobj(TkCanvas, tk); + + i = tkcnewitem(tk, TkCVimage, sizeof(TkCitem)+sizeof(TkCimag)); + if(i == nil) + return TkNomem; + + t = TKobj(TkCimag, i); + + e = tkparsepts(tk->env->top, &i->p, &arg, 0); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + if(i->p.npoint != 1) { + tkcvsfreeitem(i); + return TkFewpt; + } + + tko[0].ptr = t; + tko[0].optab = imgopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + e = tkparse(tk->env->top, arg, tko, nil); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + + e = tkcaddtag(tk, i, 1); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + + tkcvsimgsize(i); + + e = tkvalue(val, "%d", i->id); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + + tkcvsappend(c, i); + tkbbmax(&c->update, &i->p.bb); + tkcvssetdirty(tk); + return nil; +} + +char* +tkcvsimgcget(TkCitem *i, char *arg, char **val) +{ + TkOptab tko[3]; + TkCimag *t = TKobj(TkCimag, i); + + tko[0].ptr = t; + tko[0].optab = imgopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + + return tkgencget(tko, arg, val, i->env->top); +} + +char* +tkcvsimgconf(Tk *tk, TkCitem *i, char *arg) +{ + char *e; + TkOptab tko[3]; + TkCimag *t = TKobj(TkCimag, i); + + tko[0].ptr = t; + tko[0].optab = imgopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + + e = tkparse(tk->env->top, arg, tko, nil); + tkcvsimgsize(i); + return e; +} + +void +tkcvsimgfree(TkCitem *i) +{ + TkCimag *t; + + t = TKobj(TkCimag, i); + if(t->tki) + tkimgput(t->tki); +} + +void +tkcvsimgdraw(Image *img, TkCitem *i, TkEnv *pe) +{ + TkCimag *t; + TkImg *tki; + Rectangle r; + Image *fg; + + USED(pe); + + t = TKobj(TkCimag, i); + tki = t->tki; + if(tki == nil) + return; + fg = tki->img; + if(fg == nil) + return; + + r.min = addpt(t->anchorp, i->p.drawpt[0]); + r.max = r.min; + r.max.x += tki->w; + r.max.y += tki->h; + + draw(img, r, fg, nil, ZP); +} + +char* +tkcvsimgcoord(TkCitem *i, char *arg, int x, int y) +{ + char *e; + TkCpoints p; + + if(arg == nil) { + tkxlatepts(i->p.parampt, i->p.npoint, x, y); + tkxlatepts(i->p.drawpt, i->p.npoint, TKF2I(x), TKF2I(y)); + i->p.bb = rectaddpt(i->p.bb, Pt(TKF2I(x), TKF2I(y))); + } + else { + e = tkparsepts(i->env->top, &p, &arg, 0); + if(e != nil) + return e; + if(p.npoint != 1) { + tkfreepoint(&p); + return TkFewpt; + } + tkfreepoint(&i->p); + i->p = p; + tkcvsimgsize(i); + } + return nil; +} diff --git a/libtk/cline.c b/libtk/cline.c new file mode 100644 index 00000000..8d50871b --- /dev/null +++ b/libtk/cline.c @@ -0,0 +1,268 @@ +#include "lib9.h" +#include "draw.h" +#include "tk.h" +#include "canvs.h" + +#define O(t, e) ((long)(&((t*)0)->e)) + +/* Line Options (+ means implemented) + +arrow + +arrowshape + +capstyle + +fill + joinstyle + +smooth + +splinesteps + +stipple + +tags + +width +*/ + +static +TkStab tklines[] = +{ + "none", 0, + "first", TkCarrowf, + "last", TkCarrowl, + "both", TkCarrowf|TkCarrowl, + nil +}; + +static +TkStab tkcapstyle[] = +{ + "butt", Endsquare, + "projecting", Endsquare, + "round", Enddisc, + nil +}; + +static +TkOption lineopts[] = +{ + "arrow", OPTstab, O(TkCline, arrow), tklines, + "arrowshape", OPTfrac, O(TkCline, shape[0]), IAUX(3), + "width", OPTnnfrac, O(TkCline, width), nil, + "stipple", OPTbmap, O(TkCline, stipple), nil, + "smooth", OPTstab, O(TkCline, smooth), tkbool, + "splinesteps", OPTdist, O(TkCline, steps), nil, + "capstyle", OPTstab, O(TkCline, capstyle), tkcapstyle, + nil +}; + +static +TkOption itemopts[] = +{ + "tags", OPTctag, O(TkCitem, tags), nil, + "fill", OPTcolr, O(TkCitem, env), IAUX(TkCforegnd), + nil +}; + +void +tkcvslinesize(TkCitem *i) +{ + TkCline *l; + int j, w, as, shape[3], arrow; + + l = TKobj(TkCline, i); + w = TKF2I(l->width); + + i->p.bb = bbnil; + tkpolybound(i->p.drawpt, i->p.npoint, &i->p.bb); + + l->arrowf = l->capstyle; + l->arrowl = l->capstyle; + if(l->arrow != 0) { + as = w/3; + if(as < 1) + as = 1; + for(j = 0; j < 3; j++) { + shape[j] = l->shape[j]; + if(shape[j] == 0) + shape[j] = as * cvslshape[j]; + } + arrow = ARROW(TKF2I(shape[0]), TKF2I(shape[1]), TKF2I(shape[2])); + if(l->arrow & TkCarrowf) + l->arrowf = arrow; + if(l->arrow & TkCarrowl) + l->arrowl = arrow; + w += shape[2]; + } + + i->p.bb = insetrect(i->p.bb, -w); +} + +char* +tkcvslinecreat(Tk* tk, char *arg, char **val) +{ + char *e; + TkCline *l; + TkCitem *i; + TkCanvas *c; + TkOptab tko[3]; + + c = TKobj(TkCanvas, tk); + + i = tkcnewitem(tk, TkCVline, sizeof(TkCitem)+sizeof(TkCline)); + if(i == nil) + return TkNomem; + + l = TKobj(TkCline, i); + l->width = TKI2F(1); + + e = tkparsepts(tk->env->top, &i->p, &arg, 0); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + + tko[0].ptr = l; + tko[0].optab = lineopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + e = tkparse(tk->env->top, arg, tko, nil); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + tkmkpen(&l->pen, i->env, l->stipple); + + e = tkcaddtag(tk, i, 1); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + + tkcvslinesize(i); + e = tkvalue(val, "%d", i->id); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + tkcvsappend(c, i); + + tkbbmax(&c->update, &i->p.bb); + tkcvssetdirty(tk); + return nil; +} + +char* +tkcvslinecget(TkCitem *i, char *arg, char **val) +{ + TkOptab tko[3]; + TkCline *l = TKobj(TkCline, i); + + tko[0].ptr = l; + tko[0].optab = lineopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + + return tkgencget(tko, arg, val, i->env->top); +} + +char* +tkcvslineconf(Tk *tk, TkCitem *i, char *arg) +{ + char *e; + TkOptab tko[3]; + TkCline *l = TKobj(TkCline, i); + + tko[0].ptr = l; + tko[0].optab = lineopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + + e = tkparse(tk->env->top, arg, tko, nil); + + tkmkpen(&l->pen, i->env, l->stipple); + tkcvslinesize(i); + + return e; +} + +void +tkcvslinefree(TkCitem *i) +{ + TkCline *l; + + l = TKobj(TkCline, i); + if(l->stipple) + freeimage(l->stipple); + if(l->pen) + freeimage(l->pen); +} + +void +tkcvslinedraw(Image *img, TkCitem *i, TkEnv *pe) +{ + int w; + Point *p; + TkCline *l; + Image *pen; + + USED(pe); + + l = TKobj(TkCline, i); + + pen = l->pen; + if(pen == nil) + pen = tkgc(i->env, TkCforegnd); + + w = TKF2I(l->width)/2; + if(w < 0) + return; + + p = i->p.drawpt; + if(l->smooth == BoolT && i->p.npoint >= 3) + bezspline(img, p, i->p.npoint, l->arrowf, l->arrowl, w, pen, p[0]); + else + poly(img, p, i->p.npoint, l->arrowf, l->arrowl, w, pen, p[0]); +} + +char* +tkcvslinecoord(TkCitem *i, char *arg, int x, int y) +{ + char *e; + TkCpoints p; + + if(arg == nil) { + tkxlatepts(i->p.parampt, i->p.npoint, x, y); + tkxlatepts(i->p.drawpt, i->p.npoint, TKF2I(x), TKF2I(y)); + i->p.bb = rectaddpt(i->p.bb, Pt(TKF2I(x), TKF2I(y))); + } + else { + e = tkparsepts(i->env->top, &p, &arg, 0); + if(e != nil) + return e; + if(p.npoint < 2) { + tkfreepoint(&p); + return TkFewpt; + } + tkfreepoint(&i->p); + i->p = p; + tkcvslinesize(i); + } + return nil; +} + +int +tkcvslinehit(TkCitem *i, Point p) +{ + TkCline *l; + int w, np, r; + Point *pp; + + l = TKobj(TkCline, i); + w =TKF2I(l->width) + 2; /* 2 for slop */ + + if (l->smooth == BoolT) { + np = getbezsplinepts(i->p.drawpt, i->p.npoint, &pp); + r = tklinehit(pp, np, w, p); + free(pp); + } else + r = tklinehit(i->p.drawpt, i->p.npoint, w, p); + return r; +} diff --git a/libtk/colrs.c b/libtk/colrs.c new file mode 100644 index 00000000..d4fc73c4 --- /dev/null +++ b/libtk/colrs.c @@ -0,0 +1,90 @@ +#include "lib9.h" +#include "draw.h" +#include "tk.h" + +#define RGB(R,G,B) ((R<<24)|(G<<16)|(B<<8)|(0xff)) + +enum +{ + tkBackR = 0xdd, /* Background base color */ + tkBackG = 0xdd, + tkBackB = 0xdd, + + tkSelectR = 0xb0, /* Check box selected color */ + tkSelectG = 0x30, + tkSelectB = 0x60, + + tkSelectbgndR = 0x40, /* Selected item background */ + tkSelectbgndG = 0x40, + tkSelectbgndB = 0x40 +}; + +typedef struct Coltab Coltab; +struct Coltab { + int c; + ulong rgba; + int shade; +}; + +static Coltab coltab[] = +{ + TkCbackgnd, + RGB(tkBackR, tkBackG, tkBackB), + TkSameshade, + TkCbackgndlght, + RGB(tkBackR, tkBackG, tkBackB), + TkLightshade, + TkCbackgnddark, + RGB(tkBackR, tkBackG, tkBackB), + TkDarkshade, + TkCactivebgnd, + RGB(tkBackR+0x10, tkBackG+0x10, tkBackB+0x10), + TkSameshade, + TkCactivebgndlght, + RGB(tkBackR+0x10, tkBackG+0x10, tkBackB+0x10), + TkLightshade, + TkCactivebgnddark, + RGB(tkBackR+0x10, tkBackG+0x10, tkBackB+0x10), + TkDarkshade, + TkCactivefgnd, + RGB(0, 0, 0), + TkSameshade, + TkCforegnd, + RGB(0, 0, 0), + TkSameshade, + TkCselect, + RGB(tkSelectR, tkSelectG, tkSelectB), + TkSameshade, + TkCselectbgnd, + RGB(tkSelectbgndR, tkSelectbgndG, tkSelectbgndB), + TkSameshade, + TkCselectbgndlght, + RGB(tkSelectbgndR, tkSelectbgndG, tkSelectbgndB), + TkLightshade, + TkCselectbgnddark, + RGB(tkSelectbgndR, tkSelectbgndG, tkSelectbgndB), + TkDarkshade, + TkCselectfgnd, + RGB(0xff, 0xff, 0xff), + TkSameshade, + TkCdisablefgnd, + RGB(0x88, 0x88, 0x88), + TkSameshade, + TkChighlightfgnd, + RGB(0, 0, 0), + TkSameshade, + -1, +}; + +void +tksetenvcolours(TkEnv *env) +{ + Coltab *c; + + c = &coltab[0]; + while(c->c != -1) { + env->colors[c->c] = tkrgbashade(c->rgba, c->shade); + env->set |= (1<<c->c); + c++; + } +} diff --git a/libtk/coval.c b/libtk/coval.c new file mode 100644 index 00000000..c70a607e --- /dev/null +++ b/libtk/coval.c @@ -0,0 +1,248 @@ +#include "lib9.h" +#include "draw.h" +#include "tk.h" +#include "canvs.h" + +#define O(t, e) ((long)(&((t*)0)->e)) +typedef void (*Drawfn)(Image*, Point, int, int, Image*, int); + +/* Oval Options (+ means implemented) + +fill + +outline + +stipple + +tags + +width +*/ + +typedef struct TkCoval TkCoval; +struct TkCoval +{ + int width; + Image* stipple; + Image* pen; + TkCanvas* canv; +}; + +static +TkOption ovalopts[] = +{ + "width", OPTnnfrac, O(TkCoval, width), nil, /* XXX should be nnfrac */ + "stipple", OPTbmap, O(TkCoval, stipple), nil, + nil +}; + +static +TkOption itemopts[] = +{ + "tags", OPTctag, O(TkCitem, tags), nil, + "fill", OPTcolr, O(TkCitem, env), IAUX(TkCfill), + "outline", OPTcolr, O(TkCitem, env), IAUX(TkCforegnd), + nil +}; + +void +tkcvsovalsize(TkCitem *i) +{ + int w; + TkCoval *o; + + o = TKobj(TkCoval, i); + w = TKF2I(o->width)*2; + + i->p.bb = bbnil; + tkpolybound(i->p.drawpt, i->p.npoint, &i->p.bb); + i->p.bb = insetrect(i->p.bb, -w); +} + +char* +tkcvsovalcreat(Tk* tk, char *arg, char **val) +{ + char *e; + TkCoval *o; + TkCitem *i; + TkCanvas *c; + TkOptab tko[3]; + + c = TKobj(TkCanvas, tk); + + i = tkcnewitem(tk, TkCVoval, sizeof(TkCitem)+sizeof(TkCoval)); + if(i == nil) + return TkNomem; + + o = TKobj(TkCoval, i); + o->width = TKI2F(1); + o->canv = c; + e = tkparsepts(tk->env->top, &i->p, &arg, 0); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + if(i->p.npoint != 2) { + tkcvsfreeitem(i); + return TkFewpt; + } + + tko[0].ptr = o; + tko[0].optab = ovalopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + e = tkparse(tk->env->top, arg, tko, nil); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + e = tkcaddtag(tk, i, 1); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + + tkcvsovalsize(i); + + e = tkvalue(val, "%d", i->id); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + + tkcvsappend(c, i); + tkbbmax(&c->update, &i->p.bb); + tkmkpen(&o->pen, i->env, o->stipple); + tkcvssetdirty(tk); + return nil; +} + +char* +tkcvsovalcget(TkCitem *i, char *arg, char **val) +{ + TkOptab tko[3]; + TkCoval *o = TKobj(TkCoval, i); + + tko[0].ptr = o; + tko[0].optab = ovalopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + + return tkgencget(tko, arg, val, i->env->top); +} + +char* +tkcvsovalconf(Tk *tk, TkCitem *i, char *arg) +{ + char *e; + TkOptab tko[3]; + TkCoval *o = TKobj(TkCoval, i); + + tko[0].ptr = o; + tko[0].optab = ovalopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + + e = tkparse(tk->env->top, arg, tko, nil); + tkcvsovalsize(i); + tkmkpen(&o->pen, i->env, o->stipple); + + return e; +} + +void +tkcvsovalfree(TkCitem *i) +{ + TkCoval *o; + + o = TKobj(TkCoval, i); + if(o->stipple) + freeimage(o->stipple); + if(o->pen) + freeimage(o->pen); +} + +void +tkcvsovaldraw(Image *img, TkCitem *i, TkEnv *pe) +{ + Point c; + TkEnv *e; + Image *pen; + TkCoval *o; + Rectangle d; + int w, dx, dy; + + USED(pe); + + d.min = i->p.drawpt[0]; + d.max = i->p.drawpt[1]; + + e = i->env; + o = TKobj(TkCoval, i); + + pen = o->pen; + if(pen == nil && (e->set & (1<<TkCfill))) + pen = tkgc(e, TkCfill); + + w = TKF2I(o->width)/2; + if(w < 0) + return; + + d = canonrect(d); + dx = Dx(d)/2; + dy = Dy(d)/2; + c.x = d.min.x + dx; + c.y = d.min.y + dy; + if(pen != nil) + fillellipse(img, c, dx, dy, pen, c); + ellipse(img, c, dx, dy, w, tkgc(e, TkCforegnd), c); +} + +char* +tkcvsovalcoord(TkCitem *i, char *arg, int x, int y) +{ + char *e; + TkCpoints p; + + if(arg == nil) { + tkxlatepts(i->p.parampt, i->p.npoint, x, y); + tkxlatepts(i->p.drawpt, i->p.npoint, TKF2I(x), TKF2I(y)); + i->p.bb = rectaddpt(i->p.bb, Pt(TKF2I(x), TKF2I(y))); + } + else { + e = tkparsepts(i->env->top, &p, &arg, 0); + if(e != nil) + return e; + if(p.npoint != 2) { + tkfreepoint(&p); + return TkFewpt; + } + tkfreepoint(&i->p); + i->p = p; + tkcvsovalsize(i); + } + return nil; +} + +int +tkcvsovalhit(TkCitem *i, Point p) +{ + TkCoval *o; + int w, dx, dy; + Rectangle d; + + o = TKobj(TkCoval, i); + w = TKF2I(o->width)/2; + d = canonrect(Rpt(i->p.drawpt[0], i->p.drawpt[1])); + d = insetrect(d, -(w/2 + 1)); + + dx = Dx(d)/2; + dy = Dy(d)/2; + + p.x -= d.min.x + dx; + p.y -= d.min.y + dy; + + dx *= dx; + dy *= dy; + + /* XXX can we do this nicely without overflow and without vlongs? */ + return (vlong)(p.x*p.x)*dy + (vlong)(p.y*p.y)*dx < (vlong)dx*dy; +} diff --git a/libtk/cpoly.c b/libtk/cpoly.c new file mode 100644 index 00000000..4033da3c --- /dev/null +++ b/libtk/cpoly.c @@ -0,0 +1,270 @@ +#include "lib9.h" +#include "draw.h" +#include "tk.h" +#include "canvs.h" + +#define O(t, e) ((long)(&((t*)0)->e)) + +typedef struct TkCpoly TkCpoly; +struct TkCpoly +{ + int width; + Image* stipple; + Image* pen; + TkCanvas* canv; + int smooth; + int steps; + int winding; +}; + +static +TkStab tkwinding[] = +{ + "nonzero", ~0, + "odd", 1, + nil +}; + +/* Polygon Options (+ means implemented) + +fill + +smooth + +splinesteps + +stipple + +tags + +width + +outline +*/ +static +TkOption polyopts[] = +{ + "width", OPTnnfrac, O(TkCpoly, width), nil, + "stipple", OPTbmap, O(TkCpoly, stipple), nil, + "smooth", OPTstab, O(TkCpoly, smooth), tkbool, + "splinesteps", OPTdist, O(TkCpoly, steps), nil, + "winding", OPTstab, O(TkCpoly, winding), tkwinding, + nil +}; + +static +TkOption itemopts[] = +{ + "tags", OPTctag, O(TkCitem, tags), nil, + "fill", OPTcolr, O(TkCitem, env), IAUX(TkCfill), + "outline", OPTcolr, O(TkCitem, env), IAUX(TkCforegnd), + nil +}; + +void +tkcvspolysize(TkCitem *i) +{ + int w; + TkCpoly *p; + + p = TKobj(TkCpoly, i); + w = TKF2I(p->width); + + i->p.bb = bbnil; + tkpolybound(i->p.drawpt, i->p.npoint, &i->p.bb); + i->p.bb = insetrect(i->p.bb, -w); +} + +char* +tkcvspolycreat(Tk* tk, char *arg, char **val) +{ + char *e; + TkCpoly *p; + TkCitem *i; + TkCanvas *c; + TkOptab tko[3]; + + c = TKobj(TkCanvas, tk); + + i = tkcnewitem(tk, TkCVpoly, sizeof(TkCitem)+sizeof(TkCpoly)); + if(i == nil) + return TkNomem; + + p = TKobj(TkCpoly, i); + p->width = TKI2F(1); + p->winding = ~0; + + e = tkparsepts(tk->env->top, &i->p, &arg, 1); + if(e == nil && i->p.npoint < 3) + e = TkBadvl; + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + + tko[0].ptr = p; + tko[0].optab = polyopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + e = tkparse(tk->env->top, arg, tko, nil); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + p->canv = c; + + e = tkcaddtag(tk, i, 1); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + + tkcvspolysize(i); + tkmkpen(&p->pen, i->env, p->stipple); + + e = tkvalue(val, "%d", i->id); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + + tkcvsappend(c, i); + tkbbmax(&c->update, &i->p.bb); + tkcvssetdirty(tk); + return nil; +} + +char* +tkcvspolycget(TkCitem *i, char *arg, char **val) +{ + TkOptab tko[3]; + TkCpoly *p = TKobj(TkCpoly, i); + + tko[0].ptr = p; + tko[0].optab = polyopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + + return tkgencget(tko, arg, val, i->env->top); +} + +char* +tkcvspolyconf(Tk *tk, TkCitem *i, char *arg) +{ + char *e; + TkOptab tko[3]; + TkCpoly *p = TKobj(TkCpoly, i); + + tko[0].ptr = p; + tko[0].optab = polyopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + + e = tkparse(tk->env->top, arg, tko, nil); + + tkcvspolysize(i); + tkmkpen(&p->pen, i->env, p->stipple); + + return e; +} + +void +tkcvspolyfree(TkCitem *i) +{ + TkCpoly *p; + + p = TKobj(TkCpoly, i); + if(p->stipple) + freeimage(p->stipple); + if(p->pen) + freeimage(p->pen); +} + +void +tkcvspolydraw(Image *img, TkCitem *i, TkEnv *pe) +{ + int w; + TkEnv *e; + TkCpoly *p; + Image *pen; + Point *pts; + + USED(pe); + + p = TKobj(TkCpoly, i); + + e = i->env; + + pen = p->pen; + if(pen == nil && (e->set & (1<<TkCfill))) + pen = tkgc(e, TkCfill); + + pts = i->p.drawpt; + if(i->p.npoint > 0 && pen != nil) { + if (p->smooth == BoolT) + fillbezspline(img, pts, i->p.npoint+1, p->winding, pen, pts[0]); + else + fillpoly(img, pts, i->p.npoint+1, p->winding, pen, pts[0]); + } + + w = TKF2I(p->width) - 1; + if(w >= 0 && (e->set & (1<<TkCforegnd))) { + pen = tkgc(i->env, TkCforegnd); + if (p->smooth == BoolT) + bezspline(img, pts, i->p.npoint+1, Enddisc, Enddisc, w, pen, pts[0]); + else + poly(img, pts, i->p.npoint+1, Enddisc, Enddisc, w, pen, pts[0]); + } +} + +char* +tkcvspolycoord(TkCitem *i, char *arg, int x, int y) +{ + char *e; + TkCpoints p; + + if(arg == nil) { + tkxlatepts(i->p.parampt, i->p.npoint, x, y); + tkxlatepts(i->p.drawpt, i->p.npoint, TKF2I(x), TKF2I(y)); + i->p.drawpt[i->p.npoint] = i->p.drawpt[0]; + i->p.bb = rectaddpt(i->p.bb, Pt(TKF2I(x), TKF2I(y))); + } + else { + e = tkparsepts(i->env->top, &p, &arg, 1); + if(e != nil) + return e; + if(p.npoint < 2) { + tkfreepoint(&p); + return TkFewpt; + } + tkfreepoint(&i->p); + i->p = p; + tkcvspolysize(i); + } + return nil; +} + +int +tkcvspolyhit(TkCitem *item, Point p) +{ + Point *poly; + int r, np, fill, w; + TkCpoly *l; + TkEnv *e; + + l = TKobj(TkCpoly, item); + w = TKF2I(l->width) + 2; /* include some slop */ + e = item->env; + fill = e->set & (1<<TkCfill); + if (l->smooth == BoolT) { + /* this works but it's slow if used intensively... */ + np = getbezsplinepts(item->p.drawpt, item->p.npoint + 1, &poly); + if (fill) + r = tkinsidepoly(poly, np, l->winding, p); + else + r = tklinehit(poly, np, w, p); + free(poly); + } else { + if (fill) + r = tkinsidepoly(item->p.drawpt, item->p.npoint, l->winding, p); + else + r = tklinehit(item->p.drawpt, item->p.npoint + 1, w, p); + } + return r; +} diff --git a/libtk/crect.c b/libtk/crect.c new file mode 100644 index 00000000..909eafab --- /dev/null +++ b/libtk/crect.c @@ -0,0 +1,252 @@ +#include <lib9.h> +#include <kernel.h> +#include "draw.h" +#include "tk.h" +#include "canvs.h" + +#define O(t, e) ((long)(&((t*)0)->e)) + +/* Rectangle Options (+ means implemented) + +fill + +outline + +stipple + +tags + +width +*/ + +typedef struct TkCrect TkCrect; +struct TkCrect +{ + int width; + Image* stipple; +}; + +static +TkOption rectopts[] = +{ + "width", OPTnnfrac, O(TkCrect, width), nil, + "stipple", OPTbmap, O(TkCrect, stipple), nil, + nil +}; + +static +TkOption itemopts[] = +{ + "tags", OPTctag, O(TkCitem, tags), nil, + "fill", OPTcolr, O(TkCitem, env), IAUX(TkCfill), + "outline", OPTcolr, O(TkCitem, env), IAUX(TkCforegnd), + nil +}; + +void +tkcvsrectsize(TkCitem *i) +{ + TkCrect *r; + int w; + + r = TKobj(TkCrect, i); + w = TKF2I(r->width)*2; + + i->p.bb = bbnil; + tkpolybound(i->p.drawpt, i->p.npoint, &i->p.bb); + i->p.bb = insetrect(i->p.bb, -w); +} + +static void +tkmkstipple(Image *stipple) +{ + int locked; + if (stipple != nil && !stipple->repl) { + locked = lockdisplay(stipple->display); + replclipr(stipple, 1, huger); + if (locked) + unlockdisplay(stipple->display); + } +} + +char* +tkcvsrectcreat(Tk* tk, char *arg, char **val) +{ + char *e; + TkCrect *r; + TkCitem *i; + TkCanvas *c; + TkOptab tko[3]; + + c = TKobj(TkCanvas, tk); + + i = tkcnewitem(tk, TkCVrect, sizeof(TkCitem)+sizeof(TkCrect)); + if(i == nil) + return TkNomem; + + r = TKobj(TkCrect, i); + r->width = TKI2F(1); + + e = tkparsepts(tk->env->top, &i->p, &arg, 0); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + if(i->p.npoint != 2) { + tkcvsfreeitem(i); + return TkFewpt; + } + + tko[0].ptr = r; + tko[0].optab = rectopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + e = tkparse(tk->env->top, arg, tko, nil); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + tkmkstipple(r->stipple); + e = tkcaddtag(tk, i, 1); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + + tkcvsrectsize(i); + e = tkvalue(val, "%d", i->id); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + tkcvsappend(c, i); + + tkbbmax(&c->update, &i->p.bb); + tkcvssetdirty(tk); + return nil; +} + +char* +tkcvsrectcget(TkCitem *i, char *arg, char **val) +{ + TkOptab tko[3]; + TkCrect *r = TKobj(TkCrect, i); + + tko[0].ptr = r; + tko[0].optab = rectopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + + return tkgencget(tko, arg, val, i->env->top); +} + +char* +tkcvsrectconf(Tk *tk, TkCitem *i, char *arg) +{ + char *e; + TkOptab tko[3]; + TkCrect *r = TKobj(TkCrect, i); + + tko[0].ptr = r; + tko[0].optab = rectopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + + e = tkparse(tk->env->top, arg, tko, nil); + tkcvsrectsize(i); + tkmkstipple(r->stipple); + return e; +} + +void +tkcvsrectfree(TkCitem *i) +{ + TkCrect *r; + + r = TKobj(TkCrect, i); + if(r->stipple) + freeimage(r->stipple); +} + +void +tkcvsrectdraw(Image *img, TkCitem *i, TkEnv *pe) +{ + int lw, rw; + TkEnv *e; + TkCrect *r; + Rectangle d, rr; + Point tr, bl; + Image *pen; + + USED(pe); + + d.min = i->p.drawpt[0]; + d.max = i->p.drawpt[1]; + + e = i->env; + r = TKobj(TkCrect, i); + + pen = nil; + if((e->set & (1<<TkCfill))) + pen = tkgc(e, TkCfill); + + if(pen != nil) + draw(img, d, pen, r->stipple, d.min); + + tr.x = d.max.x; + tr.y = d.min.y; + bl.x = d.min.x; + bl.y = d.max.y; + + rw = (TKF2I(r->width) + 1)/2; + if(rw <= 0) + return; + lw = (TKF2I(r->width))/2; + + pen = tkgc(e, TkCforegnd); + if(pen != nil) { + /* horizontal lines first */ + rr.min.x = d.min.x - lw; + rr.max.x = d.max.x + rw; + rr.min.y = d.min.y - lw; + rr.max.y = d.min.y + rw; + draw(img, rr, pen, nil, rr.min); + rr.min.y += Dy(d); + rr.max.y += Dy(d); + draw(img, rr, pen, nil, rr.min); + /* now the vertical */ + /* horizontal lines first */ + rr.min.x = d.min.x - lw; + rr.max.x = d.min.x + rw; + rr.min.y = d.min.y + rw; + rr.max.y = d.max.y - lw; + draw(img, rr, pen, nil, rr.min); + rr.min.x += Dx(d); + rr.max.x += Dx(d); + draw(img, rr, pen, nil, rr.min); + } +} + +char* +tkcvsrectcoord(TkCitem *i, char *arg, int x, int y) +{ + char *e; + TkCpoints p; + + if(arg == nil) { + tkxlatepts(i->p.parampt, i->p.npoint, x, y); + tkxlatepts(i->p.drawpt, i->p.npoint, TKF2I(x), TKF2I(y)); + i->p.bb = rectaddpt(i->p.bb, Pt(TKF2I(x), TKF2I(y))); + } + else { + e = tkparsepts(i->env->top, &p, &arg, 0); + if(e != nil) + return e; + if(p.npoint != 2) { + tkfreepoint(&p); + return TkFewpt; + } + tkfreepoint(&i->p); + i->p = p; + tkcvsrectsize(i); + } + return nil; +} diff --git a/libtk/ctext.c b/libtk/ctext.c new file mode 100644 index 00000000..7f3f9296 --- /dev/null +++ b/libtk/ctext.c @@ -0,0 +1,666 @@ +#include <lib9.h> +#include <kernel.h> +#include "draw.h" +#include "tk.h" +#include "canvs.h" + +#define O(t, e) ((long)(&((t*)0)->e)) + +/* Text Options (+ means implemented) + +anchor + +fill + +font + +justify + +stipple + +tags + +text + +width +*/ + +/* Layout constants */ +enum { + Cvsicursor = 1, /* Extra height of insertion cursor in canvas */ +}; + +typedef struct TkCtext TkCtext; +struct TkCtext +{ + int anchor; + Point anchorp; + int justify; + int icursor; + int focus; + int pixwidth; + int pixheight; + int sell; + int self; + int selfrom; + int sbw; + int width; + int nlines; + Image* stipple; + Image* pen; + char* text; + int tlen; + TkEnv *env; +}; + +static +TkOption textopts[] = +{ + "anchor", OPTstab, O(TkCtext, anchor), tkanchor, + "justify", OPTstab, O(TkCtext, justify), tktabjust, + "width", OPTdist, O(TkCtext, width), IAUX(O(TkCtext, env)), + "stipple", OPTbmap, O(TkCtext, stipple), nil, + "text", OPTtext, O(TkCtext, text), nil, + nil +}; + +static +TkOption itemopts[] = +{ + "tags", OPTctag, O(TkCitem, tags), nil, + "font", OPTfont, O(TkCitem, env), nil, + "fill", OPTcolr, O(TkCitem, env), IAUX(TkCfill), + nil +}; + +static char* +tkcvstextgetl(TkCtext *t, Font *font, char *start, int *len) +{ + int w, n; + char *lspc, *posn; + + w = t->width; + if(w <= 0) + w = 1000000; + + n = 0; + lspc = nil; + posn = start; + while(*posn && *posn != '\n') { + if(*posn == ' ') + lspc = posn; + n += stringnwidth(font, posn, 1); + if(n >= w && posn != start) { + if(lspc != nil) + posn = lspc; + *len = posn - start; + if(lspc != nil) + posn++; + return posn; + } + posn++; + } + *len = posn - start; + if(*posn == '\n') + posn++; + return posn; +} + +void +tkcvstextsize(TkCitem *i) +{ + Point o; + Font *font; + TkCtext *t; + Display *d; + char *next, *p; + int len, pixw, locked; + + t = TKobj(TkCtext, i); + + font = i->env->font; + d = i->env->top->display; + t->pixwidth = 0; + t->pixheight = 0; + + p = t->text; + if(p != nil) { + locked = lockdisplay(d); + while(*p) { + next = tkcvstextgetl(t, font, p, &len); + pixw = stringnwidth(font, p, len); + if(pixw > t->pixwidth) + t->pixwidth = pixw; + t->pixheight += font->height; + p = next; + } + if(locked) + unlockdisplay(d); + } + + o = tkcvsanchor(i->p.drawpt[0], t->pixwidth, t->pixheight, t->anchor); + + i->p.bb.min.x = o.x; + i->p.bb.min.y = o.y - Cvsicursor; + i->p.bb.max.x = o.x + t->pixwidth; + i->p.bb.max.y = o.y + t->pixheight + Cvsicursor; + i->p.bb = insetrect(i->p.bb, -2*t->sbw); + t->anchorp = subpt(o, i->p.drawpt[0]); +} + +char* +tkcvstextcreat(Tk* tk, char *arg, char **val) +{ + char *e; + TkCtext *t; + TkCitem *i; + TkCanvas *c; + TkOptab tko[3]; + + c = TKobj(TkCanvas, tk); + + i = tkcnewitem(tk, TkCVtext, sizeof(TkCitem)+sizeof(TkCtext)); + if(i == nil) + return TkNomem; + + t = TKobj(TkCtext, i); + t->justify = Tkleft; + t->anchor = Tkcenter; + t->sell = -1; + t->self = -1; + t->icursor = -1; + t->sbw = c->sborderwidth; + t->env = tk->env; + + e = tkparsepts(tk->env->top, &i->p, &arg, 0); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + if(i->p.npoint != 1) { + tkcvsfreeitem(i); + return TkFewpt; + } + + tko[0].ptr = t; + tko[0].optab = textopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + e = tkparse(tk->env->top, arg, tko, nil); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + + e = tkcaddtag(tk, i, 1); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + + t->tlen = 0; + if(t->text != nil) + t->tlen = strlen(t->text); + + tkmkpen(&t->pen, i->env, t->stipple); + tkcvstextsize(i); + e = tkvalue(val, "%d", i->id); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + + tkcvsappend(c, i); + + tkbbmax(&c->update, &i->p.bb); + tkcvssetdirty(tk); + return nil; +} + +char* +tkcvstextcget(TkCitem *i, char *arg, char **val) +{ + TkOptab tko[3]; + TkCtext *t = TKobj(TkCtext, i); + + tko[0].ptr = t; + tko[0].optab = textopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + + return tkgencget(tko, arg, val, i->env->top); +} + +char* +tkcvstextconf(Tk *tk, TkCitem *i, char *arg) +{ + char *e; + TkOptab tko[3]; + TkCtext *t = TKobj(TkCtext, i); + + tko[0].ptr = t; + tko[0].optab = textopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + + e = tkparse(tk->env->top, arg, tko, nil); + + t->tlen = 0; + if(t->text != nil) + t->tlen = strlen(t->text); + + tkmkpen(&t->pen, i->env, t->stipple); + tkcvstextsize(i); + + return e; +} + +void +tkcvstextfree(TkCitem *i) +{ + TkCtext *t; + + t = TKobj(TkCtext, i); + if(t->stipple != nil) + freeimage(t->stipple); + if(t->pen != nil) + freeimage(t->pen); + if(t->text != nil) + free(t->text); +} + +void +tkcvstextdraw(Image *img, TkCitem *i, TkEnv *pe) +{ + TkEnv *e; + TkCtext *t; + Point o, dp; + Rectangle r; + char *p, *next; + Image *pen; + int len, lw, end, start; + + t = TKobj(TkCtext, i); + + e = i->env; + pen = t->pen; + if(pen == nil) { + if (e->set & (1<<TkCfill)) + pen = tkgc(e, TkCfill); + else + pen = img->display->black; + } + + + o = addpt(t->anchorp, i->p.drawpt[0]); + p = t->text; + while(p && *p) { + next = tkcvstextgetl(t, e->font, p, &len); + dp = o; + if(t->justify != Tkleft) { + lw = stringnwidth(e->font, p, len); + if(t->justify == Tkcenter) + dp.x += (t->pixwidth - lw)/2; + else + if(t->justify == Tkright) + dp.x += t->pixwidth - lw; + } + lw = p - t->text; + if(t->self != -1 && lw+len > t->self) { + if(t->sell >= t->self) { + start = t->self - lw; + end = t->sell - lw; + } + else { + start = t->sell - lw; + end = t->self - lw; + } + if(start < 0) + r.min.x = o.x; + else + r.min.x = dp.x + stringnwidth(e->font, p, start); + r.min.y = dp.y; + if(end > len) + r.max.x = o.x + t->pixwidth; + else + r.max.x = dp.x + stringnwidth(e->font, p, end); + r.max.y = dp.y + e->font->height; + tktextsdraw(img, r, pe, t->sbw); + r.max.y = dp.y; + if(start > 0) + stringn(img, dp, pen, dp, e->font, p, start); + if(end > start) + stringn(img, r.min, tkgc(pe, TkCselectfgnd), r.min, e->font, p+start, end-start); + if(len > end) + stringn(img, r.max, pen, r.max, e->font, p+end, len-end); + } + else + stringn(img, dp, pen, dp, e->font, p, len); + if(t->focus) { + lw = p - t->text; + if(t->icursor >= lw && t->icursor <= lw+len) { + lw = t->icursor - lw; + if(lw > 0) + lw = stringnwidth(e->font, p, lw); + r.min.x = dp.x + lw; + r.min.y = dp.y - 1; + r.max.x = r.min.x + 2; + r.max.y = r.min.y + e->font->height + 1; + draw(img, r, pen, nil, ZP); + } + } + o.y += e->font->height; + p = next; + } +} + +char* +tkcvstextcoord(TkCitem *i, char *arg, int x, int y) +{ + char *e; + TkCpoints p; + + if(arg == nil) { + tkxlatepts(i->p.parampt, i->p.npoint, x, y); + tkxlatepts(i->p.drawpt, i->p.npoint, TKF2I(x), TKF2I(y)); + i->p.bb = rectaddpt(i->p.bb, Pt(TKF2I(x), TKF2I(y))); + } + else { + e = tkparsepts(i->env->top, &p, &arg, 0); + if(e != nil) + return e; + if(p.npoint != 1) { + tkfreepoint(&p); + return TkFewpt; + } + tkfreepoint(&i->p); + i->p = p; + tkcvstextsize(i); + } + return nil; +} + +int +tkcvstextsrch(TkCitem *i, int x, int y) +{ + TkCtext *t; + Font *font; + Display *d; + char *p, *next; + int n, len, locked; + + t = TKobj(TkCtext, i); + + n = 0; + font = i->env->font; + d = i->env->top->display; + p = t->text; + if(p == nil) + return 0; + while(*p) { + next = tkcvstextgetl(t, font, p, &len); + if(y <= font->height) { + locked = lockdisplay(d); + for(n = 0; n < len && x > stringnwidth(font, p, n+1); n++) + ; + if(locked) + unlockdisplay(d); + break; + } + y -= font->height; + p = next; + } + return p - t->text + n; +} + +static char* +tkcvsparseindex(TkCitem *i, char *buf, int *index) +{ + Point o; + char *p; + int x, y; + TkCtext *t; + + t = TKobj(TkCtext, i); + + if(strcmp(buf, "end") == 0) { + *index = t->tlen; + return nil; + } + if(strcmp(buf, "sel.first") == 0) { + if(t->self < 0) + return TkBadix; + *index = t->self; + return nil; + } + if(strcmp(buf, "sel.last") == 0) { + if(t->sell < 0) + return TkBadix; + *index = t->sell; + return nil; + } + if(strcmp(buf, "insert") == 0) { + *index = t->icursor; + return nil; + } + if(buf[0] == '@') { + x = atoi(buf+1); + p = strchr(buf, ','); + if(p == nil) + return TkBadix; + y = atoi(p+1); + o = i->p.drawpt[0]; + *index = tkcvstextsrch(i, (x-t->anchorp.x)-o.x, (y-t->anchorp.y)-o.y); + return nil; + } + + if(buf[0] < '0' || buf[0] > '9') + return TkBadix; + x = atoi(buf); + if(x < 0) + x = 0; + if(x > t->tlen) + x = t->tlen; + *index = x; + return nil; +} + +char* +tkcvstextdchar(Tk *tk, TkCitem *i, char *arg) +{ + TkTop *top; + TkCtext *t; + int first, last; + char *e, buf[Tkmaxitem]; + + t = TKobj(TkCtext, i); + + top = tk->env->top; + arg = tkword(top, arg, buf, buf+sizeof(buf), nil); + e = tkcvsparseindex(i, buf, &first); + if(e != nil) + return e; + + last = first+1; + if(*arg != '\0') { + tkword(top, arg, buf, buf+sizeof(buf), nil); + e = tkcvsparseindex(i, buf, &last); + if(e != nil) + return e; + } + if(last <= first || t->tlen == 0) + return nil; + + tkbbmax(&TKobj(TkCanvas, tk)->update, &i->p.bb); + + memmove(t->text+first, t->text+last, t->tlen-last+1); + t->tlen -= last-first; + + tkcvstextsize(i); + tkbbmax(&TKobj(TkCanvas, tk)->update, &i->p.bb); + + tkcvssetdirty(tk); + return nil; +} + +char* +tkcvstextinsert(Tk *tk, TkCitem *i, char *arg) +{ + TkTop *top; + TkCtext *t; + int first, n; + char *e, *text, buf[Tkmaxitem]; + + t = TKobj(TkCtext, i); + + top = tk->env->top; + arg = tkword(top, arg, buf, buf+sizeof(buf), nil); + e = tkcvsparseindex(i, buf, &first); + if(e != nil) + return e; + + if(*arg == '\0') + return nil; + + text = malloc(Tkcvstextins); + if(text == nil) + return TkNomem; + + tkword(top, arg, text, text+Tkcvstextins, nil); + n = strlen(text); + t->text = realloc(t->text, t->tlen+n+1); + if(t->text == nil) { + free(text); + return TkNomem; + } + if(t->tlen == 0) + t->text[0] = '\0'; + + tkbbmax(&TKobj(TkCanvas, tk)->update, &i->p.bb); + + memmove(t->text+first+n, t->text+first, t->tlen-first+1); + memmove(t->text+first, text, n); + t->tlen += n; + free(text); + + tkcvstextsize(i); + tkbbmax(&TKobj(TkCanvas, tk)->update, &i->p.bb); + + tkcvssetdirty(tk); + return nil; +} + +char* +tkcvstextindex(Tk *tk, TkCitem *i, char *arg, char **val) +{ + int first; + char *e, buf[Tkmaxitem]; + + tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + e = tkcvsparseindex(i, buf, &first); + if(e != nil) + return e; + + return tkvalue(val, "%d", first); +} + +char* +tkcvstexticursor(Tk *tk, TkCitem *i, char *arg) +{ + int first; + TkCanvas *c; + char *e, buf[Tkmaxitem]; + + tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + e = tkcvsparseindex(i, buf, &first); + if(e != nil) + return e; + + TKobj(TkCtext, i)->icursor = first; + + c = TKobj(TkCanvas, tk); + if(c->focus == i) { + tkbbmax(&c->update, &i->p.bb); + tkcvssetdirty(tk); + } + return nil; +} + +void +tkcvstextfocus(Tk *tk, TkCitem *i, int x) +{ + TkCtext *t; + TkCanvas *c; + + if(i == nil) + return; + + t = TKobj(TkCtext, i); + c = TKobj(TkCanvas, tk); + + if(t->focus != x) { + t->focus = x; + tkbbmax(&c->update, &i->p.bb); + tkcvssetdirty(tk); + } +} + +void +tkcvstextclr(Tk *tk) +{ + TkCtext *t; + TkCanvas *c; + TkCitem *item; + + c = TKobj(TkCanvas, tk); + item = c->selection; + if(item == nil) + return; + + c->selection = nil; + t = TKobj(TkCtext, item); + t->sell = -1; + t->self = -1; + tkbbmax(&c->update, &item->p.bb); + tkcvssetdirty(tk); +} + +char* +tkcvstextselect(Tk *tk, TkCitem *i, char *arg, int op) +{ + int indx; + TkCtext *t; + TkCanvas *c; + char *e, buf[Tkmaxitem]; + + tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + e = tkcvsparseindex(i, buf, &indx); + if(e != nil) + return e; + + c = TKobj(TkCanvas, tk); + t = TKobj(TkCtext, i); + switch(op) { + case TkCselfrom: + t->selfrom = indx; + return nil; + case TkCseladjust: + if(c->selection == i) { + if(abs(t->self-indx) < abs(t->sell-indx)) { + t->self = indx; + t->selfrom = t->sell; + } + else { + t->sell = indx; + t->selfrom = t->self; + } + } + /* No break */ + case TkCselto: + if(c->selection != i) + tkcvstextclr(tk); + c->selection = i; + t->self = t->selfrom; + t->sell = indx; + break; + } + t->sbw = c->sborderwidth; + tkbbmax(&TKobj(TkCanvas, tk)->update, &i->p.bb); + tkcvssetdirty(tk); + return nil; +} diff --git a/libtk/cwind.c b/libtk/cwind.c new file mode 100644 index 00000000..dd25eb1b --- /dev/null +++ b/libtk/cwind.c @@ -0,0 +1,410 @@ +#include "lib9.h" +#include "draw.h" +#include "tk.h" +#include "canvs.h" + +#define O(t, e) ((long)(&((t*)0)->e)) + +/* Window Options (+ means implemented) + +tags + +width + +height + +window + +anchor +*/ + +static +TkOption windopts[] = +{ + "width", OPTdist, O(TkCwind, width), nil, + "height", OPTdist, O(TkCwind, height), nil, + "anchor", OPTstab, O(TkCwind, flags), tkanchor, + "window", OPTwinp, O(TkCwind, sub), nil, + nil +}; + +static +TkOption itemopts[] = +{ + "tags", OPTctag, O(TkCitem, tags), nil, + nil +}; + +static void +tkcvswindsize(TkCitem *i) +{ + Tk *s; + int bw; + Point p; + TkGeom old; + TkCwind *w; + + w = TKobj(TkCwind, i); + s = w->sub; + if(s == nil) + return; + + if(w->width != s->act.width || w->height != s->act.height) { + old = s->act; + s->act.width = w->width; + s->act.height = w->height; + if(s->slave) { + tkpackqit(s); + tkrunpack(s->env->top); + } + tkdeliver(s, TkConfigure, &old); + } + p = tkcvsanchor(i->p.drawpt[0], s->act.width, s->act.height, w->flags); + s->act.x = p.x; + s->act.y = p.y; + + bw = 2*s->borderwidth; + i->p.bb.min = p; + i->p.bb.max.x = p.x + s->act.width + bw; + i->p.bb.max.y = p.y + s->act.height + bw; +} + +static int +tkcvschkwfocus(TkCwind *w, Tk *tk) +{ + if(w->focus == tk) + return 1; + for(tk = tk->slave; tk; tk = tk->next) + if(tkcvschkwfocus(w, tk)) + return 1; + return 0; +} + +static void +tkcvswindgeom(Tk *sub, int x, int y, int w, int h) +{ + TkCitem *i; + Tk *parent; + TkCanvas *c; + TkCwind *win; + + USED(x); + USED(y); + parent = sub->parent; + win = nil; + c = TKobj(TkCanvas, parent); + for(i = c->head; i; i = i->next) { + if(i->type == TkCVwindow) { + win = TKobj(TkCwind, i); + if(win->sub == sub) + break; + } + } + + if(win->focus != nil) { + if(tkcvschkwfocus(win, sub) == 0) + win->focus = nil; + } + + tkbbmax(&c->update, &i->p.bb); + + if((win->flags & Tksetwidth) == 0) + win->width = w; + if ((win->flags & Tksetheight) == 0) + win->height = h; + + sub->req.width = w; + sub->req.height = h; + tkcvswindsize(i); + + tkbbmax(&c->update, &i->p.bb); + tkcvsdirty(parent); +} + +static void +tkcvssubdestry(Tk *sub) +{ + Tk *tk; + TkCitem *i; + TkCanvas *c; + TkCwind *win; + + tk = sub->parent; + if(tk == nil) + return; + + c = TKobj(TkCanvas, tk); + for(i = c->head; i; i = i->next) { + if(i->type == TkCVwindow) { + win = TKobj(TkCwind, i); + if(win->sub == sub) { + tkbbmax(&c->update, &i->p.bb); + tkcvssetdirty(tk); + + win->focus = nil; + win->sub = nil; + sub->parent = nil; + sub->geom = nil; + return; + } + } + } +} + +Point +tkcvsrelpos(Tk *sub) +{ + Tk *tk; + TkCitem *i; + TkCanvas *c; + TkCwind *win; + + tk = sub->parent; + if(tk == nil) + return ZP; + + c = TKobj(TkCanvas, tk); + for(i = c->head; i; i = i->next) { + if(i->type == TkCVwindow) { + win = TKobj(TkCwind, i); + if(win->sub == sub) + return subpt(i->p.bb.min, c->view); + } + } + return ZP; +} + +static char* +tkcvswindchk(Tk *tk, TkCwind *w, Tk *oldsub) +{ + Tk *sub; + + sub = w->sub; + if (sub != oldsub) { + w->sub = oldsub; + if(sub == nil) + return nil; + + if(sub->flag & Tkwindow) + return TkIstop; + + if(sub->master != nil || sub->parent != nil) + return TkWpack; + + if (oldsub != nil) { + oldsub->parent = nil; + oldsub->geom = nil; + oldsub->destroyed = nil; + } + w->sub = sub; + w->focus = nil; + sub->parent = tk; + tksetbits(w->sub, Tksubsub); + sub->geom = tkcvswindgeom; + sub->destroyed = tkcvssubdestry; + + if(w->width == 0) + w->width = sub->req.width; + if(w->height == 0) + w->height = sub->req.height; + } + + return nil; +} + +char* +tkcvswindcreat(Tk* tk, char *arg, char **val) +{ + char *e; + TkCwind *w; + TkCitem *i; + TkCanvas *c; + TkOptab tko[3]; + + c = TKobj(TkCanvas, tk); + + i = tkcnewitem(tk, TkCVwindow, sizeof(TkCitem)+sizeof(TkCwind)); + if(i == nil) + return TkNomem; + + w = TKobj(TkCwind, i); + w->flags = Tkcenter; + + e = tkparsepts(tk->env->top, &i->p, &arg, 0); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + if(i->p.npoint != 1) { + tkcvsfreeitem(i); + return TkFewpt; + } + + tko[0].ptr = w; + tko[0].optab = windopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + e = tkparse(tk->env->top, arg, tko, nil); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + e = tkcvswindchk(tk, w, nil); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + + e = tkcaddtag(tk, i, 1); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + + e = tkvalue(val, "%d", i->id); + if(e != nil) { + tkcvsfreeitem(i); + return e; + } + tkcvsappend(c, i); + tkcvswindsize(i); + + tkbbmax(&c->update, &i->p.bb); + tkcvssetdirty(tk); + return nil; +} + +char* +tkcvswindcget(TkCitem *i, char *arg, char **val) +{ + TkOptab tko[3]; + TkCwind *w = TKobj(TkCwind, i); + + tko[0].ptr = w; + tko[0].optab = windopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + + return tkgencget(tko, arg, val, i->env->top); +} + +char* +tkcvswindconf(Tk *tk, TkCitem *i, char *arg) +{ + char *e; + int dx, dy; + TkOptab tko[3]; + TkCwind *w = TKobj(TkCwind, i); + Tk *oldsub; + + tko[0].ptr = w; + tko[0].optab = windopts; + tko[1].ptr = i; + tko[1].optab = itemopts; + tko[2].ptr = nil; + + dx = w->width; + dy = w->height; + w->width = -1; + w->height = -1; + + oldsub = w->sub; + e = tkparse(tk->env->top, arg, tko, nil); + if(e == nil) { + e = tkcvswindchk(tk, w, oldsub); + if(e != nil) + return e; + if(w->width == -1) + w->width = dx; + else + w->flags |= Tksetwidth; + if(w->height == -1) + w->height = dy; + else + w->flags |= Tksetheight; + tkcvswindsize(i); + } else { + w->width = dx; + w->height = dy; + } + return e; +} + +void +tkcvswindfree(TkCitem *i) +{ + Tk *sub; + TkCwind *w; + + w = TKobj(TkCwind, i); + sub = w->sub; + if(w->focus == sub) + w->focus = nil; + if(sub != nil) { + sub->parent = nil; + sub->geom = nil; + sub->destroyed = nil; + } +} + +void +tkcvswinddraw(Image *img, TkCitem *i, TkEnv *pe) +{ + TkCwind *w; + Point rel; + Rectangle r; + Tk *sub; + + USED(img); /* See tkimageof */ + USED(pe); + w = TKobj(TkCwind, i); + sub = w->sub; + if(sub != nil) { + int dirty; + r = i->p.bb; + rel.x = r.min.x + sub->borderwidth; + rel.y = r.min.y + sub->borderwidth; + if (rectclip(&r, img->clipr)) { + sub->dirty = rectsubpt(r, rel); + sub->flag |= Tkrefresh; + tkdrawslaves(sub, ZP, &dirty); /* XXX - Tad: propagate err? */ + } + } +} + +char* +tkcvswindcoord(TkCitem *i, char *arg, int x, int y) +{ + char *e; + TkCpoints p; +/* + TkCwind *w; + int xi, yi; +*/ + + if(arg == nil) { + tkxlatepts(i->p.parampt, i->p.npoint, x, y); + tkxlatepts(i->p.drawpt, i->p.npoint, TKF2I(x), TKF2I(y)); + tkcvswindsize(i); +/* + w = TKobj(TkCwind, i); + xi = TKF2I(x); + yi = TKF2I(y); + if (w->sub != nil) { + w->sub->act.x += xi; + w->sub->act.y += yi; + } + i->p.bb = rectaddpt(i->p.bb, Pt(xi, yi)); +*/ + } + else { + e = tkparsepts(i->env->top, &p, &arg, 0); + if(e != nil) + return e; + if(p.npoint != 1) { + tkfreepoint(&p); + return TkFewpt; + } + tkfreepoint(&i->p); + i->p = p; + tkcvswindsize(i); + } + return nil; +} diff --git a/libtk/ebind.c b/libtk/ebind.c new file mode 100644 index 00000000..e6068b44 --- /dev/null +++ b/libtk/ebind.c @@ -0,0 +1,1033 @@ +#include "lib9.h" +#include "draw.h" +#include "tk.h" +#include <kernel.h> +#include <interp.h> + +enum +{ + Cmask, + Cctl, + Ckey, + Cbp, + Cbr, +}; + +struct +{ + char* event; + int mask; + int action; +} etab[] = +{ + "Motion", TkMotion, Cmask, + "Double", TkDouble, Cmask, + "Map", TkMap, Cmask, + "Unmap", TkUnmap, Cmask, + "Destroy", TkDestroy, Cmask, + "Enter", TkEnter, Cmask, + "Leave", TkLeave, Cmask, + "FocusIn", TkFocusin, Cmask, + "FocusOut", TkFocusout, Cmask, + "Configure", TkConfigure, Cmask, + "Control", 0, Cctl, + "Key", 0, Ckey, + "KeyPress", 0, Ckey, + "Button", 0, Cbp, + "ButtonPress", 0, Cbp, + "ButtonRelease", 0, Cbr, +}; + +static +TkOption tkcurop[] = +{ + "x", OPTdist, O(TkCursor, p.x), nil, + "y", OPTdist, O(TkCursor, p.y), nil, + "bitmap", OPTbmap, O(TkCursor, bit), nil, + "image", OPTimag, O(TkCursor, img), nil, + "default", OPTbool, O(TkCursor, def), nil, + nil +}; + +static +TkOption focusopts[] = { + "global", OPTbool, 0, nil, + nil +}; + +static char* +tkseqitem(char *buf, char *arg) +{ + while(*arg && (*arg == ' ' || *arg == '-')) + arg++; + while(*arg && *arg != ' ' && *arg != '-' && *arg != '>') + *buf++ = *arg++; + *buf = '\0'; + return arg; +} + +static char* +tkseqkey(Rune *r, char *arg) +{ + char *narg; + + while(*arg && (*arg == ' ' || *arg == '-')) + arg++; + if (*arg == '\\') { + if (*++arg == '\0') { + *r = 0; + return arg; + } + } else if (*arg == '\0' || *arg == '>' || *arg == '-') { + *r = 0; + return arg; + } + narg = arg + chartorune(r, arg); + return narg; +} + +int +tkseqparse(char *seq) +{ + Rune r; + int i, event; + char *buf; + + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return -1; + + event = 0; + + while(*seq && *seq != '>') { + seq = tkseqitem(buf, seq); + + for(i = 0; i < nelem(etab); i++) + if(strcmp(buf, etab[i].event) == 0) + break; + + if(i >= nelem(etab)) { + seq = tkextnparseseq(buf, seq, &event); + if (seq == nil) { + free(buf); + return -1; + } + continue; + } + + + switch(etab[i].action) { + case Cmask: + event |= etab[i].mask; + break; + case Cctl: + seq = tkseqkey(&r, seq); + if(r == 0) { + free(buf); + return -1; + } + if(r <= '~') + r &= 0x1f; + event |= TkKey|TKKEY(r); + break; + case Ckey: + seq = tkseqkey(&r, seq); + if(r != 0) + event |= TKKEY(r); + event |= TkKey; + break; + case Cbp: + seq = tkseqitem(buf, seq); + switch(buf[0]) { + default: + free(buf); + return -1; + case '\0': + event |= TkEpress; + break; + case '1': + event |= TkButton1P; + break; + case '2': + event |= TkButton2P; + break; + case '3': + event |= TkButton3P; + break; + case '4': + event |= TkButton4P; + break; + case '5': + event |= TkButton5P; + break; + case '6': + event |= TkButton6P; + break; + } + break; + case Cbr: + seq = tkseqitem(buf, seq); + switch(buf[0]) { + default: + free(buf); + return -1; + case '\0': + event |= TkErelease; + break; + case '1': + event |= TkButton1R; + break; + case '2': + event |= TkButton2R; + break; + case '3': + event |= TkButton3R; + break; + case '4': + event |= TkButton4R; + break; + case '5': + event |= TkButton5R; + break; + case '6': + event |= TkButton6R; + break; + } + break; + } + } + free(buf); + return event; +} + +void +tkcmdbind(Tk *tk, int event, char *s, void *data) +{ + Point p; + TkMouse *m; + TkGeom *g; + int v, len; + char *e, *c, *ec, *cmd; + TkTop *t; + + if(s == nil) + return; + cmd = malloc(2*Tkmaxitem); + if (cmd == nil) { + print("tk: bind command \"%s\": %s\n", + tk->name ? tk->name->name : "(noname)", TkNomem); + return; + } + + m = (TkMouse*)data; + c = cmd; + ec = cmd+2*Tkmaxitem-1; + while(*s && c < ec) { + if(*s != '%') { + *c++ = *s++; + continue; + } + s++; + len = ec-c; + switch(*s++) { + def: + default: + *c++ = s[-1]; + break; + case '%': + *c++ = '%'; + break; + case 'b': + v = 0; + if (!(event & TkKey)) { + if(event & (TkButton1P|TkButton1R)) + v = 1; + else + if(event & (TkButton2P|TkButton2R)) + v = 2; + else + if(event & (TkButton3P|TkButton3R)) + v = 3; + } + c += snprint(c, len, "%d", v); + break; + case 'h': + if((event & TkConfigure) == 0) + goto def; + g = (TkGeom*)data; + c += snprint(c, len, "%d", g->height); + break; + case 's': + if((event & TkKey)) + c += snprint(c, len, "%d", TKKEY(event)); + else + if((event & (TkEmouse|TkEnter))) + c += snprint(c, len, "%d", m->b); + else + if((event & TkFocusin)) + c += snprint(c, len, "%d", (int)data); + else + goto def; + break; + case 'w': + if((event & TkConfigure) == 0) + goto def; + g = (TkGeom*)data; + c += snprint(c, len, "%d", g->width); + break; + case 'x': /* Relative mouse coords */ + case 'y': + if((event & TkKey) || (event & (TkEmouse|TkEnter)) == 0) + goto def; + p = tkposn(tk); + if(s[-1] == 'x') + v = m->x - p.x; + else + v = m->y - p.y; + c += snprint(c, len, "%d", v - tk->borderwidth); + break; + case 'X': /* Absolute mouse coords */ + case 'Y': + if((event & TkKey) || (event & TkEmouse) == 0) + goto def; + c += snprint(c, len, "%d", s[-1] == 'X' ? m->x : m->y); + break; + case 'A': + if((event & TkKey) == 0) + goto def; + v = TKKEY(event); + if(v == '{' || v == '}' || v == '\\') + c += snprint(c, len, "\\%C", v); + else + if(v != '\0') + c += snprint(c, len, "%C", v); + break; + case 'K': + if((event & TkKey) == 0) + goto def; + c += snprint(c, len, "%.4X", TKKEY(event)); + break; + case 'W': + if (tk->name != nil) + c += snprint(c, len, "%s", tk->name->name); + break; + } + } + *c = '\0'; + e = nil; + t = tk->env->top; + t->execdepth = 0; + if(cmd[0] == '|') + tkexec(t, cmd+1, nil); + else + if(cmd[0] != '\0') + e = tkexec(t, cmd, nil); + t->execdepth = -1; + + if(e == nil) { + free(cmd); + return; + } + + if(tk->name != nil){ + char *s; + + if(t->errx[0] != '\0') + s = tkerrstr(t, e); + else + s = e; + print("tk: bind command \"%s\": %s: %s\n", tk->name->name, cmd, s); + if(s != e) + free(s); + } + free(cmd); +} + +char* +tkbind(TkTop *t, char *arg, char **ret) +{ + Rune r; + Tk *tk; + TkAction **ap; + int i, mode, event; + char *cmd, *tag, *seq; + char *e; + + USED(ret); + + tag = mallocz(Tkmaxitem, 0); + if(tag == nil) + return TkNomem; + seq = mallocz(Tkmaxitem, 0); + if(seq == nil) { + free(tag); + return TkNomem; + } + + arg = tkword(t, arg, tag, tag+Tkmaxitem, nil); + if(tag[0] == '\0') { + e = TkBadtg; + goto err; + } + + arg = tkword(t, arg, seq, seq+Tkmaxitem, nil); + if(seq[0] == '<') { + event = tkseqparse(seq+1); + if(event == -1) { + e = TkBadsq; + goto err; + } + } + else { + chartorune(&r, seq); + event = TkKey | r; + } + if(event == 0) { + e = TkBadsq; + goto err; + } + + arg = tkskip(arg, " \t"); + + mode = TkArepl; + if(*arg == '+') { + mode = TkAadd; + arg++; + } + else if(*arg == '-'){ + mode = TkAsub; + arg++; + } + + if(*arg == '{') { + cmd = tkskip(arg+1, " \t"); + if(*cmd == '}') { + tk = tklook(t, tag, 0); + if(tk == nil) { + for(i = 0; ; i++) { + if(i >= TKwidgets) { + e = TkBadwp; + tkerr(t, tag); + goto err; + } + if(strcmp(tag, tkmethod[i]->name) == 0) { + ap = &(t->binds[i]); + break; + } + } + } + else + ap = &tk->binds; + tkcancel(ap, event); + } + } + + tkword(t, arg, seq, seq+Tkmaxitem, nil); + if(tag[0] == '.') { + tk = tklook(t, tag, 0); + if(tk == nil) { + e = TkBadwp; + tkerr(t, tag); + goto err; + } + + cmd = strdup(seq); + if(cmd == nil) { + e = TkNomem; + goto err; + } + e = tkaction(&tk->binds, event, TkDynamic, cmd, mode); + if(e != nil) + goto err; /* tkaction does free(cmd) */ + free(tag); + free(seq); + return nil; + } + /* documented but doesn't work */ + if(strcmp(tag, "all") == 0) { + for(tk = t->root; tk; tk = tk->next) { + cmd = strdup(seq); + if(cmd == nil) { + e = TkNomem; + goto err; + } + e = tkaction(&tk->binds, event, TkDynamic, cmd, mode); + if(e != nil) + goto err; + } + free(tag); + free(seq); + return nil; + } + /* undocumented, probably unused, and doesn't work consistently */ + for(i = 0; i < TKwidgets; i++) { + if(strcmp(tag, tkmethod[i]->name) == 0) { + cmd = strdup(seq); + if(cmd == nil) { + e = TkNomem; + goto err; + } + e = tkaction(t->binds + i,event, TkDynamic, cmd, mode); + if(e != nil) + goto err; + free(tag); + free(seq); + return nil; + } + } + + e = TkBadtg; +err: + free(tag); + free(seq); + + return e; +} + +char* +tksend(TkTop *t, char *arg, char **ret) +{ + + TkVar *v; + char *var; + + USED(ret); + + var = mallocz(Tkmaxitem, 0); + if(var == nil) + return TkNomem; + + arg = tkword(t, arg, var, var+Tkmaxitem, nil); + v = tkmkvar(t, var, 0); + free(var); + if(v == nil) + return TkBadvr; + if(v->type != TkVchan) + return TkNotvt; + + arg = tkskip(arg, " \t"); + if(tktolimbo(v->value, arg) == 0) + return TkMovfw; + + return nil; +} + +static Tk* +tknextfocus(TkTop *t, int d) +{ + int i, n, j, k; + Tk *oldfocus; + + if (t->focusorder == nil) + tkbuildfocusorder(t); + + oldfocus = t->ctxt->tkkeygrab; + n = t->nfocus; + if (n == 0) + return oldfocus; + for (i = 0; i < n; i++) + if (t->focusorder[i] == oldfocus) + break; + if (i == n) { + for (i = 0; i < n; i++) + if ((t->focusorder[i]->flag & Tkdisabled) == 0) + return t->focusorder[i]; + return oldfocus; + } + for (j = 1; j < n; j++) { + k = (i + d * j + n) % n; + if ((t->focusorder[k]->flag & Tkdisabled) == 0) + return t->focusorder[k]; + } + return oldfocus; +} + +/* our dirty little secret */ +static void +focusdirty(Tk *tk) +{ + if(tk->highlightwidth > 0){ + tk->dirty = tkrect(tk, 1); + tkdirty(tk); + } +} + +void +tksetkeyfocus(TkTop *top, Tk *new, int dir) +{ + TkCtxt *c; + Tk *old; + + c = top->ctxt; + old = c->tkkeygrab; + + if(old == new) + return; + c->tkkeygrab = new; + if(top->focused == 0) + return; + if(old != nil && old != top->root){ + tkdeliver(old, TkFocusout, nil); + focusdirty(old); + } + if(new != nil && new != top->root){ + tkdeliver(new, TkFocusin, (void*)dir); + focusdirty(new); + } +} + +void +tksetglobalfocus(TkTop *top, int in) +{ + Tk *tk; + in = (in != 0); + if (in != top->focused){ + top->focused = in; + tk = top->ctxt->tkkeygrab; + if(in){ + tkdeliver(top->root, TkFocusin, (void*)0); + if(tk != nil && tk != top->root){ + tkdeliver(tk, TkFocusin, (void*)0); + focusdirty(tk); + } + }else{ + if(tk != nil && tk != top->root){ + tkdeliver(tk, TkFocusout, nil); + focusdirty(tk); + } + tkdeliver(top->root, TkFocusout, nil); + } + } +} + +char* +tkfocus(TkTop *top, char *arg, char **ret) +{ + Tk *tk; + char *wp, *e; + int dir, global; + TkOptab tko[2]; + TkName *names; + + tko[0].ptr = &global; + tko[0].optab = focusopts; + tko[1].ptr = nil; + + global = 0; + + names = nil; + e = tkparse(top, arg, tko, &names); + if (e != nil) + return e; + + if(names == nil){ + if(global) + return tkvalue(ret, "%d", top->focused); + tk = top->ctxt->tkkeygrab; + if (tk != nil && tk->name != nil) + return tkvalue(ret, "%s", tk->name->name); + return nil; + } + + if(global){ + tksetglobalfocus(top, atoi(names->name)); + return nil; + } + + wp = mallocz(Tkmaxitem, 0); + if(wp == nil) + return TkNomem; + + tkword(top, arg, wp, wp+Tkmaxitem, nil); + if (!strcmp(wp, "next")) { + tk = tknextfocus(top, 1); /* can only return nil if c->tkkeygrab is already nil */ + dir = +1; + } else if (!strcmp(wp, "previous")) { + tk = tknextfocus(top, -1); + dir = -1; + } else if(*wp == '\0') { + tk = nil; + dir = 0; + } else { + tk = tklook(top, wp, 0); + if(tk == nil){ + tkerr(top, wp); + free(wp); + return TkBadwp; + } + dir = 0; + } + free(wp); + + tksetkeyfocus(top, tk, dir); + return nil; +} + +char* +tkraise(TkTop *t, char *arg, char **ret) +{ + Tk *tk; + char *wp; + + USED(ret); + + wp = mallocz(Tkmaxitem, 0); + if(wp == nil) + return TkNomem; + tkword(t, arg, wp, wp+Tkmaxitem, nil); + tk = tklook(t, wp, 0); + if(tk == nil){ + tkerr(t, wp); + free(wp); + return TkBadwp; + } + free(wp); + + if((tk->flag & Tkwindow) == 0) + return TkNotwm; + + tkwreq(tk->env->top, "raise %s", tk->name->name); + return nil; +} + +char* +tklower(TkTop *t, char *arg, char **ret) +{ + Tk *tk; + char *wp; + + USED(ret); + wp = mallocz(Tkmaxitem, 0); + if(wp == nil) + return TkNomem; + tkword(t, arg, wp, wp+Tkmaxitem, nil); + tk = tklook(t, wp, 0); + if(tk == nil){ + tkerr(t, wp); + free(wp); + return TkBadwp; + } + free(wp); + + if((tk->flag & Tkwindow) == 0) + return TkNotwm; + + tkwreq(tk->env->top, "lower %s", tk->name->name); + return nil; +} + +char* +tkgrab(TkTop *t, char *arg, char **ret) +{ + Tk *tk; + TkCtxt *c; + char *r, *buf, *wp; + + USED(ret); + + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + + wp = mallocz(Tkmaxitem, 0); + if(wp == nil) { + free(buf); + return TkNomem; + } + arg = tkword(t, arg, buf, buf+Tkmaxitem, nil); + + tkword(t, arg, wp, wp+Tkmaxitem, nil); + tk = tklook(t, wp, 0); + if(tk == nil) { + free(buf); + tkerr(t, wp); + free(wp); + return TkBadwp; + } + free(wp); + + c = t->ctxt; + if(strcmp(buf, "release") == 0) { + free(buf); + if(c->mgrab == tk) + tksetmgrab(t, nil); + return nil; + } + if(strcmp(buf, "set") == 0) { + free(buf); + return tksetmgrab(t, tk); + } + if(strcmp(buf, "ifunset") == 0) { + free(buf); + if(c->mgrab == nil) + return tksetmgrab(t, tk); + return nil; + } + if(strcmp(buf, "status") == 0) { + free(buf); + r = "none"; + if ((c->mgrab != nil) && (c->mgrab->name != nil)) + r = c->mgrab->name->name; + return tkvalue(ret, "%s", r); + } + free(buf); + return TkBadcm; +} + +char* +tkputs(TkTop *t, char *arg, char **ret) +{ + char *buf; + + USED(ret); + + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + tkword(t, arg, buf, buf+Tkmaxitem, nil); + print("%s\n", buf); + free(buf); + return nil; +} + +char* +tkdestroy(TkTop *t, char *arg, char **ret) +{ + int found, len, isroot; + Tk *tk, **l, *next, *slave; + char *n, *e, *buf; + + USED(ret); + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + e = nil; + for(;;) { + arg = tkword(t, arg, buf, buf+Tkmaxitem, nil); + if(buf[0] == '\0') + break; + + len = strlen(buf); + found = 0; + isroot = (strcmp(buf, ".") == 0); + for(tk = t->root; tk; tk = tk->siblings) { + if (tk->name != nil) { + n = tk->name->name; + if(strcmp(buf, n) == 0) { + tk->flag |= Tkdestroy; + found = 1; + } else if(isroot || (strncmp(buf, n, len) == 0 &&n[len] == '.')) + tk->flag |= Tkdestroy; + } + } + if(!found) { + e = TkBadwp; + tkerr(t, buf); + break; + } + } + free(buf); + + for(tk = t->root; tk; tk = tk->siblings) { + if((tk->flag & Tkdestroy) == 0) + continue; + if(tk->flag & Tkwindow) { + tkunmap(tk); + if((tk->name != nil) + && (strcmp(tk->name->name, ".") == 0)) + tk->flag &= ~Tkdestroy; + else + tkdeliver(tk, TkDestroy, nil); + } else + tkdeliver(tk, TkDestroy, nil); + if(tk->destroyed != nil) + tk->destroyed(tk); + tkpackqit(tk->master); + tkdelpack(tk); + for (slave = tk->slave; slave != nil; slave = next) { + next = slave->next; + slave->master = nil; + slave->next = nil; + } + tk->slave = nil; + if(tk->parent != nil && tk->geom != nil) /* XXX this appears to be bogus */ + tk->geom(tk, 0, 0, 0, 0); + if(tk->grid){ + tkfreegrid(tk->grid); + tk->grid = nil; + } + } + tkrunpack(t); + + l = &t->windows; + for(tk = t->windows; tk; tk = next) { + next = TKobj(TkWin, tk)->next; + if(tk->flag & Tkdestroy) { + *l = next; + continue; + } + l = &TKobj(TkWin, tk)->next; + } + l = &t->root; + for(tk = t->root; tk; tk = next) { + next = tk->siblings; + if(tk->flag & Tkdestroy) { + *l = next; + tkfreeobj(tk); + continue; + } + l = &tk->siblings; + } + + return e; +} + +char* +tkupdatecmd(TkTop *t, char *arg, char **ret) +{ + Tk *tk; + int x, y; + Rectangle *dr; + char buf[Tkmaxitem]; + + USED(ret); + + tkword(t, arg, buf, buf+sizeof(buf), nil); + if(strcmp(buf, "-onscreen") == 0){ + tk = t->root; + dr = &t->screenr; + x = tk->act.x; + if(x+tk->act.width > dr->max.x) + x = dr->max.x - tk->act.width; + if(x < 0) + x = 0; + y = tk->act.y; + if(y+tk->act.height > dr->max.y) + y = dr->max.y - tk->act.height; + if(y < 0) + y = 0; + tkmovewin(tk, Pt(x, y)); + }else if(strcmp(buf, "-disable") == 0){ + t->noupdate = 1; + }else if(strcmp(buf, "-enable") == 0){ + t->noupdate = 0; + } + return tkupdate(t); +} + +char* +tkwinfo(TkTop *t, char *arg, char **ret) +{ + Tk *tk; + char *cmd, *arg1; + + cmd = mallocz(Tkmaxitem, 0); + if(cmd == nil) + return TkNomem; + + arg = tkword(t, arg, cmd, cmd+Tkmaxitem, nil); + if(strcmp(cmd, "class") == 0) { + arg1 = mallocz(Tkmaxitem, 0); + if(arg1 == nil) { + free(cmd); + return TkNomem; + } + tkword(t, arg, arg1, arg1+Tkmaxitem, nil); + tk = tklook(t, arg1, 0); + if(tk == nil){ + tkerr(t, arg1); + free(arg1); + free(cmd); + return TkBadwp; + } + free(arg1); + free(cmd); + return tkvalue(ret, "%s", tkmethod[tk->type]->name); + } + free(cmd); + return TkBadvl; +} + +char* +tkcursorcmd(TkTop *t, char *arg, char **ret) +{ + char *e; + int locked; + Display *d; + TkCursor c; + TkOptab tko[3]; + enum {Notset = 0x80000000}; + + c.def = 0; + c.p.x = Notset; + c.p.y = Notset; + c.bit = nil; + c.img = nil; + + USED(ret); + + c.def = 0; + tko[0].ptr = &c; + tko[0].optab = tkcurop; + tko[1].ptr = nil; + e = tkparse(t, arg, tko, nil); + if(e != nil) + return e; + + d = t->display; + locked = lockdisplay(d); + if(c.def) + tkcursorswitch(t, nil, nil); + if(c.img != nil || c.bit != nil){ + e = tkcursorswitch(t, c.bit, c.img); + tkimgput(c.img); + freeimage(c.bit); + } + if(e == nil){ + if(c.p.x != Notset && c.p.y != Notset) + tkcursorset(t, c.p); + } + if(locked) + unlockdisplay(d); + return e; +} + +char * +tkbindings(TkTop *t, Tk *tk, TkEbind *b, int blen) +{ + TkAction *a, **ap; + char *cmd, *e; + int i; + + e = nil; + for(i = 0; e == nil && i < blen; i++) /* default bindings */ { + int how = TkArepl; + char *cmd = b[i].cmd; + if(cmd[0] == '+') { + how = TkAadd; + cmd++; + } + else if(cmd[0] == '-'){ + how = TkAsub; + cmd++; + } + e = tkaction(&tk->binds, b[i].event, TkStatic, cmd, how); + } + + if(e != nil) + return e; + + ap = &tk->binds; + for(a = t->binds[tk->type]; a; a = a->link) { /* user "defaults" */ + cmd = strdup(a->arg); + if(cmd == nil) + return TkNomem; + + e = tkaction(ap, a->event, TkDynamic, cmd, + (a->type >> 8) & 0xff); + if(e != nil) + return e; + ap = &(*ap)->link; + } + return nil; +} diff --git a/libtk/entry.c b/libtk/entry.c new file mode 100644 index 00000000..344e39c4 --- /dev/null +++ b/libtk/entry.c @@ -0,0 +1,1379 @@ +#include <lib9.h> +#include <kernel.h> +#include "draw.h" +#include "keyboard.h" +#include "tk.h" + +/* Widget Commands (+ means implemented) + +bbox + +cget + +configure + +delete + +get + +icursor + +index + scan + +selection + +xview + +see +*/ + +#define O(t, e) ((long)(&((t*)0)->e)) + +#define CNTL(c) ((c)&0x1f) +#define DEL 0x7f + +/* Layout constants */ +enum { + Entrypady = 0, + Entrypadx = 0, + Inswidth = 2, + + Ecursoron = 1<<0, + Ecenter = 1<<1, + Eright = 1<<2, + Eleft = 1<<3, + Ewordsel = 1<<4, + + Ejustify = Ecenter|Eleft|Eright +}; + +static TkStab tkjust[] = +{ + "left", Eleft, + "right", Eright, + "center", Ecenter, + nil +}; + +static +TkEbind b[] = +{ + {TkKey, "%W delete sel.first sel.last; %W insert insert {%A};%W see insert"}, + {TkKey|CNTL('a'), "%W icursor 0;%W see insert;%W selection clear"}, + {TkKey|Home, "%W icursor 0;%W see insert;%W selection clear"}, + {TkKey|CNTL('d'), "%W delete insert; %W see insert"}, + {TkKey|CNTL('e'), "%W icursor end; %W see insert;%W selection clear"}, + {TkKey|End, "%W icursor end; %W see insert;%W selection clear"}, + {TkKey|CNTL('h'), "%W tkEntryBS;%W see insert"}, + {TkKey|CNTL('k'), "%W delete insert end;%W see insert"}, + {TkKey|CNTL('u'), "%W delete 0 end;%W see insert"}, + {TkKey|CNTL('w'), "%W delete sel.first sel.last; %W tkEntryBW;%W see insert"}, + {TkKey|DEL, "%W tkEntryBS 1;%W see insert"}, + {TkKey|CNTL('\\'), "%W selection clear"}, + {TkKey|CNTL('/'), "%W selection range 0 end"}, + {TkKey|Left, "%W icursor insert-1;%W selection clear;%W selection from insert;%W see insert"}, + {TkKey|Right, "%W icursor insert+1;%W selection clear;%W selection from insert;%W see insert"}, + {TkButton1P, "focus %W; %W tkEntryB1P %X"}, + {TkButton1P|TkMotion, "%W tkEntryB1M %X"}, + {TkButton1R, "%W tkEntryB1R"}, + {TkButton1P|TkDouble, "%W tkEntryB1P %X;%W selection word @%x"}, + {TkButton2P, "%W tkEntryB2P %x"}, + {TkButton2P|TkMotion, "%W xview scroll %x scr"}, + {TkFocusin, "%W tkEntryFocus in"}, + {TkFocusout, "%W tkEntryFocus out"}, + {TkKey|APP|'\t', ""}, + {TkKey|BackTab, ""}, +}; + +typedef struct TkEntry TkEntry; +struct TkEntry +{ + Rune* text; + int textlen; + + char* xscroll; + char* show; + int flag; + int oldx; + + int icursor; /* index of insertion cursor */ + int anchor; /* selection anchor point */ + int sel0; /* index of start of selection */ + int sel1; /* index of end of selection */ + + int x0; /* x-offset of visible area */ + + /* derived values */ + int v0; /* index of first visible character */ + int v1; /* index of last visible character + 1 */ + int xlen; /* length of text in pixels*/ + int xv0; /* position of first visible character */ + int xsel0; /* position of start of selection */ + int xsel1; /* position of end of selection */ + int xicursor; /* position of insertion cursor */ +}; + +static void blinkreset(Tk*); + +static +TkOption opts[] = +{ + "xscrollcommand", OPTtext, O(TkEntry, xscroll), nil, + "justify", OPTstab, O(TkEntry, flag), tkjust, + "show", OPTtext, O(TkEntry, show), nil, + nil +}; + +static int +xinset(Tk *tk) +{ + return Entrypadx + tk->highlightwidth; +} + +static int +yinset(Tk *tk) +{ + return Entrypady + tk->highlightwidth; +} + +static void +tksizeentry(Tk *tk) +{ + if((tk->flag & Tksetwidth) == 0) + tk->req.width = tk->env->wzero*25 + 2*xinset(tk) + Inswidth; + if((tk->flag & Tksetheight) == 0) + tk->req.height = tk->env->font->height+ 2*yinset(tk); +} + +int +entrytextwidth(Tk *tk, int n) +{ + TkEntry *tke = TKobj(TkEntry, tk); + Rune c; + Font *f; + + f = tk->env->font; + if (tke->show != nil) { + chartorune(&c, tke->show); + return n * runestringnwidth(f, &c, 1); + } + return runestringnwidth(f, tke->text, n); +} + +static int +x2index(Tk *tk, int x, int *xc) +{ + TkEntry *tke = TKobj(TkEntry, tk); + int t0, t1, r, q; + + t0 = 0; + t1 = tke->textlen; + while (t0 <= t1) { + r = (t0 + t1) / 2; + q = entrytextwidth(tk, r); + if (q == x) { + if (xc != nil) + *xc = q; + return r; + } + if (q < x) + t0 = r + 1; + else + t1 = r - 1; + } + if (xc != nil) + *xc = t1 > 0 ? entrytextwidth(tk, t1) : 0; + if (t1 < 0) + t1 = 0; + return t1; +} + +static int +x2index0(Tk *tk, int x, int *xc) +{ + int xx, z; + z = x2index(tk, x, &xx); + print("x2index(%d)-> (%d, %d)\n", x, z, xx); + if (xc) + *xc = xx; + return z; +} + +/* + * recalculate derived values + */ +static void +recalcentry(Tk *tk) +{ + TkEntry *tke = TKobj(TkEntry, tk); + int x, avail, locked; + + locked = lockdisplay(tk->env->top->display); + + tke->xlen = entrytextwidth(tk, tke->textlen) + Inswidth; + + avail = tk->act.width - 2*xinset(tk); + if (tke->xlen < avail) { + switch(tke->flag & Ejustify) { + default: + tke->x0 = 0; + break; + case Eright: + tke->x0 = -(avail - tke->xlen); + break; + case Ecenter: + tke->x0 = -(avail - tke->xlen) / 2; + break; + } + } + + tke->v0 = x2index(tk, tke->x0, &tke->xv0); + tke->v1 = x2index(tk, tk->act.width + tke->x0, &x); + /* perhaps include partial last character */ + if (tke->v1 < tke->textlen && x < avail + tke->x0) + tke->v1++; + tke->xsel0 = entrytextwidth(tk, tke->sel0); + tke->xsel1 = entrytextwidth(tk, tke->sel1); + tke->xicursor = entrytextwidth(tk, tke->icursor); + + if (locked) + unlockdisplay(tk->env->top->display); +} + +char* +tkentry(TkTop *t, char *arg, char **ret) +{ + Tk *tk; + char *e; + TkName *names; + TkEntry *tke; + TkOptab tko[3]; + + tk = tknewobj(t, TKentry, sizeof(Tk)+sizeof(TkEntry)); + if(tk == nil) + return TkNomem; + + tk->relief = TKsunken; + tk->borderwidth = 2; + tk->flag |= Tktakefocus; + tk->highlightwidth = 1; + + tke = TKobj(TkEntry, tk); + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tke; + tko[1].optab = opts; + tko[2].ptr = nil; + + names = nil; + e = tkparse(t, arg, tko, &names); + if(e != nil) { + tkfreeobj(tk); + return e; + } + tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd)); + tksizeentry(tk); + e = tkbindings(t, tk, b, nelem(b)); + + if(e != nil) { + tkfreeobj(tk); + return e; + } + + e = tkaddchild(t, tk, &names); + tkfreename(names); + if(e != nil) { + tkfreeobj(tk); + return e; + } + tk->name->link = nil; + recalcentry(tk); + + return tkvalue(ret, "%s", tk->name->name); +} + +static char* +tkentrycget(Tk *tk, char *arg, char **val) +{ + TkOptab tko[3]; + TkEntry *tke = TKobj(TkEntry, tk); + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tke; + tko[1].optab = opts; + tko[2].ptr = nil; + + return tkgencget(tko, arg, val, tk->env->top); +} + +void +tkfreeentry(Tk *tk) +{ + TkEntry *tke = TKobj(TkEntry, tk); + + free(tke->xscroll); + free(tke->text); + free(tke->show); +} + +static void +tkentrytext(Image *i, Rectangle s, Tk *tk, TkEnv *env) +{ + TkEntry *tke = TKobj(TkEntry, tk); + Point dp; + int s0, s1, xs0, xs1, j; + Rectangle r; + Rune showr, *text; + + dp = Pt(s.min.x - (tke->x0 - tke->xv0), s.min.y); + if (tke->show) { + chartorune(&showr, tke->show); + text = mallocz(sizeof(Rune) * (tke->textlen+1), 0); + if (text == nil) + return; + for (j = 0; j < tke->textlen; j++) + text[j] = showr; + } else + text = tke->text; + + runestringn(i, dp, tkgc(env, TkCforegnd), dp, env->font, + text+tke->v0, tke->v1-tke->v0); + + if (tke->sel0 < tke->v1 && tke->sel1 > tke->v0) { + if (tke->sel0 < tke->v0) { + s0 = tke->v0; + xs0 = tke->xv0 - tke->x0; + } else { + s0 = tke->sel0; + xs0 = tke->xsel0 - tke->x0; + } + + if (tke->sel1 > tke->v1) { + s1 = tke->v1; + xs1 = s.max.x; + } else { + s1 = tke->sel1; + xs1 = tke->xsel1 - tke->x0; + } + + r = rectaddpt(Rect(xs0, 0, xs1, env->font->height), s.min); + tktextsdraw(i, r, env, 1); + runestringn(i, r.min, tkgc(env, TkCselectfgnd), r.min, env->font, + text+s0, s1-s0); + } + + if((tke->flag&Ecursoron) && tke->icursor >= tke->v0 && tke->icursor <= tke->v1) { + r = Rect( + tke->xicursor - tke->x0, 0, + tke->xicursor - tke->x0 + Inswidth, env->font->height + ); + draw(i, rectaddpt(r, s.min), tkgc(env, TkCforegnd), nil, ZP); + } + if (tke->show) + free(text); +} + +char* +tkdrawentry(Tk *tk, Point orig) +{ + Point p; + TkEnv *env; + Rectangle r, s; + Image *i; + int xp, yp; + + env = tk->env; + + r.min = ZP; + r.max.x = tk->act.width + 2*tk->borderwidth; + r.max.y = tk->act.height + 2*tk->borderwidth; + i = tkitmp(env, r.max, TkCbackgnd); + if(i == nil) + return nil; + + xp = tk->borderwidth + xinset(tk); + yp = tk->borderwidth + yinset(tk); + s = r; + s.min.x += xp; + s.max.x -= xp; + s.min.y += yp; + s.max.y -= yp; + tkentrytext(i, s, tk, env); + + tkdrawrelief(i, tk, ZP, TkCbackgnd, tk->relief); + + if (tkhaskeyfocus(tk)) + tkbox(i, insetrect(r, tk->borderwidth), tk->highlightwidth, tkgc(tk->env, TkChighlightfgnd)); + + p.x = tk->act.x + orig.x; + p.y = tk->act.y + orig.y; + r = rectaddpt(r, p); + draw(tkimageof(tk), r, i, nil, ZP); + + return nil; +} + +char* +tkentrysh(Tk *tk) +{ + TkEntry *tke = TKobj(TkEntry, tk); + int dx, top, bot; + char *val, *cmd, *v, *e; + + if(tke->xscroll == nil) + return nil; + + bot = 0; + top = Tkfpscalar; + + if(tke->text != 0 && tke->textlen != 0) { + dx = tk->act.width - 2*xinset(tk); + + if (tke->xlen > dx) { + bot = TKI2F(tke->x0) / tke->xlen; + top = TKI2F(tke->x0 + dx) / tke->xlen; + } + } + + val = mallocz(Tkminitem, 0); + if(val == nil) + return TkNomem; + v = tkfprint(val, bot); + *v++ = ' '; + tkfprint(v, top); + cmd = mallocz(Tkminitem, 0); + if(cmd == nil) { + free(val); + return TkNomem; + } + sprint(cmd, "%s %s", tke->xscroll, val); + e = tkexec(tk->env->top, cmd, nil); + free(cmd); + free(val); + return e; +} + +void +tkentrygeom(Tk *tk) +{ + char *e; + e = tkentrysh(tk); + if ((e != nil) && /* XXX - Tad: should propagate not print */ + (tk->name != nil)) + print("tk: xscrollcommand \"%s\": %s\n", tk->name->name, e); + recalcentry(tk); +} + +static char* +tkentryconf(Tk *tk, char *arg, char **val) +{ + char *e; + TkGeom g; + int bd; + TkOptab tko[3]; + TkEntry *tke = TKobj(TkEntry, tk); + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tke; + tko[1].optab = opts; + tko[2].ptr = nil; + + if(*arg == '\0') + return tkconflist(tko, val); + + bd = tk->borderwidth; + g = tk->req; + e = tkparse(tk->env->top, arg, tko, nil); + tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd)); + tksizeentry(tk); + tkgeomchg(tk, &g, bd); + recalcentry(tk); + tk->dirty = tkrect(tk, 1); + return e; +} + +static char* +tkentryparseindex(Tk *tk, char *buf, int *index) +{ + TkEntry *tke = TKobj(TkEntry, tk); + TkEnv *env; + char *mod; + int i, x, locked, modstart; + + modstart = 0; + for(mod = buf; *mod != '\0'; mod++) + if(*mod == '-' || *mod == '+') { + modstart = *mod; + *mod = '\0'; + break; + } + if(strcmp(buf, "end") == 0) + i = tke->textlen; + else + if(strcmp(buf, "anchor") == 0) + i = tke->anchor; + else + if(strcmp(buf, "insert") == 0) + i = tke->icursor; + else + if(strcmp(buf, "sel.first") == 0) + i = tke->sel0; + else + if(strcmp(buf, "sel.last") == 0) + i = tke->sel1; + else + if(buf[0] >= '0' && buf[0] <= '9') + i = atoi(buf); + else + if(buf[0] == '@') { + x = atoi(buf+1) - xinset(tk); + if(tke->textlen == 0) { + *index = 0; + return nil; + } + env = tk->env; + locked = lockdisplay(env->top->display); + i = x2index(tk, x + tke->x0, nil); /* XXX could possibly select nearest character? */ + if(locked) + unlockdisplay(env->top->display); + } + else + return TkBadix; + + if(i < 0 || i > tke->textlen) + return TkBadix; + if(modstart) { + *mod = modstart; + i += atoi(mod); + if(i < 0) + i = 0; + if(i > tke->textlen) + i = tke->textlen; + } + *index = i; + return nil; +} + +/* + * return bounding box of character at index, in coords relative to + * the top left position of the text. + */ +static Rectangle +tkentrybbox(Tk *tk, int index) +{ + TkEntry *tke; + TkEnv *env; + Display *d; + int x, cw, locked; + Rectangle r; + + tke = TKobj(TkEntry, tk); + env = tk->env; + + d = env->top->display; + + locked = lockdisplay(d); + x = entrytextwidth(tk, index); + if (index < tke->textlen) + cw = entrytextwidth(tk, index+1) - x; + else + cw = Inswidth; + if(locked) + unlockdisplay(d); + + r.min.x = x; + r.min.y = 0; + r.max.x = x + cw; + r.max.y = env->font->height; + return r; +} + +static void +tkentrysee(Tk *tk, int index, int jump) +{ + TkEntry *tke = TKobj(TkEntry, tk); + int dx, margin; + Rectangle r; + + r = tkentrybbox(tk, index); + dx = tk->act.width - 2*xinset(tk); + if (jump) + margin = dx / 4; + else + margin = 0; + if (r.min.x <= tke->x0 || r.max.x > tke->x0 + dx) { + if (r.min.x <= tke->x0) { + tke->x0 = r.min.x - margin; + if (tke->x0 < 0) + tke->x0 = 0; + } else if (r.max.x >= tke->x0 + dx) { + tke->x0 = r.max.x - dx + margin; + if (tke->x0 > tke->xlen - dx) + tke->x0 = tke->xlen - dx; + } + tk->dirty = tkrect(tk, 0); + } + r = rectaddpt(r, Pt(xinset(tk) - tke->x0, yinset(tk))); + tksee(tk, r, r.min); +} + +static char* +tkentryseecmd(Tk *tk, char *arg, char **val) +{ + int index; + char *e, *buf; + + USED(val); + + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil); + e = tkentryparseindex(tk, buf, &index); + free(buf); + if(e != nil) + return e; + + tkentrysee(tk, index, 1); + recalcentry(tk); + + return nil; +} + +static char* +tkentrybboxcmd(Tk *tk, char *arg, char **val) +{ + TkEntry *tke = TKobj(TkEntry, tk); + char *r, *buf; + int index; + Rectangle bbox; + + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil); + r = tkentryparseindex(tk, buf, &index); + free(buf); + if(r != nil) + return r; + bbox = rectaddpt(tkentrybbox(tk, index), Pt(xinset(tk) - tke->x0, yinset(tk))); + return tkvalue(val, "%d %d %d %d", bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y); +} + +static char* +tkentryindex(Tk *tk, char *arg, char **val) +{ + int index; + char *r, *buf; + + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil); + r = tkentryparseindex(tk, buf, &index); + free(buf); + if(r != nil) + return r; + return tkvalue(val, "%d", index); +} + +static char* +tkentryicursor(Tk *tk, char *arg, char **val) +{ + TkEntry *tke = TKobj(TkEntry, tk); + int index, locked; + char *r, *buf; + + USED(val); + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil); + r = tkentryparseindex(tk, buf, &index); + free(buf); + if(r != nil) + return r; + tke->icursor = index; + locked = lockdisplay(tk->env->top->display); + tke->xicursor = entrytextwidth(tk, tke->icursor); + if (locked) + unlockdisplay(tk->env->top->display); + + blinkreset(tk); + tk->dirty = tkrect(tk, 1); + return nil; +} + +static int +adjustforins(int i, int n, int q) +{ + if (i <= q) + q += n; + return q; +} + +static int +adjustfordel(int d0, int d1, int q) +{ + if (d1 <= q) + q -= d1 - d0; + else if (d0 <= q && q <= d1) + q = d0; + return q; +} + +static char* +tkentryget(Tk *tk, char *arg, char **val) +{ + TkTop *top; + TkEntry *tke; + int first, last; + char *e, *buf; + + tke = TKobj(TkEntry, tk); + if(tke->text == nil) + return nil; + + arg = tkskip(arg, " \t"); + if(*arg == '\0') + return tkvalue(val, "%.*S", tke->textlen, tke->text); + + top = tk->env->top; + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + arg = tkword(top, arg, buf, buf+Tkmaxitem, nil); + e = tkentryparseindex(tk, buf, &first); + if(e != nil) { + free(buf); + return e; + } + last = first+1; + tkword(top, arg, buf, buf+Tkmaxitem, nil); + if(buf[0] != '\0') { + e = tkentryparseindex(tk, buf, &last); + if(e != nil) { + free(buf); + return e; + } + } + free(buf); + if(last <= first || tke->textlen == 0 || first == tke->textlen) + return tkvalue(val, "%S", L""); + return tkvalue(val, "%.*S", last-first, tke->text+first); +} + +static char* +tkentryinsert(Tk *tk, char *arg, char **val) +{ + TkTop *top; + TkEntry *tke; + int ins, i, n, locked; + char *e, *t, *text, *buf; + Rune *etext; + + USED(val); + tke = TKobj(TkEntry, tk); + + top = tk->env->top; + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + arg = tkword(top, arg, buf, buf+Tkmaxitem, nil); + e = tkentryparseindex(tk, buf, &ins); + free(buf); + if(e != nil) + return e; + + if(*arg == '\0') + return nil; + + n = strlen(arg) + 1; + if(n < Tkmaxitem) + n = Tkmaxitem; + text = malloc(n); + if(text == nil) + return TkNomem; + + tkword(top, arg, text, text+n, nil); + n = utflen(text); + etext = realloc(tke->text, (tke->textlen+n+1)*sizeof(Rune)); + if(etext == nil) { + free(text); + return TkNomem; + } + tke->text = etext; + + memmove(tke->text+ins+n, tke->text+ins, (tke->textlen-ins)*sizeof(Rune)); + t = text; + for(i=0; i<n; i++) + t += chartorune(tke->text+ins+i, t); + free(text); + + tke->textlen += n; + + tke->sel0 = adjustforins(ins, n, tke->sel0); + tke->sel1 = adjustforins(ins, n, tke->sel1); + tke->icursor = adjustforins(ins, n, tke->icursor); + tke->anchor = adjustforins(ins, n, tke->anchor); + + locked = lockdisplay(tk->env->top->display); + if (ins < tke->v0) + tke->x0 += entrytextwidth(tk, tke->v0 + n) + (tke->x0 - tke->xv0); + if (locked) + unlockdisplay(tk->env->top->display); + recalcentry(tk); + + e = tkentrysh(tk); + blinkreset(tk); + tk->dirty = tkrect(tk, 1); + + return e; +} + +static char* +tkentrydelete(Tk *tk, char *arg, char **val) +{ + TkTop *top; + TkEntry *tke; + int d0, d1, locked; + char *e, *buf; + Rune *text; + + USED(val); + + tke = TKobj(TkEntry, tk); + + top = tk->env->top; + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + arg = tkword(top, arg, buf, buf+Tkmaxitem, nil); + e = tkentryparseindex(tk, buf, &d0); + if(e != nil) { + free(buf); + return e; + } + + d1 = d0+1; + tkword(top, arg, buf, buf+Tkmaxitem, nil); + if(buf[0] != '\0') { + e = tkentryparseindex(tk, buf, &d1); + if(e != nil) { + free(buf); + return e; + } + } + free(buf); + if(d1 <= d0 || tke->textlen == 0 || d0 >= tke->textlen) + return nil; + + memmove(tke->text+d0, tke->text+d1, (tke->textlen-d1)*sizeof(Rune)); + tke->textlen -= d1 - d0; + + text = realloc(tke->text, (tke->textlen+1) * sizeof(Rune)); + if (text != nil) + tke->text = text; + tke->sel0 = adjustfordel(d0, d1, tke->sel0); + tke->sel1 = adjustfordel(d0, d1, tke->sel1); + tke->icursor = adjustfordel(d0, d1, tke->icursor); + tke->anchor = adjustfordel(d0, d1, tke->anchor); + + locked = lockdisplay(tk->env->top->display); + if (d1 < tke->v0) + tke->x0 = entrytextwidth(tk, tke->v0 - (d1 - d0)) + (tke->x0 - tke->xv0); + else if (d0 < tke->v0) + tke->x0 = entrytextwidth(tk, d0); + if (locked) + unlockdisplay(tk->env->top->display); + recalcentry(tk); + + e = tkentrysh(tk); + blinkreset(tk); + tk->dirty = tkrect(tk, 1); + + return e; +} + +/* Used for both backspace and DEL. If a selection exists, delete it. + * Otherwise delete the character to the left(right) of the insertion + * cursor, if any. + */ +static char* +tkentrybs(Tk *tk, char *arg, char **val) +{ + TkEntry *tke = TKobj(TkEntry, tk); + char *buf, *e; + int ix; + + USED(val); + USED(arg); + + if(tke->textlen == 0) + return nil; + + if(tke->sel0 < tke->sel1) + return tkentrydelete(tk, "sel.first sel.last", nil); + + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil); + ix = -1; + if(buf[0] != '\0') { + e = tkentryparseindex(tk, buf, &ix); + if(e != nil) { + free(buf); + return e; + } + } + if(ix > -1) { /* DEL */ + if(tke->icursor >= tke->textlen) { + free(buf); + return nil; + } + } + else { /* backspace */ + if(tke->icursor == 0) { + free(buf); + return nil; + } + tke->icursor--; + } + snprint(buf, Tkmaxitem, "%d", tke->icursor); + e = tkentrydelete(tk, buf, nil); + free(buf); + return e; +} + +static char* +tkentrybw(Tk *tk, char *arg, char **val) +{ + int start; + Rune *text; + TkEntry *tke; + char buf[32]; + + USED(val); + USED(arg); + + tke = TKobj(TkEntry, tk); + if(tke->textlen == 0 || tke->icursor == 0) + return nil; + + text = tke->text; + start = tke->icursor-1; + while(start > 0 && !tkiswordchar(text[start])) + --start; + while(start > 0 && tkiswordchar(text[start-1])) + --start; + + snprint(buf, sizeof(buf), "%d %d", start, tke->icursor); + return tkentrydelete(tk, buf, nil); +} + +char* +tkentryselect(Tk *tk, char *arg, char **val) +{ + TkTop *top; + int start, from, to, locked; + TkEntry *tke; + char *e, *buf; + + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + + tke = TKobj(TkEntry, tk); + + top = tk->env->top; + arg = tkword(top, arg, buf, buf+Tkmaxitem, nil); + if(strcmp(buf, "clear") == 0) { + tke->sel0 = 0; + tke->sel1 = 0; + } + else + if(strcmp(buf, "from") == 0) { + tkword(top, arg, buf, buf+Tkmaxitem, nil); + e = tkentryparseindex(tk, buf, &tke->anchor); + tke->flag &= ~Ewordsel; + free(buf); + return e; + } + else + if(strcmp(buf, "to") == 0) { + tkword(top, arg, buf, buf+Tkmaxitem, nil); + e = tkentryparseindex(tk, buf, &to); + if(e != nil) { + free(buf); + return e; + } + + if(to < tke->anchor) { + if(tke->flag & Ewordsel) + while(to > 0 && tkiswordchar(tke->text[to-1])) + --to; + tke->sel0 = to; + tke->sel1 = tke->anchor; + } + else + if(to >= tke->anchor) { + if(tke->flag & Ewordsel) + while(to < tke->textlen && + tkiswordchar(tke->text[to])) + to++; + tke->sel0 = tke->anchor; + tke->sel1 = to; + } + tkentrysee(tk, to, 0); + recalcentry(tk); + } + else + if(strcmp(buf, "word") == 0) { /* inferno invention */ + tkword(top, arg, buf, buf+Tkmaxitem, nil); + e = tkentryparseindex(tk, buf, &start); + if(e != nil) { + free(buf); + return e; + } + from = start; + while(from > 0 && tkiswordchar(tke->text[from-1])) + --from; + to = start; + while(to < tke->textlen && tkiswordchar(tke->text[to])) + to++; + tke->sel0 = from; + tke->sel1 = to; + tke->anchor = from; + tke->icursor = from; + tke->flag |= Ewordsel; + locked = lockdisplay(tk->env->top->display); + tke->xicursor = entrytextwidth(tk, tke->icursor); + if (locked) + unlockdisplay(tk->env->top->display); + } + else + if(strcmp(buf, "present") == 0) { + e = tkvalue(val, "%d", tke->sel1 > tke->sel0); + free(buf); + return e; + } + else + if(strcmp(buf, "range") == 0) { + arg = tkword(top, arg, buf, buf+Tkmaxitem, nil); + e = tkentryparseindex(tk, buf, &from); + if(e != nil) { + free(buf); + return e; + } + tkword(top, arg, buf, buf+Tkmaxitem, nil); + e = tkentryparseindex(tk, buf, &to); + if(e != nil) { + free(buf); + return e; + } + tke->sel0 = from; + tke->sel1 = to; + if(to <= from) { + tke->sel0 = 0; + tke->sel1 = 0; + } + } + else + if(strcmp(buf, "adjust") == 0) { + tkword(top, arg, buf, buf+Tkmaxitem, nil); + e = tkentryparseindex(tk, buf, &to); + if(e != nil) { + free(buf); + return e; + } + if(tke->sel0 == 0 && tke->sel1 == 0) { + tke->sel0 = tke->anchor; + tke->sel1 = to; + } + else { + if(abs(tke->sel0-to) < abs(tke->sel1-to)) { + tke->sel0 = to; + tke->anchor = tke->sel1; + } + else { + tke->sel1 = to; + tke->anchor = tke->sel0; + } + } + if(tke->sel0 > tke->sel1) { + to = tke->sel0; + tke->sel0 = tke->sel1; + tke->sel1 = to; + } + } + else { + free(buf); + return TkBadcm; + } + locked = lockdisplay(tk->env->top->display); + tke->xsel0 = entrytextwidth(tk, tke->sel0); + tke->xsel1 = entrytextwidth(tk, tke->sel1); + if (locked) + unlockdisplay(tk->env->top->display); + tk->dirty = tkrect(tk, 1); + free(buf); + return nil; +} + + +static char* +tkentryb2p(Tk *tk, char *arg, char **val) +{ + TkEntry *tke; + char *buf; + + USED(val); + + tke = TKobj(TkEntry, tk); + buf = malloc(Tkmaxitem); + if (buf == nil) + return TkNomem; + + tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil); + tke->oldx = atoi(buf); + return nil; +} + +static char* +tkentryxview(Tk *tk, char *arg, char **val) +{ + int locked; + TkEnv *env; + TkEntry *tke; + char *buf, *v; + int dx, top, bot, amount, ix, x; + char *e; + + tke = TKobj(TkEntry, tk); + env = tk->env; + dx = tk->act.width - 2*xinset(tk); + + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + + if(*arg == '\0') { + if (tke->textlen == 0 || tke->xlen < dx) { + bot = TKI2F(0); + top = TKI2F(1); + } else { + bot = TKI2F(tke->x0) / tke->xlen; + top = TKI2F(tke->x0 + dx) / tke->xlen; + } + v = tkfprint(buf, bot); + *v++ = ' '; + tkfprint(v, top); + e = tkvalue(val, "%s", buf); + free(buf); + return e; + } + + arg = tkitem(buf, arg); + if(strcmp(buf, "moveto") == 0) { + e = tkfracword(env->top, &arg, &top, nil); + if (e != nil) { + free(buf); + return e; + } + tke->x0 = TKF2I(top*tke->xlen); + } + else + if(strcmp(buf, "scroll") == 0) { + arg = tkitem(buf, arg); + amount = atoi(buf); + if(*arg == 'p') /* Pages */ + amount *= (9*tke->xlen)/10; + else + if(*arg == 's') { /* Inferno-ism, "scr", must be used in the context of button2p */ + x = amount; + amount = x < tke->oldx ? env->wzero : (x > tke->oldx ? -env->wzero : 0); + tke->oldx = x; + } + tke->x0 += amount; + } + else { + e = tkentryparseindex(tk, buf, &ix); + if(e != nil) { + free(buf); + return e; + } + locked = lockdisplay(env->top->display); + tke->x0 = entrytextwidth(tk, ix); + if (locked) + unlockdisplay(env->top->display); + } + free(buf); + + if (tke->x0 > tke->xlen - dx) + tke->x0 = tke->xlen - dx; + if (tke->x0 < 0) + tke->x0 = 0; + recalcentry(tk); + e = tkentrysh(tk); + blinkreset(tk); + tk->dirty = tkrect(tk, 1); + return e; +} + +static void +autoselect(Tk *tk, void *v, int cancelled) +{ + TkEntry *tke = TKobj(TkEntry, tk); + Rectangle hitr; + char buf[32]; + Point p; + + USED(v); + + if (cancelled) + return; + + p = tkscrn2local(tk, Pt(tke->oldx, 0)); + p.y = 0; + if (tkvisiblerect(tk, &hitr) && ptinrect(p, hitr)) + return; + + snprint(buf, sizeof(buf), "to @%d", p.x); + tkentryselect(tk, buf, nil); + tkdirty(tk); + tkupdate(tk->env->top); +} + +static char* +tkentryb1p(Tk *tk, char* arg, char **ret) +{ + TkEntry *tke = TKobj(TkEntry, tk); + Point p; + int i, locked, x; + char buf[32], *e; + USED(ret); + + x = atoi(arg); + p = tkscrn2local(tk, Pt(x, 0)); + sprint(buf, "@%d", p.x); + e = tkentryparseindex(tk, buf, &i); + if (e != nil) + return e; + tke->sel0 = 0; + tke->sel1 = 0; + tke->icursor = i; + tke->anchor = i; + tke->flag &= ~Ewordsel; + + locked = lockdisplay(tk->env->top->display); + tke->xsel0 = 0; + tke->xsel1 = 0; + tke->xicursor = entrytextwidth(tk, tke->icursor); + if (locked) + unlockdisplay(tk->env->top->display); + + tke->oldx = x; + blinkreset(tk); + tkrepeat(tk, autoselect, nil, TkRptpause, TkRptinterval); + tk->dirty = tkrect(tk, 0); + return nil; +} + +static char* +tkentryb1m(Tk *tk, char* arg, char **ret) +{ + TkEntry *tke = TKobj(TkEntry, tk); + Point p; + Rectangle hitr; + char buf[32]; + USED(ret); + + p.x = atoi(arg); + tke->oldx = p.x; + p = tkscrn2local(tk, p); + p.y = 0; + if (!tkvisiblerect(tk, &hitr) || !ptinrect(p, hitr)) + return nil; + snprint(buf, sizeof(buf), "to @%d", p.x); + tkentryselect(tk, buf, nil); + return nil; +} + +static char* +tkentryb1r(Tk *tk, char* arg, char **ret) +{ + USED(tk); + USED(arg); + USED(ret); + tkcancelrepeat(tk); + return nil; +} + +static void +blinkreset(Tk *tk) +{ + TkEntry *e = TKobj(TkEntry, tk); + if (!tkhaskeyfocus(tk) || tk->flag&Tkdisabled) + return; + e->flag |= Ecursoron; + tkblinkreset(tk); +} + +static void +showcaret(Tk *tk, int on) +{ + TkEntry *e = TKobj(TkEntry, tk); + + if (on) + e->flag |= Ecursoron; + else + e->flag &= ~Ecursoron; + tk->dirty = tkrect(tk, 0); +} + +char* +tkentryfocus(Tk *tk, char* arg, char **ret) +{ + int on = 0; + USED(ret); + + if (tk->flag&Tkdisabled) + return nil; + + if(strcmp(arg, " in") == 0) { + tkblink(tk, showcaret); + on = 1; + } + else + tkblink(nil, nil); + + showcaret(tk, on); + return nil; +} + +static +TkCmdtab tkentrycmd[] = +{ + "cget", tkentrycget, + "configure", tkentryconf, + "delete", tkentrydelete, + "get", tkentryget, + "icursor", tkentryicursor, + "index", tkentryindex, + "insert", tkentryinsert, + "selection", tkentryselect, + "xview", tkentryxview, + "tkEntryBS", tkentrybs, + "tkEntryBW", tkentrybw, + "tkEntryB1P", tkentryb1p, + "tkEntryB1M", tkentryb1m, + "tkEntryB1R", tkentryb1r, + "tkEntryB2P", tkentryb2p, + "tkEntryFocus", tkentryfocus, + "bbox", tkentrybboxcmd, + "see", tkentryseecmd, + nil +}; + +TkMethod entrymethod = { + "entry", + tkentrycmd, + tkfreeentry, + tkdrawentry, + tkentrygeom +}; diff --git a/libtk/extns.c b/libtk/extns.c new file mode 100644 index 00000000..c87cc29a --- /dev/null +++ b/libtk/extns.c @@ -0,0 +1,37 @@ +#include "lib9.h" +#include "draw.h" +#include "tk.h" + +int +tkextndeliver(Tk *tk, TkAction *binds, int event, void *data) +{ + return tksubdeliver(tk, binds, event, data, 1); +} + +void +tkextnfreeobj(Tk *tk) +{ + USED(tk); +} + +int +tkextnnewctxt(TkCtxt *ctxt) +{ + USED(ctxt); + return 0; +} + +void +tkextnfreectxt(TkCtxt *ctxt) +{ + USED(ctxt); +} + +char* +tkextnparseseq(char *seq, char *rest, int *event) +{ + USED(seq); + USED(rest); + USED(event); + return nil; +} diff --git a/libtk/frame.c b/libtk/frame.c new file mode 100644 index 00000000..fdd39b16 --- /dev/null +++ b/libtk/frame.c @@ -0,0 +1,277 @@ +#include <lib9.h> +#include <kernel.h> +#include "draw.h" +#include "tk.h" +#include "frame.h" + +char* +tkframe(TkTop *t, char *arg, char **ret) +{ + Tk *tk; + char *e; + TkOptab tko[2]; + TkName *names; + + tk = tknewobj(t, TKframe, sizeof(Tk)); + if(tk == nil) + return TkNomem; + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = nil; + names = nil; + + e = tkparse(t, arg, tko, &names); + if(e != nil) { + tkfreeobj(tk); + return e; + } + tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd)); + + e = tkaddchild(t, tk, &names); + + tkfreename(names); + if(e != nil) { + tkfreeobj(tk); + return e; + } + tk->name->link = nil; + + return tkvalue(ret, "%s", tk->name->name); +} + +/* + * Also used for windows, menus, separators + */ +void +tkfreeframe(Tk *tk) +{ + TkWin *tkw; + + if((tk->flag & Tkwindow) == 0) + return; + + if(tk->type == TKmenu) { + tkw = TKobj(TkWin, tk); + free(tkw->postcmd); + free(tkw->cascade); + free(tkw->cbname); + } + + tkunmap(tk); /* XXX do this only if (tk->flag&Tkswept)==0 ?? */ +} + +char* +tkdrawframe(Tk *tk, Point orig) +{ + int bw; + Point p; + Image *i; + Tk *f; + Rectangle r, slaver; /* dribbling, whipping or just square? */ + + i = tkimageof(tk); + if(i == nil) + return nil; + + p.x = orig.x + tk->act.x + tk->borderwidth; + p.y = orig.y + tk->act.y + tk->borderwidth; + + draw(i, rectaddpt(tk->dirty, p), tkgc(tk->env, TkCbackgnd), nil, ZP); + + /* + * doesn't matter about drawing TKseparator + * oblivious of dirty rect, as it never has any children to sully anyway + */ + if(tk->type == TKseparator) { + r = rectaddpt(tkrect(tk, 1), p); + r.min.x += 4; + r.max.x -= 4; + r.min.y += (Dy(r) - 2)/2; + r.max.y = r.min.y+1; + draw(i, r, tkgc(tk->env, TkCbackgnddark), nil, ZP); + r.min.y += 1; + r.max.y += 1; + draw(i, r, tkgc(tk->env, TkCbackgndlght), nil, ZP); + return nil; + } + + /* + * make sure all the slaves inside the area we've just drawn + * refresh themselves properly. + */ + for(f = tk->slave; f; f = f->next) { + bw = f->borderwidth; + slaver.min.x = f->act.x; + slaver.min.y = f->act.y; + slaver.max.x = slaver.min.x + f->act.width + 2*bw; + slaver.max.y = slaver.min.y + f->act.height + 2*bw; + if (rectclip(&slaver, tk->dirty)) { + f->flag |= Tkrefresh; + slaver = rectsubpt(slaver, Pt(f->act.x + bw, f->act.y + bw)); + combinerect(&f->dirty, slaver); + } + } + p.x -= tk->borderwidth; + p.y -= tk->borderwidth; + + if (!rectinrect(tk->dirty, tkrect(tk, 0))) + tkdrawrelief(i, tk, p, TkCbackgnd, tk->relief); + return nil; +} + +/* Frame commands */ + +static char* +tkframecget(Tk *tk, char *arg, char **val) +{ + TkOptab tko[3]; + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = nil; + if(tk->flag & Tkwindow){ + tko[1].ptr = TKobj(TkWin, tk); + tko[1].optab = tktop; + tko[2].ptr = nil; + } + + return tkgencget(tko, arg, val, tk->env->top); +} + +static char* +tkframeconf(Tk *tk, char *arg, char **val) +{ + char *e; + TkGeom g; + int bd; + Point oldp; + TkOptab tko[3]; + TkWin *tkw; + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = nil; + tkw = nil; + if(tk->flag & Tkwindow) { + tkw = TKobj(TkWin, tk); + tko[1].ptr = tkw; + tko[1].optab = tktop; + tko[2].ptr = nil; + oldp = tkw->act; + } + + if(*arg == '\0') + return tkconflist(tko, val); + + if(tkw != nil){ + /* + * see whether only -x or -y is being configured, + * in which case just move the window; don't redraw + * everything + */ + e = tkparse(tk->env->top, arg, &tko[1], nil); + if(e == nil){ + if(!eqpt(oldp, tkw->req)) + tkmovewin(tk, tkw->req); + return nil; + } + } + + g = tk->req; + bd = tk->borderwidth; + e = tkparse(tk->env->top, arg, tko, nil); + tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd)); + tk->req.x = tk->act.x; + tk->req.y = tk->act.y; + tkgeomchg(tk, &g, bd); + if(tkw != nil && !eqpt(oldp, tkw->act)) + tkmovewin(tk, tkw->req); + + tk->dirty = tkrect(tk, 1); + + return e; +} + +static char* +tkframesuspend(Tk *tk, char *arg, char **val) +{ + USED(arg); + USED(val); + if((tk->flag & Tkwindow) == 0) + return TkNotwm; + tk->flag |= Tksuspended; + return nil; +} + +static char* +tkframemap(Tk *tk, char *arg, char **val) +{ + USED(arg); + USED(val); + if(tk->flag & Tkwindow) + return tkmap(tk); + return TkNotwm; +} + +static char* +tkframeunmap(Tk *tk, char *arg, char **val) +{ + USED(arg); + USED(val); + if(tk->flag & Tkwindow) { + tkunmap(tk); + return nil; + } + return TkNotwm; +} + +static void +tkframefocusorder(Tk *tk) +{ + int i, n; + Tk *sub; + TkWinfo *inf; + + n = 0; + for (sub = tk->slave; sub != nil; sub = sub->next) + n++; + + if (n == 0) + return; + + inf = malloc(sizeof(*inf) * n); + if (inf == nil) + return; + i = 0; + for (sub = tk->slave; sub != nil; sub = sub->next) { + inf[i].w = sub; + inf[i].r = rectaddpt(tkrect(sub, 1), Pt(sub->act.x, sub->act.y)); + i++; + } + tksortfocusorder(inf, n); + for (i = 0; i < n; i++) + tkappendfocusorder(inf[i].w); + free(inf); +} + +static +TkCmdtab tkframecmd[] = +{ + "cget", tkframecget, + "configure", tkframeconf, + "map", tkframemap, + "unmap", tkframeunmap, + "suspend", tkframesuspend, + nil +}; + +TkMethod framemethod = { + "frame", + tkframecmd, + tkfreeframe, + tkdrawframe, + nil, + nil, + tkframefocusorder +}; diff --git a/libtk/frame.h b/libtk/frame.h new file mode 100644 index 00000000..11c415f5 --- /dev/null +++ b/libtk/frame.h @@ -0,0 +1,2 @@ +extern char* tkdrawframe(Tk*, Point); +extern void tkfreeframe(Tk*); diff --git a/libtk/grids.c b/libtk/grids.c new file mode 100644 index 00000000..4403fba2 --- /dev/null +++ b/libtk/grids.c @@ -0,0 +1,1552 @@ +#include "lib9.h" +#include "draw.h" +#include "tk.h" + +/* + * XXX TODO + * - grid rowcget|columncget + * - grid columnconfigure/rowconfigure accepts a list of indexes? + */ + +#define O(t, e) ((long)(&((t*)0)->e)) + +typedef struct TkGridparam TkGridparam; +typedef struct TkBeamparam TkBeamparam; + +struct TkGridparam{ + Point span; + Tk* in; + Point pad; + Point ipad; + char *row; + char *col; + int sticky; +}; + +struct TkBeamparam{ + int minsize; + int maxsize; + int weight; + int pad; + char *name; + int equalise; +}; + +static +TkOption opts[] = +{ + "padx", OPTnndist, O(TkGridparam, pad.x), nil, + "pady", OPTnndist, O(TkGridparam, pad.y), nil, + "ipadx", OPTnndist, O(TkGridparam, ipad.x), nil, + "ipady", OPTnndist, O(TkGridparam, ipad.y), nil, + "in", OPTwinp, O(TkGridparam, in), nil, + "row", OPTtext, O(TkGridparam, row), nil, + "column", OPTtext, O(TkGridparam, col), nil, + "rowspan", OPTnndist, O(TkGridparam, span.y), nil, + "columnspan", OPTnndist, O(TkGridparam, span.x), nil, + "sticky", OPTsticky, O(TkGridparam, sticky), nil, + nil +}; + +static +TkOption beamopts[] = +{ + "minsize", OPTnndist, O(TkBeamparam, minsize), nil, + "maxsize", OPTnndist, O(TkBeamparam, maxsize), nil, + "weight", OPTnndist, O(TkBeamparam, weight), nil, + "pad", OPTnndist, O(TkBeamparam, pad), nil, + "name", OPTtext, O(TkBeamparam, name), nil, + "equalise", OPTstab, O(TkBeamparam, equalise), tkbool, + nil +}; + +void +printgrid(TkGrid *grid) +{ + int x, y; + Point dim; + + dim = grid->dim; + print("grid %P\n", grid->dim); + print(" row heights: "); + for(y = 0; y < dim.y; y++) + print("%d[%d,%d,w%d,p%d]%s ", + grid->rows[y].act, + grid->rows[y].minsize, + grid->rows[y].maxsize < 0x7fffffff ? grid->rows[y].maxsize : -1, + grid->rows[y].weight, + grid->rows[y].pad, + grid->rows[y].name ? grid->rows[y].name : ""); + print("\n"); + print(" col widths: "); + for(x = 0; x < dim.x; x++) + print("%d[%d,%d,w%d,p%d]%s ", + grid->cols[x].act, + grid->cols[x].minsize, + grid->cols[x].maxsize < 0x7fffffff ? grid->cols[x].maxsize : -1, + grid->cols[x].weight, + grid->cols[x].pad, + grid->cols[x].name ? grid->cols[x].name : ""); + print("\n"); + for(y = 0; y < dim.y; y++){ + print(" row %d: ", y); + for(x = 0; x < dim.x; x++){ + print("%p;", grid->cells[y][x].tk); + print("%s%P\t", grid->cells[y][x].tk?grid->cells[y][x].tk->name->name:"(nil)", + grid->cells[y][x].span); + } + print("\n"); + } +} + +static void +tkgridsetopt(TkGridparam *p, Tk *tk) +{ + if(p->pad.x != -1) + tk->pad.x = p->pad.x*2; + if(p->pad.y != -1) + tk->pad.y = p->pad.y*2; + if(p->ipad.x != -1) + tk->ipad.x = p->ipad.x*2; + if(p->ipad.y != -1) + tk->ipad.y = p->ipad.y*2; + if(p->sticky != -1) + tk->flag = (tk->flag & ~(Tkanchor|Tkfill)) | (p->sticky & (Tkanchor|Tkfill)); +} + +static void +initbeam(TkGridbeam *beam, int n) +{ + int i; + memset(beam, 0, n * sizeof(TkGridbeam)); + for(i = 0; i < n; i++) + beam[i].maxsize = 0x7fffffff; +} + +static char* +ensuregridsize(TkGrid *grid, Point dim) +{ + TkGridcell **cells, *cellrow; + TkGridbeam *cols, *rows; + Point olddim; + int i; + olddim = grid->dim; + if(dim.x < olddim.x) + dim.x = olddim.x; + if(dim.y < olddim.y) + dim.y = olddim.y; + if(dim.y > olddim.y){ + cells = realloc(grid->cells, sizeof(TkGridcell*)*dim.y); + if(cells == nil) + return TkNomem; + grid->cells = cells; + for(i = olddim.y; i < dim.y; i++){ + cells[i] = malloc(sizeof(TkGridcell)*dim.x); + if(cells[i] == nil){ + for(i--; i >= olddim.y; i--) + free(cells[i]); + return TkNomem; + } + } + rows = realloc(grid->rows, sizeof(TkGridbeam)*dim.y); + if(rows == nil) + return TkNomem; + grid->rows = rows; + initbeam(rows + olddim.y, dim.y - olddim.y); + grid->dim.y = dim.y; + } + + if(dim.x > olddim.x){ + /* + * any newly allocated rows will have the correct number of + * columns, so we don't need to reallocate them + */ + cells = grid->cells; + for(i = 0; i < olddim.y; i++){ + cellrow = realloc(cells[i], sizeof(TkGridcell) * dim.x); + if(cellrow == nil) + return TkNomem; /* leak some earlier rows, but not permanently */ + memset(cellrow + olddim.x, 0, (dim.x-olddim.x)*sizeof(TkGridcell)); + cells[i] = cellrow; + } + cols = realloc(grid->cols, sizeof(TkGridbeam)*dim.x); + if(cols == nil) + return TkNomem; + initbeam(cols + olddim.x, dim.x - olddim.x); + grid->cols = cols; + grid->dim.x = dim.x; + } + return nil; +} + +static TkGridbeam* +delbeams(TkGridbeam *beam, int nb, int x0, int x1) +{ + int i; + TkGridbeam *b; + for(i = x0; i < x1; i++) + free(beam[i].name); + memmove(&beam[x0], &beam[x1], sizeof(TkGridbeam) * (nb-x1)); + b = realloc(beam, sizeof(TkGridbeam) * (nb-(x1-x0))); + return b ? b : beam; +} + +static void +delrows(TkGrid *grid, int y0, int y1) +{ + TkGridcell **cells; + memmove(grid->cells+y0, grid->cells+y1, sizeof(TkGridcell*) * (grid->dim.y-y1)); + grid->dim.y -= (y1 - y0); + cells = realloc(grid->cells, sizeof(TkGridcell*) * grid->dim.y); + if(cells != nil || grid->dim.y == 0) + grid->cells = cells; /* can realloc to a smaller size ever fail? */ +} + +static void +delcols(TkGrid *grid, int x0, int x1) +{ + TkGridcell **cells, *row; + int y, ndx; + Point dim; + dim = grid->dim; + ndx = dim.x - (x1 - x0); + cells = grid->cells; + for(y = 0; y < dim.y; y++){ + row = cells[y]; + memmove(row+x0, row+x1, sizeof(TkGridcell) * (dim.x - x1)); + row = realloc(row, sizeof(TkGridcell) * ndx); + if(row != nil || ndx == 0) + cells[y] = row; + } + grid->dim.x = ndx; +} + +/* + * insert items into rows/cols; the beam has already been expanded appropriately. + */ +void +insbeams(TkGridbeam *beam, int nb, int x, int n) +{ + memmove(&beam[x+n], &beam[x], sizeof(TkGridbeam)*(nb-x-n)); + initbeam(beam+x, n); +} + +static char* +insrows(TkGrid *grid, int y0, int n) +{ + Point olddim; + char *e; + TkGridcell **cells, *tmp; + int y; + + olddim = grid->dim; + if(y0 > olddim.y){ + n = y0 + n - olddim.y; + y0 = olddim.y; + } + + e = ensuregridsize(grid, Pt(olddim.x, olddim.y + n)); + if(e != nil) + return e; + /* + * we know the extra rows will have been filled + * with blank, properly allocated rows, so just swap 'em with the + * ones that need moving. + */ + cells = grid->cells; + for(y = olddim.y - 1; y >= y0; y--){ + tmp = cells[y + n]; + cells[y + n] = cells[y]; + cells[y] = tmp; + } + insbeams(grid->rows, grid->dim.y, y0, n); + return nil; +} + +static char* +inscols(TkGrid *grid, int x0, int n) +{ + TkGridcell **cells; + Point olddim; + int y; + char *e; + + olddim = grid->dim; + if(x0 > olddim.x){ + n = x0 + n - olddim.x; + x0 = olddim.x; + } + + e = ensuregridsize(grid, Pt(olddim.x + n, olddim.y)); + if(e != nil) + return e; + + cells = grid->cells; + for(y = 0; y < olddim.y; y++){ + memmove(cells[y] + x0 + n, cells[y] + x0, sizeof(TkGridcell) * (olddim.x - x0)); + memset(cells[y] + x0, 0, sizeof(TkGridcell) * n); + } + insbeams(grid->cols, grid->dim.x, x0, n); + return nil; +} + +static int +maximum(int a, int b) +{ + if(a > b) + return a; + return b; +} + +/* + * return the width of cols/rows between x0 and x1 in the beam, + * excluding the padding at either end, but including padding in the middle. + */ +static int +beamsize(TkGridbeam *cols, int x0, int x1) +{ + int tot, fpad, x; + + if(x0 >= x1) + return 0; + + tot = cols[x0].act; + fpad = cols[x0].pad; + for(x = x0 + 1; x < x1; x++){ + tot += cols[x].act + maximum(cols[x].pad, fpad); + fpad = cols[x].pad; + } + return tot; +} + +/* + * return starting position of cell index on beam, relative + * to top-left of grid + */ +static int +beamcellpos(TkGridbeam *beam, int blen, int index) +{ + int x; + if(blen == 0 || index >= blen || index < 0) + return 0; + x = beam[0].pad + beamsize(beam, 0, index); + if(index > 0) + x += maximum(beam[index-1].pad, beam[index].pad); + return x; +} + +static Rectangle +cellbbox(TkGrid *grid, Point pos) +{ + Point dim; + Rectangle r; + + dim = grid->dim; + if(pos.x > dim.x) + pos.x = dim.x; + if(pos.y > dim.y) + pos.y = dim.y; + + r.min.x = beamcellpos(grid->cols, dim.x, pos.x); + r.min.y = beamcellpos(grid->rows, dim.y, pos.y); + if(pos.x == dim.x) + r.max.x = r.min.x; + else + r.max.x = r.min.x + grid->cols[pos.x].act; + if(pos.y == dim.y) + r.max.y = r.min.y; + else + r.max.y = r.min.y + grid->rows[pos.y].act; + return rectaddpt(r, grid->origin); +} + +/* + * return true ifthere are any spanning cells covering row _index_ + */ +static int +gridrowhasspan(TkGrid *grid, int index) +{ + int i, d; + Point dim; + TkGridcell *cell; + + dim = grid->dim; + if(index > 0 && index < dim.y){ + for(i = 0; i < dim.x; i++){ + cell = &grid->cells[index][i]; + if(cell->tk != nil){ + d = cell->span.x; + if(d == 0) + return 1; + i += d - 1; + } + } + } + return 0; +} + +/* + * return true ifthere are any spanning cells covering column _index_ + */ +static int +gridcolhasspan(TkGrid *grid, int index) +{ + int i, d; + Point dim; + TkGridcell *cell; + + dim = grid->dim; + if(index > 0 && index < dim.x){ + for(i = 0; i < dim.y; i++){ + cell = &grid->cells[i][index]; + if(cell->tk != nil){ + d = cell->span.y; + if(d == 0) + return 1; + i += d - 1; + } + } + } + return 0; +} + +/* + * find cell that's spanning the grid position p + */ +static int +findspan(TkGrid *grid, Point p, Point *cp) +{ + Point dim; + TkGridcell **cells; + Tk *tk; + + dim = grid->dim; + cells = grid->cells; + + if(p.x < 0 || p.y < 0 || p.x >= dim.x || p.y >= dim.y) + return 0; + + if(cells[p.y][p.x].tk == nil) + return 0; + + if(cells[p.y][p.x].span.x == 0){ + tk = cells[p.y][p.x].tk; + for(; p.y >= 0; p.y--) + if(cells[p.y][p.x].tk != tk) + break; + p.y++; + for(; p.x >= 0; p.x--) + if(cells[p.y][p.x].tk != tk) + break; + p.x++; + } + *cp = p; + return 1; +} + +static int +parsegridindex(TkGridbeam *beam, int blen, char *s) +{ + int n, i; + char *e; + + if(s[0] == '\0') + return -1; + + n = strtol(s, &e, 10); + if(*e == '\0') + return n; + + if(strcmp(s, "end") == 0) + return blen; + + for(i = 0; i < blen; i++) + if(beam[i].name != nil && strcmp(beam[i].name, s) == 0) + return i; + return -1; +} + +static char* +tkgridconfigure(TkTop *t, TkGridparam *p, TkName *names) +{ + TkGrid *grid; + TkGridcell **cells; + TkName *n; + Tk *tkf, *tkp; + Point dim, pos, q, span, startpos; + int maxcol, c, i, j, x; + char *e; + + if(names == nil) + return nil; + + if(p->span.x < 1 || p->span.y < 1) + return TkBadvl; + + tkf = nil; + + maxcol = 0; + for(n = names; n; n = n->link){ + c = n->name[0]; + if((c=='-' || c=='^' || c=='x') && n->name[1] == '\0'){ + maxcol++; + continue; + } + tkp = tklook(t, n->name, 0); + if(tkp == nil){ + tkerr(t, n->name); + return TkBadwp; + } + if(tkp->flag & Tkwindow) + return TkIstop; + if(tkp->parent != nil) + return TkWpack; + + /* + * unpacking now does give an non-reversible side effect + * ifthere's an error encountered later, but also means + * that a widget repacked in the same grid will + * have its original cell still available + */ + if(tkp->master != nil){ + tkpackqit(tkp->master); + tkdelpack(tkp); + } + if(tkf == nil) + tkf = tkp; + n->obj = tkp; + tkp->flag &= ~Tkgridpack; + maxcol += p->span.x; + } + + if(p->in == nil && tkf != nil) + p->in = tklook(t, tkf->name->name, 1); + + if(p->in == nil) + return TkNomaster; + + grid = p->in->grid; + if(grid == nil && p->in->slave != nil) + return TkNotgrid; + + if(grid == nil){ + grid = malloc(sizeof(TkGrid)); + if(grid == nil) + return TkNomem; + p->in->grid = grid; + } + + dim = grid->dim; + pos = ZP; + if(p->row != nil){ + pos.y = parsegridindex(grid->rows, dim.y, p->row); + if(pos.y < 0) + return TkBadix; + } + if(p->col != nil){ + pos.x = parsegridindex(grid->cols, dim.x, p->col); + if(pos.x < 0) + return TkBadix; + } + /* + * ifrow is not specified, find first unoccupied row + */ + if(p->row == nil){ + for(pos.y = 0; pos.y < dim.y; pos.y++){ + for(x = 0; x < dim.x; x++) + if(grid->cells[pos.y][x].tk != nil) + break; + if(x == dim.x) + break; + } + } + e = ensuregridsize(grid, Pt(pos.x + maxcol, pos.y + p->span.y)); + if(e != nil) + return e; + cells = grid->cells; + + startpos = pos; + /* + * check that all our grid cells are empty, and that row/col spans + * are well formed + */ + n = names; + while(n != nil){ + c = n->name[0]; + switch (c){ + case 'x': + n = n->link; + pos.x++; + break; + case '^': + if(findspan(grid, Pt(pos.x, pos.y - 1), &q) == 0) + return TkBadspan; + span = cells[q.y][q.x].span; + for(i = 0; i < span.x; i++){ + if(n == nil || strcmp(n->name, "^")) + return TkBadspan; + if(cells[pos.y][pos.x + i].tk != nil) + return TkBadgridcell; + n = n->link; + } + pos.x += span.x; + break; + case '-': + return TkBadspan; + case '.': + tkp = n->obj; + if(tkisslave(p->in, tkp)) + return TkRecur; + n = n->link; + if(tkp->flag & Tkgridpack) + return TkWpack; + tkp->flag |= Tkgridpack; + span = p->span; + for(; n != nil && strcmp(n->name, "-") == 0; n = n->link) + span.x++; + for(i = pos.x; i < pos.x + span.x; i++) + for(j = pos.y; j < pos.y + span.y; j++) + if(cells[j][i].tk != nil) + return TkBadgridcell; + pos.x = i; + break; + } + } + + /* + * actually insert the items into the grid + */ + n = names; + pos = startpos; + while(n != nil){ + c = n->name[0]; + switch (c){ + case 'x': + n = n->link; + pos.x++; + break; + case '^': + findspan(grid, Pt(pos.x, pos.y - 1), &q); + span = cells[q.y][q.x].span; + tkf = cells[q.y][q.x].tk; + if(q.y + span.y == pos.y) + cells[q.y][q.x].span.y++; + + for(i = 0; i < span.x; i++){ + cells[pos.y][pos.x++].tk = tkf; + n = n->link; + } + break; + case '.': + tkf = n->obj; + n = n->link; + span = p->span; + for(; n != nil && strcmp(n->name, "-") == 0; n = n->link) + span.x++; + for(i = pos.x; i < pos.x + span.x; i++) + for(j = pos.y; j < pos.y + span.y; j++) + cells[j][i].tk = tkf; + cells[pos.y][pos.x].span = span; + tkf->master = p->in; + tkf->next = p->in->slave; + p->in->slave = tkf; + if(p->in->flag & Tksubsub) + tksetbits(tkf, Tksubsub); + tkgridsetopt(p, tkf); + pos.x = i; + break; + } + } + tkpackqit(p->in); + tkrunpack(t); + return nil; +} + +void +tkgriddelslave(Tk *tk) +{ + int y, x, yy; + TkGrid *grid; + TkGridcell **cells, *cell; + Point dim, span; + + if(tk == nil || tk->master == nil || tk->master->grid == nil) + return; + grid = tk->master->grid; + cells = grid->cells; + dim = grid->dim; + for(y = 0; y < dim.y; y++){ + for(x = 0; x < dim.x; x++){ + cell = &cells[y][x]; + if(cell->tk == tk){ + span = cell->span; + for(yy = y; yy < y + span.y; yy++) + memset(cells[yy] + x, 0, span.x * sizeof(TkGridcell)); + return; + } + } + } +} + +char* +tkgetgridmaster(TkTop *t, char **arg, char *buf, char *ebuf, Tk **master) +{ + TkGrid *grid; + + *arg = tkword(t, *arg, buf, ebuf, nil); + *master = tklook(t, buf, 0); + if(*master == nil) + return TkBadwp; + grid = (*master)->grid; + if(grid == nil && (*master)->slave != nil) + return TkNotgrid; + return nil; +} + +static int +gridfindloc(TkGridbeam *beam, int blen, int f) +{ + int x, i, fpad; + if(blen == 0 || f < 0) + return -1; + + fpad = 0; + x = 0; + for(i = 0; i < blen; i++){ + x += maximum(fpad, beam[i].pad); + if(x <= f && f < x + beam[i].act) + return i; + x += beam[i].act; + } + return -1; +} + +/* + * optimised way to find a given slave, but somewhat more fragile + * as it assumes the slave has already been placed on the grid. + * not tested. + */ +static int +findslave(TkGrid *grid, Tk *tk, Point *pt) +{ + Point loc, dim, p; + TkGridcell **cells; + dim = grid->dim; + cells = grid->cells; + loc.x = gridfindloc(grid->cols, grid->dim.x, tk->act.x); + if(loc.x == -1) + loc.x = 0; + loc.y = gridfindloc(grid->rows, grid->dim.y, tk->act.y); + if(loc.y == -1) + loc.y = 0; + for(p.y = loc.y; p.y < dim.y; p.y++) + for(p.x = loc.x; p.x < dim.x; p.x++) + if(cells[p.y][p.x].tk == tk){ + *pt = p; + return 1; + } + return 0; +} +static char* +tkgridcellinfo(TkTop *t, char *arg, char **val, char *buf, char *ebuf) +{ + /* grid cellinfo master x y */ + Tk *master; + char *e; + Point p; + TkGrid *grid; + TkGridcell **cells; + + e = tkgetgridmaster(t, &arg, buf, ebuf, &master); + if(e != nil || master->grid == nil) + return e; + grid = master->grid; + + e = tkfracword(t, &arg, &p.x, nil); + if(e != nil) + return e; + e = tkfracword(t, &arg, &p.y, nil); + if(e != nil) + return e; + + p.x = TKF2I(p.x); + p.y = TKF2I(p.y); + if(p.x < 0 || p.x >= grid->dim.x || p.y < 0 || p.y >= grid->dim.y) + return nil; + + if(!findspan(grid, p, &p)) + return nil; + + cells = grid->cells; + return tkvalue(val, "%s -in %s -column %d -row %d -columnspan %d -rowspan %d", + cells[p.y][p.x].tk->name->name, + cells[p.y][p.x].tk->master->name->name, p.x, p.y, + cells[p.y][p.x].span.x, cells[p.y][p.x].span.y); +} + +static char* +tkgridlocation(TkTop *t, char *arg, char **val, char *buf, char *ebuf) +{ + /* grid location master x y */ + Tk *master; + char *e; + Point p; + int col, row; + TkGrid *grid; + + e = tkgetgridmaster(t, &arg, buf, ebuf, &master); + if(e != nil || master->grid == nil) + return e; + grid = master->grid; + + e = tkfracword(t, &arg, &p.x, nil); + if(e != nil) + return e; + e = tkfracword(t, &arg, &p.y, nil); + if(e != nil) + return e; + + p.x = TKF2I(p.x); + p.y = TKF2I(p.y); + + p = subpt(p, grid->origin); + col = gridfindloc(grid->cols, grid->dim.x, p.x); + row = gridfindloc(grid->rows, grid->dim.y, p.y); + if(col < 0 || row < 0) + return nil; + return tkvalue(val, "%d %d", col, row); +} + +static char* +tkgridinfo(TkTop *t, char *arg, char **val, char *buf, char *ebuf) +{ + Tk *tk; + TkGrid *grid; + int x, y; + Point dim; + TkGridcell *row; + + tkword(t, arg, buf, ebuf, nil); + tk = tklook(t, buf, 0); + if(tk == nil) + return TkBadwp; + if(tk->master == nil || tk->master->grid == nil) + return TkNotgrid; + grid = tk->master->grid; + dim = grid->dim; + for(y = 0; y < dim.y; y++){ + row = grid->cells[y]; + for(x = 0; x < dim.x; x++) + if(row[x].tk == tk) + goto Found; + } + return TkNotgrid; /* should not happen */ +Found: + return tkvalue(val, "-in %s -column %d -row %d -columnspan %d -rowspan %d", + tk->master->name->name, x, y, grid->cells[y][x].span.x, grid->cells[y][x].span.y); +} + +static char* +tkgridforget(TkTop *t, char *arg, char *buf, char *ebuf) +{ + Tk *tk; + for(;;){ + arg = tkword(t, arg, buf, ebuf, nil); + if(arg == nil || buf[0] == '\0') + break; + tk = tklook(t, buf, 0); + if(tk == nil){ + tkrunpack(t); + tkerr(t, buf); + return TkBadwp; + } + tkpackqit(tk->master); + tkdelpack(tk); + } + tkrunpack(t); + return nil; +} + +static char* +tkgridslaves(TkTop *t, char *arg, char **val, char *buf, char *ebuf) +{ + Tk *master, *tk; + char *fmt; + int i, isrow, index; + TkGrid *grid; + TkGridcell *cell; + char *e; + e = tkgetgridmaster(t, &arg, buf, ebuf, &master); + if(e != nil || master->grid == nil) + return e; + grid = master->grid; + arg = tkword(t, arg, buf, ebuf, nil); + fmt = "%s"; + if(buf[0] == '\0'){ + for(tk = master->slave; tk != nil; tk = tk->next){ + if(tk->name != nil){ + e = tkvalue(val, fmt, tk->name->name); + if(e != nil) + return e; + fmt = " %s"; + } + } + return nil; + } + if(strcmp(buf, "-row") == 0) + isrow = 1; + else if(strcmp(buf, "-column") == 0) + isrow = 0; + else + return TkBadop; + tkword(t, arg, buf, ebuf, nil); + if(isrow) + index = parsegridindex(grid->rows, grid->dim.y, buf); + else + index = parsegridindex(grid->cols, grid->dim.x, buf); + if(index < 0) + return TkBadix; + if(isrow){ + if(index >= grid->dim.y) + return nil; + for(i = 0; i < grid->dim.x; i++){ + cell = &grid->cells[index][i]; + if(cell->tk != nil && cell->span.x > 0 && cell->tk->name != nil){ + e = tkvalue(val, fmt, cell->tk->name->name); + if(e != nil) + return e; + fmt = " %s"; + } + } + } else{ + if(index >= grid->dim.x) + return nil; + for(i = 0; i < grid->dim.y; i++){ + cell = &grid->cells[i][index]; + if(cell->tk != nil && cell->span.x > 0 && cell->tk->name != nil){ + e = tkvalue(val, fmt, cell->tk->name->name); + if(e != nil) + return e; + fmt = " %s"; + } + } + } + + return nil; +} + +static char* +tkgriddelete(TkTop *t, char *arg, char *buf, char *ebuf, int delrow) +{ + Tk *master, **l, *f; + TkGrid *grid; + TkGridbeam *beam; + int blen, i0, i1, x, y; + Point dim; + TkGridcell **cells; + char *e; + + /* + * grid (columndelete|rowdelete) master index0 ?index1? + */ + + e = tkgetgridmaster(t, &arg, buf, ebuf, &master); + if(e != nil || master->grid == nil) + return e; + grid = master->grid; + + if(delrow){ + beam = grid->rows; + blen = grid->dim.y; + } else{ + beam = grid->cols; + blen = grid->dim.x; + } + + arg = tkword(t, arg, buf, ebuf, nil); + i0 = parsegridindex(beam, blen, buf); + if(i0 < 0) + return TkBadix; + + tkword(t, arg, buf, ebuf, nil); + if(buf[0] == '\0') + i1 = i0 + 1; + else + i1 = parsegridindex(beam, blen, buf); + if(i1 < 0 || i0 > i1) + return TkBadix; + if(i0 > blen || i0 == i1) + return nil; + if(i1 > blen) + i1 = blen; + cells = grid->cells; + dim = grid->dim; + if(delrow){ + if(gridrowhasspan(grid, i0) || gridrowhasspan(grid, i1)) + return TkBadgridcell; + for(y = i0; y < i1; y++) + for(x = 0; x < dim.x; x++) + if(cells[y][x].tk != nil) + cells[y][x].tk->flag |= Tkgridremove; + delrows(grid, i0, i1); + grid->rows = delbeams(beam, blen, i0, i1); + } else{ + if(gridcolhasspan(grid, i0) || gridcolhasspan(grid, i1)) + return TkBadgridcell; + for(y = 0; y < dim.y; y++) + for(x = i0; x < i1; x++) + if(cells[y][x].tk != nil) + cells[y][x].tk->flag |= Tkgridremove; + delcols(grid, i0, i1); + grid->cols = delbeams(beam, blen, i0, i1); + } + l = &master->slave; + for(f = *l; f; f = f->next){ + if(f->flag & Tkgridremove){ + *l = f->next; + f->master = nil; + f->flag &= ~Tkgridremove; + } else + l = &f->next; + } + tkpackqit(master); + tkrunpack(t); + return nil; +} + + +static char* +tkgridinsert(TkTop *t, char *arg, char *buf, char *ebuf, int insertrow) +{ + int index, count; + Point dim; + Tk *master; + TkGrid *grid; + int gotarg; + char *e; + + /* + * grid (rowinsert|columninsert) master index ?count? + * it's an error ifthe insert splits any spanning cells. + */ + e = tkgetgridmaster(t, &arg, buf, ebuf, &master); + if(e != nil || master->grid == nil) + return e; + grid = master->grid; + dim = grid->dim; + + arg = tkword(t, arg, buf, ebuf, nil); + if(insertrow) + index = parsegridindex(grid->rows, dim.y, buf); + else + index = parsegridindex(grid->cols, dim.x, buf); + if(index < 0 || index > (insertrow ? dim.y : dim.x)) + return TkBadix; + + tkword(t, arg, buf, ebuf, &gotarg); + if(gotarg){ + count = strtol(buf, &buf, 10); + if(buf[0] != '\0' || count < 0) + return TkBadvl; + } else + count = 1; + + /* + * check that we're not splitting any spanning cells + */ + if(insertrow){ + if(gridrowhasspan(grid, index)) + return TkBadgridcell; + e = insrows(grid, index, count); + } else{ + if(gridcolhasspan(grid, index)) + return TkBadgridcell; + e = inscols(grid, index, count); + } + tkpackqit(master); + tkrunpack(t); + return e; +} + +/* + * (rowconfigure|columnconfigure) master index ?-option value ...? + */ +static char* +tkbeamconfigure(TkTop *t, char *arg, int isrow) +{ + TkBeamparam p; + TkOptab tko[2]; + TkName *names; + Tk *master; + int index; + TkGrid *grid; + TkGridbeam *beam; + Point dim; + char *e; + + p.equalise = BoolX; + p.name = nil; + p.weight = -1; + p.minsize = -1; + p.maxsize = -1; + p.pad = -1; + + tko[0].ptr = &p; + tko[0].optab = beamopts; + tko[1].ptr = nil; + + names = nil; + e = tkparse(t, arg, tko, &names); + if(e != nil) + return e; + + if(names == nil || names->link == nil) + return TkBadvl; + + master = tklook(t, names->name, 0); + if(master == nil) + return TkBadwp; + + grid = master->grid; + if(grid == nil){ + if(master->slave != nil) + return TkNotgrid; + grid = master->grid = malloc(sizeof(TkGrid)); + if(grid == nil){ + tkfreename(names); + return TkNomem; + } + } + + if(isrow){ + index = parsegridindex(grid->rows, grid->dim.y, names->link->name); + } else + index = parsegridindex(grid->cols, grid->dim.x, names->link->name); + if(index < 0){ + e = TkBadix; + goto Error; + } + if(isrow) + dim = Pt(grid->dim.x, index + 1); + else + dim = Pt(index + 1, grid->dim.y); + e = ensuregridsize(grid, dim); + if(e != nil) + goto Error; + + if(isrow) + beam = &grid->rows[index]; + else + beam = &grid->cols[index]; + + if(p.minsize >= 0) + beam->minsize = p.minsize; + if(p.maxsize >= 0) + beam->maxsize = p.maxsize; + if(p.weight >= 0) + beam->weight = p.weight; + if(p.pad >= 0) + beam->pad = p.pad; + if(p.name != nil){ + free(beam->name); + beam->name = p.name; + } + if(p.equalise != BoolX) + beam->equalise = p.equalise == BoolT; + + tkpackqit(master); + tkrunpack(t); + +Error: + tkfreename(names); + return e; +} + +char* +tkgridsize(TkTop *t, char *arg, char **val, char *buf, char *ebuf) +{ + Tk *master; + TkGrid *grid; + char *e; + + e = tkgetgridmaster(t, &arg, buf, ebuf, &master); + if(e != nil) + return e; + grid = master->grid; + if(grid == nil) + return tkvalue(val, "0 0"); + else + return tkvalue(val, "%d %d", grid->dim.x, grid->dim.y); +} + +char* +tkgridbbox(TkTop *t, char *arg, char **val, char *buf, char *ebuf) +{ + Point p0, p1; + Tk *master; + TkGrid *grid; + char *e; + int gotarg; + Point dim; + Rectangle r; + + e = tkgetgridmaster(t, &arg, buf, ebuf, &master); + if(e != nil || master->grid == nil) + return e; + + grid = master->grid; + dim = grid->dim; + arg = tkword(t, arg, buf, ebuf, &gotarg); + if(!gotarg){ + p0 = ZP; + p1 = dim; + } else{ + p0.x = parsegridindex(grid->cols, dim.x, buf); + arg = tkword(t, arg, buf, ebuf, &gotarg); + if(!gotarg) + return TkFewpt; + p0.y = parsegridindex(grid->rows, dim.y, buf); + arg = tkword(t, arg, buf, ebuf, &gotarg); + if(!gotarg){ + p1 = p0; + } else{ + p1.x = parsegridindex(grid->cols, dim.x, buf); + arg = tkword(t, arg, buf, ebuf, &gotarg); + if(!gotarg) + return TkFewpt; + p1.y = parsegridindex(grid->rows, dim.y, buf); + } + } + if(p0.x < 0 || p0.y < 0 || p1.x < 0 || p1.y < 0) + return TkBadix; + + r = cellbbox(grid, p0); + if(!eqpt(p0, p1)) + combinerect(&r, cellbbox(grid, p1)); + return tkvalue(val, "%d %d %d %d", r.min.x, r.min.y, r.max.x, r.max.y); +} + +char* +tkgridindex(TkTop *t, char *arg, char **val, char *buf, char *ebuf, int isrow) +{ + Tk *master; + TkGrid *grid; + TkGridbeam *beam; + int blen, i; + + arg = tkword(t, arg, buf, ebuf, nil); + master = tklook(t, buf, 0); + if(master == nil) + return TkBadwp; + tkword(t, arg, buf, ebuf, nil); + grid = master->grid; + if(grid == nil){ + beam = nil; + blen = 0; + } else if(isrow){ + beam = grid->rows; + blen = grid->dim.y; + } else{ + beam = grid->cols; + blen = grid->dim.x; + } + i = parsegridindex(beam, blen, buf); + if(i < 0) + return TkBadix; + return tkvalue(val, "%d", i); +} + +void +tkfreegrid(TkGrid *grid) +{ + Point dim; + int i; + dim = grid->dim; + for(i = 0; i < dim.x; i++) + free(grid->cols[i].name); + for(i = 0; i < dim.y; i++) + free(grid->rows[i].name); + for(i = 0; i < dim.y; i++) + free(grid->cells[i]); + free(grid->cells); + free(grid->rows); + free(grid->cols); + free(grid); +} + +char* +tkgrid(TkTop *t, char *arg, char **val) +{ + TkGridparam *p; + TkOptab tko[2]; + TkName *names; + char *e, *w, *buf; + + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + + w = tkword(t, arg, buf, buf+Tkmaxitem, nil); + if('a' <= buf[0] && buf[0] <= 'z'){ + if(strcmp(buf, "debug") == 0){ + Tk *tk; + e = tkgetgridmaster(t, &w, buf, buf+Tkmaxitem, &tk); + if(e == nil) + printgrid(tk->grid); + } else + if(strcmp(buf, "forget") == 0) + e = tkgridforget(t, w, buf, buf+Tkmaxitem); + else if(strcmp(buf, "propagate") == 0) + e = tkpropagate(t, w); + else if(strcmp(buf, "slaves") == 0) + e = tkgridslaves(t, w, val, buf, buf+Tkmaxitem); + else if(strcmp(buf, "rowconfigure") == 0) + e = tkbeamconfigure(t, w, 1); + else if(strcmp(buf, "columnconfigure") == 0) + e = tkbeamconfigure(t, w, 0); + else if(strcmp(buf, "rowinsert") == 0) + e = tkgridinsert(t, w, buf, buf+Tkmaxitem, 1); + else if(strcmp(buf, "columninsert") == 0) + e = tkgridinsert(t, w, buf, buf+Tkmaxitem, 0); + else if(strcmp(buf, "size") == 0) + e = tkgridsize(t, w, val, buf, buf+Tkmaxitem); + else if(strcmp(buf, "rowdelete") == 0) + e = tkgriddelete(t, w, buf, buf+Tkmaxitem, 1); + else if(strcmp(buf, "columndelete") == 0) + e = tkgriddelete(t, w, buf, buf+Tkmaxitem, 0); + else if(strcmp(buf, "rowindex") == 0) + e = tkgridindex(t, w, val, buf, buf+Tkmaxitem, 1); + else if(strcmp(buf, "columnindex") == 0) + e = tkgridindex(t, w, val, buf, buf+Tkmaxitem, 0); + else if(strcmp(buf, "bbox") == 0) + e = tkgridbbox(t, w, val, buf, buf+Tkmaxitem); + else if(strcmp(buf, "location") == 0) + e = tkgridlocation(t, w, val, buf, buf+Tkmaxitem); + else if(strcmp(buf, "cellinfo") == 0) + e = tkgridcellinfo(t, w, val, buf, buf+Tkmaxitem); + else if(strcmp(buf, "info") == 0) + e = tkgridinfo(t, w, val, buf, buf+Tkmaxitem); + else{ + tkerr(t, buf); + e = TkBadcm; + } + } else{ + p = malloc(sizeof(TkGridparam)); + if(p == nil) + return TkNomem; + tko[0].ptr = p; + tko[0].optab = opts; + tko[1].ptr = nil; + + p->span.x = 1; + p->span.y = 1; + p->pad.x = p->pad.y = p->ipad.x = p->ipad.y = -1; + p->sticky = -1; + + names = nil; + e = tkparse(t, arg, tko, &names); + if(e != nil){ + free(p); + return e; + } + + e = tkgridconfigure(t, p, names); + free(p->row); + free(p->col); + free(p); + tkfreename(names); + } + free(buf); + return e; +} + +/* + * expand widths of rows/columns according to weight. + * return amount of space still left over. + */ +static int +expandwidths(int x0, int x1, int totwidth, TkGridbeam *cols, int expandzero) +{ + int share, x, slack, m, w, equal; + + if(x0 >= x1) + return 0; + + share = 0; + for(x = x0; x < x1; x++) + share += cols[x].weight; + + slack = totwidth - beamsize(cols, x0, x1); + if(slack <= 0) + return 0; + + if(share == 0 && expandzero){ + share = x1 - x0; + equal = 1; + } else + equal = 0; + + for(x = x0; x < x1 && share > 0 ; x++){ + w = equal ? 1 : cols[x].weight; + m = slack * w / share; + cols[x].act += m; + slack -= m; + share -= w; + } + return slack; +} + +static void +gridequalise(TkGridbeam *beam, int blen) +{ + int i, max; + + max = 0; + for(i = 0; i < blen; i++) + if(beam[i].equalise == BoolT && beam[i].act > max) + max = beam[i].act; + + if(max > 0) + for(i = 0; i < blen; i++) + if(beam[i].equalise == BoolT) + beam[i].act = max; +} + +/* + * take into account min/max beam sizes. + * max takes precedence + */ +static void +beamminmax(TkGridbeam *beam, int n) +{ + TkGridbeam *e; + e = &beam[n]; + for(; beam < e; beam++){ + if(beam->act < beam->minsize) + beam->act = beam->minsize; + if(beam->act > beam->maxsize) + beam->act = beam->maxsize; + } +} + +int +tkgridder(Tk *master) +{ + TkGrid *grid; + TkGridcell **cells, *cell; + TkGridbeam *rows, *cols; + TkGeom pos; + Point org; + Tk *slave; + int dx, dy, x, y, w, bw2, fpadx, fpady; + Point req; + + grid = master->grid; + dx = grid->dim.x; + dy = grid->dim.y; + cells = grid->cells; + rows = grid->rows; + cols = grid->cols; + + for(x = 0; x < dx; x++) + cols[x].act = 0; + + /* calculate column widths and row heights (ignoring multi-column cells) */ + for(y = 0; y < dy; y++){ + rows[y].act = 0; + for(x = 0; x < dx; x++){ + cell = &cells[y][x]; + if((slave = cell->tk) != nil){ + bw2 = slave->borderwidth * 2; + w = slave->req.width + bw2 + slave->pad.x + slave->ipad.x; + if(cell->span.x == 1 && w > cols[x].act) + cols[x].act = w; + w = slave->req.height + bw2 + slave->pad.y + slave->ipad.y; + if(cell->span.y == 1 && w > rows[y].act) + rows[y].act = w; + } + } + } + + beamminmax(rows, dy); + beamminmax(cols, dx); + + /* now check that spanning cells fit in their rows/columns */ + for(y = 0; y < dy; y++) + for(x = 0; x < dx; x++){ + cell = &cells[y][x]; + if((slave = cell->tk) != nil){ + bw2 = slave->borderwidth * 2; + if(cell->span.x > 1){ + w = slave->req.width + bw2 + slave->pad.x + slave->ipad.x; + expandwidths(x, x+cell->span.x, w, cols, 1); + } + if(cell->span.y > 1){ + w = slave->req.height + bw2 + slave->pad.y + slave->ipad.y; + expandwidths(y, y+cell->span.y, w, rows, 1); + } + } + } + + gridequalise(rows, dy); + gridequalise(cols, dx); + + if(dx == 0) + req.x = 0; + else + req.x = beamsize(cols, 0, dx) + cols[0].pad + cols[dx-1].pad; + + if(dy == 0) + req.y = 0; + else + req.y = beamsize(rows, 0, dy) + rows[0].pad + rows[dy-1].pad; + + if(req.x != master->req.width || req.y != master->req.height) + if((master->flag & Tknoprop) == 0){ + if(master->geom != nil){ + master->geom(master, master->act.x, master->act.y, + req.x, req.y); + } else{ + master->req.width = req.x; + master->req.height = req.y; + tkpackqit(master->master); + } + return 0; + } + org = ZP; + if(dx > 0 && master->act.width > req.x) + org.x = expandwidths(0, dx, + master->act.width - (cols[0].pad + cols[dx-1].pad), + cols, 0) / 2; + if(dy > 0 && master->act.height > req.y) + org.y = expandwidths(0, dy, + master->act.height - (rows[0].pad + rows[dy-1].pad), + rows, 0) / 2; + + grid->origin = org; + pos.y = org.y; + fpady = 0; + for(y = 0; y < dy; y++){ + pos.y += maximum(fpady, rows[y].pad); + fpady = rows[y].pad; + + pos.x = org.x; + fpadx = 0; + for(x = 0; x < dx; x++){ + cell = &cells[y][x]; + pos.x += maximum(fpadx, cols[x].pad); + fpadx = cols[x].pad; + if((slave = cell->tk) != nil && cell->span.x > 0){ + pos.width = beamsize(cols, x, x + cell->span.x); + pos.height = beamsize(rows, y, y + cell->span.y); + tksetslavereq(slave, pos); + } + pos.x += cols[x].act; + } + pos.y += rows[y].act; + } + + master->dirty = tkrect(master, 1); + tkdirty(master); + return 1; +} diff --git a/libtk/image.c b/libtk/image.c new file mode 100644 index 00000000..3b256eb3 --- /dev/null +++ b/libtk/image.c @@ -0,0 +1,380 @@ +#include "lib9.h" +#include <kernel.h> +#include "draw.h" +#include "tk.h" + +#define O(t, e) ((long)(&((t*)0)->e)) + +char* tkimgbmcreate(TkTop*, char*, int, char**); +char* tkimgbmdel(TkImg*); +void tkimgbmfree(TkImg*); + +static Rectangle huger = { -1000000, -1000000, 1000000, 1000000 }; + +typedef struct TkImgtype TkImgtype; +struct TkImgtype +{ + char* type; + char* (*create)(TkTop*, char*, int, char**); + char* (*delete)(TkImg*); + void (*destroy)(TkImg*); +} tkimgopts[] = +{ + "bitmap", tkimgbmcreate, tkimgbmdel, tkimgbmfree, + nil, +}; + +typedef struct Imgargs Imgargs; +struct Imgargs { + Image* fgimg; + Image* maskimg; +}; + +TkImg* +tkname2img(TkTop *t, char *name) +{ + TkImg *tki; + + for(tki = t->imgs; tki; tki = tki->link) + if((tki->name != nil) && strcmp(tki->name->name, name) == 0) + return tki; + + return nil; +} + +TkOption +bitopt[] = +{ + "file", OPTbmap, O(Imgargs, fgimg), nil, + "maskfile", OPTbmap, O(Imgargs, maskimg), nil, + nil +}; + +void +tksizeimage(Tk *tk, TkImg *tki) +{ + int dx, dy, repack; + + dx = 0; + dy = 0; + if(tki->img != nil) { + dx = Dx(tki->img->r); + dy = Dy(tki->img->r); + } + repack = 0; + if(tki->ref > 1 && (tki->w != dx || tki->h != dy)) + repack = 1; + tki->w = dx; + tki->h = dy; + + if(repack) { + tkpackqit(tk); + tkrunpack(tk->env->top); + } +} + +char* +tkimgbmcreate(TkTop *t, char *arg, int type, char **ret) +{ + TkName *names; + TkImg *tki, *f; + TkOptab tko[2]; + char buf[32]; + static int id; + char *e = nil; + Imgargs iargs; + Rectangle r; + Display *d; + int chan; + int locked; + + d = t->display; + locked = 0; + + tki = malloc(sizeof(TkImg)); + if(tki == nil) + return TkNomem; + + tki->env = tkdefaultenv(t); + if(tki->env == nil) + goto err; + tki->type = type; + tki->ref = 1; + tki->top = t; + + iargs.fgimg = nil; + iargs.maskimg = nil; + + tko[0].ptr = &iargs; + tko[0].optab = bitopt; + tko[1].ptr = nil; + + names = nil; + e = tkparse(t, arg, tko, &names); + if(e != nil) + goto err; + + if (iargs.fgimg == nil && iargs.maskimg != nil) { + locked = lockdisplay(d); + r = Rect(0, 0, Dx(iargs.maskimg->r), Dy(iargs.maskimg->r)); + tki->img = allocimage(d, r, CHAN2(CAlpha, 8, CGrey, 8), 0, DTransparent); + if (tki->img != nil) + draw(tki->img, r, nil, iargs.maskimg, iargs.maskimg->r.min); + freeimage(iargs.maskimg); + + } else if (iargs.fgimg != nil && iargs.maskimg != nil) { + locked = lockdisplay(d); + r = Rect(0, 0, Dx(iargs.fgimg->r), Dy(iargs.fgimg->r)); + if (tkchanhastype(iargs.fgimg->chan, CGrey)) + chan = CHAN2(CAlpha, 8, CGrey, 8); + else + chan = RGBA32; + tki->img = allocimage(d, r, chan, 0, DTransparent); + if (tki->img != nil) + draw(tki->img, r, iargs.fgimg, iargs.maskimg, iargs.fgimg->r.min); + freeimage(iargs.fgimg); + freeimage(iargs.maskimg); + } else { + tki->img = iargs.fgimg; + } + if (locked) + unlockdisplay(d); + + if(names == nil) { + sprint(buf, "image%d", id++); + tki->name = tkmkname(buf); + if(tki->name == nil) + goto err; + } + else { + /* XXX should mark as dirty any widgets using the named + * image - some notification scheme needs putting in place + */ + tki->name = names; + tkfreename(names->link); + names->link = nil; + } + + tksizeimage(t->root, tki); + + if (tki->name != nil) { + f = tkname2img(t, tki->name->name); + if(f != nil) + tkimgopts[f->type].delete(f); + } + + tki->link = t->imgs; + t->imgs = tki; + + if (tki->name != nil) { + e = tkvalue(ret, "%s", tki->name->name); + if(e == nil) + return nil; + } +err: + tkputenv(tki->env); + if(tki->img != nil) { + locked = lockdisplay(d); + freeimage(tki->img); + if (locked) + unlockdisplay(d); + } + tkfreename(tki->name); + free(tki); + return e != nil ? e : TkNomem; +} + +char* +tkimgbmdel(TkImg *tki) +{ + TkImg **l, *f; + + l = &tki->top->imgs; + for(f = *l; f; f = f->link) { + if(f == tki) { + *l = tki->link; + tkimgput(tki); + return nil; + } + l = &f->link; + } + return TkBadvl; +} + +void +tkimgbmfree(TkImg *tki) +{ + int locked; + Display *d; + + d = tki->top->display; + locked = lockdisplay(d); + freeimage(tki->img); + if(locked) + unlockdisplay(d); + + free(tki->cursor); + tkfreename(tki->name); + tkputenv(tki->env); + + free(tki); +} + +char* +tkimage(TkTop *t, char *arg, char **ret) +{ + int i; + TkImg *tkim; + char *fmt, *e, *buf, *cmd; + + /* Note - could actually allocate buf and cmd in one buffer - DBK */ + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + cmd = mallocz(Tkminitem, 0); + if(cmd == nil) { + free(buf); + return TkNomem; + } + + arg = tkword(t, arg, cmd, cmd+Tkminitem, nil); + if(strcmp(cmd, "create") == 0) { + arg = tkword(t, arg, buf, buf+Tkmaxitem, nil); + for(i = 0; tkimgopts[i].type != nil; i++) + if(strcmp(buf, tkimgopts[i].type) == 0) { + e = tkimgopts[i].create(t, arg, i, ret); + goto ret; + } + e = TkBadvl; + goto ret; + } + if(strcmp(cmd, "names") == 0) { + fmt = "%s"; + for(tkim = t->imgs; tkim; tkim = tkim->link) { + if (tkim->name != nil) { + e = tkvalue(ret, fmt, tkim->name->name); + if(e != nil) + goto ret; + } + fmt = " %s"; + } + e = nil; + goto ret; + } + + arg = tkword(t, arg, buf, buf+Tkmaxitem, nil); + tkim = tkname2img(t, buf); + if(tkim == nil) { + e = TkBadvl; + goto ret; + } + + if(strcmp(cmd, "height") == 0) { + e = tkvalue(ret, "%d", tkim->h); + goto ret; + } + if(strcmp(cmd, "width") == 0) { + e = tkvalue(ret, "%d", tkim->w); + goto ret; + } + if(strcmp(cmd, "type") == 0) { + e = tkvalue(ret, "%s", tkimgopts[tkim->type].type); + goto ret; + } + if(strcmp(cmd, "delete") == 0) { + for (;;) { + e = tkimgopts[tkim->type].delete(tkim); + if (e != nil) + break; + arg = tkword(t, arg, buf, buf+Tkmaxitem, nil); + if (buf[0] == '\0') + break; + tkim = tkname2img(t, buf); + if (tkim == nil) { + e = TkBadvl; + break; + } + } + goto ret; + } + + e = TkBadcm; +ret: + free(cmd); + free(buf); + return e; +} + +void +tkimgput(TkImg *tki) +{ + if(tki == nil) + return; + + if(--tki->ref > 0) + return; + + tkimgopts[tki->type].destroy(tki); +} + +TkImg* +tkauximage(TkTop *t, char* s, uchar* bytes, int nbytes, int chans, Rectangle r, int repl) +{ + TkName *name; + TkCtxt *c; + TkImg *tki; + Display *d; + Image *i; + int locked; + + tki = tkname2img(t, s); + if (tki != nil) { + tki->ref++; + return tki; + } + + name = tkmkname(s); + if (name == nil) + return nil; + tki = mallocz(sizeof(*tki), 0); + if (tki == nil) + goto err; + tki->env = tkdefaultenv(t); + if(tki->env == nil) + goto err; + + c = t->ctxt; + d = c->display; + + locked = lockdisplay(d); + i = allocimage(d, r, chans, repl, DTransparent); + if (i != nil) { + if (loadimage(i, r, bytes, nbytes) != nbytes) { + freeimage(i); + i = nil; + } + if (repl) + replclipr(i, 1, huger); + } + if (locked) + unlockdisplay(d); + if (i == nil) + goto err; + tki->top = t; + tki->ref = 2; /* t->imgs ref and the ref we are returning */ + tki->type = 0; /* bitmap */ + tki->w = Dx(r); + tki->h = Dy(r); + tki->img = i; + tki->name = name; + tki->link = t->imgs; + t->imgs = tki; + return tki; +err: + if (tki != nil) { + tkputenv(tki->env); + free(tki); + } + tkfreename(name); + return nil; +} diff --git a/libtk/label.c b/libtk/label.c new file mode 100644 index 00000000..79d6344a --- /dev/null +++ b/libtk/label.c @@ -0,0 +1,539 @@ +#include <lib9.h> +#include <kernel.h> +#include "draw.h" +#include "tk.h" +#include "label.h" + +#define O(t, e) ((long)(&((t*)0)->e)) + +/* Layout constants */ +enum { + CheckSpace = CheckButton + 2*CheckButtonBW + 2*ButtonBorder, +}; + +TkOption tklabelopts[] = +{ + "text", OPTtext, O(TkLabel, text), nil, + "label", OPTtext, O(TkLabel, text), nil, + "underline", OPTdist, O(TkLabel, ul), nil, + "justify", OPTflag, O(TkLabel, justify), tkjustify, + "anchor", OPTflag, O(TkLabel, anchor), tkanchor, + "bitmap", OPTbmap, O(TkLabel, bitmap), nil, + "image", OPTimag, O(TkLabel, img), nil, + nil +}; + +char* +tklabel(TkTop *t, char *arg, char **ret) +{ + Tk *tk; + char *e; + TkLabel *tkl; + TkName *names; + TkOptab tko[3]; + + tk = tknewobj(t, TKlabel, sizeof(Tk)+sizeof(TkLabel)); + if(tk == nil) + return TkNomem; + + tkl = TKobj(TkLabel, tk); + tkl->ul = -1; + tkl->justify = Tkleft; + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tkl; + tko[1].optab = tklabelopts; + tko[2].ptr = nil; + + names = nil; + e = tkparse(t, arg, tko, &names); + if(e != nil) { + tkfreeobj(tk); + return e; + } + + tksizelabel(tk); + tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd)); + + e = tkaddchild(t, tk, &names); + tkfreename(names); + if(e != nil) { + tkfreeobj(tk); + return e; + } + tk->name->link = nil; + + return tkvalue(ret, "%s", tk->name->name); +} + +static char* +tklabelcget(Tk *tk, char *arg, char **val) +{ + TkOptab tko[3]; + TkLabel *tkl = TKobj(TkLabel, tk); + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tkl; + tko[1].optab = tklabelopts; + tko[2].ptr = nil; + + return tkgencget(tko, arg, val, tk->env->top); +} + +static char* +tklabelconf(Tk *tk, char *arg, char **val) +{ + char *e; + TkGeom g; + int bd; + TkOptab tko[3]; + TkLabel *tkl = TKobj(TkLabel, tk); + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tkl; + tko[1].optab = tklabelopts; + 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); + tksizelabel(tk); + tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd)); + tkgeomchg(tk, &g, bd); + + tk->dirty = tkrect(tk, 1); + return e; +} + +void +tksizelabel(Tk *tk) +{ + Point p; + int w, h; + TkLabel *tkl; + + tkl = TKobj(TkLabel, tk); + if(tkl->anchor == 0) + tkl->anchor = Tkcenter; + + w = 0; + h = 0; + tkl->textheight = 0; + if(tkl->img != nil) { + w = tkl->img->w + 2*Bitpadx; + h = tkl->img->h + 2*Bitpady; + } + else + if(tkl->bitmap != nil) { + w = Dx(tkl->bitmap->r) + 2*Bitpadx; + h = Dy(tkl->bitmap->r) + 2*Bitpady; + } + else + if(tkl->text != nil) { + p = tkstringsize(tk, tkl->text); + w = p.x + 2*Textpadx; + h = p.y + 2*Textpady; + if(tkl->ul != -1 && tkl->ul > strlen(tkl->text)) + tkl->ul = -1; + tkl->textheight = p.y; + } + + if((tk->type == TKcheckbutton || tk->type == TKradiobutton) && tkl->indicator != BoolF) { + w += CheckSpace; + if(h < CheckSpace) + h = CheckSpace; + } else if(tk->type == TKcascade) { + w += CheckButton + 2*CheckButtonBW; + if(h < CheckButton) + h = CheckButton; + } + w += 2*tk->highlightwidth; + h += 2*tk->highlightwidth; + tkl->w = w; + tkl->h = h; + if((tk->flag & Tksetwidth) == 0) + tk->req.width = w; + if((tk->flag & Tksetheight) == 0) + tk->req.height = h; +} + +int +tklabelmargin(Tk *tk) +{ + TkLabel *tkl; + Image *img; + + if (tk->type == TKseparator) + return 0; + if (tk->type == TKlabel || tk->type == TKcascade) { + tkl = TKobj(TkLabel, tk); + img = nil; + if (tkl->img != nil) + img = tkl->img->img; + else if (tkl->bitmap != nil) + img = tkl->bitmap; + if (img != nil) + return Bitpadx; + return Textpadx; + } + return tkbuttonmargin(tk); +} + +void +tkfreelabel(Tk *tk) +{ + Image *i; + int locked; + Display *d; + TkLabel *tkl; + + tkl = TKobj(TkLabel, tk); + + if(tkl->text != nil) + free(tkl->text); + if(tkl->command != nil) + free(tkl->command); + if(tkl->value != nil) + free(tkl->value); + if(tkl->variable != nil) { + tkfreevar(tk->env->top, tkl->variable, tk->flag & Tkswept); + free(tkl->variable); + } + if(tkl->img != nil) + tkimgput(tkl->img); + i = tkl->bitmap; + if(i != nil) { + d = i->display; + locked = lockdisplay(d); + freeimage(i); + if(locked) + unlockdisplay(d); + } + if(tkl->menu != nil) + free(tkl->menu); +} + +static void +tktriangle(Point u, Image *i, TkEnv *e) +{ + int j; + Point p[3]; + + u.y++; + p[0].x = u.x + CheckButton; + p[0].y = u.y + CheckButton/2; + p[1].x = u.x; + p[1].y = u.y + CheckButton; + p[2].x = u.x; + p[2].y = u.y; + fillpoly(i, p, 3, ~0, tkgc(e, TkCbackgnddark), p[0]); + for(j = 0; j < 3; j++) + p[j].y -= 2; + + fillpoly(i, p, 3, ~0, tkgc(e, TkCbackgndlght), p[0]); +} + +/* + * draw TKlabel, TKcheckbutton, TKradiobutton + */ +char* +tkdrawlabel(Tk *tk, Point orig) +{ + TkEnv *e; + TkLabel *tkl; + Rectangle r, s, mainr, focusr; + int dx, dy, h; + Point p, u, v, *pp; + Image *i, *dst, *cd, *cl, *ct, *img; + char *o; + int relief, bgnd, fgnd; + + e = tk->env; + + dst = tkimageof(tk); + if(dst == nil) + return nil; + + v.x = tk->act.width + 2*tk->borderwidth; + v.y = tk->act.height + 2*tk->borderwidth; + + r.min = ZP; + r.max.x = v.x; + r.max.y = v.y; + focusr = insetrect(r, tk->borderwidth); + mainr = insetrect(focusr, tk->highlightwidth); + relief = tk->relief; + + tkl = TKobj(TkLabel, tk); + + fgnd = TkCforegnd; + bgnd = TkCbackgnd; + if (tk->flag & Tkdisabled) + fgnd = TkCdisablefgnd; + else if ((tk->type == TKcheckbutton || tk->type == TKradiobutton) && tkl->indicator == BoolF && tkl->check) + bgnd = TkCselect; + else if (tk->flag & Tkactive) { + fgnd = TkCactivefgnd; + bgnd = TkCactivebgnd; + } + + i = tkitmp(e, r.max, bgnd); + if(i == nil) + return nil; + + if(tk->flag & Tkactive) + draw(i, r, tkgc(e, bgnd), nil, ZP); + + p = mainr.min; + h = tkl->h - 2 * tk->highlightwidth; + + dx = tk->act.width - tkl->w - tk->ipad.x; + dy = tk->act.height - tkl->h - tk->ipad.y; + if((tkl->anchor & (Tknorth|Tksouth)) == 0) + p.y += dy/2; + else + if(tkl->anchor & Tksouth) + p.y += dy; + + if((tkl->anchor & (Tkeast|Tkwest)) == 0) + p.x += dx/2; + else + if(tkl->anchor & Tkeast) + p.x += dx; + + switch(tk->type) { + case TKcheckbutton: + if (tkl->indicator == BoolF) { + relief = tkl->check?TKsunken:TKraised; + break; + } + u.x = p.x + ButtonBorder; + u.y = p.y + ButtonBorder + (h - CheckSpace) / 2; + + cl = tkgc(e, bgnd+TkLightshade); + cd = tkgc(e, bgnd+TkDarkshade); + if(tkl->check) { + tkbevel(i, u, CheckButton, CheckButton, CheckButtonBW, cd, cl); + u.x += CheckButtonBW; + u.y += CheckButtonBW; + s.min = u; + s.max.x = u.x + CheckButton; + s.max.y = u.y + CheckButton; + draw(i, s, tkgc(e, TkCselect), nil, ZP); + } + else + tkbevel(i, u, CheckButton, CheckButton, CheckButtonBW, cl, cd); + break; + case TKradiobutton: + if (tkl->indicator == BoolF) { + relief = tkl->check?TKsunken:TKraised; + break; + } + u.x = p.x + ButtonBorder; + u.y = p.y + ButtonBorder + (h - CheckSpace) / 2; + pp = mallocz(4*sizeof(Point), 0); + if(pp == nil) + return TkNomem; + pp[0].x = u.x + CheckButton/2; + pp[0].y = u.y; + pp[1].x = u.x + CheckButton; + pp[1].y = u.y + CheckButton/2; + pp[2].x = pp[0].x; + pp[2].y = u.y + CheckButton; + pp[3].x = u.x; + pp[3].y = pp[1].y; + cl = tkgc(e, bgnd+TkLightshade); + cd = tkgc(e, bgnd+TkDarkshade); + if(tkl->check) + fillpoly(i, pp, 4, ~0, tkgc(e, TkCselect), pp[0]); + else { + ct = cl; + cl = cd; + cd = ct; + } + line(i, pp[0], pp[1], 0, Enddisc, CheckButtonBW/2, cd, pp[0]); + line(i, pp[1], pp[2], 0, Enddisc, CheckButtonBW/2, cl, pp[1]); + line(i, pp[2], pp[3], 0, Enddisc, CheckButtonBW/2, cl, pp[2]); + line(i, pp[3], pp[0], 0, Enddisc, CheckButtonBW/2, cd, pp[3]); + free(pp); + break; + case TKcascade: + u.x = mainr.max.x - CheckButton - CheckButtonBW; + u.y = p.y + ButtonBorder + (h-CheckSpace)/2; + tktriangle(u, i, e); + break; + case TKbutton: + if ((tk->flag & (Tkactivated|Tkactive)) == (Tkactivated|Tkactive)) + relief = TKsunken; + break; + } + + p.x += tk->ipad.x/2; + p.y += tk->ipad.y/2; + u = ZP; + if(tk->type == TKbutton && relief == TKsunken) { + u.x++; + u.y++; + } + if((tk->type == TKcheckbutton || tk->type == TKradiobutton) && tkl->indicator != BoolF) + u.x += CheckSpace; + + img = nil; + if (tkl->img != nil && tkl->img->img != nil) + img = tkl->img->img; + else if (tkl->bitmap != nil) + img = tkl->bitmap; + if (img != nil) { + s.min.x = p.x + Bitpadx; + s.min.y = p.y + Bitpady; + s.max.x = s.min.x + Dx(img->r); + s.max.y = s.min.y + Dy(img->r); + s = rectaddpt(s, u); + if (tkchanhastype(img->chan, CGrey)) + draw(i, s, tkgc(e, fgnd), img, ZP); + else + draw(i, s, img, nil, ZP); + } else if(tkl->text != nil) { + u.x += Textpadx; + u.y += Textpady; + ct = tkgc(e, fgnd); + + p.y += (h - tkl->textheight) / 2; + o = tkdrawstring(tk, i, addpt(u, p), tkl->text, tkl->ul, ct, tkl->justify); + if(o != nil) + return o; + } + + if (tkhaskeyfocus(tk)) + tkbox(i, focusr, tk->highlightwidth, tkgc(e, TkChighlightfgnd)); + tkdrawrelief(i, tk, ZP, bgnd, relief); + + p.x = tk->act.x + orig.x; + p.y = tk->act.y + orig.y; + r = rectaddpt(r, p); + draw(dst, r, i, nil, ZP); + + return nil; +} + +char* +tksetvar(TkTop *top, char *c, char *newval) +{ + TkVar *v; + TkWin *tkw; + Tk *f, *m; + void (*vc)(Tk*, char*, char*); + + if (c == nil || c[0] == '\0') + return nil; + + v = tkmkvar(top, c, TkVstring); + if(v == nil) + return TkNomem; + if(v->type != TkVstring) + return TkNotvt; + + if(newval == nil) + newval = ""; + + if(v->value != nil) { + if (strcmp(v->value, newval) == 0) + return nil; + free(v->value); + } + + v->value = strdup(newval); + if(v->value == nil) + return TkNomem; + + for(f = top->root; f; f = f->siblings) { + if(f->type == TKmenu) { + tkw = TKobj(TkWin, f); + for(m = tkw->slave; m; m = m->next) + if ((vc = tkmethod[m->type]->varchanged) != nil) + (*vc)(m, c, newval); + } else + if ((vc = tkmethod[f->type]->varchanged) != nil) + (*vc)(f, c, newval); + } + + return nil; +} + +char* +tkvariable(TkTop *t, char *arg, char **ret) +{ + TkVar *v; + char *fmt, *e, *buf, *ebuf, *val; + int l; + + l = strlen(arg) + 2; + buf = malloc(l); + if(buf == nil) + return TkNomem; + ebuf = buf+l; + + arg = tkword(t, arg, buf, ebuf, nil); + arg = tkskip(arg, " \t"); + if (*arg == '\0') { + if(strcmp(buf, "lasterror") == 0) { + free(buf); + if(t->err == nil) + return nil; + fmt = "%s: %s"; + if(strlen(t->errcmd) == sizeof(t->errcmd)-1) + fmt = "%s...: %s"; + e = tkvalue(ret, fmt, t->errcmd, t->err); + t->err = nil; + return e; + } + v = tkmkvar(t, buf, 0); + free(buf); + if(v == nil || v->value == nil) + return nil; + if(v->type != TkVstring) + return TkNotvt; + return tkvalue(ret, "%s", v->value); + } + val = buf+strlen(buf)+1; + tkword(t, arg, val, ebuf, nil); + e = tksetvar(t, buf, val); + free(buf); + return e; +} + +void +tklabelgetimgs(Tk *tk, Image **image, Image **mask) +{ + TkLabel *tkl; + + tkl = TKobj(TkLabel, tk); + *mask = nil; + if (tkl->img != nil) + *image = tkl->img->img; + else + *image = tkl->bitmap; +} + +static +TkCmdtab tklabelcmd[] = +{ + "cget", tklabelcget, + "configure", tklabelconf, + nil +}; + +TkMethod labelmethod = { + "label", + tklabelcmd, + tkfreelabel, + tkdrawlabel, + nil, + tklabelgetimgs +}; diff --git a/libtk/label.h b/libtk/label.h new file mode 100644 index 00000000..da820c39 --- /dev/null +++ b/libtk/label.h @@ -0,0 +1,73 @@ +typedef struct TkLabel TkLabel; + +/* + * widgets that use the label code: + * label + * checkbutton + * button + * menubutton + * separator + * cascade + * radiobutton + */ + +struct TkLabel +{ + char* text; /* Label value */ + Image* bitmap; /* Bitmap to display */ + TkImg* img; + int justify; + int anchor; +// int flags; /* justify/anchor */ + int w; + int h; + int textheight; + + /* button fields */ + char* command; /* Command to execute at invoke */ + char* value; /* Variable value in radio button */ + char* offvalue; /* Off value for check button */ + char* variable; /* Variable name in radio button */ + int ul; + int check; /* check/radiobutton/choicebutton state */ + int indicator; /* -indicatoron setting */ + char* menu; + + char** values; + int nvalues; + /* current value of choicebutton is represented by check */ +}; + +/* Layout constants */ +enum { + Textpadx = 3, + Textpady = 0, + Bitpadx = 0, /* Bitmap padding in labels */ + Bitpady = 0, + CheckButton = 10, + CheckButtonBW = 2, + ButtonBorder = 4, +}; + +extern TkOption tkbutopts[]; +extern TkOption tkradopts[]; +extern TkOption tkcbopts[]; + +/* label.c */ +extern char* tklabelsaverelief(Tk*, char*, char**); +extern char* tklabelrestorerelief(Tk*, char*, char**); +extern void tksizelabel(Tk*); +extern char* tkdrawlabel(Tk*, Point); +extern void tkfreelabel(Tk*); +extern void tklabelgetimgs(Tk*, Image**, Image**); +extern char* tksetvar(TkTop*, char*, char*); + +/* buton.c */ +extern Tk* tkmkbutton(TkTop*, int); +extern void tksizebutton(Tk*); +extern char* tkbuttoninvoke(Tk*, char*, char**); +extern char* tkradioinvoke(Tk*, char*, char**); + +/* support for menus */ +extern int tklabelmargin(Tk*); +extern int tkbuttonmargin(Tk*); diff --git a/libtk/listb.c b/libtk/listb.c new file mode 100644 index 00000000..af8850bd --- /dev/null +++ b/libtk/listb.c @@ -0,0 +1,1065 @@ +#include "lib9.h" +#include "draw.h" +#include "keyboard.h" +#include "tk.h" +#include "listb.h" + +#define O(t, e) ((long)(&((t*)0)->e)) + +/* Layout constants */ +enum { + Listpadx = 2, /* X padding of text in listboxes */ +}; + +typedef struct TkLentry TkLentry; +typedef struct TkListbox TkListbox; + +struct TkLentry +{ + TkLentry* link; + int flag; + int width; + char text[TKSTRUCTALIGN]; +}; + +struct TkListbox +{ + TkLentry* head; + TkLentry* anchor; + TkLentry* active; + int yelem; /* Y element at top of box */ + int xdelta; /* h-scroll position */ + int nitem; + int nwidth; + int selmode; + int sborderwidth; + char* xscroll; + char* yscroll; +}; + +TkStab tkselmode[] = +{ + "single", TKsingle, + "browse", TKbrowse, + "multiple", TKmultiple, + "extended", TKextended, + nil +}; + +static +TkOption opts[] = +{ + "xscrollcommand", OPTtext, O(TkListbox, xscroll), nil, + "yscrollcommand", OPTtext, O(TkListbox, yscroll), nil, + "selectmode", OPTstab, O(TkListbox, selmode), tkselmode, + "selectborderwidth", OPTnndist, O(TkListbox, sborderwidth), nil, + nil +}; + +static +TkEbind b[] = +{ + {TkButton1P, "%W tkListbButton1P %y"}, + {TkButton1R, "%W tkListbButton1R"}, + {TkButton1P|TkMotion, "%W tkListbButton1MP %y"}, + {TkMotion, ""}, + {TkKey, "%W tkListbKey 0x%K"}, +}; + + +static int +lineheight(Tk *tk) +{ + TkListbox *l = TKobj(TkListbox, tk); + return tk->env->font->height+2*(l->sborderwidth+tk->highlightwidth); +} + +char* +tklistbox(TkTop *t, char *arg, char **ret) +{ + Tk *tk; + char *e; + TkName *names; + TkListbox *tkl; + TkOptab tko[3]; + + tk = tknewobj(t, TKlistbox, sizeof(Tk)+sizeof(TkListbox)); + if(tk == nil) + return TkNomem; + + tkl = TKobj(TkListbox, tk); + tkl->sborderwidth = 1; + tk->relief = TKsunken; + tk->borderwidth = 2; + tk->highlightwidth = 1; + tk->flag |= Tktakefocus; + tk->req.width = 170; + tk->req.height = lineheight(tk)*10; + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tkl; + tko[1].optab = opts; + tko[2].ptr = nil; + + names = nil; + e = tkparse(t, arg, tko, &names); + if(e != nil) { + tkfreeobj(tk); + return e; + } + tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd)); + + e = tkbindings(t, tk, b, nelem(b)); + if(e != nil) { + tkfreeobj(tk); + return e; + } + + e = tkaddchild(t, tk, &names); + tkfreename(names); + if(e != nil) { + tkfreeobj(tk); + return e; + } + tk->name->link = nil; + + return tkvalue(ret, "%s", tk->name->name); +} + +char* +tklistbcget(Tk *tk, char *arg, char **val) +{ + TkOptab tko[3]; + TkListbox *tkl = TKobj(TkListbox, tk); + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tkl; + tko[1].optab = opts; + tko[2].ptr = nil; + + return tkgencget(tko, arg, val, tk->env->top); +} + +void +tkfreelistb(Tk *tk) +{ + TkLentry *e, *next; + TkListbox *l = TKobj(TkListbox, tk); + + for(e = l->head; e; e = next) { + next = e->link; + free(e); + } + if(l->xscroll != nil) + free(l->xscroll); + if(l->yscroll != nil) + free(l->yscroll); +} + +char* +tkdrawlistb(Tk *tk, Point orig) +{ + Point p; + TkEnv *env; + TkLentry *e; + int lh, w, n, ly; + Rectangle r, a; + Image *i, *fg; + TkListbox *l = TKobj(TkListbox, tk); + + env = tk->env; + + r.min = ZP; + r.max.x = tk->act.width + 2*tk->borderwidth; + r.max.y = tk->act.height + 2*tk->borderwidth; + i = tkitmp(env, r.max, TkCbackgnd); + if(i == nil) + return nil; + + w = tk->act.width; + if (w < l->nwidth) + w = l->nwidth; + lh = lineheight(tk); + ly = tk->borderwidth; + p.x = tk->borderwidth+l->sborderwidth+tk->highlightwidth+Listpadx-l->xdelta; + p.y = tk->borderwidth+l->sborderwidth+tk->highlightwidth; + n = 0; + for(e = l->head; e && ly < r.max.y; e = e->link) { + if(n++ < l->yelem) + continue; + + a.min.x = tk->borderwidth; + a.min.y = ly; + a.max.x = a.min.x + tk->act.width; + a.max.y = a.min.y + lh; + if(e->flag & Tkactivated) { + draw(i, a, tkgc(env, TkCselectbgnd), nil, ZP); + } + + if(e->flag & Tkactivated) + fg = tkgc(env, TkCselectfgnd); + else + fg = tkgc(env, TkCforegnd); + string(i, p, fg, p, env->font, e->text); + if((e->flag & Tkactive) && tkhaskeyfocus(tk)) { + a.min.x = tk->borderwidth-l->xdelta; + a.max.x = a.min.x+w; + a = insetrect(a, l->sborderwidth); + tkbox(i, a, tk->highlightwidth, fg); + } + ly += lh; + p.y += lh; + } + + tkdrawrelief(i, tk, ZP, TkCbackgnd, tk->relief); + + p.x = tk->act.x + orig.x; + p.y = tk->act.y + orig.y; + r = rectaddpt(r, p); + draw(tkimageof(tk), r, i, nil, ZP); + + return nil; +} + +int +tklindex(Tk *tk, char *buf) +{ + int index; + TkListbox *l; + TkLentry *e, *s; + + l = TKobj(TkListbox, tk); + + if(*buf == '@') { + while(*buf && *buf != ',') + buf++; + index = l->yelem + atoi(buf+1)/lineheight(tk); + if (index < 0) + return 0; + if (index > l->nitem) + return l->nitem; + return index; + } + if(*buf >= '0' && *buf <= '9') + return atoi(buf); + + if(strcmp(buf, "end") == 0) { + if(l->nitem == 0) + return 0; + return l->nitem-1; + } + + index = 0; + if(strcmp(buf, "active") == 0) + s = l->active; + else + if(strcmp(buf, "anchor") == 0) + s = l->anchor; + else + return -1; + + for(e = l->head; e; e = e->link) { + if(e == s) + return index; + index++; + } + return -1; +} + +void +tklistsv(Tk *tk) +{ + TkListbox *l; + int nl, lh, top, bot; + char val[Tkminitem], cmd[Tkmaxitem], *v, *e; + + l = TKobj(TkListbox, tk); + if(l->yscroll == nil) + return; + + top = 0; + bot = TKI2F(1); + + if(l->nitem != 0) { + lh = lineheight(tk); + nl = tk->act.height/lh; /* Lines in the box */ + top = TKI2F(l->yelem)/l->nitem; + bot = TKI2F(l->yelem+nl)/l->nitem; + } + + v = tkfprint(val, top); + *v++ = ' '; + tkfprint(v, bot); + snprint(cmd, sizeof(cmd), "%s %s", l->yscroll, val); + e = tkexec(tk->env->top, cmd, nil); + if ((e != nil) && (tk->name != nil)) + print("tk: yscrollcommand \"%s\": %s\n", tk->name->name, e); +} + +void +tklistsh(Tk *tk) +{ + int nl, top, bot; + char val[Tkminitem], cmd[Tkmaxitem], *v, *e; + TkListbox *l = TKobj(TkListbox, tk); + + if(l->xscroll == nil) + return; + + top = 0; + bot = TKI2F(1); + + if(l->nwidth != 0) { + nl = tk->act.width; + top = TKI2F(l->xdelta)/l->nwidth; + bot = TKI2F(l->xdelta+nl)/l->nwidth; + } + + v = tkfprint(val, top); + *v++ = ' '; + tkfprint(v, bot); + snprint(cmd, sizeof(cmd), "%s %s", l->xscroll, val); + e = tkexec(tk->env->top, cmd, nil); + if ((e != nil) && (tk->name != nil)) + print("tk: xscrollcommand \"%s\": %s\n", tk->name->name, e); +} + +void +tklistbgeom(Tk *tk) +{ + tklistsv(tk); + tklistsh(tk); +} + +static void +listbresize(Tk *tk) +{ + TkLentry *e; + TkListbox *l = TKobj(TkListbox, tk); + + l->nwidth = 0; + for (e = l->head; e != nil; e = e->link) { + e->width = stringwidth(tk->env->font, e->text)+2*(Listpadx+l->sborderwidth+tk->highlightwidth); + if(e->width > l->nwidth) + l->nwidth = e->width; + } + tklistbgeom(tk); +} + + +/* Widget Commands (+ means implemented) + +activate + bbox + +cget + +configure + +curselection + +delete + +get + +index + +insert + +nearest + +see + +selection + +size + +xview + +yview +*/ + +char* +tklistbconf(Tk *tk, char *arg, char **val) +{ + char *e; + TkGeom g; + int bd, sbw, hlw; + TkOptab tko[3]; + Font *f; + TkListbox *tkl = TKobj(TkListbox, tk); + + sbw = tkl->sborderwidth; + hlw = tk->highlightwidth; + f = tk->env->font; + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tkl; + tko[1].optab = opts; + 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)); + tkgeomchg(tk, &g, bd); + + if (sbw != tkl->sborderwidth || f != tk->env->font || hlw != tk->highlightwidth) + listbresize(tk); + tk->dirty = tkrect(tk, 1); + return e; +} + +static void +entryactivate(Tk *tk, int index) +{ + TkListbox *l = TKobj(TkListbox, tk); + TkLentry *e; + int flag = Tkactive; + + if (l->selmode == TKbrowse) + flag |= Tkactivated; + for(e = l->head; e; e = e->link) { + if(index-- == 0) { + e->flag |= flag; + l->active = e; + } else + e->flag &= ~flag; + } + tk->dirty = tkrect(tk, 1); +} + +char* +tklistbactivate(Tk *tk, char *arg, char **val) +{ + int index; + char buf[Tkmaxitem]; + + USED(val); + tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + index = tklindex(tk, buf); + if(index == -1) + return TkBadix; + + entryactivate(tk, index); + return nil; +} + +char* +tklistbnearest(Tk *tk, char *arg, char **val) +{ + int lh, y, index; + TkListbox *l = TKobj(TkListbox, tk); + + lh = lineheight(tk); /* Line height */ + y = atoi(arg); + index = l->yelem + y/lh; + if(index > l->nitem) + index = l->nitem; + return tkvalue(val, "%d", index); +} + +char* +tklistbindex(Tk *tk, char *arg, char **val) +{ + int index; + char buf[Tkmaxitem]; + tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + index = tklindex(tk, buf); + if(index == -1) + return TkBadix; + return tkvalue(val, "%d", index); +} + +char* +tklistbsize(Tk *tk, char *arg, char **val) +{ + TkListbox *l = TKobj(TkListbox, tk); + + USED(arg); + return tkvalue(val, "%d", l->nitem); +} + +char* +tklistbinsert(Tk *tk, char *arg, char **val) +{ + int n, index; + TkListbox *l; + TkLentry *e, **el; + char *tbuf, buf[Tkmaxitem]; + + USED(val); + l = TKobj(TkListbox, tk); + + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + if(strcmp(buf, "end") == 0) { + el = &l->head; + if(*el != nil) { + for(e = *el; e->link; e = e->link) + ; + el = &e->link; + } + } + else { + index = tklindex(tk, buf); + if(index == -1) + return TkBadix; + el = &l->head; + for(e = *el; e && index-- > 0; e = e->link) + el = &e->link; + } + + n = strlen(arg); + if(n > Tkmaxitem) { + n = (n*3)/2; + tbuf = malloc(n); + if(tbuf == nil) + return TkNomem; + } + else { + tbuf = buf; + n = sizeof(buf); + } + + while(*arg) { + arg = tkword(tk->env->top, arg, tbuf, &tbuf[n], nil); + e = malloc(sizeof(TkLentry)+strlen(tbuf)+1); + if(e == nil) + return TkNomem; + + e->flag = 0; + strcpy(e->text, tbuf); + e->link = *el; + *el = e; + el = &e->link; + e->width = stringwidth(tk->env->font, e->text)+2*(Listpadx+l->sborderwidth+tk->highlightwidth); + if(e->width > l->nwidth) + l->nwidth = e->width; + l->nitem++; + } + + if(tbuf != buf) + free(tbuf); + + tklistbgeom(tk); + tk->dirty = tkrect(tk, 1); + return nil; +} + +int +tklistbrange(Tk *tk, char *arg, int *s, int *e) +{ + char buf[Tkmaxitem]; + + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + *s = tklindex(tk, buf); + if(*s == -1) + return -1; + *e = *s; + if(*arg == '\0') + return 0; + + tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + *e = tklindex(tk, buf); + if(*e == -1) + return -1; + return 0; +} + +char* +tklistbselection(Tk *tk, char *arg, char **val) +{ + TkTop *t; + TkLentry *f; + TkListbox *l; + int s, e, indx; + char buf[Tkmaxitem]; + + l = TKobj(TkListbox, tk); + + t = tk->env->top; + arg = tkword(t, arg, buf, buf+sizeof(buf), nil); + if(strcmp(buf, "includes") == 0) { + tkword(t, arg, buf, buf+sizeof(buf), nil); + indx = tklindex(tk, buf); + if(indx == -1) + return TkBadix; + for(f = l->head; f && indx > 0; f = f->link) + indx--; + s = 0; + if(f && (f->flag&Tkactivated)) + s = 1; + return tkvalue(val, "%d", s); + } + + if(strcmp(buf, "anchor") == 0) { + tkword(t, arg, buf, buf+sizeof(buf), nil); + indx = tklindex(tk, buf); + if(indx == -1) + return TkBadix; + for(f = l->head; f && indx > 0; f = f->link) + indx--; + if(f != nil) + l->anchor = f; + return nil; + } + indx = 0; + if(strcmp(buf, "clear") == 0) { + if(tklistbrange(tk, arg, &s, &e) != 0) + return TkBadix; + for(f = l->head; f; f = f->link) { + if(indx <= e && indx++ >= s) + f->flag &= ~Tkactivated; + } + tk->dirty = tkrect(tk, 1); + return nil; + } + if(strcmp(buf, "set") == 0) { + if(tklistbrange(tk, arg, &s, &e) != 0) + return TkBadix; + for(f = l->head; f; f = f->link) { + if(indx <= e && indx++ >= s) + f->flag |= Tkactivated; + } + tk->dirty = tkrect(tk, 1); + return nil; + } + return TkBadcm; +} + +char* +tklistbdelete(Tk *tk, char *arg, char **val) +{ + TkLentry *e, **el; + int start, end, indx, bh; + TkListbox *l = TKobj(TkListbox, tk); + + USED(val); + if(tklistbrange(tk, arg, &start, &end) != 0) + return TkBadix; + + indx = 0; + el = &l->head; + for(e = l->head; e && indx < start; e = e->link) { + indx++; + el = &e->link; + } + while(e != nil && indx <= end) { + *el = e->link; + if(e->width == l->nwidth) + l->nwidth = 0; + if (e == l->anchor) + l->anchor = nil; + if (e == l->active) + l->active = nil; + free(e); + e = *el; + indx++; + l->nitem--; + } + if(l->nwidth == 0) { + for(e = l->head; e; e = e->link) + if(e->width > l->nwidth) + l->nwidth = e->width; + } + bh = tk->act.height/lineheight(tk); /* Box height */ + if(l->yelem + bh > l->nitem) + l->yelem = l->nitem - bh; + if(l->yelem < 0) + l->yelem = 0; + + tklistbgeom(tk); + tk->dirty = tkrect(tk, 1); + return nil; +} + +char* +tklistbget(Tk *tk, char *arg, char **val) +{ + TkLentry *e; + char *r, *fmt; + int start, end, indx; + TkListbox *l = TKobj(TkListbox, tk); + + if(tklistbrange(tk, arg, &start, &end) != 0) + return TkBadix; + + indx = 0; + for(e = l->head; e && indx < start; e = e->link) + indx++; + fmt = "%s"; + while(e != nil && indx <= end) { + r = tkvalue(val, fmt, e->text); + if(r != nil) + return r; + indx++; + fmt = " %s"; + e = e->link; + } + return nil; +} + +char* +tklistbcursel(Tk *tk, char *arg, char **val) +{ + int indx; + TkLentry *e; + char *r, *fmt; + TkListbox *l = TKobj(TkListbox, tk); + + USED(arg); + indx = 0; + fmt = "%d"; + for(e = l->head; e; e = e->link) { + if(e->flag & Tkactivated) { + r = tkvalue(val, fmt, indx); + if(r != nil) + return r; + fmt = " %d"; + } + indx++; + } + return nil; +} + +static char* +tklistbview(Tk *tk, char *arg, char **val, int nl, int *posn, int max) +{ + int top, bot, amount; + char buf[Tkmaxitem]; + char *v, *e; + + top = 0; + if(*arg == '\0') { + if ( max <= nl || max == 0 ) { /* Double test redundant at + * this time, but want to + * protect against future + * calls. -- DBK */ + top = 0; + bot = TKI2F(1); + } + else { + top = TKI2F(*posn)/max; + bot = TKI2F(*posn+nl)/max; + } + 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+1)*max); + } + else + if(strcmp(buf, "scroll") == 0) { + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + amount = atoi(buf); + tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + if(buf[0] == 'p') /* Pages */ + amount *= nl; + *posn += amount; + } + else { + top = tklindex(tk, buf); + if(top == -1) + return TkBadix; + *posn = top; + } + + bot = max - nl; + if(*posn > bot) + *posn = bot; + if(*posn < 0) + *posn = 0; + + tk->dirty = tkrect(tk, 1); + return nil; +} + +static int +entrysee(Tk *tk, int index) +{ + TkListbox *l = TKobj(TkListbox, tk); + int bh; + + /* Box height in lines */ + bh = tk->act.height/lineheight(tk); + if (bh > l->nitem) + bh = l->nitem; + if (index >= l->nitem) + index = l->nitem -1; + if (index < 0) + index = 0; + + /* If the item is already visible, do nothing */ + if (l->nitem == 0 || index >= l->yelem && index < l->yelem+bh) + return index; + + if (index < l->yelem) + l->yelem = index; + else + l->yelem = index - (bh-1); + + tklistsv(tk); + tk->dirty = tkrect(tk, 1); + return index; +} + +char* +tklistbsee(Tk *tk, char *arg, char **val) +{ + int index; + char buf[Tkmaxitem]; + + USED(val); + tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + index = tklindex(tk, buf); + if(index == -1) + return TkBadix; + + entrysee(tk, index); + return nil; +} + +char* +tklistbyview(Tk *tk, char *arg, char **val) +{ + int bh; + char *e; + TkListbox *l = TKobj(TkListbox, tk); + + bh = tk->act.height/lineheight(tk); /* Box height */ + e = tklistbview(tk, arg, val, bh, &l->yelem, l->nitem); + tklistsv(tk); + return e; +} + +char* +tklistbxview(Tk *tk, char *arg, char **val) +{ + char *e; + TkListbox *l = TKobj(TkListbox, tk); + + e = tklistbview(tk, arg, val, tk->act.width, &l->xdelta, l->nwidth); + tklistsh(tk); + return e; +} + +static TkLentry* +entryset(TkListbox *l, int indx, int toggle) +{ + TkLentry *e, *anchor; + + anchor = nil; + for(e = l->head; e; e = e->link) { + if (indx-- == 0) { + anchor = e; + if (toggle) { + e->flag ^= Tkactivated; + break; + } else + e->flag |= Tkactivated; + continue; + } + if (!toggle) + e->flag &= ~Tkactivated; + } + return anchor; +} + +static void +selectto(TkListbox *l, int indx) +{ + TkLentry *e; + int inrange; + + if (l->anchor == nil) + return; + inrange = 0; + for(e = l->head; e; e = e->link) { + if(indx == 0) + inrange = !inrange; + if(e == l->anchor) + inrange = !inrange; + if(inrange || e == l->anchor || indx == 0) + e->flag |= Tkactivated; + else + e->flag &= ~Tkactivated; + indx--; + } +} + +static char* +dragto(Tk *tk, int y) +{ + int indx; + TkLentry *e; + TkListbox *l = TKobj(TkListbox, tk); + + indx = y/lineheight(tk); + if (y < 0) + indx--; /* int division rounds towards 0 */ + if (y < tk->act.height && indx >= l->nitem) + return nil; + indx = entrysee(tk, l->yelem+indx); + entryactivate(tk, indx); + + if(l->selmode == TKsingle || l->selmode == TKmultiple) + return nil; + + if(l->selmode == TKbrowse) { + for(e = l->head; e; e = e->link) { + if(indx-- == 0) { + if (e == l->anchor) + return nil; + l->anchor = e; + e->flag |= Tkactivated; + } else + e->flag &= ~Tkactivated; + } + return nil; + } + /* extended selection mode */ + selectto(l, indx); + tk->dirty = tkrect(tk, 1); + return nil; +} + +static void +autoselect(Tk *tk, void *v, int cancelled) +{ + Point pt; + int y, eh, ne; + + USED(v); + if (cancelled) + return; + + pt = tkposn(tk); + pt.y += tk->borderwidth; + y = tk->env->top->ctxt->mstate.y; + y -= pt.y; + eh = lineheight(tk); + ne = tk->act.height/eh; + if (y >= 0 && y < eh*ne) + return; + dragto(tk, y); + tkdirty(tk); + tkupdate(tk->env->top); +} + +static char* +tklistbbutton1p(Tk *tk, char *arg, char **val) +{ + TkListbox *l = TKobj(TkListbox, tk); + int y, indx; + + USED(val); + + y = atoi(arg); + indx = y/lineheight(tk); + indx += l->yelem; + if (indx < l->nitem) { + l->anchor = entryset(l, indx, l->selmode == TKmultiple); + entryactivate(tk, indx); + entrysee(tk, indx); + } + tkrepeat(tk, autoselect, nil, TkRptpause, TkRptinterval); + return nil; +} + +char * +tklistbbutton1r(Tk *tk, char *arg, char **val) +{ + USED(arg); + USED(val); + tkcancelrepeat(tk); + return nil; +} + +char* +tklistbbutton1m(Tk *tk, char *arg, char **val) +{ + int y, eh, ne; + USED(val); + + eh = lineheight(tk); + ne = tk->act.height/eh; + y = atoi(arg); + /* If outside the box, let autoselect handle it */ + if (y < 0 || y >= ne * eh) + return nil; + return dragto(tk, y); +} + +char* +tklistbkey(Tk *tk, char *arg, char **val) +{ + TkListbox *l = TKobj(TkListbox, tk); + TkLentry *e; + int key, active; + USED(val); + + if(tk->flag & Tkdisabled) + return nil; + + key = atoi(arg); + active = 0; + for (e = l->head; e != nil; e = e->link) { + if (e->flag & Tkactive) + break; + active++; + } + + if (key == '\n' || key == ' ') { + l->anchor = entryset(l, active, l->selmode == TKmultiple); + tk->dirty = tkrect(tk, 0); + return nil; + } + if (key == Up) + active--; + else if (key == Down) + active++; + else + return nil; + + if (active < 0) + active = 0; + if (active >= l->nitem) + active = l->nitem-1; + entryactivate(tk, active); + if (l->selmode == TKextended) { + selectto(l, active); + tk->dirty = tkrect(tk, 0); + } + entrysee(tk, active); + return nil; +} + +static +TkCmdtab tklistcmd[] = +{ + "activate", tklistbactivate, + "cget", tklistbcget, + "configure", tklistbconf, + "curselection", tklistbcursel, + "delete", tklistbdelete, + "get", tklistbget, + "index", tklistbindex, + "insert", tklistbinsert, + "nearest", tklistbnearest, + "selection", tklistbselection, + "see", tklistbsee, + "size", tklistbsize, + "xview", tklistbxview, + "yview", tklistbyview, + "tkListbButton1P", tklistbbutton1p, + "tkListbButton1R", tklistbbutton1r, + "tkListbButton1MP", tklistbbutton1m, + "tkListbKey", tklistbkey, + nil +}; + +TkMethod listboxmethod = { + "listbox", + tklistcmd, + tkfreelistb, + tkdrawlistb, + tklistbgeom +}; diff --git a/libtk/listb.h b/libtk/listb.h new file mode 100644 index 00000000..6a81c664 --- /dev/null +++ b/libtk/listb.h @@ -0,0 +1 @@ +extern char* tklistbselection(Tk*, char*, char**); diff --git a/libtk/mail.tk b/libtk/mail.tk new file mode 100644 index 00000000..abb85d9c --- /dev/null +++ b/libtk/mail.tk @@ -0,0 +1,224 @@ + +# This is the first screen of the Sun mailtool + +# Frame .frame1 contains top-row buttons, label and entry +# Frame .frame2 contains bottom-row buttons +frame .frame1 -relief flat -bd 2 +frame .frame1.frame11 -relief flat -bd 2 +frame .frame1.frame12 -relief flat -bd 2 +frame .frame2 -relief flat -bd 2 +frame .frame2.frame21 -relief flat -bd 2 +frame .frame2.frame22 -relief flat -bd 2 +#frame .dummy -width 18c +pack .frame1 .frame2 -side top -fill x +pack .frame2.frame21 -side left -fill x +pack .frame2.frame22 -side left -fill x +pack .frame1.frame11 -side left -fill x +pack .frame1.frame12 -side left -fill x +# Scrolltext frame +frame .frame3 -relief sunken -bd 2 +frame .frame3.frame + +# File View Edit Compose buttons and associated menus + +# Build File button +menubutton .frame1.frame11.file -text {File} -relief raised -width 8 \ + -menu {.frame1.frame11.file.menu} + +# Build File-menu +menu .frame1.frame11.file.menu +.frame1.frame11.file.menu add command \ + -label {Load In-Box} \ + -state {active} +.frame1.frame11.file.menu add command \ + -label {Print} +.frame1.frame11.file.menu add command \ + -label {Save Changes} +.frame1.frame11.file.menu add command \ + -label {Done} +.frame1.frame11.file.menu add command \ + -label {Mail Files...} + +# Build View button +menubutton .frame1.frame11.view -text {View} -relief raised -width 8 \ + -menu {.frame1.frame11.view.menu} + +# Build View-menu +menu .frame1.frame11.view.menu + +.frame1.frame11.view.menu add command \ + -label {Messages} +.frame1.frame11.view.menu add command \ + -label {Previous} +.frame1.frame11.view.menu add command \ + -label {Next} \ + -state {active} +.frame1.frame11.view.menu add command \ + -label {Sort By} +.frame1.frame11.view.menu add command \ + -label {Find...} + +# Build Edit button +menubutton .frame1.frame11.edit -text {Edit} -relief raised -width 8 \ + -menu {.frame1.frame11.edit.menu} + +# Build Edit-menu + menu .frame1.frame11.edit.menu + .frame1.frame11.edit.menu add command \ + -label {Cut} + .frame1.frame11.edit.menu add command \ + -label {Copy} + .frame1.frame11.edit.menu add command \ + -label {Delete} + .frame1.frame11.edit.menu add command \ + -label {Undelete} + .frame1.frame11.edit.menu add separator + .frame1.frame11.edit.menu add command \ + -label {Properties....} + + +# Build Compose button +menubutton .frame1.frame11.compose -text {Compose} -relief raised -width 12 \ + -menu {.frame1.frame11.compose.menu} + +# Build Compose-menu + menu .frame1.frame11.compose.menu + .frame1.frame11.compose.menu add command \ + -label {New} + .frame1.frame11.compose.menu add command \ + -label {Reply} + .frame1.frame11.compose.menu add command \ + -label {Forward} + .frame1.frame11.compose.menu add separator + .frame1.frame11.compose.menu add command \ + -label { Vacation} + +# Pack the buttons File, View, Edit, Compose +pack .frame1.frame11.file \ + .frame1.frame11.view \ + .frame1.frame11.edit \ + .frame1.frame11.compose \ + -side left + +update + +# Build Done, Next, Delete, Reply buttons and associated menus +# Build Done button +menubutton .frame2.frame21.done -text {Done} -relief raised -width 8 \ + -menu {.frame2.frame21.done.menu} + +# Build Done-menu (empty) +menu .frame2.frame21.done.menu + +# Build Next button +menubutton .frame2.frame21.next -text {Next} -relief raised -width 8 \ + -menu {.frame2.frame21.next.menu} + +# Build Next-menu (empty) +menu .frame2.frame21.next.menu + +# Build Delete button +menubutton .frame2.frame21.delete -text {Delete} -relief raised -width 8 \ + -menu {.frame2.frame21.delete.menu} + +# Build Delete-menu (empty) +menu .frame2.frame21.delete.menu + +# Build Reply button +menubutton .frame2.frame21.reply -text {Reply} -relief raised -width 12 \ + -menu {.frame2.frame21.reply.menu} + +# Build Reply-menu +menu .frame2.frame21.reply.menu + .frame2.frame21.reply.menu add command \ + -label {To Sender} + .frame2.frame21.reply.menu add command \ + -label {To All} + .frame2.frame21.reply.menu add command \ + -label {To Sender, Include} + .frame2.frame21.reply.menu add command \ + -label {To All, Include} + +# Pack buttons Done, Next, Delete, Reply +pack .frame2.frame21.done \ + .frame2.frame21.next \ + .frame2.frame21.delete \ + .frame2.frame21.reply \ + -side left + +update + +# Build buttons Move, Copy, Load and associated menus +menubutton .frame2.frame22.move -text {Move} -relief raised -width 8 \ + -menu {.frame2.frame22.move.menu} +menu .frame2.frame22.move.menu +.frame2.frame22.move.menu add command \ + -label {Entry} + +menubutton .frame2.frame22.copy -text {Copy} -relief raised -width 8 \ + -menu {.frame2.frame22.copy.menu} +menu .frame2.frame22.copy.menu +.frame2.frame22.copy.menu add command \ + -label {Entry} + +menubutton .frame2.frame22.load -text {Load} -relief raised -width 8 \ + -menu {.frame2.frame22.load.menu} +menu .frame2.frame22.load.menu +.frame2.frame22.load.menu add command \ + -label {Entry} + +pack .frame2.frame22.move \ + .frame2.frame22.copy \ + .frame2.frame22.load \ + -side left -fill x + +update + +# Build Mail-File label and Dir-Path entry widgets +label .frame1.frame12.lab_mailfile -relief flat -text {Mail File: } +entry .frame1.frame12.entry_mailfile -width 30 -relief sunken -bd 2 +pack .frame1.frame12.entry_mailfile -side right +pack .frame1.frame12.lab_mailfile -side left + +update + +# Build scrolltext w/ scrollbars +scrollbar .frame3.frame.scrollbar1 \ + -command {.frame3.frame.listbox1 xview} \ + -orient {horizontal} \ + -relief {raised} +scrollbar .frame3.frame.scrollbar2 \ + -command {.frame3.frame.listbox1 yview} \ + -relief {raised} +text .frame3.frame.listbox1 \ + -relief {raised} \ + -xscrollcommand {.frame3.frame.scrollbar1 set} \ + -yscrollcommand {.frame3.frame.scrollbar2 set} +.frame3.frame.listbox1 insert end {Some Text} +pack .frame3.frame +pack .frame3.frame.scrollbar2 -side left -fill y +pack .frame3.frame.listbox1 -side top -expand 1 -fill both +pack .frame3.frame.scrollbar1 -side bottom -fill x + +# Pack frame3 to the rest of container frames +pack .frame3 .frame3.frame -side top -expand 1 -fill both + +update + +# Build label(???) at the bottom +label .lab_msgnum -relief flat +pack .lab_msgnum -side top -fill x + +# Now make everything visible +update + +# Enable keyboard traversal of a menu (Is this needed in inferno Tk?) +#tk_menuBar .frame1.frame11 .frame1.frame11.file .frame1.frame11.view \ + #.frame1.frame11.edit .frame1.frame11.compose +#tk_menuBar .frame2.frame21 .frame2.frame21.done .frame2.frame21.next \ + #.frame2.frame21.delete .frame2.frame21.reply \ + #.frame2.frame22 .frame2.frame22.move .frame2.frame22.copy .frame2.frame22.load +focus .frame1.frame11 +update + + diff --git a/libtk/menu.tk b/libtk/menu.tk new file mode 100644 index 00000000..0bacab34 --- /dev/null +++ b/libtk/menu.tk @@ -0,0 +1,49 @@ +frame .spacer -width 10c -height 5c + +listbox .spacer.l -yscrollcommand {.spacer.scroll set} -xscrollcommand {.scrollh set} +scrollbar .spacer.scroll -command {.spacer.l yview} +scrollbar .scrollh -orient horizontal -command {.spacer.l xview} -width [.spacer.l cget width] +.spacer.l insert end This is some text for the +.spacer.l insert end listboxandthisentryisverymuchlongerthattherest +.spacer.l insert end and here is some more stuff to box +.spacer.l insert end and here is some more stuff to box +.spacer.l insert end and here is some more stuff to box +.spacer.l insert end and here is some more stuff to box +.spacer.l insert end and here is some more stuff to box + +canvas .c -width 5c -height 3c +.c create rectangle 1c 1c 2c 2c -fill red +.c create line 1c 1c 2c 2c -arrow both + +pack .spacer.l .spacer.scroll .c -side left -fill y +.spacer.l selection set 3 + +frame .mbar -relief raised -bd 2 +pack .mbar -fill x +pack .mbar .spacer .scrollh -anchor w + +menubutton .mbar.file -text File -menu .mbar.file.m -underline 0 +menubutton .mbar.edit -text Edit -menu .mbar.edit.m -underline 0 +pack .mbar.file .mbar.edit -side left + +menu .mbar.file.m +.mbar.file.m add checkbutton -label Italic +.mbar.file.m add checkbutton -label Bold +.mbar.file.m add checkbutton -label Underline +.mbar.file.m add separator +.mbar.file.m add command -label Quit +.mbar.file.m add command -label Dictionary +.mbar.file.m add cascade -label Search -menu .mbar.file.m.global + +menu .mbar.edit.m +.mbar.edit.m add checkbutton -label Italic +.mbar.edit.m add checkbutton -label Bold +.mbar.edit.m add checkbutton -label Underline + +menu .mbar.file.m.global +.mbar.file.m.global add checkbutton -label "\{Case Sensitive\}" +.mbar.file.m.global add checkbutton -label Forward +.mbar.file.m.global add checkbutton -label Backward +.mbar.file.m.global add radiobutton -label "\{Upper case\}" -variable case -value u +.mbar.file.m.global add radiobutton -label "\{Lower case\}" -variable case -value l +.mbar.file.m.global add radiobutton -label "\{Case Insensitive\}" -variable case -value i diff --git a/libtk/menus.c b/libtk/menus.c new file mode 100644 index 00000000..ca8f424c --- /dev/null +++ b/libtk/menus.c @@ -0,0 +1,1840 @@ +#include "lib9.h" +#include "draw.h" +#include "keyboard.h" +#include "tk.h" +#include "frame.h" +#include "label.h" + +/* +arrow annotation for choicebutton: how do we make sure +the menu items come up the same size? + - set menu items to same req.width & height as button itself. + +autorepeat: +when we get mouse event at the edge of the screen +and the menu overlaps that edge, +start autorepeat timer to slide the menu the opposite direction. + +variable setting + command invocation: +is the value of the variable the text or the index? +same for the value appended to the command, text or index? + +if it's reimplemented as a custom widget, how does the custom widget +get notified of variable changes? +*/ + +/* Widget Commands (+ means implemented) + +activate + +add + +cget + +configure + +delete + +entrycget + +entryconfigure + +index + +insert + +invoke + +post + +postcascade + +type + +unpost + +yposition +*/ + +#define O(t, e) ((long)(&((t*)0)->e)) + +/* Layout constants */ +enum { + Sepheight = 6, /* Height of menu separator */ +}; + +#define NOCHOICE "-----" + +enum { + Startspeed = TKI2F(1), +}; + +static +TkOption mbopts[] = +{ + "text", OPTtext, O(TkLabel, text), nil, + "anchor", OPTflag, O(TkLabel, anchor), tkanchor, + "underline", OPTdist, O(TkLabel, ul), nil, + "justify", OPTstab, O(TkLabel, justify), tkjustify, + "menu", OPTtext, O(TkLabel, menu), nil, + "bitmap", OPTbmap, O(TkLabel, bitmap), nil, + "image", OPTimag, O(TkLabel, img), nil, + nil +}; + +static +TkOption choiceopts[] = +{ + "variable", OPTtext, O(TkLabel, variable), nil, + "values", OPTlist, O(TkLabel, values), nil, + "command", OPTtext, O(TkLabel, command), nil, + nil +}; + +static +TkEbind mbbindings[] = +{ + {TkEnter, "%W tkMBenter %s"}, + {TkLeave, "%W tkMBleave"}, + {TkButton1P, "%W tkMBpress 1"}, + {TkKey, "%W tkMBkey 0x%K"}, + {TkButton1P|TkMotion, "%W tkMBpress 0"}, +}; + +extern Rectangle bbnil; +static char* tkmpost(Tk*, int, int, int, int, int); +static void menuclr(Tk*); +static void freemenu(Tk*); +static void appenditem(Tk*, Tk*, int); +static void layout(Tk*); +static Tk* tkmenuindex2ptr(Tk*, char**); +static void activateitem(Tk*); + +/* + * unmap menu cascade upto (but not including) tk + */ +static void +tkunmapmenus(TkTop *top, Tk *tk) +{ + TkTop *t; + Tk *menu; + TkWin *tkw; + + menu = top->ctxt->tkmenu; + if (menu == nil) + return; + t = menu->env->top; + + /* if something went wrong, clear down all menus */ + if (tk != nil && tk->env->top != t) + tk = nil; + + while (menu != nil && menu != tk) { + menuclr(menu); + tkunmap(menu); + tkcancelrepeat(menu); + tkw = TKobj(TkWin, menu); + if (tkw->cascade != nil) { + menu = tklook(t, tkw->cascade, 0); + free(tkw->cascade); + tkw->cascade = nil; + } else + menu = nil; + } + top->ctxt->tkmenu = menu; + tksetmgrab(top, menu); +} + +static void +tkunmapmenu(Tk *tk) +{ + TkTop *t; + TkWin *tkw; + Tk *parent; + + parent = nil; + tkw = TKobj(TkWin, tk); + t = tk->env->top; + if (tkw->cascade != nil) + parent = tklook(t, tkw->cascade, 0); + tkunmapmenus(t, parent); + if (tkw->freeonunmap) + freemenu(tk); +} + +static void +tksizemenubutton(Tk *tk) +{ + int w, h; + char **v, *cur; + TkLabel *tkl = TKobj(TkLabel, tk); + + tksizelabel(tk); + if (tk->type != TKchoicebutton) + return; + w = tk->req.width; + h = tk->req.height; + v = tkl->values; + if (v == nil || *v == nil) + return; + cur = tkl->text; + for (; *v; v++) { + tkl->text = *v; + tksizelabel(tk); + if (tk->req.width > w) + w = tk->req.width; + if (tk->req.height > h) + h = tk->req.height; + } + tkl->text = cur; + tksizelabel(tk); + tk->req.width = w; + tk->req.height = h; +} + +static char* +tkmkmenubutton(TkTop *t, char *arg, char **ret, int type, TkOption *opts) +{ + Tk *tk; + char *e, **v; + TkName *names; + TkLabel *tkl; + TkOptab tko[3]; + +/* need to get the label from elsewhere */ + tk = tknewobj(t, type, sizeof(Tk)+sizeof(TkLabel)); + if(tk == nil) + return TkNomem; + tk->borderwidth = 2; + tk->flag |= Tknograb; + + tkl = TKobj(TkLabel, tk); + tkl->ul = -1; + if(type == TKchoicebutton) + tkl->anchor = Tknorth|Tkwest; + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tkl; + tko[1].optab = opts; + tko[2].ptr = nil; + + names = nil; + e = tkparse(t, arg, tko, &names); + if(e != nil) { + tkfreeobj(tk); + return e; + } + tkl->nvalues = 0; + if (tkl->values != nil) { + for (v = tkl->values; *v; v++) + ; + tkl->nvalues = v - tkl->values; + } + if(type == TKchoicebutton){ + if(tkl->nvalues > 0) + tkl->text = strdup(tkl->values[0]); + else + tkl->text = strdup(NOCHOICE); + } + tksettransparent(tk, + tkhasalpha(tk->env, TkCbackgnd) || + tkhasalpha(tk->env, TkCselectbgnd) || + tkhasalpha(tk->env, TkCactivebgnd)); + + e = tkbindings(t, tk, mbbindings, nelem(mbbindings)); + + if(e != nil) { + tkfreeobj(tk); + return e; + } + tksizemenubutton(tk); + + e = tkaddchild(t, tk, &names); + tkfreename(names); + if(e != nil) { + tkfreeobj(tk); + return e; + } + tk->name->link = nil; + + return tkvalue(ret, "%s", tk->name->name); +} + +char* +tkchoicebutton(TkTop *t, char *arg, char **ret) +{ + return tkmkmenubutton(t, arg, ret, TKchoicebutton, choiceopts); +} + +char* +tkmenubutton(TkTop *t, char *arg, char **ret) +{ + return tkmkmenubutton(t, arg, ret, TKmenubutton, mbopts); +} + +static char* +tkmenubutcget(Tk *tk, char *arg, char **val) +{ + TkOptab tko[3]; + TkLabel *tkl = TKobj(TkLabel, tk); + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tkl; + tko[1].optab = (tk->type == TKchoicebutton ? choiceopts : mbopts); + tko[2].ptr = nil; + + return tkgencget(tko, arg, val, tk->env->top); +} + +static char* +tkmenubutconf(Tk *tk, char *arg, char **val) +{ + char *e, **v; + TkGeom g; + int bd; + TkOptab tko[3]; + TkLabel *tkl = TKobj(TkLabel, tk); + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tkl; + tko[1].optab = (tk->type == TKchoicebutton ? choiceopts : mbopts); + 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); + + if (tk->type == TKchoicebutton) { + tkl->nvalues = 0; + if (tkl->values != nil) { + for (v = tkl->values; *v; v++) + ; + tkl->nvalues = v - tkl->values; + } + if (tkl->check >= tkl->nvalues || strcmp(tkl->text, tkl->values[tkl->check])) { + /* + * try to keep selected value the same if possible + */ + for (v = tkl->values; v && *v; v++) + if (!strcmp(*v, tkl->text)) + break; + free(tkl->text); + if (v == nil || *v == nil) { + tkl->text = strdup(tkl->nvalues > 0 ? tkl->values[0] : NOCHOICE); + tkl->check = 0; + } else { + tkl->check = v - tkl->values; + tkl->text = strdup(*v); + } + } + } + tksettransparent(tk, + tkhasalpha(tk->env, TkCbackgnd) || + tkhasalpha(tk->env, TkCselectbgnd) || + tkhasalpha(tk->env, TkCactivebgnd)); + tksizemenubutton(tk); + tkgeomchg(tk, &g, bd); + + tk->dirty = tkrect(tk, 1); + return e; +} + +static char* +tkMBleave(Tk *tk, char *arg, char **val) +{ + USED(arg); + USED(val); + + tk->flag &= ~Tkactive; + tk->dirty = tkrect(tk, 1); + return nil; +} + +static Tk* +mkchoicemenu(Tk *tkb) +{ + Tk *menu, *tkc; + int i; + TkLabel *tkl, *tkcl; + TkWin *tkw; + TkTop *t; + + tkl = TKobj(TkLabel, tkb); + t = tkb->env->top; + + menu = tknewobj(t, TKmenu, sizeof(Tk)+sizeof(TkWin)); + if(menu == nil) + return nil; + + menu->relief = TKraised; + menu->flag |= Tknograb; + menu->borderwidth = 2; + tkputenv(menu->env); + menu->env = tkb->env; + menu->env->ref++; + + menu->flag |= Tkwindow; + menu->geom = tkmoveresize; + tkw = TKobj(TkWin, menu); + tkw->cbname = strdup(tkb->name->name); + tkw->di = (void*)-1; // XXX + + for(i = tkl->nvalues - 1; i >= 0; i--){ + tkc = tknewobj(t, TKlabel, sizeof(Tk)+sizeof(TkLabel)); + /* XXX recover from malloc failure */ + tkc->flag = Tkwest|Tkfillx|Tktop; + tkc->highlightwidth = 0; + tkc->borderwidth = 1; + tkc->relief = TKflat; + tkputenv(tkc->env); + tkc->env = tkb->env; + tkc->env->ref++; + tkcl = TKobj(TkLabel, tkc); + tkcl->anchor = Tkwest; + tkcl->ul = -1; + tkcl->justify = Tkleft; + tkcl->text = strdup(tkl->values[i]); + tkcl->command = smprint("%s invoke %d", tkb->name->name, i); + /* XXX recover from malloc failure */ + tksizelabel(tkc); + tkc->req.height = tkb->req.height; + appenditem(menu, tkc, 0); + } + layout(menu); + + tkw->next = t->windows; + tkw->freeonunmap = 1; + t->windows = menu; + return menu; +} + +static char* +tkMBpress(Tk *tk, char *arg, char **val) +{ + Tk *menu, *item; + TkLabel *tkl = TKobj(TkLabel, tk); + Point g; + char buf[12], *bufp, *e; + + USED(arg); + USED(val); + + g = tkposn(tk); + if (tk->type == TKchoicebutton) { + menu = mkchoicemenu(tk); + if (menu == nil) + return TkNomem; + sprint(buf, "%d", tkl->check); + bufp = buf; + item = tkmenuindex2ptr(menu, &bufp); + if(item == nil) + return nil; + g.y -= item->act.y; + e = tkmpost(menu, g.x, g.y, 0, 0, 0); + activateitem(item); + return e; + } else { + if (tkl->menu == nil) + return nil; + menu = tklook(tk->env->top, tkl->menu, 0); + if(menu == nil || menu->type != TKmenu) + return TkBadwp; + + if(menu->flag & Tkmapped) { + if(atoi(arg)) + tkunmapmenu(menu); + return nil; + } + return tkmpost(menu, g.x, g.y, 0, tk->act.height + 2*tk->borderwidth, 1); + } +} + +static char* +tkMBkey(Tk *tk, char *arg, char **val) +{ + int key; + USED(val); + + if(tk->flag & Tkdisabled) + return nil; + + key = atoi(arg); + if (key == '\n' || key == ' ') + return tkMBpress(tk, "1", nil); + return nil; +} + +static char* +tkMBenter(Tk *tk, char *arg, char **val) +{ + USED(arg); + USED(val); + + tk->flag |= Tkactive; + tk->dirty = tkrect(tk, 1); + return nil; +} + +static char* +tkchoicebutset(Tk *tk, char *arg, char **val) +{ + char buf[12], *e; + int v; + TkLabel *tkl = TKobj(TkLabel, tk); + + USED(val); + + tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + if (*buf == '\0') + return TkBadvl; + v = atoi(buf); + if (v < 0 || v >= tkl->nvalues) + return TkBadvl; + if (v == tkl->check) + return nil; + free(tkl->text); + tkl->text = strdup(tkl->values[v]); + /* XXX recover from malloc error */ + tkl->check = v; + + sprint(buf, "%d", v); + e = tksetvar(tk->env->top, tkl->variable, buf); + if(e != nil) + return e; + + tk->dirty = tkrect(tk, 1); + return nil; +} + +static char* +tkchoicebutinvoke(Tk *tk, char *arg, char **val) +{ + TkLabel *tkl = TKobj(TkLabel, tk); + char *e; + + e = tkchoicebutset(tk, arg, val); + if(e != nil) + return e; + if(tkl->command) + return tkexec(tk->env->top, tkl->command, val); + return nil; +} + +static char* +tkchoicebutgetvalue(Tk *tk, char *arg, char **val) +{ + char buf[12]; + int gotarg, v; + TkLabel *tkl = TKobj(TkLabel, tk); + if (tkl->nvalues == 0) + return nil; + tkword(tk->env->top, arg, buf, buf+sizeof(buf), &gotarg); + if (!gotarg) + return tkvalue(val, "%s", tkl->values[tkl->check]); + v = atoi(buf); + if (buf[0] < '0' || buf[0] > '9' || v >= tkl->nvalues) + return TkBadvl; + return tkvalue(val, "%s", tkl->values[tkl->check]); +} + +static char* +tkchoicebutsetvalue(Tk *tk, char *arg, char **val) +{ + char *buf; + char **v; + int gotarg; + TkLabel *tkl = TKobj(TkLabel, tk); + + USED(val); + if (tkl->nvalues == 0) + return TkBadvl; + buf = mallocz(Tkmaxitem, 0); + if (buf == nil) + return TkNomem; + tkword(tk->env->top, arg, buf, buf+Tkmaxitem, &gotarg); + if (!gotarg) { + free(buf); + return TkBadvl; + } + for (v = tkl->values; *v; v++) + if (strcmp(*v, buf) == 0) + break; + free(buf); + if (*v == nil) + return TkBadvl; + free(tkl->text); + tkl->text = strdup(*v); + /* XXX recover from malloc error */ + tkl->check = v - tkl->values; + + tk->dirty = tkrect(tk, 1); + return nil; +} + +static char* +tkchoicebutget(Tk *tk, char *arg, char **val) +{ + TkLabel *tkl = TKobj(TkLabel, tk); + char *buf, **v; + int gotarg; + + if (tkl->nvalues == 0) + return nil; + buf = mallocz(Tkmaxitem, 0); + if (buf == nil) + return TkNomem; + tkword(tk->env->top, arg, buf, buf+Tkmaxitem, &gotarg); + if (!gotarg) { + free(buf); + return tkvalue(val, "%d", tkl->check); + } + + for (v = tkl->values; *v; v++) + if (strcmp(*v, buf) == 0) + break; + free(buf); + if (*v) + return tkvalue(val, "%d", v - tkl->values); + return nil; +} + +static char* +tkchoicebutvaluecount(Tk *tk, char *arg, char **val) +{ + TkLabel *tkl = TKobj(TkLabel, tk); + USED(arg); + return tkvalue(val, "%d", tkl->nvalues); +} + + +static void +tkchoicevarchanged(Tk *tk, char *var, char *value) +{ + TkLabel *tkl = TKobj(TkLabel, tk); + int v; + + if(tkl->variable != nil && strcmp(tkl->variable, var) == 0){ + if(value[0] < '0' || value[0] > '9') + return; + v = atoi(value); + if(v < 0 || v > tkl->nvalues) + return; /* what else can we do? */ + free(tkl->text); + tkl->text = strdup(tkl->values[v]); + /* XXX recover from malloc error */ + tkl->check = v; + tk->dirty = tkrect(tk, 0); + tkdirty(tk); + } +} + +Tk * +tkfindchoicemenu(Tk *tkb) +{ + Tk *tk, *next; + TkTop *top; + TkWin *tkw; + + top = tkb->env->top; + for (tk = top->windows; tk != nil; tk = next){ + tkw = TKobj(TkWin, tk); + if(tk->name == nil){ + assert(strcmp(tkw->cbname, tkb->name->name) == 0); + return tk; + } + next = tkw->next; + } + return nil; +} + +static +TkOption menuopt[] = +{ + "postcommand", OPTtext, O(TkWin, postcmd), nil, + nil, +}; + +char* +tkmenu(TkTop *t, char *arg, char **ret) +{ + Tk *tk; + char *e; + TkWin *tkw; + TkName *names; + TkOptab tko[3]; + + tk = tknewobj(t, TKmenu, sizeof(Tk)+sizeof(TkWin)); + if(tk == nil) + return TkNomem; + + tkw = TKobj(TkWin, tk); + tkw->di = (void*)-1; // XXX + tk->relief = TKraised; + tk->flag |= Tknograb; + tk->borderwidth = 2; + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tkw; + tko[1].optab = menuopt; + tko[2].ptr = nil; + + names = nil; + e = tkparse(t, arg, tko, &names); + if(e != nil) { + tkfreeobj(tk); + return e; + } + + e = tkaddchild(t, tk, &names); + tkfreename(names); + if(e != nil) { + tkfreeobj(tk); + return e; + } + tk->name->link = nil; + + tk->flag |= Tkwindow; + tk->geom = tkmoveresize; + + tkw->next = t->windows; + t->windows = tk; + + return tkvalue(ret, "%s", tk->name->name); +} + +static void +freemenu(Tk *top) +{ + Tk *tk, *f, *nexttk, *nextf; + TkWin *tkw; + + tkunmapmenu(top); + tkw = TKobj(TkWin, top); + for(tk = tkw->slave; tk; tk = nexttk) { + nexttk = tk->next; + for(f = tk->slave; f; f = nextf) { + nextf = f->next; + tkfreeobj(f); + } + tkfreeobj(tk); + } + top->slave = nil; + tkfreeframe(top); +} + +static +TkOption mopt[] = +{ + "menu", OPTtext, O(TkLabel, menu), nil, + nil, +}; + +static void +tkbuildmopt(TkOptab *tko, int n, Tk *tk) +{ + memset(tko, 0, n*sizeof(TkOptab)); + + n = 0; + tko[n].ptr = tk; + tko[n++].optab = tkgeneric; + + switch(tk->type) { + case TKcascade: + tko[n].ptr = TKobj(TkLabel, tk); + tko[n++].optab = mopt; + goto norm; + case TKradiobutton: + tko[n].ptr = TKobj(TkLabel, tk); + tko[n++].optab = tkradopts; + goto norm; + case TKcheckbutton: + tko[n].ptr = TKobj(TkLabel, tk); + tko[n++].optab = tkcbopts; + /* fall through */ + case TKlabel: + norm: + tko[n].ptr = TKobj(TkLabel, tk); + tko[n].optab = tkbutopts; + break; + } +} + +static char* +tkmenuentryconf(Tk *menu, Tk *tk, char *arg) +{ + char *e; + TkOptab tko[4]; + + USED(menu); + + tkbuildmopt(tko, nelem(tko), tk); + e = tkparse(tk->env->top, arg, tko, nil); + switch (tk->type) { + case TKlabel: + case TKcascade: + tksizelabel(tk); + break; + case TKradiobutton: + case TKcheckbutton: + tksizebutton(tk); + } + + return e; +} + +static void +layout(Tk *menu) +{ + TkWin *tkw; + Tk *tk; + int m, w, y, maxmargin, maxw; + + y = 0; + maxmargin = 0; + maxw = 0; + + tkw = TKobj(TkWin, menu); + + /* determine padding for item text alignment */ + for (tk = tkw->slave; tk != nil; tk = tk->next) { + m = tklabelmargin(tk); + tk->act.x = m; /* temp store */ + if (m > maxmargin) + maxmargin = m; + } + /* set x pos and determine max width */ + for (tk = tkw->slave; tk != nil; tk = tk->next) { + tk->act.x = tk->borderwidth + maxmargin - tk->act.x; + tk->act.y = y + tk->borderwidth; + tk->act.height = tk->req.height; + tk->act.width = tk->req.width; + y += tk->act.height+2*tk->borderwidth; + w = tk->act.x + tk->req.width + 2* tk->borderwidth; + if (w > maxw) + maxw = w; + } + /* expand separators and cascades and mark all as dirty */ + for (tk = tkw->slave; tk != nil; tk = tk->next) { + switch (tk->type) { + case TKseparator: + tk->act.x = tk->borderwidth; + /*FALLTHRU*/ + case TKcascade: + tk->act.width = (maxw - tk->act.x) - tk->borderwidth; + } + tk->dirty = tkrect(tk, 1); + } + menu->dirty = tkrect(menu, 1); + tkmoveresize(menu, 0, 0, maxw, y); +} + +static void +menuitemgeom(Tk *sub, int x, int y, int w, int h) +{ + if (sub->parent == nil) + return; + if(w < 0) + w = 0; + if(h < 0) + h = 0; + sub->req.x = x; + sub->req.y = y; + sub->req.width = w; + sub->req.height = h; + layout(sub->parent); +} + +static void +appenditem(Tk *menu, Tk *item, int where) +{ + TkWin *tkw; + Tk *f, **l; + + tkw = TKobj(TkWin, menu); + l = &tkw->slave; + for (f = *l; f != nil; f = f->next) { + if (where-- == 0) + break; + l = &f->next; + } + *l = item; + item->next = f; + item->parent = menu; + item->geom = menuitemgeom; +} + +static char* +menuadd(Tk *menu, char *arg, int where) +{ + Tk *tkc; + int configure; + char *e; + TkTop *t; + TkLabel *tkl; + char buf[Tkmaxitem]; + + t = menu->env->top; + arg = tkword(t, arg, buf, buf+sizeof(buf), nil); + configure = 1; + e = nil; + + if(strcmp(buf, "checkbutton") == 0) + tkc = tkmkbutton(t, TKcheckbutton); + else if(strcmp(buf, "radiobutton") == 0) + tkc = tkmkbutton(t, TKradiobutton); + else if(strcmp(buf, "command") == 0) + tkc = tknewobj(t, TKlabel, sizeof(Tk)+sizeof(TkLabel)); + else if(strcmp(buf, "cascade") == 0) + tkc = tknewobj(t, TKcascade, sizeof(Tk)+sizeof(TkLabel)); + else if(strcmp(buf, "separator") == 0) { + tkc = tknewobj(t, TKseparator, sizeof(Tk)); /* it's really a frame */ + if (tkc != nil) { + tkc->flag = Tkfillx|Tktop; + tkc->req.height = Sepheight; + configure = 0; + } + } + else + return TkBadvl; + + if (tkc == nil) + e = TkNomem; + + if (e == nil) { + if(tkc->env == t->env && menu->env != t->env) { + tkputenv(tkc->env); + tkc->env = menu->env; + tkc->env->ref++; + } + if (configure) { + tkc->flag = Tkwest|Tkfillx|Tktop; + tkc->highlightwidth = 0; + tkc->borderwidth = 1; + tkc->relief = TKflat; + tkl = TKobj(TkLabel, tkc); + tkl->anchor = Tkwest; + tkl->ul = -1; + tkl->justify = Tkleft; + e = tkmenuentryconf(menu, tkc, arg); + } + } + + if(e != nil) { + if (tkc != nil) + tkfreeobj(tkc); + return e; + } + + appenditem(menu, tkc, where); + layout(menu); + return nil; +} + +static int +tkmindex(Tk *tk, char *p) +{ + TkWin *tkw; + int y, n; + + if(*p >= '0' && *p <= '9') + return atoi(p); + + tkw = TKobj(TkWin, tk); + n = 0; + if(*p == '@') { + y = atoi(p+1); + for(tk = tkw->slave; tk; tk = tk->next) { + if(y >= tk->act.y && y < tk->act.y+tk->act.height+2*tk->borderwidth ) + return n; + n++; + } + } + if(strcmp(p, "end") == 0 || strcmp(p, "last") == 0) { + for(tk = tkw->slave; tk && tk->next; tk = tk->next) + n++; + return n; + } + if(strcmp(p, "active") == 0) { + for(tk = tkw->slave; tk; tk = tk->next) { + if(tk->flag & Tkactive) + return n; + n++; + } + return -2; + } + if(strcmp(p, "none") == 0) + return -2; + + return -1; +} + +static int +tkmenudel(Tk *tk, int y) +{ + TkWin *tkw; + Tk *f, **l, *next; + + tkw = TKobj(TkWin, tk); + l = &tkw->slave; + for(tk = *l; tk; tk = tk->next) { + if(y-- == 0) { + *l = tk->next; + for(f = tk->slave; f; f = next) { + next = f->next; + tkfreeobj(f); + } + tkfreeobj(tk); + return 1; + } + l = &tk->next; + } + return 0; +} + +static char* +tkmpost(Tk *tk, int x, int y, int cascade, int bh, int adjust) +{ + char *e; + TkWin *w; + TkTop *t; + Rectangle *dr; + + t = tk->env->top; + if(adjust){ + dr = &t->screenr; + if(x+tk->act.width > dr->max.x) + x = dr->max.x - tk->act.width; + if(x < 0) + x = 0; + if(y+bh+tk->act.height > dr->max.y) + y -= tk->act.height + 2* tk->borderwidth; + else + y += bh; + if(y < 0) + y = 0; + } + menuclr(tk); + tkmovewin(tk, Pt(x, y)); + + /* stop possible postcommand recursion */ + if (tk->flag & Tkmapped) + return nil; + + w = TKobj(TkWin, tk); + if(w->postcmd != nil) { + e = tkexec(tk->env->top, w->postcmd, nil); + if(e != nil) { + print("%s: postcommand: %s: %s\n", tkname(tk), w->postcmd, e); + return e; + } + } + if (!cascade) + tkunmapmenus(t, nil); + + e = tkmap(tk); + if(e != nil) + return e; + + if (t->ctxt->tkmenu != nil) + w->cascade = strdup(t->ctxt->tkmenu->name->name); + t->ctxt->tkmenu = tk; + tksetmgrab(t, tk); + + /* Make sure slaves are redrawn */ + return tkupdate(tk->env->top); +} + +static Tk* +tkmenuindex2ptr(Tk *tk, char **arg) +{ + TkWin *tkw; + int index; + char *buf; + + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return nil; + *arg = tkword(tk->env->top, *arg, buf, buf+Tkmaxitem, nil); + index = tkmindex(tk, buf); + free(buf); + if(index < 0) + return nil; + + tkw = TKobj(TkWin, tk); + for(tk = tkw->slave; tk && index; tk = tk->next) + index--; + + if(tk == nil) + return nil; + + return tk; +} + +static char* +tkmenuentrycget(Tk *tk, char *arg, char **val) +{ + Tk *etk; + TkOptab tko[4]; + + etk = tkmenuindex2ptr(tk, &arg); + if(etk == nil) + return TkBadix; + + tkbuildmopt(tko, nelem(tko), etk); + return tkgencget(tko, arg, val, tk->env->top); +} + +static char* +tkmenucget(Tk *tk, char *arg, char **val) +{ + TkWin *tkw; + TkOptab tko[4]; + + tkw = TKobj(TkWin, tk); + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tkw; + tko[1].optab = tktop; + tko[2].ptr = tkw; + tko[2].optab = menuopt; + tko[3].ptr = nil; + + return tkgencget(tko, arg, val, tk->env->top); +} + +static char* +tkmenuconf(Tk *tk, char *arg, char **val) +{ + char *e; + TkGeom g; + int bd; + TkWin *tkw; + TkOptab tko[3]; + + tkw = TKobj(TkWin, tk); + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tkw; + tko[1].optab = menuopt; + 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); + tkgeomchg(tk, &g, bd); + tk->dirty = tkrect(tk, 1); + return e; +} + +static char* +tkmenuadd(Tk *tk, char *arg, char **val) +{ + USED(val); + return menuadd(tk, arg, -1); +} + +static char* +tkmenuinsert(Tk *tk, char *arg, char **val) +{ + int index; + char *buf; + + USED(val); + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + arg = tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil); + index = tkmindex(tk, buf); + free(buf); + if (index < 0) + return TkBadix; + return menuadd(tk, arg, index); +} + +static void +menuitemdirty(Tk *item) +{ + Tk *menu; + Rectangle r; + + menu = item->parent; + if (menu == nil) + return; + item->dirty = tkrect(item, 1); + r = rectaddpt(item->dirty, Pt(item->act.x, item->act.y)); + combinerect(&menu->dirty, r); +} + +static void +menuclr(Tk *tk) +{ + TkWin *tkw; + Tk *f; + tkw = TKobj(TkWin, tk); + for(f = tkw->slave; f; f = f->next) { + if(f->flag & Tkactive) { + f->flag &= ~Tkactive; + menuitemdirty(f); + } + } +} + +static char* +tkpostcascade(Tk *parent, Tk *tk, int toggle) +{ + Tk *tkm; + TkWin *tkw; + Point g; + TkTop *t; + TkLabel *tkl; + char *e; + + if(tk->flag & Tkdisabled) + return nil; + + tkl = TKobj(TkLabel, tk); + t = tk->env->top; + tkm = tklook(t, tkl->menu, 0); + if(tkm == nil || tkm->type != TKmenu) + return TkBadwp; + + if((tkm->flag & Tkmapped)) { + if (toggle) { + tkunmapmenus(t, parent); + return nil; + } else { + /* check that it is immediate cascade */ + tkw = TKobj(TkWin, t->ctxt->tkmenu); + if (strcmp(tkw->cascade, parent->name->name) == 0) + return nil; + } + } + + tkunmapmenus(t, parent); + + tkl = TKobj(TkLabel, tk); + if(tkl->command != nil) { + e = tkexec(t, tkl->command, nil); + if (e != nil) + return e; + } + + g = tkposn(tk); + g.x += tk->act.width; + g.y -= tkm->borderwidth; + e = tkmpost(tkm, g.x, g.y, 1, 0, 1); + return e; +} + +static void +activateitem(Tk *item) +{ + Tk *menu; + if (item == nil || (menu = item->parent) == nil) + return; + menuclr(menu); + if (!(item->flag & Tkdisabled)) { + item->flag |= Tkactive; + menuitemdirty(item); + } +} + +static char* +tkmenuactivate(Tk *tk, char *arg, char **val) +{ + Tk *f; + TkWin *tkw; + int index; + char *buf; + + USED(val); + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil); + index = tkmindex(tk, buf); + free(buf); + if (index == -1) + return TkBadix; + if (index == -2) { + menuclr(tk); + return nil; + } + + tkw = TKobj(TkWin, tk); + for(f = tkw->slave; f; f = f->next) + if(index-- == 0) + break; + + if(f == nil || f->flag & Tkdisabled) { + menuclr(tk); + return nil; + } + if(f->flag & Tkactive) + return nil; + + activateitem(f); + return nil; +} + +static int +iteminvoke(Tk *tk, Tk *tki, char *arg) +{ + int unmap = 0; + menuitemdirty(tki); + switch(tki->type) { + case TKlabel: + unmap = 1; + case TKcheckbutton: + case TKradiobutton: + tkbuttoninvoke(tki, arg, nil); + break; + case TKcascade: + tkpostcascade(tk, tki, 0); + break; + } + return unmap; +} + +static char* +tkmenuinvoke(Tk *tk, char *arg, char **val) +{ + Tk *tki; + USED(val); + tki = tkmenuindex2ptr(tk, &arg); + if(tki == nil) + return nil; + iteminvoke(tk, tki, arg); + return nil; +} + +static char* +tkmenudelete(Tk *tk, char *arg, char **val) +{ + int index1, index2; + char *buf; + + USED(val); + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + arg = tkitem(buf, arg); + index1 = tkmindex(tk, buf); + if(index1 < 0) { + free(buf); + return TkBadix; + } + index2 = index1; + if(*arg != '\0') { + tkitem(buf, arg); + index2 = tkmindex(tk, buf); + } + free(buf); + if(index2 < 0) + return TkBadix; + while(index2 >= index1 && tkmenudel(tk, index2)) + index2--; + + layout(tk); + return nil; +} + +static char* +tkmenupost(Tk *tk, char *arg, char **val) +{ + int x, y; + TkTop *t; + char *buf; + + USED(val); + + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + t = tk->env->top; + arg = tkword(t, arg, buf, buf+Tkmaxitem, nil); + if(buf[0] == '\0') { + free(buf); + return TkBadvl; + } + x = atoi(buf); + tkword(t, arg, buf, buf+Tkmaxitem, nil); + if(buf[0] == '\0') { + free(buf); + return TkBadvl; + } + y = atoi(buf); + free(buf); + + return tkmpost(tk, x, y, 0, 0, 1); +} + +static char* +tkmenuunpost(Tk *tk, char *arg, char **val) +{ + USED(arg); + USED(val); + tkunmapmenu(tk); + return nil; +} + +static char* +tkmenuindex(Tk *tk, char *arg, char **val) +{ + char *buf; + int index; + + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil); + index = tkmindex(tk, buf); + free(buf); + if (index == -1) + return TkBadix; + if (index == -2) + return "none"; + return tkvalue(val, "%d", index); +} + +static char* +tkmenuyposn(Tk *tk, char *arg, char **val) +{ + tk = tkmenuindex2ptr(tk, &arg); + if(tk == nil) + return TkBadix; + return tkvalue(val, "%d", tk->act.y); +} + +static char* +tkmenupostcascade(Tk *tk, char *arg, char **val) +{ + Tk *tki; + USED(val); + tki = tkmenuindex2ptr(tk, &arg); + if(tki == nil || tki->type != TKcascade) + return nil; + + return tkpostcascade(tk, tki, 0); +} + +static char* +tkmenutype(Tk *tk, char *arg, char **val) +{ + tk = tkmenuindex2ptr(tk, &arg); + if(tk == nil) + return TkBadix; + + return tkvalue(val, tk->type == TKlabel ? "command" : tkmethod[tk->type]->name); +} + +static char* +tkmenususpend(Tk *tk, char *arg, char **val) +{ + USED(arg); + USED(val); + if(tk->type == TKchoicebutton){ + tk = tkfindchoicemenu(tk); + if(tk == nil) + return TkNotwm; + } + tk->flag |= Tksuspended; + return nil; +} + +static char* +tkmenuentryconfig(Tk *tk, char *arg, char **val) +{ + Tk *etk; + char *e; + + USED(val); + etk = tkmenuindex2ptr(tk, &arg); + if(etk == nil) + return TkBadix; + + e = tkmenuentryconf(tk, etk, arg); + layout(tk); + return e; +} + +static Tk* +xymenuitem(Tk *tk, int x, int y) +{ + TkWin *tkw = TKobj(TkWin, tk); + x -= tkw->act.x; + y -= tkw->act.y; + + x -= tk->borderwidth; + y -= tk->act.y + tk->borderwidth; + if (x < tk->act.x || x > tk->act.x+tk->act.width) + return nil; + for(tk = tkw->slave; tk; tk = tk->next) { + if(y >= tk->act.y && y < tk->act.y+tk->act.height+2*tk->borderwidth) + return tk; + } + return nil; +} + +static char * +menukey(Tk *tk, int key) +{ + Tk *scan, *active, *first, *last, *prev, *next; + TkWin *tkw; + TkTop *top; + + top = tk->env->top; + + active = first = last = prev = next = nil; + tkw = TKobj(TkWin, tk); + for(scan = tkw->slave; scan != nil; scan = scan->next) { + if(scan->type == TKseparator) + continue; + if(first == nil) + first = scan; + if (active != nil && next == nil) + next = scan; + if(active == nil && scan->flag & Tkactive) + active = scan; + if (active == nil) + prev = scan; + last = scan; + } + if (next == nil) + next = first; + if (prev == nil) + prev = last; + + switch (key) { + case Esc: + tkunmapmenus(top, nil); + break; + case Left: + if (tkw->cascade != nil) + tkunmapmenu(tk); + break; + case Right: + if (active == nil || active->type != TKcascade) + break; + case ' ': + case '\n': + if (active != nil) { + if (iteminvoke(tk, active, nil)) + tkunmapmenus(top, nil); + } + break; + case Up: + next = prev; + case Down: + if (next != nil) + activateitem(next); + } + return nil; +} + +static char* +drawmenu(Tk *tk, Point orig) +{ + Image *dst; + TkWin *tkw; + Tk *sub; + Point p, bd; + int bg; + Rectangle mainr, clientr, subr; + + tkw = TKobj(TkWin, tk); + dst = tkimageof(tk); + + bd = Pt(tk->borderwidth, tk->borderwidth); + mainr.min = addpt(orig, Pt(tk->act.x, tk->act.y)); + clientr.min = addpt(mainr.min, bd); + clientr.max = addpt(clientr.min, Pt(tk->act.width, tk->act.height)); + mainr.max = addpt(clientr.max, bd); + + /* + * note that we draw item background to get full menu width + * active indicator, this means we must dirty the entire + * item rectangle to ensure it is fully redrawn + */ + p = clientr.min; + subr = clientr; + for (sub = tkw->slave; sub != nil; sub = sub->next) { + if (Dx(sub->dirty) == 0) + continue; + subr.min.y = p.y + sub->act.y - sub->borderwidth; + subr.max.y = p.y + sub->act.y + sub->act.height + sub->borderwidth; + bg = TkCbackgnd; + if (sub->flag & Tkactive) + bg = TkCactivebgnd; + draw(dst, subr, tkgc(sub->env, bg), nil, ZP); + sub->dirty = tkrect(sub, 1); + sub->flag |= Tkrefresh; + tkmethod[sub->type]->draw(sub, p); + sub->dirty = bbnil; + sub->flag &= ~Tkrefresh; + } + /* todo: dirty check */ + tkdrawrelief(dst, tk, mainr.min, TkCbackgnd, tk->relief); + return nil; +} + +static void +menudirty(Tk *sub) +{ + menuitemdirty(sub); +} + +static Point +menurelpos(Tk *sub) +{ + return Pt(sub->act.x-sub->borderwidth, sub->act.y-sub->borderwidth); +} + +static void +autoscroll(Tk *tk, void *v, int cancelled) +{ + TkWin *tkw; + Rectangle r, dr; + Point delta, od; + TkMouse *m; + Tk *item; + USED(v); + + tkw = TKobj(TkWin, tk); + if (cancelled) { + tkw->speed = 0; + return; + } + if(!eqpt(tkw->act, tkw->req)){ +print("not autoscrolling, act: %P, req: %P\n", tkw->act, tkw->req); + return; +} + dr = tk->env->top->screenr; + delta.x = TKF2I(tkw->delta.x * tkw->speed); + delta.y = TKF2I(tkw->delta.y * tkw->speed); + r = rectaddpt(tkrect(tk, 1), Pt(tk->borderwidth + tkw->act.x, tk->borderwidth + tkw->act.y)); + + od = delta; + /* make sure we don't go too far */ + if (delta.x > 0 && r.min.x + delta.x > dr.min.x) + delta.x = dr.min.x - r.min.x; + else if (delta.x < 0 && r.max.x + delta.x < dr.max.x) + delta.x = dr.max.x - r.max.x; + if (delta.y > 0 && r.min.y + delta.y > dr.min.y) + delta.y = dr.min.y - r.min.y; + else if (delta.y < 0 && r.max.y + delta.y < dr.max.y) + delta.y = dr.max.y - r.max.y; + + m = &tk->env->top->ctxt->mstate; + item = xymenuitem(tk, m->x - delta.x, m->y - delta.y); + if (item == nil) + menuclr(tk); + else + activateitem(item); + tkmovewin(tk, Pt(tkw->req.x + delta.x, tkw->req.y + delta.y)); + tkupdate(tk->env->top); + /* tkenterleave won't do this for us, so we have to do it ourselves */ + + tkw->speed += tkw->speed / 3; + + r = rectaddpt(tkrect(tk, 1), Pt(tk->borderwidth + tkw->act.x, tk->borderwidth + tkw->act.y)); + if((delta.y > 0 && r.min.x >= dr.min.x) || (delta.x < 0 && r.max.x <= dr.max.x)) + tkw->delta.x = 0; + if((delta.y > 0 && r.min.y >= dr.min.y) || (delta.y < 0 && r.max.y <= dr.max.y)) + tkw->delta.y = 0; + if (eqpt(tkw->delta, ZP)) { + tkcancelrepeat(tk); + tkw->speed = 0; + } +} + +static void +startautoscroll(Tk *tk, TkMouse *m) +{ + Rectangle dr, r; + Point d; + TkWin *tkw; + tkw = TKobj(TkWin, tk); + dr = tk->env->top->screenr; + r = rectaddpt(tkrect(tk, 1), Pt(tk->borderwidth + tkw->act.x, tk->borderwidth + tkw->act.y)); + d = Pt(0, 0); + if(m->x <= 0 && r.min.x < dr.min.x) + d.x = 1; + else if (m->x >= dr.max.x - 1 && r.max.x >= dr.max.x) + d.x = -1; + if(m->y <= 0 && r.min.y < dr.min.y) + d.y = 1; + else if (m->y >= dr.max.y - 1 && r.max.y >= dr.max.y) + d.y = -1; +//print("startautoscroll, delta %P\n", d); + if (d.x == 0 && d.y == 0){ + if (tkw->speed > 0){ + tkcancelrepeat(tk); + tkw->speed = 0; + } + return; + } + if (tkw->speed == 0) { + tkw->speed = TKI2F(Dy(r)) / 100; + tkrepeat(tk, autoscroll, nil, 0, TkRptinterval/2); + } + tkw->delta = d; +} + +static void +menuevent1(Tk *tk, int event, void *a) +{ + TkMouse *m; + Tk *item; + + if (event & TkKey) { + menukey(tk, event & 0xffff); + return; + } + + if (event & TkLeave) { + menuclr(tk); + return; + } + + if ((!(event & TkEmouse) || (event & TkTakefocus)) && !(event & TkEnter)) + return; + + m = (TkMouse*)a; + + startautoscroll(tk, m); + + item = xymenuitem(tk, m->x, m->y); + if (item == nil) + menuclr(tk); + else + activateitem(item); + if ((event & (TkMotion|TkEnter)) && item == nil) + return; + if (event & TkEpress) { + if (item == nil) { + tkunmapmenus(tk->env->top, nil); + return; + } + if (item->type == TKcascade) + tkpostcascade(tk, item, !(event & TkMotion)); + else + tkunmapmenus(tk->env->top, tk); + return; + } + if ((event & TkErelease) && m->b == 0) { + if (item != nil) { + if (item->type == TKcascade) + return; + if (!iteminvoke(tk, item, nil)) + return; + } + tkunmapmenus(tk->env->top, nil); + } +} + +static Tk* +menuevent(Tk *tk, int event, void *a) +{ + menuevent1(tk, event, a); + tksubdeliver(tk, tk->binds, event, a, 0); + return nil; +} + +static +TkCmdtab menucmd[] = +{ + "activate", tkmenuactivate, + "add", tkmenuadd, + "cget", tkmenucget, + "configure", tkmenuconf, + "delete", tkmenudelete, + "entryconfigure", tkmenuentryconfig, + "entrycget", tkmenuentrycget, + "index", tkmenuindex, + "insert", tkmenuinsert, + "invoke", tkmenuinvoke, + "post", tkmenupost, + "postcascade", tkmenupostcascade, + "type", tkmenutype, + "unpost", tkmenuunpost, + "yposition", tkmenuyposn, + "suspend", tkmenususpend, + nil +}; + +static +TkCmdtab menubutcmd[] = +{ + "cget", tkmenubutcget, + "configure", tkmenubutconf, + "tkMBenter", tkMBenter, + "tkMBleave", tkMBleave, + "tkMBpress", tkMBpress, + "tkMBkey", tkMBkey, + nil +}; + +static +TkCmdtab choicebutcmd[] = +{ + "cget", tkmenubutcget, + "configure", tkmenubutconf, + "set", tkchoicebutset, + "get", tkchoicebutget, + "setvalue", tkchoicebutsetvalue, + "getvalue", tkchoicebutgetvalue, + "invoke", tkchoicebutinvoke, + "valuecount", tkchoicebutvaluecount, + "tkMBenter", tkMBenter, + "tkMBleave", tkMBleave, + "tkMBpress", tkMBpress, + "tkMBkey", tkMBkey, + "suspend", tkmenususpend, + nil +}; + +TkMethod menumethod = { + "menu", + menucmd, + freemenu, + drawmenu, + nil, + nil, + nil, + menudirty, + menurelpos, + menuevent +}; + +TkMethod menubuttonmethod = { + "menubutton", + menubutcmd, + tkfreelabel, + tkdrawlabel +}; + +TkMethod choicebuttonmethod = { + "choicebutton", + choicebutcmd, + tkfreelabel, + tkdrawlabel, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + tkchoicevarchanged +}; + +TkMethod separatormethod = { + "separator", + nil, + tkfreeframe, + tkdrawframe +}; + +TkMethod cascademethod = { + "cascade", + nil, + tkfreelabel, + tkdrawlabel +}; diff --git a/libtk/mkfile b/libtk/mkfile new file mode 100644 index 00000000..f52a0148 --- /dev/null +++ b/libtk/mkfile @@ -0,0 +1,26 @@ +<../mkconfig + +LIB=libtk.a + +OFILES=\ + ebind.$O\ + grids.$O\ + image.$O\ + packr.$O\ + panel.$O\ + parse.$O\ + utils.$O\ + windw.$O\ + xdata.$O\ + +HFILES=\ + $ROOT/include/tk.h\ + $ROOT/include/draw.h\ + +default:V: all + +<mkfile-$TKSTYLE +<$ROOT/mkfiles/mksyslib-$SHELLTYPE + +ebind.$O: $ROOT/include/interp.h +windw.$O: canvs.h textw.h diff --git a/libtk/mkfile-std b/libtk/mkfile-std new file mode 100644 index 00000000..f8b453b1 --- /dev/null +++ b/libtk/mkfile-std @@ -0,0 +1,44 @@ +CANVSFILES=\ + canvs.$O\ + canvu.$O\ + carcs.$O\ + cbits.$O\ + cimag.$O\ + cline.$O\ + coval.$O\ + cpoly.$O\ + crect.$O\ + ctext.$O\ + cwind.$O\ + +LABELFILES=\ + buton.$O\ + label.$O\ + +TEXTWFILES=\ + textu.$O\ + textw.$O\ + tindx.$O\ + tmark.$O\ + ttags.$O\ + twind.$O\ + +OFILES=$OFILES\ + $CANVSFILES\ + colrs.$O\ + entry.$O\ + extns.$O\ + frame.$O\ + $LABELFILES\ + listb.$O\ + menus.$O\ + scale.$O\ + scrol.$O\ + $TEXTWFILES\ + +frame.$O: frame.h +$CANVSFILES: canvs.h +$LABELFILES: label.h +listb.$O: listb.h +menus.$O: frame.h label.h +$TEXTWFILES: textw.h diff --git a/libtk/packr.c b/libtk/packr.c new file mode 100644 index 00000000..14572e79 --- /dev/null +++ b/libtk/packr.c @@ -0,0 +1,689 @@ +#include "lib9.h" +#include "draw.h" +#include "tk.h" + +#define O(t, e) ((long)(&((t*)0)->e)) + +typedef struct Pack Pack; +struct Pack +{ + Tk* t; + Pack* next; +}; +static Pack *packorder; + +static int tkpacker(Tk *); + +typedef struct TkParam TkParam; +struct TkParam +{ + Point pad; + Point ipad; + int side; + int anchor; + int fill; + Tk* in; + Tk* before; + Tk* after; + int expand; +}; + +TkParam defparam = { + {-1, -1}, /* p.pad */ + {-1, -1}, /* p.ipad */ + -1, /* side */ + -1, /* anchor */ + -1, /* fill */ + nil, /* in */ + nil, /* before */ + nil, /* after */ + BoolX /* expand */ +}; + +static +TkStab tkside[] = +{ + "top", Tktop, + "bottom", Tkbottom, + "left", Tkleft, + "right", Tkright, + nil +}; + +static +TkStab tkfill[] = +{ + "none", 0, + "x", Tkfillx, + "y", Tkfilly, + "both", Tkfillx|Tkfilly, + nil +}; + +static +TkOption opts[] = +{ + "padx", OPTnndist, O(TkParam, pad.x), nil, + "pady", OPTnndist, O(TkParam, pad.y), nil, + "ipadx", OPTnndist, O(TkParam, ipad.x), nil, + "ipady", OPTnndist, O(TkParam, ipad.y), nil, + "side", OPTstab, O(TkParam, side), tkside, + "anchor", OPTstab, O(TkParam, anchor), tkanchor, + "fill", OPTstab, O(TkParam, fill), tkfill, + "in", OPTwinp, O(TkParam, in), nil, + "before", OPTwinp, O(TkParam, before), nil, + "after", OPTwinp, O(TkParam, after), nil, + "expand", OPTstab, O(TkParam, expand), tkbool, + nil +}; + +void +tkdelpack(Tk *t) +{ + Tk *f, **l; + + if(t->master == nil) + return; + + if(t->master->grid != nil) + tkgriddelslave(t); + + l = &t->master->slave; + for(f = *l; f; f = f->next) { + if(f == t) { + *l = t->next; + break; + } + l = &f->next; + } + t->master = nil; +} + +void +tkappendpack(Tk *parent, Tk *tk, int where) +{ + Tk *f, **l; + + tk->master = parent; + l = &parent->slave; + for(f = *l; f; f = f->next) { + if(where-- == 0) + break; + l = &f->next; + } + *l = tk; + tk->next = f; + + for( ; parent != nil; parent = parent->master) + if(parent->parent != nil){ + tk->flag |= Tksubsub; + break; + } +} + +static void +tkpackqrm(Tk *t) +{ + Pack *f, **l; + + l = &packorder; + for(f = *l; f; f = f->next) { + if(f->t == t) { + *l = f->next; + free(f); + break; + } + l = &f->next; + } +} + +/* XXX - Tad: leaky... should propagate */ +void +tkpackqit(Tk *t) +{ + Pack *f; + + if(t == nil || (t->flag & Tkdestroy)) + return; + + tkpackqrm(t); + f = malloc(sizeof(Pack)); + if(f == nil) { + print("tkpackqit: malloc failed\n"); + return; + } + + f->t = t; + f->next = packorder; + packorder = f; +} + +void +tkrunpack(TkTop *t) +{ + Tk *tk; + int done; + + while(packorder != nil) { + tk = packorder->t; + if (tk->grid != nil) + done = tkgridder(tk); + else + done = tkpacker(tk); + if (done) + tkpackqrm(tk); + } + tkenterleave(t); + tkdirtyfocusorder(t); +} + +static void +tksetopt(TkParam *p, Tk *tk) +{ + if(p->pad.x != -1) + tk->pad.x = p->pad.x*2; + if(p->pad.y != -1) + tk->pad.y = p->pad.y*2; + if(p->ipad.x != -1) + tk->ipad.x = p->ipad.x*2; + if(p->ipad.y != -1) + tk->ipad.y = p->ipad.y*2; + if(p->side != -1) { + tk->flag &= ~Tkside; + tk->flag |= p->side; + } + if(p->anchor != -1) { + tk->flag &= ~Tkanchor; + tk->flag |= p->anchor; + } + if(p->fill != -1) { + tk->flag &= ~Tkfill; + tk->flag |= p->fill; + } + if(p->expand != BoolX) { + if(p->expand == BoolT) { + tk->flag |= Tkexpand; + } + else + tk->flag &= ~Tkexpand; + } +} + +static char* +tkforget(TkTop *t, char *arg) +{ + Tk *tk; + char *buf; + + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + for(;;) { + arg = tkword(t, arg, buf, buf+Tkmaxitem, nil); + if(buf[0] == '\0') + break; + tk = tklook(t, buf, 0); + if(tk == nil) { + tkrunpack(t); + tkerr(t, buf); + free(buf); + return TkBadwp; + } + tkpackqit(tk->master); + tkdelpack(tk); + } + free(buf); + tkrunpack(t); + return nil; +} + +char* +tkpropagate(TkTop *t, char *arg) +{ + Tk *tk; + TkStab *s; + char *buf; + + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + arg = tkword(t, arg, buf, buf+Tkmaxitem, nil); + tk = tklook(t, buf, 0); + if(tk == nil) { + tkerr(t, buf); + free(buf); + return TkBadwp; + } + + tkword(t, arg, buf, buf+Tkmaxitem, nil); + for(s = tkbool; s->val; s++) { + if(strcmp(s->val, buf) == 0) { + if(s->con == BoolT) { + tk->flag &= ~Tknoprop; + tkpackqit(tk); + tkrunpack(t); + } else + tk->flag |= Tknoprop; + free(buf); + return nil; + } + } + free(buf); + return TkBadvl; +} + +static char* +tkslaves(TkTop *t, char *arg, char **val) +{ + Tk *tk; + char *fmt, *e, *buf; + + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + tkword(t, arg, buf, buf+Tkmaxitem, nil); + tk = tklook(t, buf, 0); + if(tk == nil){ + tkerr(t, buf); + free(buf); + return TkBadwp; + } + free(buf); + + fmt = "%s"; + for(tk = tk->slave; tk; tk = tk->next) { + if (tk->name != nil) { + e = tkvalue(val, fmt, tk->name->name); + if(e != nil) + return e; + fmt = " %s"; + } + } + + return nil; +} + +int +tkisslave(Tk *in, Tk *tk) +{ + if(in == nil) + return 0; + if(in == tk) + return 1; + for(tk = tk->slave; tk; tk = tk->next) + if(tkisslave(in, tk)) + return 1; + return 0; +} + +static char* +tkcanpack(Tk *tk, Tk *parent) +{ + if(tkisslave(parent, tk)) + return TkRecur; + if (parent->grid != nil) { + if (parent->slave != nil) + return TkIsgrid; + tkfreegrid(parent->grid); + parent->grid = nil; + } + return nil; +} + +char* +tkpack(TkTop *t, char *arg, char **val) +{ + TkParam param = defparam; + TkParam *p = ¶m; + TkOptab tko[2]; + Tk *tk, **l, *tkp; + TkName *names, *n; + char *e, *w, *buf; + + buf = mallocz(Tkminitem, 0); + if(buf == nil) + return TkNomem; + + w = tkword(t, arg, buf, buf+Tkminitem, nil); + if(strcmp(buf, "forget") == 0) { + e = tkforget(t, w); + free(buf); + return e; + } + if(strcmp(buf, "propagate") == 0) { + e = tkpropagate(t, w); + free(buf); + return e; + } + if(strcmp(buf, "slaves") == 0) { + e = tkslaves(t, w, val); + free(buf); + return e; + } + free(buf); + + tko[0].ptr = p; + tko[0].optab = opts; + tko[1].ptr = nil; + + names = nil; + e = tkparse(t, arg, tko, &names); + if(e != nil) + return e; + + if((p->before && p->before->master == nil) || + (p->after && p->after->master == nil)) { + tkfreename(names); + return TkNotpk; + } + + for(n = names; n; n = n->link) { + tkp = tklook(t, n->name, 0); + if(tkp == nil) { + tkerr(t, n->name); + tkfreename(names); + return TkBadwp; + } + if(tkp->flag & Tkwindow) { + tkfreename(names); + return TkIstop; + } + if(tkp->parent != nil) { + tkfreename(names); + return TkWpack; + } + n->obj = tkp; + } + + e = nil; + for(n = names; n; n = n->link) { + tk = n->obj; + if(tk->master == nil) { + tk->pad = ZP; + tk->ipad = ZP; + tk->flag &= ~(Tkanchor|Tkside|Tkfill|Tkexpand); + tk->flag |= Tktop; + } + if(tk->master != nil) { + tkpackqit(tk->master); + tkdelpack(tk); + } + if(p->before == nil && p->after == nil && p->in == nil) { + tkp = tklook(t, n->name, 1); + if(tkp == nil) { + e = TkBadwp; + tkerr(t, n->name); + goto Error; + } + e = tkcanpack(tk, tkp); + if (e != nil) + goto Error; + tkappendpack(tkp, tk, -1); + } + else { + if(p->in != nil) { + e = tkcanpack(tk, p->in); + if(e != nil) + goto Error; + tkappendpack(p->in, tk, -1); + } + else + if(p->before != nil) { + e = tkcanpack(tk, p->before->master); + if (e != nil) + goto Error; + tk->master = p->before->master; + l = &tk->master->slave; + for(;;) { + if(*l == p->before) { + tk->next = *l; + *l = tk; + break; + } + l = &(*l)->next; + } + p->before = tk; + } + else { + e = tkcanpack(tk, p->after->master); + if (e != nil) + goto Error; + tk->master = p->after->master; + tk->next = p->after->next; + p->after->next = tk; + p->after = tk; + } + } + tksetopt(p, tk); + if (tk->master->flag&Tksubsub) + tksetbits(tk, Tksubsub); + tkpackqit(tk->master); + } + +Error: + tkfreename(names); + tkrunpack(t); + + return e; +} + +void +tksetslavereq(Tk *slave, TkGeom frame) +{ + Point border; + TkGeom pos, old; + int slave2BW; + void (*geomfn)(Tk*); + + border.x = slave->pad.x; + border.y = slave->pad.y; + + slave2BW = slave->borderwidth * 2; + + pos.width = slave->req.width + slave2BW + slave->ipad.x; + if((slave->flag&Tkfillx) || (pos.width > (frame.width - border.x))) + pos.width = frame.width - border.x; + + pos.height = slave->req.height + slave2BW + slave->ipad.y; + if((slave->flag&Tkfilly) || (pos.height > (frame.height - border.y))) + pos.height = frame.height - border.y; + + border.x /= 2; + border.y /= 2; + + if(slave->flag & Tknorth) + pos.y = frame.y + border.y; + else + if(slave->flag & Tksouth) + pos.y = frame.y + frame.height - pos.height - border.y; + else + pos.y = frame.y + (frame.height - pos.height)/2; + + if(slave->flag & Tkwest) + pos.x = frame.x + border.x; + else + if(slave->flag & Tkeast) + pos.x = frame.x + frame.width - pos.width - border.x; + else + pos.x = frame.x + (frame.width - pos.width)/2; + + pos.width -= slave2BW; + pos.height -= slave2BW; + + if(memcmp(&slave->act, &pos, sizeof(TkGeom)) != 0) { + old = slave->act; + slave->act = pos; + geomfn = tkmethod[slave->type]->geom; + if(geomfn != nil) + geomfn(slave); + if(slave->slave) + tkpackqit(slave); + tkdeliver(slave, TkConfigure, &old); + + slave->dirty = tkrect(slave, 1); + slave->flag |= Tkrefresh; + } +} +static int +tkexpandx(Tk* slave, int cavityWidth) +{ + int numExpand, minExpand, curExpand, childWidth; + + minExpand = cavityWidth; + numExpand = 0; + for( ;slave != nil; slave = slave->next) { + childWidth = slave->req.width + slave->borderwidth*2 + + slave->pad.x + slave->ipad.x; + if(slave->flag & (Tktop|Tkbottom)) { + curExpand = (cavityWidth - childWidth)/numExpand; + if (curExpand < minExpand) + minExpand = curExpand; + } + else { + cavityWidth -= childWidth; + if(slave->flag & Tkexpand) + numExpand++; + } + } + curExpand = cavityWidth/numExpand; + if(curExpand < minExpand) + minExpand = curExpand; + + return (minExpand < 0) ? 0 : minExpand; +} + +static int +tkexpandy(Tk *slave, int cavityHeight) +{ + int numExpand, minExpand, curExpand, childHeight; + + minExpand = cavityHeight; + numExpand = 0; + for ( ;slave != nil; slave = slave->next) { + childHeight = slave->req.height + slave->borderwidth*2 + + + slave->pad.y + slave->ipad.y; + if(slave->flag & (Tkleft|Tkright)) { + curExpand = (cavityHeight - childHeight)/numExpand; + if(curExpand < minExpand) + minExpand = curExpand; + } + else { + cavityHeight -= childHeight; + if(slave->flag & Tkexpand) + numExpand++; + } + } + curExpand = cavityHeight/numExpand; + if(curExpand < minExpand) + minExpand = curExpand; + + return (minExpand < 0) ? 0 : minExpand; +} + +static int +tkpacker(Tk *master) +{ + Tk *slave; + TkGeom frame, cavity, pos; + int maxwidth, maxheight, tmp, slave2BW; + + pos.width = 0; + pos.height = 0; + maxwidth = 0; + maxheight = 0; + + master->flag |= Tkrefresh; + + for (slave = master->slave; slave != nil; slave = slave->next) { + slave2BW = slave->borderwidth*2; + if(slave->flag & (Tktop|Tkbottom)) { + tmp = slave->req.width + slave2BW + + slave->pad.x + slave->ipad.x + pos.width; + if(tmp > maxwidth) + maxwidth = tmp; + pos.height += slave->req.height + slave2BW + + slave->pad.y + slave->ipad.y; + } + else { + tmp = slave->req.height + slave2BW + + slave->pad.y + slave->ipad.y + pos.height; + if(tmp > maxheight) + maxheight = tmp; + pos.width += slave->req.width + slave2BW + + + slave->pad.x + slave->ipad.x; + } + } + if(pos.width > maxwidth) + maxwidth = pos.width; + if(pos.height > maxheight) + maxheight = pos.height; + + if(maxwidth != master->req.width || maxheight != master->req.height) + if((master->flag & Tknoprop) == 0) { + if(master->geom != nil) { + master->geom(master, master->act.x, master->act.y, + maxwidth, maxheight); + } else { + master->req.width = maxwidth; + master->req.height = maxheight; + tkpackqit(master->master); + } + return 0; + } + + cavity.x = 0; + cavity.y = 0; + pos.x = 0; + pos.y = 0; + cavity.width = master->act.width; + cavity.height = master->act.height; + + for(slave = master->slave; slave != nil; slave = slave->next) { + slave2BW = slave->borderwidth*2; + if(slave->flag & (Tktop|Tkbottom)) { + frame.width = cavity.width; + frame.height = slave->req.height + slave2BW + + slave->pad.y + slave->ipad.y; + if(slave->flag & Tkexpand) + frame.height += tkexpandy(slave, cavity.height); + cavity.height -= frame.height; + if(cavity.height < 0) { + frame.height += cavity.height; + cavity.height = 0; + } + frame.x = cavity.x; + if(slave->flag & Tktop) { + frame.y = cavity.y; + cavity.y += frame.height; + } + else + frame.y = cavity.y + cavity.height; + } + else { + frame.height = cavity.height; + frame.width = slave->req.width + slave2BW + + slave->pad.x + slave->ipad.x; + if(slave->flag & Tkexpand) + frame.width += tkexpandx(slave, cavity.width); + cavity.width -= frame.width; + if(cavity.width < 0) { + frame.width += cavity.width; + cavity.width = 0; + } + frame.y = cavity.y; + if(slave->flag & Tkleft) { + frame.x = cavity.x; + cavity.x += frame.width; + } + else + frame.x = cavity.x + cavity.width; + } + + tksetslavereq(slave, frame); + } + + master->dirty = tkrect(master, 1); + tkdirty(master); + return 1; +} + diff --git a/libtk/panel.c b/libtk/panel.c new file mode 100644 index 00000000..f482a796 --- /dev/null +++ b/libtk/panel.c @@ -0,0 +1,408 @@ +#include "lib9.h" +#include "draw.h" +#include "tk.h" + +#define O(t, e) ((long)(&((t*)0)->e)) + +typedef struct TkPanel TkPanel; +struct TkPanel +{ + Image* image; + Image* matte; + Point view; /* vector from image origin to widget origin */ + Rectangle r; /* drawn rectangle (in image coords) */ + int anchor; + int hasalpha; /* does the image include an alpha channel? */ +}; + +static TkOption tkpanelopts[] = +{ + "anchor", OPTflag, O(TkPanel, anchor), tkanchor, + nil +}; + +static int +tkdrawnrect(Image *image, Image *matte, Rectangle *r) +{ + *r = image->clipr; + if (matte != nil) { + if (!rectclip(r, matte->clipr)) + return 0; + if (!matte->repl && !rectclip(r, matte->r)) + return 0; + } + if (!image->repl && !rectclip(r, image->r)) + return 0; + return 1; +} + +char* +tkpanel(TkTop *t, char *arg, char **ret) +{ + TkOptab tko[3]; + Tk *tk; + TkPanel *tkp; + TkName *names; + char *e; + + tk = tknewobj(t, TKpanel, sizeof(Tk)+sizeof(TkPanel)); + if(tk == nil) + return TkNomem; + + tkp = TKobj(TkPanel, tk); + tkp->anchor = Tkcenter; + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tkp; + tko[1].optab = tkpanelopts; + tko[2].ptr = nil; + names = nil; + + e = tkparse(t, arg, tko, &names); + if(e != nil) { + tkfreeobj(tk); + return e; + } + + tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd)); + + e = tkaddchild(t, tk, &names); + + tkfreename(names); + if (e != nil) { + tkfreeobj(tk); + return e; + } + + tk->name->link = nil; + return tkvalue(ret, "%s", tk->name->name); +} + +void +tkgetpanelimage(Tk *tk, Image **i, Image **m) +{ + TkPanel *tkp = TKobj(TkPanel, tk); + *i = tkp->image; + *m = tkp->matte; +} + +void +tksetpanelimage(Tk *tk, Image *image, Image *matte) +{ + TkPanel *tkp = TKobj(TkPanel, tk); + int ishuge; + TkGeom g; + + g = tk->req; + + tkp->image = image; + tkp->matte = matte; + + if (!tkdrawnrect(image, matte, &tkp->r)) { + tkp->r.min = image->r.min; + tkp->r.max = image->r.min; + } + + tkp->view = tkp->r.min; /* XXX do we actually want to keep the old one? */ + /* + * if both image and matte are replicated, then we've got no idea what + * the rectangle should be, so request zero size, and set origin to (0, 0). + */ + ishuge = (Dx(tkp->r) >= 10000000); + if((tk->flag & Tksetwidth) == 0){ + if(ishuge) + tk->req.width = 0; + else + tk->req.width = Dx(tkp->r); + } + if(ishuge) + tkp->view.x = 0; + + ishuge = (Dy(tkp->r) >= 10000000); + if((tk->flag & Tksetheight) == 0){ + if(ishuge) + tk->req.height = 0; + else + tk->req.height = Dy(tkp->r); + } + if(ishuge) + tkp->view.y = 0; + + tkp->hasalpha = tkchanhastype(image->chan, CAlpha); + tkgeomchg(tk, &g, tk->borderwidth); + tksettransparent(tk, tkp->hasalpha || tkhasalpha(tk->env, TkCbackgnd)); + tk->dirty = tkrect(tk, 0); +} + +static void +tkfreepanel(Tk *tk) +{ + TkPanel *tkp = TKobj(TkPanel, tk); + tkdelpanelimage(tk->env->top, tkp->image); + tkdelpanelimage(tk->env->top, tkp->matte); +} + +static Point +tkpanelview(Tk *tk) +{ + int dx, dy; + Point view; + TkPanel *tkp = TKobj(TkPanel, tk); + + dx = tk->act.width - Dx(tkp->r); + dy = tk->act.height - Dy(tkp->r); + + view = tkp->view; + + if (dx > 0) { + if((tkp->anchor & (Tkeast|Tkwest)) == 0) + view.x -= dx/2; + else + if(tkp->anchor & Tkeast) + view.x -= dx; + } + if (dy > 0) { + if((tkp->anchor & (Tknorth|Tksouth)) == 0) + view.y -= dy/2; + else + if(tkp->anchor & Tksouth) + view.y -= dy; + } + return view; +} + +static char* +tkdrawpanel(Tk *tk, Point orig) +{ + Rectangle r, pr; + TkPanel *tkp = TKobj(TkPanel, tk); + Image *i; + int any; + Point view, p; + + i = tkimageof(tk); + if (i == nil) + return nil; + + p.x = orig.x + tk->act.x + tk->borderwidth; + p.y = orig.y + tk->act.y + tk->borderwidth; + + view = tkpanelview(tk); + + /* + * if the image doesn't fully cover the dirty rectangle, then + * paint some background in there + */ + r = rectsubpt(tkp->r, view); /* convert to widget coords */ + pr = tkrect(tk, 0); + any = rectclip(&r, pr); /* clip to inside widget borders */ + + if (!any || tkp->hasalpha || !rectinrect(tk->dirty, r)) + draw(i, rectaddpt(tk->dirty, p), tkgc(tk->env, TkCbackgnd), nil, ZP); + + if (any && rectclip(&r, tk->dirty)) + draw(i, rectaddpt(r, p), tkp->image, tkp->matte, addpt(r.min, view)); + + if (!rectinrect(tk->dirty, pr)) { + p.x -= tk->borderwidth; + p.y -= tk->borderwidth; + tkdrawrelief(i, tk, p, TkCbackgnd, tk->relief); + } + return nil; +} + +static char* +tkpanelcget(Tk *tk, char *arg, char **val) +{ + TkOptab tko[3]; + TkPanel *tkp = TKobj(TkPanel, tk); + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tkp; + tko[1].optab = tkpanelopts; + tko[2].ptr = nil; + + return tkgencget(tko, arg, val, tk->env->top); +} + +static char* +tkpanelcvt(Tk *tk, char *arg, int rel, int *p) +{ + char buf[Tkmaxitem]; + + tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + if(buf[0] == '\0') + return TkBadvl; + *p = atoi(buf) + rel; + return nil; +} + +/* + * screen to image + */ +static char* +tkpanelpanelx(Tk *tk, char *arg, char **val) +{ + Point p; + char *e; + + USED(val); + p = subpt(tkposn(tk), tkpanelview(tk)); + e = tkpanelcvt(tk, arg, -p.x, &p.x); + if (e != nil) + return e; + return tkvalue(val, "%d", p.x); +} + +static char* +tkpanelpanely(Tk *tk, char *arg, char **val) +{ + Point p; + char *e; + + USED(val); + p = subpt(tkposn(tk), tkpanelview(tk)); + e = tkpanelcvt(tk, arg, -p.y, &p.y); + if (e != nil) + return e; + return tkvalue(val, "%d", p.y); +} + +/* + * image to screen + */ +static char* +tkpanelscreenx(Tk *tk, char *arg, char **val) +{ + Point p; + char *e; + + USED(val); + p = subpt(tkposn(tk), tkpanelview(tk)); + e = tkpanelcvt(tk, arg, p.x, &p.x); + if (e != nil) + return e; + return tkvalue(val, "%d", p.x); +} + +static char* +tkpanelscreeny(Tk *tk, char *arg, char **val) +{ + Point p; + char *e; + + USED(val); + p = subpt(tkposn(tk), tkpanelview(tk)); + e = tkpanelcvt(tk, arg, p.y, &p.y); + if (e != nil) + return e; + return tkvalue(val, "%d", p.y); +} + +static char* +tkpanelconf(Tk *tk, char *arg, char **val) +{ + char *e; + TkGeom g; + int bd; + TkOptab tko[3]; + TkPanel *tkp = TKobj(TkPanel, tk); + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tkp; + tko[1].optab = tkpanelopts; + 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); + tkgeomchg(tk, &g, bd); + tksettransparent(tk, tkp->hasalpha || tkhasalpha(tk->env, TkCbackgnd)); + + tk->dirty = tkrect(tk, 1); + + return e; +} + +static char* +tkpaneldirty(Tk *tk, char *arg, char **val) +{ + char buf[Tkmaxitem]; + int n, coords[4]; + Rectangle r; + char *e, *p; + TkPanel *tkp = TKobj(TkPanel, tk); + + USED(val); + n = 0; + while (n < 4) { + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + if (buf[0] == 0) + break; + p = buf; + e = tkfrac(&p, &coords[n++], nil); + if (e != nil) + return TkBadvl; + } + if (n == 0) + r = tkp->r; + else { + if (n != 4) + return TkBadvl; + r.min.x = TKF2I(coords[0]); + r.min.y = TKF2I(coords[1]); + r.max.x = TKF2I(coords[2]); + r.max.y = TKF2I(coords[3]); + } + if (rectclip(&r, tkp->r)) { + r = rectsubpt(r, tkpanelview(tk)); /* convert to widget coords */ + if (rectclip(&r, tkrect(tk, 0))) /* clip to visible area */ + combinerect(&tk->dirty, r); + } + return nil; +} + +static char* +tkpanelorigin(Tk *tk, char *arg, char **val) +{ + char *e; + Point view; + TkPanel *tkp = TKobj(TkPanel, tk); + + e = tkxyparse(tk, &arg, &view); + if (e != nil) { + if (e == TkOparg) + return tkvalue(val, "%d %d", tkp->view.x, tkp->view.y); + return e; + } + tkp->view = view; + tk->dirty = tkrect(tk, 0); + return nil; +} + +static +TkCmdtab tkpanelcmd[] = +{ + "cget", tkpanelcget, + "configure", tkpanelconf, + "dirty", tkpaneldirty, + "origin", tkpanelorigin, + "panelx", tkpanelpanelx, + "panely", tkpanelpanely, + "screenx", tkpanelscreenx, + "screeny", tkpanelscreeny, + nil +}; + +TkMethod panelmethod = { + "panel", + tkpanelcmd, + tkfreepanel, + tkdrawpanel +}; diff --git a/libtk/parse.c b/libtk/parse.c new file mode 100644 index 00000000..d2a31dd3 --- /dev/null +++ b/libtk/parse.c @@ -0,0 +1,1165 @@ +#include "lib9.h" +#include "kernel.h" +#include "draw.h" +#include "tk.h" + +#define O(t, e) ((long)(&((t*)0)->e)) + +static char* pdist(TkTop*, TkOption*, void*, char**, char*, char*); +static char* pstab(TkTop*, TkOption*, void*, char**, char*, char*); +static char* ptext(TkTop*, TkOption*, void*, char**, char*, char*); +static char* pwinp(TkTop*, TkOption*, void*, char**, char*, char*); +static char* pbmap(TkTop*, TkOption*, void*, char**, char*, char*); +static char* pbool(TkTop*, TkOption*, void*, char**, char*, char*); +static char* pfont(TkTop*, TkOption*, void*, char**, char*, char*); +static char* pfrac(TkTop*, TkOption*, void*, char**, char*, char*); +static char* pnnfrac(TkTop*, TkOption*, void*, char**, char*, char*); +static char* pctag(TkTop*, TkOption*, void*, char**, char*, char*); +static char* ptabs(TkTop*, TkOption*, void*, char**, char*, char*); +static char* pcolr(TkTop*, TkOption*, void*, char**, char*, char*); +static char* pimag(TkTop*, TkOption*, void*, char**, char*, char*); +static char* psize(TkTop*, TkOption*, void*, char**, char*, char*); +static char* pnndist(TkTop*, TkOption*, void*, char**, char*, char*); +static char* pact(TkTop*, TkOption*, void*, char**, char*, char*); +static char* pignore(TkTop*, TkOption*, void*, char**, char*, char*); +static char* psticky(TkTop*, TkOption*, void*, char**, char*, char*); +static char* plist(TkTop*, TkOption*, void*, char**, char*, char*); + +static char* (*oparse[])(TkTop*, TkOption*, void*, char**, char*, char*) = +{ + /* OPTdist */ pdist, + /* OPTstab */ pstab, + /* OPTtext */ ptext, + /* OPTwinp */ pwinp, + /* OPTflag */ pstab, + /* OPTbmap */ pbmap, + /* OPTbool */ pbool, + /* OPTfont */ pfont, + /* OPTfrac */ pfrac, + /* OPTnnfrac */ pnnfrac, + /* OPTctag */ pctag, + /* OPTtabs */ ptabs, + /* OPTcolr */ pcolr, + /* OPTimag */ pimag, + /* OPTsize */ psize, + /* OPTnndist */ pnndist, + /* OPTact */ pact, + /* OPTignore */ pignore, + /* OPTsticky */ psticky, + /* OPTlist */ plist, +}; + +char* +tkskip(char *s, char *bl) +{ + char *p; + + while(*s) { + for(p = bl; *p; p++) + if(*p == *s) + break; + if(*p == '\0') + return s; + s++; + } + return s; +} + +/* XXX - Tad: error propagation? */ +char* +tkword(TkTop *t, char *str, char *buf, char *ebuf, int *gotarg) +{ + int c, lev, tmp; + char *val, *e, *p, *cmd; + if (gotarg == nil) + gotarg = &tmp; + + /* + * ebuf is one beyond last byte in buf; leave room for nul byte in + * all cases. + */ + --ebuf; + + str = tkskip(str, " \t"); + *gotarg = 1; + lev = 1; + switch(*str) { + case '{': + /* XXX - DBK: According to Ousterhout (p.37), while back= + * slashed braces don't count toward finding the matching + * closing braces, the backslashes should not be removed. + * Presumably this also applies to other backslashed + * characters: the backslash should not be removed. + */ + str++; + while(*str && buf < ebuf) { + c = *str++; + if(c == '\\') { + if(*str == '}' || *str == '{' || *str == '\\') + c = *str++; + } + else + if(c == '}') { + lev--; + if(lev == 0) + break; + } + else + if(c == '{') + lev++; + *buf++ = c; + } + break; + case '[': + /* XXX - DBK: According to Ousterhout (p. 33) command + * substitution may occur anywhere within a word, not + * only (as here) at the beginning. + */ + cmd = malloc(strlen(str)); /* not strlen+1 because the first character is skipped */ + if ( cmd == nil ) { + buf[0] = '\0'; /* DBK - Why not an error message? */ + return str; + } + p = cmd; + str++; + while(*str) { + c = *str++; + if(c == '\\') { + if(*str == ']' || *str == '[' || *str == '\\') + c = *str++; + } + else + if(c == ']') { + lev--; + if(lev == 0) + break; + } + else + if(c == '[') + lev++; + *p++ = c; + } + *p = '\0'; + val = nil; + e = tkexec(t, cmd, &val); + free(cmd); + /* XXX - Tad: is this appropriate behavior? + * Am I sure that the error doesn't need to be + * propagated back to the caller? + */ + if(e == nil && val != nil) { + strncpy(buf, val, ebuf-buf); + buf = ebuf; + free(val); + } + break; + case '\'': + str++; + while(*str && buf < ebuf) + *buf++ = *str++; + break; + case '\0': + *gotarg = 0; + break; + default: + /* XXX - DBK: See comment above about command substitution. + * Also, any backslashed character should be replaced by + * itself (e.g. to put a space, tab, or [ into a word. + * We assume that the C compiler has already done the + * standard ANSI C substitutions. (But should we?) + */ + while(*str && *str != ' ' && *str != '\t' && buf < ebuf) + *buf++ = *str++; + } + *buf = '\0'; + return str; +} + +static TkOption* +Getopt(TkOption *o, char *buf) +{ + while(o->o != nil) { + if(strcmp(buf, o->o) == 0) + return o; + o++; + } + return nil; +} + +TkName* +tkmkname(char *name) +{ + TkName *n; + + n = malloc(sizeof(struct TkName)+strlen(name)); + if(n == nil) + return nil; + strcpy(n->name, name); + n->link = nil; + n->obj = nil; + return n; +} + +char* +tkparse(TkTop *t, char *str, TkOptab *ot, TkName **nl) +{ + int l; + TkOptab *ft; + TkOption *o; + TkName *f, *n; + char *e, *buf, *ebuf; + + l = strlen(str); + if (l < Tkmaxitem) + l = Tkmaxitem; + buf = malloc(l + 1); + if(buf == 0) + return TkNomem; + ebuf = buf + l + 1; + + e = nil; + while(e == nil) { + str = tkword(t, str, buf, ebuf, nil); + switch(*buf) { + case '\0': + goto done; + case '-': + if (buf[1] != '\0') { + for(ft = ot; ft->ptr; ft++) { + o = Getopt(ft->optab, buf+1); + if(o != nil) { + e = oparse[o->type](t, o, ft->ptr, &str, buf, ebuf); + break; + } + } + if(ft->ptr == nil){ + e = TkBadop; + tkerr(t, buf); + } + break; + } + /* fall through if we've got a singleton '-' */ + default: + if(nl == nil) { + e = TkBadop; + tkerr(t, buf); + break; + } + n = tkmkname(buf); + if(n == nil) { + e = TkNomem; + break; + } + if(*nl == nil) + *nl = n; + else { + for(f = *nl; f->link; f = f->link) + ; + f->link = n; + } + } + } + + if(e != nil && nl != nil) + tkfreename(*nl); +done: + free(buf); + return e; +} + +char* +tkconflist(TkOptab *ot, char **val) +{ + TkOption *o; + char *f, *e; + + f = "-%s"; + while(ot->ptr != nil) { + o = ot->optab; + while(o->o != nil) { + e = tkvalue(val, f, o->o); + if(e != nil) + return e; + f = " -%s"; + o++; + } + ot++; + } + return nil; +} + +char* +tkgencget(TkOptab *ft, char *arg, char **val, TkTop *t) +{ + Tk *w; + char *c; + Point g; + TkEnv *e; + TkStab *s; + TkOption *o; + int wh, con, i, n, flag, *v; + char *r, *buf, *fmt; + + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + + tkitem(buf, arg); + r = buf; + if(*r == '-') + r++; + o = nil; + while(ft->ptr) { + o = Getopt(ft->optab, r); + if(o != nil) + break; + ft++; + } + if(o == nil) { + tkerr(t, r); + free(buf); + return TkBadop; + } + + switch(o->type) { + default: + tkerr(t, r); + free(buf); + return TkBadop; + case OPTignore: + return nil; + case OPTact: + w = ft->ptr; + g = tkposn(w); + n = g.y; + if(o->aux == 0) + n = g.x; + free(buf); + return tkvalue(val, "%d", n); + case OPTdist: + case OPTnndist: + free(buf); + return tkvalue(val, "%d", OPTION(ft->ptr, int, o->offset)); + case OPTsize: + w = ft->ptr; + if(strcmp(r, "width") == 0) + wh = w->req.width; + else + wh = w->req.height; + free(buf); + return tkvalue(val, "%d", wh); + case OPTtext: + c = OPTION(ft->ptr, char*, o->offset); + if(c == nil) + c = ""; + free(buf); + return tkvalue(val, "%s", c); + case OPTwinp: + w = OPTION(ft->ptr, Tk*, o->offset); + if(w == nil || w->name == nil) + c = ""; + else + c = w->name->name; + free(buf); + return tkvalue(val, "%s", c); + case OPTstab: + s = o->aux; + c = ""; + con = OPTION(ft->ptr, int, o->offset); + while(s->val) { + if(con == s->con) { + c = s->val; + break; + } + s++; + } + free(buf); + return tkvalue(val, "%s", c); + case OPTflag: + con = OPTION(ft->ptr, int, o->offset); + flag = 0; + for (s = o->aux; s->val != nil; s++) + flag |= s->con; + c = ""; + for (s = o->aux; s->val != nil; s++) { + if ((con & flag) == s->con) { + c = s->val; + break; + } + } + free(buf); + return tkvalue(val, "%s", c); + case OPTfont: + e = OPTION(ft->ptr, TkEnv*, o->offset); + free(buf); + if (e->font != nil) + return tkvalue(val, "%s", e->font->name); + return nil; + case OPTcolr: + e = OPTION(ft->ptr, TkEnv*, o->offset); + i = AUXI(o->aux); + free(buf); + return tkvalue(val, "#%.8lux", e->colors[i]); + case OPTfrac: + case OPTnnfrac: + v = &OPTION(ft->ptr, int, o->offset); + n = (int)o->aux; + if(n == 0) + n = 1; + fmt = "%s"; + for(i = 0; i < n; i++) { + tkfprint(buf, *v++); + r = tkvalue(val, fmt, buf); + if(r != nil) { + free(buf); + return r; + } + fmt = " %s"; + } + free(buf); + return nil; + case OPTbmap: + return tkvalue(val, "%d", OPTION(ft->ptr, Image*, o->offset) != nil); + case OPTimag: + return tkvalue(val, "%d", OPTION(ft->ptr, TkImg*, o->offset) != nil); + } +} + +static char* +pact(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf) +{ + USED(buf); + USED(ebuf); + USED(str); + USED(place); + tkerr(t, o->o); + return TkBadop; +} + +static char* +pignore(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf) +{ + char *p; + USED(t); + USED(o); + USED(place); + + p = tkword(t, *str, buf, ebuf, nil); + if(*buf == '\0') + return TkOparg; + *str = p; + return nil; +} + +static char* +pdist(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf) +{ + int d; + char *e; + TkEnv *env; + + USED(buf); + USED(ebuf); + + /* + * this is a bit of a hack, as 0 is a valid option offset, + * but a nil aux is commonly used when 'w' and 'h' suffixes + * aren't appropriate. + * just make sure that no structure placed in TkOptab->ptr + * with an OPTdist element has a TkEnv as its first member. + */ + + if (o->aux == nil) + env = nil; + else + env = OPTION(place, TkEnv*, AUXI(o->aux)); + e = tkfracword(t, str, &d, env); + if(e != nil) + return e; + OPTION(place, int, o->offset) = TKF2I(d); + return nil; +} + +static char* +pnndist(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf) +{ + char* e; + int oldv; + + oldv = OPTION(place, int, o->offset); + e = pdist(t, o, place, str, buf, ebuf); + if(e == nil && OPTION(place, int, o->offset) < 0) { + OPTION(place, int, o->offset) = oldv; + return TkBadvl; + } + return e; +} + +static char* +psize(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf) +{ + Tk *tk; + char *e; + int d, off; + + USED(ebuf); + e = tkfracword(t, str, &d, OPTION(place, TkEnv*, AUXI(o->aux))); + if (e != nil) + return e; + if(d < 0) + return TkBadvl; + + tk = place; + /* + * XXX there's no way of resetting Tksetwidth or Tksetheight. + * could perhaps allow it by setting width/height to {} + */ + if(strcmp(buf+1, "width") == 0) { + tk->flag |= Tksetwidth; + off = O(Tk, req.width); + } + else { + tk->flag |= Tksetheight; + off = O(Tk, req.height); + } + OPTION(place, int, off) = TKF2I(d); + return nil; +} + +static char* +pstab(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf) +{ + char *p; + int mask; + TkStab *s, *c; + + p = tkword(t, *str, buf, ebuf, nil); + if(*buf == '\0') + return TkOparg; + + for(s = o->aux; s->val; s++) + if(strcmp(s->val, buf) == 0) + break; + if(s->val == nil) + return TkBadvl; + + *str = p; + if(o->type == OPTstab) { + OPTION(place, int, o->offset) = s->con; + return nil; + } + + mask = 0; + for(c = o->aux; c->val; c++) + mask |= c->con; + + OPTION(place, int, o->offset) &= ~mask; + OPTION(place, int, o->offset) |= s->con; + + /* + * a hack, but otherwise we have to dirty the focus order + * every time any command is executed on a widget + */ + if (!strcmp(o->o, "takefocus")) + tkdirtyfocusorder(t); + return nil; +} + +enum { + Stickyn = (1<<0), + Stickye = (1<<1), + Stickys = (1<<2), + Stickyw = (1<<3) +}; + +static int stickymap[16] = +{ + 0, + Tknorth, + Tkeast, + Tknorth|Tkeast, + Tksouth, + Tkfilly, + Tksouth|Tkeast, + Tkeast|Tkfilly, + Tkwest, + Tknorth|Tkwest, + Tkfillx, + Tknorth|Tkfillx, + Tksouth|Tkwest, + Tkwest|Tkfilly, + Tksouth|Tkfillx, + Tkfillx|Tkfilly, +}; + +static char* +psticky(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf) +{ + char *p, *s; + int flag, sflag; + + p = tkword(t, *str, buf, ebuf, nil); + *str = p; + + flag = 0; + for (s = buf; *s; s++) { + switch (*s) { + case 'n': + flag |= Stickyn; + break; + case 's': + flag |= Stickys; + break; + case 'e': + flag |= Stickye; + break; + case 'w': + flag |= Stickyw; + break; + case ' ': + case ',': + break; + default: + return TkBadvl; + } + } + sflag = OPTION(place, int, o->offset) & ~(Tkanchor|Tkfill); + OPTION(place, int, o->offset) = sflag | stickymap[flag]; + return nil; +} + +static char* +ptext(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf) +{ + char **p; + + *str = tkword(t, *str, buf, ebuf, nil); + + p = &OPTION(place, char*, o->offset); + if(*p != nil) + free(*p); + if(buf[0] == '\0') + *p = nil; + else { + *p = strdup(buf); + if(*p == nil) + return TkNomem; + } + return nil; +} + +static char* +pimag(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf) +{ + int locked; + Display *d; + TkImg **p, *i; + + i = nil; + p = &OPTION(place, TkImg*, o->offset); + *str = tkword(t, *str, buf, ebuf, nil); + if(*buf != '\0') { + i = tkname2img(t, buf); + if(i == nil) + return TkBadvl; + i->ref++; + } + + if(*p != nil) { + d = t->display; + locked = lockdisplay(d); + tkimgput(*p); + if(locked) + unlockdisplay(d); + } + *p = i; + return nil; +} + +static char* +pbmap(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf) +{ + Display *d; + Image *i, **p; + int locked, fd; + char *c; + + p = &OPTION(place, Image*, o->offset); + + d = t->display; + *str = tkword(t, *str, buf, ebuf, nil); + if(*buf == '\0' || *buf == '-') { + if(*p != nil) { + locked = lockdisplay(d); + freeimage(*p); + if(locked) + unlockdisplay(d); + *p = nil; + } + return nil; + } + + if(buf[0] == '@') + i = display_open(d, buf+1); + else + if(buf[0] == '<') { + buf++; + fd = strtoul(buf, &c, 0); + if(c == buf) { + return TkBadvl; + } + i = readimage(d, fd, 1); + } + else { + char *file; + + file = mallocz(Tkmaxitem, 0); + if(file == nil) + return TkNomem; + + snprint(file, Tkmaxitem, "/icons/tk/%s", buf); + i = display_open(d, file); + free(file); + } + if(i == nil) + return TkBadbm; + + if(*p != nil) { + locked = lockdisplay(d); + freeimage(*p); + if(locked) + unlockdisplay(d); + } + *p = i; + return nil; +} + +static char* +pfont(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf) +{ + TkEnv *e; + Display *d; + int locked; + Font *font; + + *str = tkword(t, *str, buf, ebuf, nil); + if(*buf == '\0') + return TkOparg; + + d = t->display; + font = font_open(d, buf); + if(font == nil) + return TkBadft; + + e = tkdupenv(&OPTION(place, TkEnv*, o->offset)); + if(e == nil) { + freefont(font); /* XXX lockdisplay around this? */ + return TkNomem; + } + if(e->font) + font_close(e->font); + e->font = font; + + locked = lockdisplay(d); + e->wzero = stringwidth(font, "0"); + if ( e->wzero <= 0 ) + e->wzero = e->font->height / 2; + if(locked) + unlockdisplay(d); + + return nil; +} + +static int +hex(int c) +{ + if(c >= 'a') + c -= 'a'-'A'; + if(c >= 'A') + c = 10 + (c - 'A'); + else + c -= '0'; + return c; +} + +static ulong +changecol(TkEnv *e, int setcol, int col, ulong rgba) +{ + if (setcol) { + e->set |= (1<<col); + } else { + rgba = 0; + e->set &= ~(1<<col); + } + e->colors[col] = rgba; + return rgba; +} + +char* +tkparsecolor(char *buf, ulong *rgba) +{ + char *p, *q, *e; + int R, G, B, A; + int i, alpha, len, alen; + /* + * look for alpha modifier in *#AA or *0.5 format + */ + len = strlen(buf); + p = strchr(buf, '*'); + if(p != nil) { + alen = len - (p - buf); + if(p[1] == '#') { + if(alen != 4) + return TkBadvl; + alpha = (hex(p[2])<<4) | (hex(p[3])); + } else { + q = p+1; + e = tkfrac(&q, &alpha, nil); + if (e != nil) + return e; + alpha = TKF2I(alpha * 0xff); + } + *p = '\0'; + len -= alen; + } else + alpha = 0xff; + + if (*buf == '#') { + switch(len) { + case 4: /* #RGB */ + R = hex(buf[1]); + G = hex(buf[2]); + B = hex(buf[3]); + *rgba = (R<<28) | (G<<20) | (B<<12) | 0xff; + break; + case 7: /* #RRGGBB */ + R = (hex(buf[1])<<4)|(hex(buf[2])); + G = (hex(buf[3])<<4)|(hex(buf[4])); + B = (hex(buf[5])<<4)|(hex(buf[6])); + *rgba = (R<<24) | (G<<16) | (B<<8) | 0xff; + break; + case 9: /* #RRGGBBAA */ + R = (hex(buf[1])<<4)|(hex(buf[2])); + G = (hex(buf[3])<<4)|(hex(buf[4])); + B = (hex(buf[5])<<4)|(hex(buf[6])); + A = (hex(buf[7])<<4)|(hex(buf[8])); + *rgba = (R<<24) | (G<<16) | (B<<8) | A; + break; + default: + return TkBadvl; + } + } else { + for(i = 0; tkcolortab[i].val != nil; i++) + if (!strcmp(tkcolortab[i].val, buf)) + break; + if (tkcolortab[i].val == nil) + return TkBadvl; + *rgba = tkcolortab[i].con; + } + if (alpha != 0xff) { + tkrgbavals(*rgba, &R, &G, &B, &A); + A = (A * alpha) / 255; + *rgba = tkrgba(R, G, B, A); + } + return nil; +} + +static char* +pcolr(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf) +{ + TkEnv *env; + char *e; + ulong rgba, dark, light; + int color, setcol; + + *str = tkword(t, *str, buf, ebuf, nil); + rgba = 0; + if(*buf == '\0') { + setcol = 0; + } else { + setcol = 1; + e = tkparsecolor(buf, &rgba); + if(e != nil) + return e; + } + + env = tkdupenv(&OPTION(place, TkEnv*, o->offset)); + if(env == nil) + return TkNomem; + + color = AUXI(o->aux); + rgba = changecol(env, setcol, color, rgba); + if(color == TkCbackgnd || color == TkCselectbgnd || color == TkCactivebgnd) { + if (setcol) { + light = tkrgbashade(rgba, TkLightshade); + dark = tkrgbashade(rgba, TkDarkshade); + } else + light = dark = 0; + changecol(env, setcol, color+1, light); + changecol(env, setcol, color+2, dark); + } + return nil; +} + +static char* +pbool(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf) +{ + USED(buf); + USED(ebuf); + USED(str); + USED(t); + OPTION(place, int, o->offset) = 1; + return nil; +} + +static char* +pwinp(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf) +{ + Tk *f; + char *p; + + p = tkword(t, *str, buf, ebuf, nil); + if(*buf == '\0') + return TkOparg; + *str = p; + + f = tklook(t, buf, 0); + if(f == nil){ + tkerr(t, buf); + return TkBadwp; + } + + OPTION(place, Tk*, o->offset) = f; + return nil; +} + +static char* +pctag(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf) +{ + char *p; + TkName *n, *l; + + *str = tkword(t, *str, buf, ebuf, nil); + + l = nil; + p = buf; + while(*p) { + p = tkskip(p, " \t"); + buf = p; + while(*p && *p != ' ' && *p != '\t') + p++; + if(*p != '\0') + *p++ = '\0'; + + if(p == buf || buf[0] >= '0' && buf[0] <= '9') { + tkfreename(l); + return TkBadtg; + } + n = tkmkname(buf); + if(n == nil) { + tkfreename(l); + return TkNomem; + } + n->link = l; + l = n; + } + tkfreename(OPTION(place, TkName*, o->offset)); + OPTION(place, TkName*, o->offset) = l; + return nil; +} + +static char* +pfrac(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf) +{ + char *p, *e; + int i, n, d, *v; + + *str = tkword(t, *str, buf, ebuf, nil); + + v = &OPTION(place, int, o->offset); + n = (int)o->aux; + if(n == 0) + n = 1; + p = buf; + for(i = 0; i < n; i++) { + p = tkskip(p, " \t"); + if(*p == '\0') + return TkOparg; + e = tkfracword(t, &p, &d, nil); + if (e != nil) + return e; + *v++ = d; + } + return nil; +} + +/* + * N.B. nnfrac only accepts aux==nil (can't deal with several items) + */ +static char* +pnnfrac(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf) +{ + int oldv; + char *e; + + oldv = OPTION(place, int, o->offset); + + e = pfrac(t, o, place, str, buf, ebuf); + if(e == nil && OPTION(place, int, o->offset) < 0) { + OPTION(place, int, o->offset) = oldv; + return TkBadvl; + } + return e; + +} + +typedef struct Tabspec { + int dist; + int just; + TkEnv *env; +} Tabspec; + +static char* +ptabs(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf) +{ + char *e, *p, *eibuf; + TkOption opd, opj; + Tabspec tspec; + TkTtabstop *tabfirst, *tab, *tabprev; + char *ibuf; + + ibuf = mallocz(Tkmaxitem, 0); + if(ibuf == nil) + return TkNomem; + eibuf = ibuf + Tkmaxitem; + tspec.env = OPTION(place, TkEnv*, AUXI(o->aux)); + opd.offset = O(Tabspec, dist); + opd.aux = IAUX(O(Tabspec, env)); + opj.offset = O(Tabspec, dist); + opj.aux = tktabjust; + tabprev = nil; + tabfirst = nil; + + p = tkword(t, *str, buf, ebuf, nil); + if(*buf == '\0') { + free(ibuf); + return TkOparg; + } + *str = p; + + p = buf; + while(*p != '\0') { + e = pdist(t, &opd, &tspec, &p, ibuf, eibuf); + if(e != nil) { + free(ibuf); + return e; + } + + e = pstab(t, &opj, &tspec, &p, ibuf, eibuf); + if(e != nil) + tspec.just = Tkleft; + + tab = malloc(sizeof(TkTtabstop)); + if(tab == nil) { + free(ibuf); + return TkNomem; + } + + tab->pos = tspec.dist; + tab->justify = tspec.just; + tab->next = nil; + if(tabfirst == nil) + tabfirst = tab; + else + tabprev->next = tab; + tabprev = tab; + } + free(ibuf); + + tab = OPTION(place, TkTtabstop*, o->offset); + if(tab != nil) + free(tab); + OPTION(place, TkTtabstop*, o->offset) = tabfirst; + return nil; +} + +char* +tkxyparse(Tk* tk, char **parg, Point *p) +{ + char *buf; + + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + + *parg = tkword(tk->env->top, *parg, buf, buf+Tkmaxitem, nil); + if(*buf == '\0') { + free(buf); + return TkOparg; + } + p->x = atoi(buf); + + *parg = tkword(tk->env->top, *parg, buf, buf+Tkmaxitem, nil); + if(*buf == '\0') { + free(buf); + return TkOparg; + } + p->y = atoi(buf); + + free(buf); + return nil; +} + +static char* +plist(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf) +{ + char *w, ***p, *wbuf, *ewbuf, **v, **nv; + int n, m, i, found; + + *str = tkword(t, *str, buf, ebuf, nil); + n = strlen(buf) + 1; + wbuf = mallocz(n, 0); + if (wbuf == nil) + return TkNomem; /* XXX should we free old values too? */ + ewbuf = &wbuf[n]; + + p = &OPTION(place, char**, o->offset); + if (*p != nil){ + for (v = *p; *v; v++) + free(*v); + free(*p); + } + n = 0; + m = 4; + w = buf; + v = malloc(m * sizeof(char*)); + if (v == nil) + goto Error; + for (;;) { + w = tkword(t, w, wbuf, ewbuf, &found); + if (!found) + break; + if (n == m - 1) { + m += m/2; + nv = realloc(v, m * sizeof(char*)); + if (nv == nil) + goto Error; + v = nv; + } + v[n] = strdup(wbuf); + if (v[n] == nil) + goto Error; + n++; + } + v[n++] = nil; + *p = realloc(v, n * sizeof(char*)); + free(wbuf); + return nil; +Error: + free(buf); + for (i = 0; i < n; i++) + free(v[i]); + free(v); + *p = nil; + return TkNomem; +} diff --git a/libtk/radio.tk b/libtk/radio.tk new file mode 100644 index 00000000..4882dbf3 --- /dev/null +++ b/libtk/radio.tk @@ -0,0 +1,4 @@ +radiobutton .u -text {Upper case} -variable case -value u +radiobutton .l -text {Lower case} -variable case -value l +radiobutton .i -text {Ignore case} -variable case -value i +pack .u .l .i -anchor w -fill x diff --git a/libtk/scale.c b/libtk/scale.c new file mode 100644 index 00000000..e32f7e69 --- /dev/null +++ b/libtk/scale.c @@ -0,0 +1,958 @@ +#include <lib9.h> +#include <kernel.h> +#include "draw.h" +#include "tk.h" +#include "keyboard.h" + +#define O(t, e) ((long)(&((t*)0)->e)) + +typedef struct TkScale TkScale; +struct TkScale +{ + int value; + int bigi; + int digits; + int digwidth; + int from; /* Base of range */ + int to; /* Limit of range */ + int len; /* Length of groove */ + int res; /* Resolution */ + int sv; /* Show value */ + int sl; /* Slider length */ + int sw; /* Slider width div 2 */ + int relief; + int tick; + int orient; + char* command; + char* label; + int pixmin; + int pixmax; + int pixpos; + int center; + int pix; + int base; + int flag; + int jump; +}; + +enum { + Dragging = (1<<0), + Autorepeat = (1<<1), +}; + +static +TkOption opts[] = +{ + "bigincrement", OPTnnfrac, O(TkScale, bigi), nil, + "digits", OPTdist, O(TkScale, digits), nil, + "from", OPTfrac, O(TkScale, from), nil, + "to", OPTfrac, O(TkScale, to), nil, + "length", OPTdist, O(TkScale, len), nil, + "resolution", OPTnnfrac, O(TkScale, res), nil, + "showrange", OPTignore, 0, nil, + "showvalue", OPTstab, O(TkScale, sv), tkbool, + "jump", OPTstab, O(TkScale, jump), tkbool, + "sliderlength", OPTdist, O(TkScale, sl), nil, + "sliderrelief", OPTstab, O(TkScale, relief), tkrelief, + "tickinterval", OPTfrac, O(TkScale, tick), nil, + "tick", OPTfrac, O(TkScale, tick), nil, + "label", OPTtext, O(TkScale, label), nil, + "command", OPTtext, O(TkScale, command), nil, + "orient", OPTstab, O(TkScale, orient), tkorient, + nil +}; + +static char trough1[] = "trough1"; +static char trough2[] = "trough2"; +static char slider[] = "slider"; + +static +TkEbind b[] = +{ + {TkMotion, "%W tkScaleMotion %x %y"}, + {TkButton1P|TkMotion, "%W tkScaleDrag %x %y"}, + {TkButton1P, "%W tkScaleMotion %x %y; %W tkScaleBut1P %x %y"}, + {TkButton1P|TkDouble, "%W tkScaleMotion %x %y; %W tkScaleBut1P %x %y"}, + {TkButton1R, "%W tkScaleDrag %x %y; %W tkScaleBut1R; %W tkScaleMotion %x %y"}, + {TkKey, "%W tkScaleKey 0x%K"}, +}; + +enum +{ + Scalewidth = 18, + ScalePad = 2, + ScaleBW = 2, + ScaleSlider = 16, + ScaleLen = 80, + +}; + +static int +maximum(int a, int b) +{ + if (a > b) + return a; + return b; +} + +void +tksizescale(Tk *tk) +{ + Point p; + char buf[32]; + TkScale *tks; + int fh, w, h, digits, digits2; + + tks = TKobj(TkScale, tk); + + digits = tks->digits; + if(digits <= 0) { + digits = tkfprint(buf, tks->from) - buf; + digits2 = tkfprint(buf, tks->to) - buf; + digits = maximum(digits, digits2); + if (tks->res > 0) { + digits2 = tkfprint(buf, tks->from + tks->res) - buf; + digits = maximum(digits, digits2); + digits2 = tkfprint(buf, tks->to - tks->res) - buf; + digits = maximum(digits, digits2); + } + } + + digits *= tk->env->wzero; + if(tks->sv != BoolT) + digits = 0; + + tks->digwidth = digits; + + p = tkstringsize(tk, tks->label); + if(tks->orient == Tkvertical) { + h = tks->len + 2*ScaleBW + 2*ScalePad; + w = Scalewidth + 2*ScalePad + 2*ScaleBW; + if (p.x) + w += p.x + ScalePad; + if (tks->sv == BoolT) + w += digits + ScalePad; + } + else { + w = maximum(p.x, tks->len + ScaleBW + 2*ScalePad); + h = Scalewidth + 2*ScalePad + 2*ScaleBW; + fh = tk->env->font->height; + if(tks->label != nil) + h += fh + ScalePad; + if(tks->sv == BoolT) + h += fh + ScalePad; + } + w += 2*tk->highlightwidth; + h += 2*tk->highlightwidth; + if(!(tk->flag & Tksetwidth)) + tk->req.width = w; + if(!(tk->flag & Tksetheight)) + tk->req.height = h; +} + +static int +tkscalecheckvalue(Tk *tk) +{ + int v; + TkScale *tks = TKobj(TkScale, tk); + int limit = 1; + + v = tks->value; + if (tks->res > 0) + v = (v / tks->res) * tks->res; + if (tks->to >= tks->from) { + if (v < tks->from) + v = tks->from; + else if (v > tks->to) + v = tks->to; + else + limit = 0; + } else { + if (v < tks->to) + v = tks->to; + else if (v > tks->from) + v = tks->from; + else + limit = 0; + } + /* + * it's possible for the value to end up as a non-whole + * multiple of resolution here, if the end points aren't + * themselves such a multiple. if so, tough - that's + * what you asked for! (it does mean that the endpoints + * are always accessible however, which could be a good thing). + */ + tks->value = v; + return limit; +} + +char* +tkscale(TkTop *t, char *arg, char **ret) +{ + Tk *tk; + char *e; + TkName *names; + TkScale *tks; + TkOptab tko[3]; + + tk = tknewobj(t, TKscale, sizeof(Tk)+sizeof(TkScale)); + if(tk == nil) + return TkNomem; + + tk->flag |= Tktakefocus; + tks = TKobj(TkScale, tk); + tks->res = TKI2F(1); + tks->to = TKI2F(100); + tks->len = ScaleLen; + tks->orient = Tkvertical; + tks->relief = TKraised; + tks->sl = ScaleSlider; + tks->sv = BoolT; + tks->bigi = 0; + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tks; + tko[1].optab = opts; + tko[2].ptr = nil; + + names = nil; + e = tkparse(t, arg, tko, &names); + if(e != nil) { + tkfreeobj(tk); + return e; + } + tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd)); + tkscalecheckvalue(tk); + tksizescale(tk); + if (tks->bigi == 0) + tks->bigi = TKI2F(TKF2I(tks->to - tks->from) / 10); + e = tkbindings(t, tk, b, nelem(b)); + + if(e != nil) { + tkfreeobj(tk); + return e; + } + + e = tkaddchild(t, tk, &names); + tkfreename(names); + if(e != nil) { + tkfreeobj(tk); + return e; + } + tk->name->link = nil; + + return tkvalue(ret, "%s", tk->name->name); +} + +static char* +tkscalecget(Tk *tk, char *arg, char **val) +{ + TkOptab tko[3]; + TkScale *tks = TKobj(TkScale, tk); + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tks; + tko[1].optab = opts; + tko[2].ptr = nil; + + return tkgencget(tko, arg, val, tk->env->top); +} + +void +tkfreescale(Tk *tk) +{ + TkScale *tks = TKobj(TkScale, tk); + + if(tks->command != nil) + free(tks->command); + if(tks->label != nil) + free(tks->label); +} + +static void +tkscalehoriz(Tk *tk, Image *i) +{ + TkEnv *e; + char sv[32]; + TkScale *tks; + Image *d, *l; + Rectangle r, r2, sr; + Point p, q; + int fh, sh, sl, v, w, h, len; + int fgnd; + + e = tk->env; + tks = TKobj(TkScale, tk); + + + fh = e->font->height; + fgnd = TkCforegnd; + if (tk->flag & Tkdisabled) + fgnd = TkCdisablefgnd; + + r = Rect(0, 0, tk->act.width, tk->act.height); + r = rectaddpt(r, Pt(tk->borderwidth, tk->borderwidth)); + r = insetrect(r, tk->highlightwidth); + r = insetrect(r, ScalePad); + + if(tks->label != nil) { + string(i, r.min, tkgc(e, fgnd), ZP, e->font, tks->label); + r.min.y += fh + ScalePad; + } + if(tks->sv == BoolT) + r.min.y += fh + ScalePad; + + sr = insetrect(r, ScaleBW); + w = Dx(sr); + h = Dy(sr); + sl = tks->sl + 2*ScaleBW; + + l = tkgc(e, TkCbackgndlght); + d = tkgc(e, TkCbackgnddark); + tkbevel(i, r.min, w, h, ScaleBW, d, l); + + tks->pixmin = sr.min.x; + tks->pixmax = sr.max.x; + + sh = h - 2*ScaleBW; + tks->sw = sh/2; + + w -= sl; + if (w <= 0) + w = 1; + p.x = sr.min.x; + p.y = sr.max.y; + if(tks->tick > 0){ + int j, t, l; + t = tks->tick; + l = tks->to-tks->from; + if (l < 0) + l = -l; + if (l == 0) + l = 1; + r2.min.y = p.y; + r2.max.y = p.y + ScaleBW + ScalePad; + for(j = 0; j <= l; j += t){ + r2.min.x = p.x+((vlong)j*w)/l+sl/2; + r2.max.x = r2.min.x+1; + draw(i, r2, tkgc(e, fgnd), nil, ZP); + } + } + v = tks->value-tks->from; + len = tks->to-tks->from; + if (len != 0) + p.x += ((vlong)v*w)/len; + p.y = sr.min.y; + q = p; + q.x += tks->sl/2 + 1; + q.y++; + if(tk->flag & Tkactivated) { + r2.min = p; + r2.max.x = p.x+sl; + r2.max.y = sr.max.y; + draw(i, r2, tkgc(e, TkCactivebgnd), nil, ZP); + } + switch(tks->relief) { + case TKsunken: + tkbevel(i, p, tks->sl, sh, ScaleBW, d, l); + tkbevel(i, q, 0, sh, 1, l, d); + break; + case TKraised: + tkbevel(i, p, tks->sl, sh, ScaleBW, l, d); + tkbevel(i, q, 0, sh, 1, d, l); + break; + } + tks->pixpos = p.x; + tks->center = p.y + sh/2 + ScaleBW; + + if(tks->sv != BoolT) + return; + + tkfprint(sv, tks->value); + if(tks->digits > 0 && tks->digits < strlen(sv)) + sv[tks->digits] = '\0'; + + w = stringwidth(e->font, sv); + p.x = q.x; + p.x -= w/2; + p.y = r.min.y - fh - ScalePad; + if(p.x < tks->pixmin) + p.x = tks->pixmin; + if(p.x+w > tks->pixmax) + p.x = tks->pixmax - w; + + string(i, p, tkgc(e, fgnd), ZP, e->font, sv); +} + +static void +tkscalevert(Tk *tk, Image *i) +{ + TkEnv *e; + TkScale *tks; + char sv[32]; + Image *d, *l; + Rectangle r, r2, sr; + Point p, q; + int fh, v, sw, w, h, len, sl; + int fgnd; + + e = tk->env; + tks = TKobj(TkScale, tk); + + fh = e->font->height; + fgnd = TkCforegnd; + if (tk->flag & Tkdisabled) + fgnd = TkCdisablefgnd; + + r = Rect(0, 0, tk->act.width, tk->act.height); + r = rectaddpt(r, Pt(tk->borderwidth, tk->borderwidth)); + r = insetrect(r, tk->highlightwidth); + r = insetrect(r, ScalePad); + + if (tks->sv) + r.min.x += tks->digwidth + ScalePad; + + if(tks->label != nil) { + p = stringsize(e->font, tks->label); + r.max.x -= p.x; + string(i, Pt(r.max.x, r.min.y), tkgc(e, fgnd), ZP, e->font, tks->label); + r.max.x -= ScalePad; + } + + sr = insetrect(r, ScaleBW); + h = Dy(sr); + w = Dx(sr); + sl = tks->sl + 2*ScaleBW; + + l = tkgc(e, TkCbackgndlght); + d = tkgc(e, TkCbackgnddark); + tkbevel(i, r.min, w, h, ScaleBW, d, l); + + tks->pixmin = sr.min.y; + tks->pixmax = sr.max.y; + + sw = w - 2*ScaleBW; + tks->sw = sw/2; + + h -= sl; + if (h <= 0) + h = 1; + p.x = sr.max.x; + p.y = sr.min.y; + if(tks->tick > 0){ + int j, t, l; + t = tks->tick; + l = tks->to-tks->from; + if (l < 0) + l = -l; + if (l == 0) + l = 1; + r2.min = p; + r2.max.x = p.x + ScaleBW + ScalePad; + for(j = 0; j <= l; j += t){ + r2.min.y = p.y+((vlong)j*h)/l+sl/2; + r2.max.y = r2.min.y+1; + draw(i, r2, tkgc(e, fgnd), nil, ZP); + } + } + + v = tks->value-tks->from; + len = tks->to-tks->from; + if (len != 0) + p.y += ((vlong)v*h)/len; + p.x = sr.min.x; + q = p; + q.x++; + q.y += tks->sl/2 + 1; + if(tk->flag & Tkactivated) { + r2.min = p; + r2.max.x = sr.max.x; + r2.max.y = p.y+sl; + draw(i, r2, tkgc(e, TkCactivebgnd), nil, ZP); + } + switch(tks->relief) { + case TKsunken: + tkbevel(i, p, sw, tks->sl, ScaleBW, d, l); + tkbevel(i, q, sw, 0, 1, l, d); + break; + case TKraised: + tkbevel(i, p, sw, tks->sl, ScaleBW, l, d); + tkbevel(i, q, sw, 0, 1, d, l); + break; + } + tks->pixpos = p.y; + tks->center = p.x + sw/2 + ScaleBW; + + if(tks->sv != BoolT) + return; + + tkfprint(sv, tks->value); + if(tks->digits > 0 && tks->digits < strlen(sv)) + sv[tks->digits] = '\0'; + + p.x = r.min.x - ScalePad - stringwidth(e->font, sv); + p.y = q.y; + p.y -= fh/2; + if (p.y < tks->pixmin) + p.y = tks->pixmin; + if (p.y + fh > tks->pixmax) + p.y = tks->pixmax - fh; + string(i, p, tkgc(e, fgnd), ZP, e->font, sv); +} + +char* +tkdrawscale(Tk *tk, Point orig) +{ + Point p; + TkEnv *env; + TkScale *tks; + Rectangle r, fr; + Image *i; + + tks = TKobj(TkScale, tk); + env = tk->env; + + r.min = ZP; + r.max.x = tk->act.width + 2*tk->borderwidth; + r.max.y = tk->act.height + 2*tk->borderwidth; + i = tkitmp(env, r.max, TkCbackgnd); + if(i == nil) + return nil; + + if(tks->orient == Tkvertical) + tkscalevert(tk, i); + else + tkscalehoriz(tk, i); + + tkdrawrelief(i, tk, ZP, TkCbackgnd, tk->relief); + if (tkhaskeyfocus(tk)) { + fr = insetrect(r, tk->borderwidth); + tkbox(i, fr, tk->highlightwidth, tkgc(env, TkChighlightfgnd)); + } + + p.x = tk->act.x + orig.x; + p.y = tk->act.y + orig.y; + r = rectaddpt(r, p); + draw(tkimageof(tk), r, i, nil, ZP); + + return nil; +} + +/* Widget Commands (+ means implemented) + +cget + +configure + +coords + +get + +identify + +set +*/ + +static char* +tkscaleconf(Tk *tk, char *arg, char **val) +{ + char *e; + TkGeom g; + int bd; + TkOptab tko[3]; + TkScale *tks = TKobj(TkScale, tk); + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tks; + tko[1].optab = opts; + 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)); + tkscalecheckvalue(tk); + tksizescale(tk); + tkgeomchg(tk, &g, bd); + + tk->dirty = tkrect(tk, 1); + return e; +} + +char* +tkscaleposn(TkEnv *env, Tk *tk, char *arg, int *z) +{ + int x, y; + TkScale *tks = TKobj(TkScale, tk); + char *e; + + e = tkfracword(env->top, &arg, &x, env); + if(e != nil) + return e; + e = tkfracword(env->top, &arg, &y, env); + if(e != nil) + return e; + + x = TKF2I(x) + tk->borderwidth; + y = TKF2I(y) + tk->borderwidth; + + if(tks->orient == Tkvertical) { + if(z != nil) { + z[0] = x; + z[1] = y; + } + x = y; + } + else { + if(z != nil) { + z[0] = y; + z[1] = x; + } + } + if(x > tks->pixmin && x < tks->pixpos) + return trough1; + else + if(x >= tks->pixpos && x < tks->pixpos+tks->sl+2*ScaleBW) + return slider; + else + if(x >= tks->pixpos+tks->sl+2*ScaleBW && x < tks->pixmax) + return trough2; + + return ""; +} + +static char* +tkscaleident(Tk *tk, char *arg, char **val) +{ + char *v; + + v = tkscaleposn(tk->env, tk, arg, nil); + if(v == nil) + return TkBadvl; + return tkvalue(val, "%s", v); +} + +static char* +tkscalecoords(Tk *tk, char *arg, char **val) +{ + int p, x, y, l, value; + TkScale *tks = TKobj(TkScale, tk); + char *e; + + value = tks->value; + if(arg != nil && arg[0] != '\0') { + e = tkfracword(tk->env->top, &arg, &value, tk->env); + if (e != nil) + return e; + } + + value -= tks->from; + p = tks->pixmax - tks->pixmin; + l = TKF2I(tks->to-tks->from); + if (l==0) + p /= 2; + else + p = TKF2I(value*p/l); + p += tks->pixmin; + if(tks->orient == Tkvertical) { + x = tks->center; + y = p; + } + else { + x = p; + y = tks->center; + } + return tkvalue(val, "%d %d", x, y); +} + +static char* +tkscaleget(Tk *tk, char *arg, char **val) +{ + int x, y, value, v, l; + char buf[Tkminitem], *e; + TkScale *tks = TKobj(TkScale, tk); + + value = tks->value; + if(arg[0] != '\0') { + e = tkfracword(tk->env->top, &arg, &x, tk->env); + if (e != nil) + return e; + e = tkfracword(tk->env->top, &arg, &y, tk->env); + if (e != nil) + return e; + if(tks->orient == Tkvertical) + v = TKF2I(y) + tk->borderwidth; + else + v = TKF2I(x) + tk->borderwidth; + + if(v < tks->pixmin) + value = tks->from; + else + if(v > tks->pixmax) + value = tks->to; + else { + l = tks->pixmax-tks->pixmin; + value = 0; + if (l!=0) + value = v * ((tks->to-tks->from)/l); + value += tks->from; + } + if(tks->res > 0) + value = (value/tks->res)*tks->res; + } + tkfprint(buf, value); + return tkvalue(val, "%s", buf); +} + +static char* +tkscaleset(Tk *tk, char *arg, char **val) +{ + TkScale *tks = TKobj(TkScale, tk); + char *e; + + USED(val); + + e = tkfracword(tk->env->top, &arg, &tks->value, tk->env); + if (e != nil) + return e; + tkscalecheckvalue(tk); + tk->dirty = tkrect(tk, 1); + return nil; +} + +/* tkScaleMotion %x %y */ +static char* +tkscalemotion(Tk *tk, char *arg, char **val) +{ + int o, z[2]; + char *v; + TkScale *tks = TKobj(TkScale, tk); + extern int tkstylus; + + USED(val); + v = tkscaleposn(tk->env, tk, arg, z); + if(v == nil) + return TkBadvl; + + o = tk->flag; + if(v != slider || z[0] < tks->center-tks->sw || z[0] > tks->center+tks->sw) + tk->flag &= ~Tkactivated; + else if(tkstylus == 0 || tk->env->top->ctxt->mstate.b != 0) + tk->flag |= Tkactivated; + + if((o & Tkactivated) != (tk->flag & Tkactivated)) + tk->dirty = tkrect(tk, 1); + + return nil; +} + +static char* +tkscaledrag(Tk *tk, char *arg, char **val) +{ + int x, y, v; + char *e, buf[Tkmaxitem], f[32]; + TkScale *tks = TKobj(TkScale, tk); + + USED(val); + if((tks->flag & Dragging) == 0) + return nil; + if(tks->flag & Autorepeat) + return nil; + + e = tkfracword(tk->env->top, &arg, &x, tk->env); + if(e != nil) + return e; + e = tkfracword(tk->env->top, &arg, &y, tk->env); + if(e != nil) + return e; + + if(tks->orient == Tkvertical) + v = TKF2I(y) + tk->borderwidth; + else + v = TKF2I(x) + tk->borderwidth; + + v -= tks->pix; + x = tks->pixmax-tks->pixmin; + if (x!=tks->sl) + v = tks->base + (vlong)v * (tks->to-tks->from)/(x-tks->sl); + else + v = tks->base; + if(tks->res > 0) { + int a = tks->res / 2; + if (v < 0) + a = -a; + v = ((v+a)/tks->res)*tks->res; + } + + tks->value = v; + tkscalecheckvalue(tk); + + if(tks->command != nil && tks->jump != BoolT) { + tkfprint(f, tks->value); + snprint(buf, sizeof(buf), "%s %s", tks->command, f); + e = tkexec(tk->env->top, buf, nil); + } + tk->dirty = tkrect(tk, 1); + return e; +} + +static int +sgn(int v) +{ + return v >= 0 ? 1 : -1; +} + +static char* +stepscale(Tk *tk, char *pos, int *end) +{ + TkScale *tks = TKobj(TkScale, tk); + char *e, buf[Tkmaxitem], f[32]; + int s; + + s = sgn(tks->to - tks->from); + if(pos == trough1) { + tks->value -= s * tks->bigi; + } else { + /* trough2 */ + tks->value += s * tks->bigi; + } + s = !tkscalecheckvalue(tk); + if (end != nil) + *end = s; + e = nil; + if(tks->command != nil) { + /* XXX perhaps should only send command if value has actually changed */ + tkfprint(f, tks->value); + snprint(buf, sizeof(buf), "%s %s", tks->command, f); + e = tkexec(tk->env->top, buf, nil); + } + return e; +} + +static void +screpeat(Tk *tk, void *v, int cancelled) +{ + char *e, *pos; + int repeat; + TkScale *tks = TKobj(TkScale, tk); + + pos = v; + if (cancelled) { + tks->flag &= ~Autorepeat; + return; + } + e = stepscale(tk, pos, &repeat); + if(e != nil || !repeat) { + tks->flag &= ~Autorepeat; + tkcancelrepeat(tk); + } + tk->dirty = tkrect(tk, 1); + tkupdate(tk->env->top); +} + +static char* +tkscalebut1p(Tk *tk, char *arg, char **val) +{ + int z[2]; + char *v, *e; + TkScale *tks = TKobj(TkScale, tk); + int repeat; + + USED(val); + v = tkscaleposn(tk->env, tk, arg, z); + if(v == nil) + return TkBadvl; + + e = nil; + if(v[0] == '\0' || z[0] < tks->center-tks->sw || z[0] > tks->center+tks->sw) + return nil; + if(v == slider) { + tks->flag |= Dragging; + tks->relief = TKsunken; + tks->pix = z[1]; + tks->base = tks->value; + tkscalecheckvalue(tk); + } else { + e = stepscale(tk, v, &repeat); + if (e == nil && repeat) { + tks->flag |= Autorepeat; + tkrepeat(tk, screpeat, v, TkRptpause, TkRptinterval); + } + } + + tk->dirty = tkrect(tk, 1); + return e; +} + +static char* +tkscalebut1r(Tk *tk, char *arg, char **val) +{ + TkScale *tks = TKobj(TkScale, tk); + char *e, buf[Tkmaxitem], f[32]; + USED(val); + USED(arg); + if(tks->flag & Autorepeat) { + tkcancelrepeat(tk); + tks->flag &= ~Autorepeat; + } + e = nil; + if (tks->flag & Dragging) { + if (tks->command != nil && tks->jump == BoolT && (tks->flag & Dragging)) { + tkfprint(f, tks->value); + snprint(buf, sizeof(buf), "%s %s", tks->command, f); + e = tkexec(tk->env->top, buf, nil); + } + tks->relief = TKraised; + tks->flag &= ~Dragging; + tk->dirty = tkrect(tk, 1); + } + return e; +} + +static char* +tkscalekey(Tk *tk, char *arg, char **val) +{ + char *e; + int key; + char *pos = nil; + USED(arg); + USED(val); + + if(tk->flag & Tkdisabled) + return nil; + + key = atoi(arg); + if (key == Up || key == Left) + pos = trough1; + else if (key == Down || key == Right) + pos = trough2; + if (pos != nil) { + e = stepscale(tk, pos, nil); + tk->dirty = tkrect(tk, 1); + return e; + } + return nil; +} + +TkCmdtab tkscalecmd[] = +{ + "cget", tkscalecget, + "configure", tkscaleconf, + "set", tkscaleset, + "identify", tkscaleident, + "get", tkscaleget, + "coords", tkscalecoords, + "tkScaleMotion", tkscalemotion, + "tkScaleDrag", tkscaledrag, + "tkScaleBut1P", tkscalebut1p, + "tkScaleBut1R", tkscalebut1r, + "tkScaleKey", tkscalekey, + nil +}; + +TkMethod scalemethod = { + "scale", + tkscalecmd, + tkfreescale, + tkdrawscale +}; diff --git a/libtk/scrol.c b/libtk/scrol.c new file mode 100644 index 00000000..5f0db22b --- /dev/null +++ b/libtk/scrol.c @@ -0,0 +1,781 @@ +#include "lib9.h" +#include "draw.h" +#include "tk.h" + +#define O(t, e) ((long)(&((t*)0)->e)) + +/* Layout constants */ +enum { + Triangle = 10, /* Height of scroll bar triangle */ + Elembw = 2, /* border around elements (triangles etc.) */ +}; + +typedef struct TkScroll TkScroll; +struct TkScroll +{ + int activer; + int orient; /* Horitontal or Vertical */ + int dragpix; /* Scroll delta in button drag */ + int dragtop; + int dragbot; + int jump; /* Jump scroll enable */ + int flag; /* Display flags */ + int top; /* Top fraction */ + int bot; /* Bottom fraction */ + int a1; /* Pixel top/left arrow1 */ + int t1; /* Pixel top/left trough */ + int t2; /* Pixel top/left lower trough */ + int a2; /* Pixel top/left arrow2 */ + char* cmd; +}; + +enum { + ActiveA1 = (1<<0), /* Scrollbar control */ + ActiveA2 = (1<<1), + ActiveB1 = (1<<2), + ButtonA1 = (1<<3), + ButtonA2 = (1<<4), + ButtonB1 = (1<<5), + Autorepeat = (1<<6) +}; + +static +TkOption opts[] = +{ + "activerelief", OPTstab, O(TkScroll, activer), tkrelief, + "command", OPTtext, O(TkScroll, cmd), nil, + "elementborderwidth", OPTignore, 0, nil, /* deprecated */ + "jump", OPTstab, O(TkScroll, jump), tkbool, + "orient", OPTstab, O(TkScroll, orient), tkorient, + nil +}; + +static +TkEbind b[] = +{ + {TkLeave, "%W activate {}"}, + {TkEnter, "%W activate [%W identify %x %y]"}, + {TkMotion, "%W activate [%W identify %x %y]"}, + {TkButton1P|TkMotion, "%W tkScrollDrag %x %y"}, + {TkButton1P, "%W tkScrolBut1P %x %y"}, + {TkButton1P|TkDouble, "%W tkScrolBut1P %x %y"}, + {TkButton1R, "%W tkScrolBut1R; %W activate [%W identify %x %y]"}, + {TkButton2P, "%W tkScrolBut2P [%W fraction %x %y]"}, +}; + +static char* +tkinitscroll(Tk *tk) +{ + int gap; + TkScroll *tks; + + tks = TKobj(TkScroll, tk); + + gap = 2*tk->borderwidth; + if(tks->orient == Tkvertical) { + if(tk->req.width == 0) + tk->req.width = Triangle + gap; + if(tk->req.height == 0) + tk->req.height = 2*Triangle + gap + 6*Elembw; + } + else { + if(tk->req.width == 0) + tk->req.width = 2*Triangle + gap + 6*Elembw; + if(tk->req.height == 0) + tk->req.height = Triangle + gap; + } + + + return tkbindings(tk->env->top, tk, b, nelem(b)); +} + +char* +tkscrollbar(TkTop *t, char *arg, char **ret) +{ + Tk *tk; + char *e; + TkName *names; + TkScroll *tks; + TkOptab tko[3]; + + tk = tknewobj(t, TKscrollbar, sizeof(Tk)+sizeof(TkScroll)); + if(tk == nil) + return TkNomem; + + tks = TKobj(TkScroll, tk); + + tk->relief = TKsunken; + tk->borderwidth = 2; + tks->activer = TKraised; + tks->orient = Tkvertical; + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tks; + tko[1].optab = opts; + tko[2].ptr = nil; + + names = nil; + e = tkparse(t, arg, tko, &names); + if(e != nil) { + tkfreeobj(tk); + return e; + } + tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd)); + + e = tkinitscroll(tk); + if(e != nil) { + tkfreeobj(tk); + return e; + } + + e = tkaddchild(t, tk, &names); + tkfreename(names); + if(e != nil) { + tkfreeobj(tk); + return e; + } + tk->name->link = nil; + + return tkvalue(ret, "%s", tk->name->name); +} + +static char* +tkscrollcget(Tk *tk, char *arg, char **val) +{ + TkOptab tko[3]; + TkScroll *tks = TKobj(TkScroll, tk); + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tks; + tko[1].optab = opts; + tko[2].ptr = nil; + + return tkgencget(tko, arg, val, tk->env->top); +} + +void +tkfreescrlb(Tk *tk) +{ + TkScroll *tks = TKobj(TkScroll, tk); + + if(tks->cmd != nil) + free(tks->cmd); +} + +static void +tkvscroll(Tk *tk, TkScroll *tks, Image *i, Point size) +{ + TkEnv *e; + Rectangle r; + Point p[3], o; + Image *d, *l, *t; + int bo, w, h, triangle, bgnd; + + e = tk->env; + + triangle = tk->act.width - Elembw; + + bo = tk->borderwidth + Elembw; + p[0].x = size.x/2; + p[0].y = bo; + p[1].x = p[0].x - triangle/2; + p[1].y = p[0].y + triangle; + p[2].x = p[0].x + triangle/2; + p[2].y = p[0].y + triangle; + + bgnd = TkCbackgnd; + if(tks->flag & (ActiveA1|ButtonA1)) { + bgnd = TkCactivebgnd; + fillpoly(i, p, 3, ~0, tkgc(e, bgnd), p[0]); + } + + l = tkgc(e, bgnd+TkLightshade); + d = tkgc(e, bgnd+TkDarkshade); + if(tks->flag & ButtonA1) { + t = d; + d = l; + l = t; + } + line(i, p[1], p[2], 0, 0, 1, d, p[1]); + line(i, p[2], p[0], 0, 0, 1, d, p[2]); + line(i, p[0], p[1], 0, 0, 1, l, p[0]); + + tks->a1 = p[2].y; + h = p[2].y + Elembw; + + p[0].y = size.y - bo - 1; + p[1].y = p[0].y - triangle; + p[2].y = p[0].y - triangle; + + bgnd = TkCbackgnd; + if(tks->flag & (ActiveA2|ButtonA2)) { + bgnd = TkCactivebgnd; + fillpoly(i, p, 3, ~0, tkgc(e, bgnd), p[0]); + } + + l = tkgc(e, bgnd+TkLightshade); + d = tkgc(e, bgnd+TkDarkshade); + if(tks->flag & ButtonA2) { + t = d; + d = l; + l = t; + } + line(i, p[1], p[2], 0, 0, 1, l, p[1]); + line(i, p[2], p[0], 0, 0, 1, d, p[2]); + line(i, p[0], p[1], 0, 0, 1, l, p[0]); + + tks->a2 = p[2].y; + + o.x = tk->borderwidth ; + o.y = bo + triangle + 2*Elembw; + w = size.x - 2*bo; + h = p[2].y - 2*Elembw - h - 2*tk->borderwidth; + + o.y += TKF2I(tks->top*h); + h *= tks->bot - tks->top; + h = TKF2I(h); + + bgnd = TkCbackgnd; + if(tks->flag & (ActiveB1|ButtonB1)) { + r.min = o; + r.max.x = o.x + w + 2*2; + r.max.y = o.y + h + 2*2; + bgnd = TkCactivebgnd; + draw(i, r, tkgc(e, bgnd), nil, ZP); + } + + tks->t1 = o.y - Elembw; + tks->t2 = o.y + h + Elembw; + l = tkgc(e, bgnd+TkLightshade); + d = tkgc(e, bgnd+TkDarkshade); + if(tks->flag & ButtonB1) + tkbevel(i, o, w, h, 2, d, l); + else + tkbevel(i, o, w, h, 2, l, d); +} + +static void +tkhscroll(Tk *tk, TkScroll *tks, Image *i, Point size) +{ + TkEnv *e; + Rectangle r; + Point p[3], o; + Image *d, *l, *t; + int bo, w, h, triangle, bgnd; + + e = tk->env; + + triangle = tk->act.height - Elembw; + + bo = tk->borderwidth + Elembw; + p[0].x = bo; + p[0].y = size.y/2; + p[1].x = p[0].x + triangle; + p[1].y = p[0].y - triangle/2 + 1; + p[2].x = p[0].x + triangle; + p[2].y = p[0].y + triangle/2 - 2; + + bgnd = TkCbackgnd; + if(tks->flag & (ActiveA1|ButtonA1)) { + bgnd = TkCactivebgnd; + fillpoly(i, p, 3, ~0, tkgc(e, bgnd), p[0]); + } + + l = tkgc(e, bgnd+TkLightshade); + d = tkgc(e, bgnd+TkDarkshade); + + if(tks->flag & ButtonA1) { + t = d; + d = l; + l = t; + } + line(i, p[1], p[2], 0, 0, 1, d, p[1]); + line(i, p[2], p[0], 0, 0, 1, d, p[2]); + line(i, p[0], p[1], 0, 0, 1, l, p[0]); + + tks->a1 = p[2].x; + w = p[2].x + Elembw; + + p[0].x = size.x - bo - 1; + p[1].x = p[0].x - triangle; + p[2].x = p[0].x - triangle; + + bgnd = TkCbackgnd; + if(tks->flag & (ActiveA2|ButtonA2)) { + bgnd = TkCactivebgnd; + fillpoly(i, p, 3, ~0, tkgc(e, bgnd), p[0]); + } + + l = tkgc(e, bgnd+TkLightshade); + d = tkgc(e, bgnd+TkDarkshade); + if(tks->flag & ButtonA2) { + t = d; + d = l; + l = t; + } + line(i, p[1], p[2], 0, 0, 1, l, p[1]); + line(i, p[2], p[0], 0, 0, 1, d, p[2]); + line(i, p[0], p[1], 0, 0, 1, l, p[0]); + + tks->a2 = p[2].x; + + o.x = bo + triangle + 2*Elembw; + o.y = tk->borderwidth; + w = p[2].x - 2*Elembw - w - 2*tk->borderwidth; + h = size.y - 2*bo; + + o.x += TKF2I(tks->top*w); + w *= tks->bot - tks->top; + w = TKF2I(w); + + bgnd = TkCbackgnd; + if(tks->flag & (ActiveB1|ButtonB1)) { + r.min = o; + r.max.x = o.x + w + 2*2; + r.max.y = o.y + h + 2*2; + bgnd = TkCactivebgnd; + draw(i, r, tkgc(e, bgnd), nil, ZP); + } + + tks->t1 = o.x - Elembw; + tks->t2 = o.x + w + Elembw; + l = tkgc(e, bgnd+TkLightshade); + d = tkgc(e, bgnd+TkDarkshade); + if(tks->flag & ButtonB1) + tkbevel(i, o, w, h, 2, d, l); + else + tkbevel(i, o, w, h, 2, l, d); +} + +char* +tkdrawscrlb(Tk *tk, Point orig) +{ + Point p; + TkEnv *e; + Rectangle r; + Image *i, *dst; + TkScroll *tks = TKobj(TkScroll, tk); + + e = tk->env; + + dst = tkimageof(tk); + if(dst == nil) + return nil; + + r.min = ZP; + r.max.x = tk->act.width + 2*tk->borderwidth; + r.max.y = tk->act.height + 2*tk->borderwidth; + + i = tkitmp(e, r.max, TkCbackgnd); + if(i == nil) + return nil; + + if(tks->orient == Tkvertical) + tkvscroll(tk, tks, i, r.max); + else + tkhscroll(tk, tks, i, r.max); + + tkdrawrelief(i, tk, ZP, TkCbackgnd, tk->relief); + + p.x = tk->act.x + orig.x; + p.y = tk->act.y + orig.y; + r = rectaddpt(r, p); + draw(dst, r, i, nil, ZP); + + return nil; +} + +/* Widget Commands (+ means implemented) + +activate + +cget + +configure + +delta + +fraction + +get + +identify + +set +*/ + +static char* +tkscrollconf(Tk *tk, char *arg, char **val) +{ + char *e; + TkGeom g; + int bd; + TkOptab tko[3]; + TkScroll *tks = TKobj(TkScroll, tk); + + tko[0].ptr = tk; + tko[0].optab = tkgeneric; + tko[1].ptr = tks; + tko[1].optab = opts; + 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)); + tkgeomchg(tk, &g, bd); + + tk->dirty = tkrect(tk, 1); + return e; +} + +static char* +tkscrollactivate(Tk *tk, char *arg, char **val) +{ + int s, gotarg; + char buf[Tkmaxitem]; + TkScroll *tks = TKobj(TkScroll, tk); + + USED(val); + tkword(tk->env->top, arg, buf, buf+sizeof(buf), &gotarg); + s = tks->flag; + if (!gotarg) { + char *a; + if (s & ActiveA1) + a = "arrow1"; + else if (s & ActiveA2) + a = "arrow2"; + else if (s & ActiveB1) + a = "slider"; + else + a = ""; + return tkvalue(val, a); + } + tks->flag &= ~(ActiveA1 | ActiveA2 | ActiveB1); + if(strcmp(buf, "arrow1") == 0) + tks->flag |= ActiveA1; + else + if(strcmp(buf, "arrow2") == 0) + tks->flag |= ActiveA2; + else + if(strcmp(buf, "slider") == 0) + tks->flag |= ActiveB1; + + if(s ^ tks->flag) + tk->dirty = tkrect(tk, 1); + return nil; +} + +static char* +tkscrollset(Tk *tk, char *arg, char **val) +{ + TkTop *t; + char *e; + TkScroll *tks = TKobj(TkScroll, tk); + + USED(val); + t = tk->env->top; + e = tkfracword(t, &arg, &tks->top, nil); + if (e != nil) + return e; + e = tkfracword(t, &arg, &tks->bot, nil); + if (e != nil) + return e; + if(tks->top < 0) + tks->top = 0; + if(tks->top > TKI2F(1)) + tks->top = TKI2F(1); + if(tks->bot < 0) + tks->bot = 0; + if(tks->bot > TKI2F(1)) + tks->bot = TKI2F(1); + + tk->dirty = tkrect(tk, 1); + return nil; +} + +static char* +tkscrolldelta(Tk *tk, char *arg, char **val) +{ + int l, delta; + char buf[Tkmaxitem]; + TkScroll *tks = TKobj(TkScroll, tk); + + arg = tkitem(buf, arg); + if(tks->orient == Tkvertical) + tkitem(buf, arg); + if(*arg == '\0' || *buf == '\0') + return TkBadvl; + + l = tks->a2-tks->a1-4*Elembw; + delta = TKI2F(1); + if(l != 0) + delta = TKI2F(atoi(buf)) / l; + tkfprint(buf, delta); + + return tkvalue(val, "%s", buf); +} + +static char* +tkscrollget(Tk *tk, char *arg, char **val) +{ + char *v, buf[Tkmaxitem]; + TkScroll *tks = TKobj(TkScroll, tk); + + USED(arg); + v = tkfprint(buf, tks->top); + *v++ = ' '; + tkfprint(v, tks->bot); + + return tkvalue(val, "%s", buf); +} + +static char* +tkscrollidentify(Tk *tk, char *arg, char **val) +{ + int gotarg; + TkTop *t; + char *v, buf[Tkmaxitem]; + Point p; + TkScroll *tks = TKobj(TkScroll, tk); + + t = tk->env->top; + arg = tkword(t, arg, buf, buf+sizeof(buf), &gotarg); + if (!gotarg) + return TkBadvl; + p.x = atoi(buf); + tkword(t, arg, buf, buf+sizeof(buf), &gotarg); + if (!gotarg) + return TkBadvl; + p.y = atoi(buf); + if (!ptinrect(p, tkrect(tk, 0))) + return nil; + if (tks->orient == Tkvertical) + p.x = p.y; + p.x += tk->borderwidth; + + v = ""; + if(p.x <= tks->a1) + v = "arrow1"; + if(p.x > tks->a1 && p.x <= tks->t1) + v = "trough1"; + if(p.x > tks->t1 && p.x < tks->t2) + v = "slider"; + if(p.x >= tks->t2 && p.x < tks->a2) + v = "trough2"; + if(p.x >= tks->a2) + v = "arrow2"; + return tkvalue(val, "%s", v); +} + +static char* +tkscrollfraction(Tk *tk, char *arg, char **val) +{ + int len, frac, pos; + char buf[Tkmaxitem]; + TkScroll *tks = TKobj(TkScroll, tk); + + arg = tkitem(buf, arg); + if(tks->orient == Tkvertical) + tkitem(buf, arg); + if(*arg == '\0' || *buf == '\0') + return TkBadvl; + + pos = atoi(buf); + if(pos < tks->a1) + pos = tks->a1; + if(pos > tks->a2) + pos = tks->a2; + len = tks->a2 - tks->a1 - 4*Elembw; + frac = TKI2F(1); + if(len != 0) + frac = TKI2F(pos-tks->a1)/len; + tkfprint(buf, frac); + return tkvalue(val, "%s", buf); +} + +static char* +tkScrolBut1R(Tk *tk, char *arg, char **val) +{ + TkScroll *tks = TKobj(TkScroll, tk); + + USED(val); + USED(arg); + tkcancelrepeat(tk); + tks->flag &= ~(ActiveA1|ActiveA2|ActiveB1|ButtonA1|ButtonA2|ButtonB1|Autorepeat); + tk->dirty = tkrect(tk, 1); + return nil; +} + +/* tkScrolBut2P fraction */ +static char* +tkScrolBut2P(Tk *tk, char *arg, char **val) +{ + TkTop *t; + char *e, buf[Tkmaxitem], fracbuf[Tkmaxitem]; + TkScroll *tks = TKobj(TkScroll, tk); + + + USED(val); + t = tk->env->top; + + if(arg[0] == '\0') + return TkBadvl; + + tkword(t, arg, fracbuf, fracbuf+sizeof(fracbuf), nil); + + e = nil; + if(tks->cmd != nil) { + snprint(buf, sizeof(buf), "%s moveto %s", tks->cmd, fracbuf); + e = tkexec(t, buf, nil); + } + return e; +} + +static void +sbrepeat(Tk *tk, void *v, int cancelled) +{ + char *e, buf[Tkmaxitem]; + TkScroll *tks = TKobj(TkScroll, tk); + char *fmt = (char *)v; + + if (cancelled) { + tks->flag &= ~Autorepeat; + return; + } + + if(tks->cmd != nil && fmt != nil) { + snprint(buf, sizeof(buf), fmt, tks->cmd); + e = tkexec(tk->env->top, buf, nil); + if (e != nil) { + tks->flag &= ~Autorepeat; + tkcancelrepeat(tk); + } else + tkupdate(tk->env->top); + } +} + +/* tkScrolBut1P %x %y */ +static char* +tkScrolBut1P(Tk *tk, char *arg, char **val) +{ + int pix; + TkTop *t; + char *e, *fmt, buf[Tkmaxitem]; + TkScroll *tks = TKobj(TkScroll, tk); + + USED(val); + t = tk->env->top; + + if (tks->flag & Autorepeat) + return nil; + arg = tkword(t, arg, buf, buf+sizeof(buf), nil); + if(tks->orient == Tkvertical) + tkword(t, arg, buf, buf+sizeof(buf), nil); + if(buf[0] == '\0') + return TkBadvl; + + pix = atoi(buf); + + tks->dragpix = pix; + tks->dragtop = tks->top; + tks->dragbot = tks->bot; + + pix += tk->borderwidth; + + fmt = nil; + e = nil; + if(pix <= tks->a1) { + fmt = "%s scroll -1 unit"; + tks->flag |= ButtonA1; + } + if(pix > tks->a1 && pix <= tks->t1) + fmt = "%s scroll -1 page"; + if(pix > tks->t1 && pix < tks->t2) + tks->flag |= ButtonB1; + if(pix >= tks->t2 && pix < tks->a2) + fmt = "%s scroll 1 page"; + if(pix >= tks->a2) { + fmt = "%s scroll 1 unit"; + tks->flag |= ButtonA2; + } + if(tks->cmd != nil && fmt != nil) { + snprint(buf, sizeof(buf), fmt, tks->cmd); + e = tkexec(t, buf, nil); + tks->flag |= Autorepeat; + tkrepeat(tk, sbrepeat, fmt, TkRptpause, TkRptinterval); + } + tk->dirty = tkrect(tk, 1); + return e; +} + +/* tkScrolDrag %x %y */ +static char* +tkScrollDrag(Tk *tk, char *arg, char **val) +{ + TkTop *t; + int pix, delta; + char frac[32], buf[Tkmaxitem]; + TkScroll *tks = TKobj(TkScroll, tk); + + USED(val); + t = tk->env->top; + + if (tks->flag & Autorepeat) + return nil; + if((tks->flag & ButtonB1) == 0) + return nil; + + arg = tkword(t, arg, buf, buf+sizeof(buf), nil); + if(tks->orient == Tkvertical) + tkword(t, arg, buf, buf+sizeof(buf), nil); + if(buf[0] == '\0') + return TkBadvl; + + pix = atoi(buf); + + delta = TKI2F(pix-tks->dragpix); + if ( tks->a2 == tks->a1 ) + return TkBadvl; + delta = delta/(tks->a2-tks->a1-4*Elembw); + if(tks->jump == BoolT) { + if(tks->dragtop+delta >= 0 && + tks->dragbot+delta <= TKI2F(1)) { + tks->top = tks->dragtop+delta; + tks->bot = tks->dragbot+delta; + } + return nil; + } + if(tks->cmd != nil) { + delta += tks->dragtop; + if(delta < 0) + delta = 0; + if(delta > TKI2F(1)) + delta = TKI2F(1); + tkfprint(frac, delta); + snprint(buf, sizeof(buf), "%s moveto %s", tks->cmd, frac); + return tkexec(t, buf, nil); + } + return nil; +} + +TkCmdtab tkscrlbcmd[] = +{ + "activate", tkscrollactivate, + "cget", tkscrollcget, + "configure", tkscrollconf, + "delta", tkscrolldelta, + "fraction", tkscrollfraction, + "get", tkscrollget, + "identify", tkscrollidentify, + "set", tkscrollset, + "tkScrollDrag", tkScrollDrag, + "tkScrolBut1P", tkScrolBut1P, + "tkScrolBut1R", tkScrolBut1R, + "tkScrolBut2P", tkScrolBut2P, + nil +}; + +TkMethod scrollbarmethod = { + "scrollbar", + tkscrlbcmd, + tkfreescrlb, + tkdrawscrlb +}; diff --git a/libtk/textu.c b/libtk/textu.c new file mode 100644 index 00000000..854dc8d5 --- /dev/null +++ b/libtk/textu.c @@ -0,0 +1,1076 @@ +#include "lib9.h" +#include "draw.h" +#include "tk.h" +#include "textw.h" + +#define istring u.string +#define iwin u.win +#define imark u.mark +#define iline u.line + +/* debugging */ +int tktdbg; +extern void tktprinttext(TkText*); +extern void tktprintindex(TkTindex*); +extern void tktprintitem(TkTitem*); +extern void tktprintline(TkTline*); +extern void tktcheck(TkText*, char*); +int tktutfpos(char*, int); + +char* +tktnewitem(int kind, int tagextra,TkTitem **ret) +{ + int n; + TkTitem *i; + + n = sizeof(TkTitem) + tagextra * sizeof(ulong); + i = malloc(n); + if(i == nil) + return TkNomem; + + memset(i, 0, n); + i->kind = kind; + i->tagextra = tagextra; + *ret = i; + return nil; +} + +char* +tktnewline(int flags, TkTitem *items, TkTline *prev, TkTline *next, TkTline **ret) +{ + TkTline *l; + TkTitem *i; + + l = malloc(sizeof(TkTline)); + if(l == nil) + return TkNomem; + + memset(l, 0, sizeof(TkTline)); + l->flags = flags; + l->items = items; + l->prev = prev; + l->next = next; + next->prev = l; + prev->next = l; + + for(i = items; i->next != nil;) + i = i->next; + if(tktdbg && !(i->kind == TkTnewline || i->kind == TkTcontline)) + print("text:tktnewline botch\n"); + i->iline = l; + + *ret = l; + return nil; +} + +/* + * free items; freewins is 0 when the subwindows will be + * freed anyway as the main text widget is being destroyed. + */ +void +tktfreeitems(TkText *tkt, TkTitem *i, int freewins) +{ + TkTitem *n; + Tk *tk; + + while(i != nil) { + n = i->next; + if(tkt->mouse == i) + tkt->mouse = nil; + switch(i->kind) { + case TkTascii: + case TkTrune: + if(i->istring != nil) + free(i->istring); + break; + case TkTwin: + if (i->iwin != nil) { + tk = i->iwin->sub; + if (tk != nil) { + tk->geom = nil; + tk->destroyed = nil; + if (i->iwin->owned && freewins) { + if (tk->name != nil) + tkdestroy(tk->env->top, tk->name->name, nil); + } else { + tk->parent = nil; + tk->geom = nil; + tk->destroyed = nil; + } + } + if(i->iwin->create != nil) + free(i->iwin->create); + free(i->iwin); + } + break; + case TkTmark: + break; + } + free(i); + i = n; + } +} + +void +tktfreelines(TkText *tkt, TkTline *l, int freewins) +{ + TkTline *n; + + while(l != nil) { + n = l->next; + tktfreeitems(tkt, l->items, freewins); + free(l); + l = n; + } +} + +void +tktfreetabs(TkTtabstop *t) +{ + TkTtabstop *n; + + while(t != nil) { + n = t->next; + free(t); + t = n; + } +} + +void +tkfreetext(Tk *tk) +{ + TkText *tkt = TKobj(TkText, tk); + + if(tkt->start.next != nil && tkt->start.next != &(tkt->end)) { + tkt->end.prev->next = nil; + tktfreelines(tkt, tkt->start.next, 0); + } + tktfreeitems(tkt, tkt->start.items, 0); + tktfreeitems(tkt, tkt->end.items, 0); + tktfreetabs(tkt->tabs); + if(tkt->tagshare == nil) + tktfreetags(tkt->tags); + else + tk->binds = nil; + tktfreemarks(tkt->marks); + if(tkt->xscroll != nil) + free(tkt->xscroll); + if(tkt->yscroll != nil) + free(tkt->yscroll); + /* don't free image because it belongs to window */ +} + +/* + * Remove the item at ix, joining previous and next items. + * If item is at end of line, remove next line and join + * its items to this one (except at end). + * On return, ix is adjusted to point to the next item. + */ +void +tktremitem(TkText *tkt, TkTindex *ix) +{ + TkTline *l, *lnext; + TkTindex prev, nx; + TkTitem *i, *ilast; + + l = ix->line; + i = ix->item; + + if(i->next == nil) { + if(tktdbg && !(i->kind == TkTnewline || i->kind == TkTcontline)) { + print("tktremitem: botch 1\n"); + return; + } + lnext = l->next; + if(lnext == &tkt->end) + /* not supposed to remove final newline */ + return; + if(i->kind == TkTnewline) + tkt->nlines--; + ilast = tktlastitem(lnext->items); + ilast->iline = l; + i->next = lnext->items; + l->flags = (l->flags & ~TkTlast) | (lnext->flags & TkTlast); + l->next = lnext->next; + lnext->next->prev = l; + free(lnext); + } + if(l->items == i) + l->items = i->next; + else { + prev = *ix; + if(!tktadjustind(tkt, TkTbyitemback, &prev) && tktdbg) { + print("tktremitem: botch 2\n"); + return; + } + prev.item->next = i->next; + } + ix->item = i->next; + ix->pos = 0; + i->next = nil; + nx = *ix; + tktadjustind(tkt, TkTbycharstart, &nx); + + /* check against cached items */ + if(tkt->selfirst == i) + tkt->selfirst = nx.item; + if(tkt->sellast == i) + tkt->sellast = nx.item; + if(tkt->selfirst == tkt->sellast) { + tkt->selfirst = nil; + tkt->sellast = nil; + } + + tktfreeitems(tkt, i, 1); +} + +int +tktdispwidth(Tk *tk, TkTtabstop *tb, TkTitem *i, Font *f, int x, int pos, int nchars) +{ + int w, del, locked; + TkTtabstop *tbprev; + Display *d; + TkText *tkt; + TkEnv env; + + tkt = TKobj(TkText, tk); + d = tk->env->top->display; + if (tb == nil) + tb = tkt->tabs; + + switch(i->kind) { + case TkTrune: + pos = tktutfpos(i->istring, pos); + /* FALLTHRU */ + case TkTascii: + if(f == nil) { + if(!tktanytags(i)) + f = tk->env->font; + else { + tkttagopts(tk, i, nil, &env, nil, 1); + f = env.font; + } + } + locked = 0; + if(!(tkt->tflag&TkTdlocked)) + locked = lockdisplay(d); + if(nchars >= 0) + w = stringnwidth(f, i->istring+pos, nchars); + else + w = stringwidth(f, i->istring+pos); + if(locked) + unlockdisplay(d); + break; + case TkTtab: + if(tb == nil) + w = 0; + else { + tbprev = nil; + while(tb->pos <= x && tb->next != nil) { + tbprev = tb; + tb = tb->next; + } + w = tb->pos - x; + if(w <= 0) { + del = tb->pos; + if(tbprev != nil) + del -= tbprev->pos; + while(w <= 0) + w += del; + } + /* todo: other kinds of justification */ + } + break; + case TkTwin: + if(i->iwin->sub == 0) + w = 0; + else + w = i->iwin->sub->act.width + 2*i->iwin->padx + 2*i->iwin->sub->borderwidth; + break; + default: + w = 0; + } + return w; +} + +int +tktindrune(TkTindex *ix) +{ + int ans; + Rune r; + + switch(ix->item->kind) { + case TkTascii: + ans = ix->item->istring[ix->pos]; + break; + case TkTrune: + chartorune(&r, ix->item->istring + tktutfpos(ix->item->istring, ix->pos)); + ans = r; + break; + case TkTtab: + ans = '\t'; + break; + case TkTnewline: + ans = '\n'; + break; + default: + /* only care that it isn't a word char */ + ans = 0x80; + } + return ans; +} + +TkTitem* +tktlastitem(TkTitem *i) +{ + while(i->next != nil) + i = i->next; + if(tktdbg && !(i->kind == TkTnewline || i->kind == TkTcontline)) + print("text:tktlastitem botch\n"); + + return i; +} + +TkTline* +tktitemline(TkTitem *i) +{ + i = tktlastitem(i); + return i->iline; +} + +int +tktlinenum(TkText *tkt, TkTindex *p) +{ + int n; + TkTline *l; + + if(p->line->orig.y <= tkt->end.orig.y / 2) { + /* line seems closer to beginning */ + n = 1; + for(l = tkt->start.next; l != p->line; l = l->next) { + if(tktdbg && l->next == nil) { + print("text: tktlinenum botch\n"); + break; + } + if(l->flags & TkTlast) + n++; + } + } + else { + n = tkt->nlines; + for(l = tkt->end.prev; l != p->line; l = l->prev) { + if(tktdbg && l->prev == nil) { + print("text: tktlinenum botch\n"); + break; + } + if(l->flags & TkTfirst) + n--; + } + } + return n; +} + +int +tktlinepos(TkText *tkt, TkTindex *p) +{ + int n; + TkTindex ix; + TkTitem *i; + + n = 0; + ix = *p; + i = ix.item; + tktadjustind(tkt, TkTbylinestart, &ix); + while(ix.item != i) { + if(tktdbg && ix.item->next == nil && (ix.line->flags&TkTlast)) { + print("text: tktlinepos botch\n"); + break; + } + n += tktposcount(ix.item); + if(!tktadjustind(tkt, TkTbyitem, &ix)) { + if(tktdbg) + print("tktlinepos botch\n"); + break; + } + } + return (n+p->pos); +} + +int +tktposcount(TkTitem *i) +{ + int n; + + if(i->kind == TkTascii) + n = strlen(i->istring); + else + if(i->kind == TkTrune) + n = utflen(i->istring); + else + if(i->kind == TkTmark || i->kind == TkTcontline) + n = 0; + else + n = 1; + return n; +} + +/* + * Insert item i before position ins. + * If i is a newline or a contline, make a new line to contain the items up to + * and including the new newline, and make the original line + * contain the items from ins on. + * Adjust ins so that it points just after inserted item. + */ +char* +tktiteminsert(TkText *tkt, TkTindex *ins, TkTitem *i) +{ + int hasprev, flags; + char *e; + TkTindex prev; + TkTline *l; + TkTitem *items; + + prev = *ins; + hasprev = tktadjustind(tkt, TkTbyitemback, &prev); + + if(i->kind == TkTnewline || i->kind == TkTcontline) { + i->next = nil; + if(hasprev && prev.line == ins->line) { + items = ins->line->items; + prev.item->next = i; + } + else + items = i; + + flags = ins->line->flags&TkTfirst; + if(i->kind == TkTnewline) + flags |= TkTlast; + e = tktnewline(flags, items, ins->line->prev, ins->line, &l); + if(e != nil) { + if(hasprev && prev.line == ins->line) + prev.item->next = ins->item; + return e; + } + + if(i->kind == TkTnewline) + ins->line->flags |= TkTfirst; + + if(i->kind == TkTcontline) + ins->line->flags &= ~TkTfirst; + ins->line->items = ins->item; + ins->pos = 0; + } + else { + if(hasprev && prev.line == ins->line) + prev.item->next = i; + else + ins->line->items = i; + i->next = ins->item; + } + + return nil; +} + +/* + * If index p doesn't point at the beginning of an item, + * split the item at p. Adjust p to point to the beginning of + * the item after the split (same character it used to point at). + * If there is a split, the old item gets the characters before + * the split, and a new item gets the characters after it. + */ +char* +tktsplititem(TkTindex *p) +{ + int l1, l2; + char *s1, *s2, *e; + TkTitem *i, *i2; + + i = p->item; + + if(p->pos != 0) { + /* + * Must be TkTascii or TkTrune + * + * Make new item i2, to be inserted after i, + * with portion of string from p->pos on + */ + + if (i->kind == TkTascii) + l1 = p->pos; + else + l1 = tktutfpos(i->istring, p->pos); + l2 = strlen(i->istring) - l1; + if (l2 == 0) + print("tktsplititem botch\n"); + s1 = malloc(l1+1); + if(s1 == nil) + return TkNomem; + s2 = malloc(l2+1); + if(s2 == nil) { + free(s1); + return TkNomem; + } + + memmove(s1, i->istring, l1); + s1[l1] = '\0'; + memmove(s2, i->istring + l1, l2); + s2[l2] = '\0'; + + e = tktnewitem(i->kind, i->tagextra, &i2); + if(e != nil) { + free(s1); + free(s2); + return e; + } + + free(i->istring); + + tkttagcomb(i2, i, 1); + i2->next = i->next; + i->next = i2; + i->istring = s1; + i2->istring = s2; + + p->item = i2; + p->pos = 0; + } + + return nil; +} + +int +tktmaxwid(TkTline *l) +{ + int w, maxw; + + maxw = 0; + while(l != nil) { + w = l->width; + if(w > maxw) + maxw = w; + l = l->next; + } + return maxw; +} + +Rectangle +tktbbox(Tk *tk, TkTindex *ix) +{ + Rectangle r; + int d, w; + TkTitem *i; + TkTline *l; + TkEnv env; + TkTtabstop *tb = nil; + Tk *sub; + TkText *tkt = TKobj(TkText, tk); + int opts[TkTnumopts]; + + l = ix->line; + + /* r in V space */ + r.min = subpt(l->orig, tkt->deltatv); + r.min.y += l->ascent; + + /* tabs dependon tags of first non-mark on display line */ + for(i = l->items; i->kind == TkTmark; ) + i = i->next; + tkttagopts(tk, i, opts, &env, &tb, 1); + + for(i = l->items; i != nil; i = i->next) { + if(i == ix->item) { + tkttagopts(tk, i, opts, &env, nil, 1); + r.min.y -= opts[TkToffset]; + switch(i->kind) { + case TkTascii: + case TkTrune: + d = tktdispwidth(tk, tb, i, nil, r.min.x, 0, ix->pos); + w = tktdispwidth(tk, tb, i, nil, r.min.x, ix->pos, 1); + r.min.x += d; + r.min.y -= env.font->ascent; + r.max.x = r.min.x + w; + r.max.y = r.min.y + env.font->height; + break; + case TkTwin: + sub = i->iwin->sub; + if(sub == nil) + break; + r.min.x += sub->act.x; + r.min.y += sub->act.y; + r.max.x = r.min.x + sub->act.width + 2*sub->borderwidth; + r.max.y = r.min.y + sub->act.height + 2*sub->borderwidth; + break; + case TkTnewline: + r.max.x = r.min.x; + r.min.y -= l->ascent; + r.max.y = r.min.y + l->height; + break; + default: + d = tktdispwidth(tk, tb, i, nil, r.min.x, 0, -1); + r.max.x = r.min.x + d; + r.max.y = r.min.y; + break; + } + return r; + } + r.min.x += tktdispwidth(tk, tb, i, nil, r.min.x, 0, -1); + } + r.min.x = 0; + r.min.y = 0; + r.max.x = 0; + r.max.x = 0; + return r; +} + +/* Return left-at-baseline position of given item, in V coords */ +static Point +tktitempos(Tk *tk, TkTindex *ix) +{ + Point p; + TkTitem *i; + TkTline *l; + TkText *tkt = TKobj(TkText, tk); + + l = ix->line; + + /* p in V space */ + p = subpt(l->orig, tkt->deltatv); + p.y += l->ascent; + + for(i = l->items; i != nil && i != ix->item; i = i->next) + p.x += i->width; + return p; +} + +static Tk* +tktdeliver(Tk *tk, TkTitem *i, TkTitem *tagit, int event, void *data, Point deltasv) +{ + Tk *ftk, *dest; + TkTwind *w; + TkText *tkt; + TkTtaginfo *t; + TkTline *l; + TkMouse m; + Point mp, p; + TkTindex ix; + int bd; + + dest = nil; + if(i != nil) { + tkt = TKobj(TkText, tk); + + if(i->kind == TkTwin) { + w = i->iwin; + if(w->sub != nil) { + if(!(event & TkKey) && (event & TkEmouse)) { + m = *(TkMouse*)data; + mp.x = m.x; + mp.y = m.y; + ix.item = i; + ix.pos = 0; + ix.line = tktitemline(i); + p = tktitempos(tk, &ix); + bd = w->sub->borderwidth; + mp.x = m.x - (deltasv.x + p.x + w->sub->act.x + bd); + mp.y = m.y - (deltasv.y + p.y + w->sub->act.y + bd); + ftk = tkinwindow(w->sub, mp, 0); + if(ftk != w->focus) { + tkdeliver(w->focus, TkLeave, data); + tkdeliver(ftk, TkEnter, data); + + w->focus = ftk; + } + if(ftk != nil) + dest = tkdeliver(ftk, event, &m); + } + else { + if ((event & TkLeave) && (w->focus != w->sub)) { + tkdeliver(w->focus, TkLeave, data); + w->focus = nil; + event &= ~TkLeave; + } + if (event) + tkdeliver(w->sub, event, data); + } + if(Dx(w->sub->dirty) > 0) { + l = tktitemline(i); + tktfixgeom(tk, tktprevwrapline(tk, l), l, 0); + } + if(event & TkKey) + return dest; + } + } + + if(tagit != 0) { + for(t = tkt->tags; t != nil; t = t->next) { + if(t->binds != nil && tkttagset(tagit, t->id)) { + if(tksubdeliver(tk, t->binds, event, data, 0) == TkDbreak) { + return dest; + } + } + } + } + } + return dest; +} + +Tk* +tktinwindow(Tk *tk, Point *p) +{ + TkTindex ix; + Point q; + Tk *sub; + + tktxyind(tk, p->x, p->y, &ix); + if (ix.item == nil || ix.item->kind != TkTwin || ix.item->iwin->sub == nil) + return tk; + sub = ix.item->iwin->sub; + q = tktitempos(tk, &ix); + p->x -= q.x + sub->borderwidth + sub->act.x; + p->y -= q.y + sub->borderwidth + sub->act.y; + return sub; +} + +Tk* +tktextevent(Tk *tk, int event, void *data) +{ + char *e; + TkMouse m, vm; + TkTitem *f, *tagit; + TkText *tkt; + TkTindex ix; + Tk *dest; + Point deltasv; + + tkt = TKobj(TkText, tk); + deltasv = tkposn(tk); + deltasv.x += tk->borderwidth + tk->ipad.x/2; + deltasv.y += tk->borderwidth + tk->ipad.y/2; + + dest = nil; + if(event == TkLeave && tkt->mouse != nil) { + vm.x = 0; + vm.y = 0; + tktdeliver(tk, tkt->mouse, tkt->mouse, TkLeave, data, deltasv); + tkt->mouse = nil; + } + else if((event & TkKey) == 0 && (event & TkEmouse)) { + /* m in S space, tm in V space */ + m = *(TkMouse*)data; + vm = m; + vm.x -= deltasv.x; + vm.y -= deltasv.y; + if((event & TkMotion) == 0 || m.b == 0) { + tkt->current.x = vm.x; + tkt->current.y = vm.y; + } + tktxyind(tk, vm.x, vm.y, &ix); + f = ix.item; + if(tkt->mouse != f) { + tagit = nil; + if(tkt->mouse != nil) { + if(tktanytags(tkt->mouse)) { + e = tktnewitem(TkTascii, tkt->mouse->tagextra, &tagit); + if(e != nil) + return dest; /* XXX propagate error? */ + tkttagcomb(tagit, tkt->mouse, 1); + tkttagcomb(tagit, f, -1); + } + tktdeliver(tk, tkt->mouse, tagit, TkLeave, data, deltasv); + if(tagit) + free(tagit); + tagit = nil; + } + if(tktanytags(f)) { + e = tktnewitem(TkTascii, f->tagextra, &tagit); + if(e != nil) + return dest; /* XXX propagate error? */ + tkttagcomb(tagit, f, 1); + if(tkt->mouse) + tkttagcomb(tagit, tkt->mouse, -1); + } + tktdeliver(tk, f, tagit, TkEnter, data, deltasv); + tkt->mouse = f; + if(tagit) + free(tagit); + } + if(tkt->mouse != nil) + dest = tktdeliver(tk, tkt->mouse, tkt->mouse, event, &m, deltasv); + } + else if(event == TkFocusin) + tktextcursor(tk, " insert", (char **) nil); + /* pass all "real" events on to parent text widget - DBK */ + tksubdeliver(tk, tk->binds, event, data, 0); + return dest; +} + +/* Debugging */ +void +tktprintitem(TkTitem *i) +{ + int j; + + print("%p:", i); + switch(i->kind){ + case TkTascii: + print("\"%s\"", i->istring); + break; + case TkTrune: + print("<rune:%s>", i->istring); + break; + case TkTnewline: + print("<nl:%p>", i->iline); + break; + case TkTcontline: + print("<cont:%p>", i->iline); + break; + case TkTtab: + print("<tab>"); + break; + case TkTmark: + print("<mk:%s>", i->imark->name); + break; + case TkTwin: + if (i->iwin->sub->name != nil) + print("<win:%s>", i->iwin->sub? i->iwin->sub->name->name : "<null>"); + } + print("[%d]", i->width); + if(i->tags !=0 || i->tagextra !=0) { + print("{%lux", i->tags[0]); + for(j=0; j < i->tagextra; j++) + print(" %lux", i->tags[j+1]); + print("}"); + } + print(" "); +} + +void +tktprintline(TkTline *l) +{ + TkTitem *i; + + print("line %p: orig=(%d,%d), w=%d, h=%d, a=%d, f=%x\n\t", + l, l->orig.x, l->orig.y, l->width, l->height, l->ascent, l->flags); + for(i = l->items; i != nil; i = i->next) + tktprintitem(i); + print("\n"); +} + +void +tktprintindex(TkTindex *ix) +{ + print("line=%p,item=%p,pos=%d\n", ix->line, ix->item, ix->pos); +} + +void +tktprinttext(TkText *tkt) +{ + TkTline *l; + TkTtaginfo *ti; + TkTmarkinfo *mi; + + for(ti=tkt->tags; ti != nil; ti=ti->next) + print("%s{%d} ", ti->name, ti->id); + print("\n"); + for(mi = tkt->marks; mi != nil; mi=mi->next) + print("%s{%p} ", mi->name? mi->name : "nil", mi->cur); + print("\n"); + print("selfirst=%p sellast=%p\n", tkt->selfirst, tkt->sellast); + + for(l = &tkt->start; l != nil; l = l->next) + tktprintline(l); +} + +/* + * Check that assumed invariants are true. + * + * - start line and end line have no items + * - all other lines have at least one item + * - start line leads to end line via next pointers + * - prev pointers point to previous lines + * - each line ends in either a TkTnewline or a TkTcontline + * whose iline pointer points to the line itself + * - TkTcontline and TkTmark items have no tags + * (this is so they don't get realloc'd because of tag combination) + * - all cur fields of marks point to nil or a TkTmark + * - selfirst and sellast correctly define select region + * - nlines counts the number of lines + */ +void +tktcheck(TkText *tkt, char *fun) +{ + int nl, insel, selfound; + TkTline *l; + TkTitem *i; + TkTmarkinfo *mi; + TkTindex ix; + char *prob; + + prob = nil; + nl = 0; + + if(tkt->start.items != nil || tkt->end.items != nil) + prob = "start/end has items"; + for(l = tkt->start.next; l != &tkt->end; l = l->next) { + if(l->prev->next != l) { + prob = "prev mismatch"; + break; + } + if(l->next->prev != l) { + prob = "next mismatch"; + break; + } + i = l->items; + if(i == nil) { + prob = "empty line"; + break; + } + while(i->next != nil) { + if(i->kind == TkTnewline || i->kind == TkTcontline) { + prob = "premature end of line"; + break; + } + if(i->kind == TkTmark && (i->tags[0] != 0 || i->tagextra != 0)) { + prob = "mark has tags"; + break; + } + i = i->next; + } + if(i->kind == TkTnewline) + nl++; + if(!(i->kind == TkTnewline || i->kind == TkTcontline)) { + prob = "bad end of line"; + break; + } + if(i->kind == TkTcontline && (i->tags[0] != 0 || i->tagextra != 0)) { + prob = "contline has tags"; + break; + } + if(i->iline != l) { + prob = "bad end-of-line pointer"; + break; + } + } + for(mi = tkt->marks; mi != nil; mi=mi->next) { + if(mi->cur != nil) { + tktstartind(tkt, &ix); + do { + if(ix.item->kind == TkTmark && ix.item == mi->cur) + goto foundmark; + } while(tktadjustind(tkt, TkTbyitem, &ix)); + prob = "bad mark cur"; + break; + foundmark: ; + } + } + insel = 0; + selfound = 0; + tktstartind(tkt, &ix); + do { + i = ix.item; + if(i == tkt->selfirst) { + if(i->kind == TkTmark || i->kind == TkTcontline) { + prob = "selfirst not on character"; + break; + } + if(i == tkt->sellast) { + prob = "selfirst==sellast, not nil"; + break; + } + insel = 1; + selfound = 1; + } + if(i == tkt->sellast) { + if(i->kind == TkTmark || i->kind == TkTcontline) { + prob = "sellast not on character"; + break; + } + insel = 0; + } + if(i->kind != TkTmark && i->kind != TkTcontline) { + if(i->tags[0] & (1<<TkTselid)) { + if(!insel) { + prob = "sel set outside selfirst..sellast"; + break; + } + } + else { + if(insel) { + prob = "sel not set inside selfirst..sellast"; + break; + } + } + } + } while(tktadjustind(tkt, TkTbyitem, &ix)); + if(tkt->selfirst != nil && !selfound) + prob = "selfirst not found"; + + if(prob != nil) { + print("tktcheck problem: %s: %s\n", fun, prob); + tktprinttext(tkt); +abort(); + } +} + +int +tktutfpos(char *s, int pos) +{ + char *s1; + int c; + Rune r; + + for (s1 = s; pos > 0; pos--) { + c = *(uchar *)s1; + if (c < Runeself) { + if (c == '\0') + break; + s1++; + } + else + s1 += chartorune(&r, s1); + } + return s1 - s; +} + +/* +struct timerec { + char *name; + ulong ms; +}; + +static struct timerec tt[100]; +static int ntt = 1; + +int +tktalloctime(char *name) +{ + if(ntt >= 100) + abort(); + tt[ntt].name = strdup(name); + tt[ntt].ms = 0; + return ntt++; +} + +void +tktstarttime(int ind) +{ +return; + tt[ind].ms -= osmillisec(); +} + +void +tktendtime(int ind) +{ +return; + tt[ind].ms += osmillisec(); +} + +void +tktdumptime(void) +{ + int i; + + for(i = 1; i < ntt; i++) + print("%s: %d\n", tt[i].name, tt[i].ms); +} +*/ diff --git a/libtk/textw.c b/libtk/textw.c new file mode 100644 index 00000000..bf560d9f --- /dev/null +++ b/libtk/textw.c @@ -0,0 +1,3693 @@ +#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 delete insert"}, + {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 +c"}, + {TkKey|CNTL('k'), "%W delete insert {insert lineend}"}, + {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 1 page"}, + {TkKey|Pgdown, "%W yview scroll 1 page"}, + {TkKey|CNTL('w'), "%W tkTextDelIns -w"}, + {TkKey|Pgup, "%W yview scroll -1 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 = 2; + 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) { + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + amount = atoi(buf); + 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; + 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 + 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 +}; diff --git a/libtk/textw.h b/libtk/textw.h new file mode 100644 index 00000000..c1519f0e --- /dev/null +++ b/libtk/textw.h @@ -0,0 +1,229 @@ +typedef struct TkText TkText; +typedef struct TkTitem TkTitem; +typedef struct TkTline TkTline; +typedef struct TkTindex TkTindex; +typedef struct TkTmarkinfo TkTmarkinfo; +typedef struct TkTtaginfo TkTtaginfo; +typedef struct TkTwind TkTwind; + +enum +{ + /* text item types */ + TkTascii, /* contiguous string of ascii chars, all with same tags */ + TkTrune, /* printable utf (one printing position) */ + TkTtab, + TkTnewline, /* line field contains pointer to containing line */ + TkTcontline, /* end of non-newline line; line field as with TkTnewline */ + TkTwin, + TkTmark, + + TkTbyitem = 0, /* adjustment units */ + TkTbyitemback, + TkTbytline, + TkTbytlineback, + TkTbychar, + TkTbycharback, + TkTbycharstart, + TkTbyline, + TkTbylineback, + TkTbylinestart, + TkTbylineend, + TkTbywordstart, + TkTbywordend, + TkTbywrapstart, + TkTbywrapend, + + TkTselid = 0, /* id of sel tag */ + TkTmaxtag = 32, + Textwidth = 40, /* default width, in chars */ + Textheight = 10, /* default height, in chars */ + + TkTfirst = (1<<0), /* first line in buffer, or after a TkTlast */ + TkTlast = (1<<1), /* TkTnewline at end of line */ + TkTdrawn = (1<<2), /* screen cache copy is ok */ + TkTdlocked = (1<<3), /* display already locked */ + TkTjustfoc = (1<<4), /* got focus on last B1 press */ + TkTnodrag = (1<<5), /* ignore B1 drag until B1 up */ + TkTunset = (1<<31), /* marks int tag options "unspecified" */ + + TkTborderwidth = 0, + TkTjustify, + TkTlmargin1, + TkTlmargin2, + TkTlmargin3, + TkTrmargin, + TkTspacing1, + TkTspacing2, + TkTspacing3, + TkToffset, + TkTunderline, + TkToverstrike, + TkTrelief, + TkTwrap, + TkTlineheight, + + TkTnumopts +}; + +struct TkTline +{ + Point orig; /* where to put first item of line */ + int width; + int height; + int ascent; + int flags; + TkTitem* items; + TkTline* next; + TkTline* prev; +}; + +struct TkText +{ + TkTline start; /* fake before-the-first line */ + TkTline end; /* fake after-the-last line */ + Tk* tagshare; + TkTtabstop* tabs; + TkTtaginfo* tags; + TkTmarkinfo* marks; + char* xscroll; + char* yscroll; + uchar selunit; /* select adjustment unit */ + uchar tflag; /* various text-specific flags */ + int nlines; /* number of nl items in widget */ + TkTitem* selfirst; /* first item marked with sel tag */ + TkTitem* sellast; /* item after last marked with sel tag */ + Point deltatv; /* vector from text-space to view-space */ + Point deltaiv; /* vector from image-space to view-space */ + Point current; /* last known mouse pos */ + Point track; /* for use when B1 or B2 is down */ + int nexttag; /* next usable tag index */ + TkTitem* mouse; /* mouse focus */ + int inswidth; /* width of insertion cursor */ + int sborderwidth; + int opts[TkTnumopts]; + int propagate; + int scrolltop[2]; + int scrollbot[2]; + Image* image; + uchar cur_flag; /* text cursor to be shown up? */ + Rectangle cur_rec; /* last text cursor rectangle */ +}; + +struct TkTwind +{ + Tk* sub; /* Subwindow of canvas */ + int align; /* how to align within line */ + char* create; /* creation script */ + int padx; /* extra space on each side */ + int pady; /* extra space on top and bot */ + int width; /* current internal width */ + int height; /* current internal height */ + int ascent; /* distance from top of widget to baseline */ + int stretch; /* true if need to stretch height */ + int owned; /* true if window is destroyed on item deletion */ + Tk* focus; /* Current Mouse focus */ +}; + +struct TkTitem +{ + uchar kind; /* e.g. TkTascii, etc */ + uchar tagextra; + short width; + TkTitem *next; + union { + char* string; + TkTwind* win; + TkTmarkinfo* mark; + TkTline* line; + } u; + ulong tags[1]; + /* TkTitem length extends tagextra ulongs beyond */ +}; + +struct TkTmarkinfo +{ + char* name; + int gravity; + TkTitem* cur; + TkTmarkinfo* next; +}; + +struct TkTtaginfo +{ + int id; + char* name; + TkEnv* env; + TkTtabstop* tabs; + TkTtaginfo* next; + TkAction* binds; /* Binding of current events */ + int opts[TkTnumopts]; +}; + +struct TkTindex +{ + TkTitem* item; + TkTline* line; + int pos; /* index within multichar item */ +}; + +extern TkCmdtab tkttagcmd[]; +extern TkCmdtab tktmarkcmd[]; +extern TkCmdtab tktwincmd[]; + +extern void tkfreetext(Tk*); +extern char* tktaddmarkinfo(TkText*, char*, TkTmarkinfo**); +extern char* tktaddtaginfo(Tk*, char*, TkTtaginfo**); +extern int tktadjustind(TkText*, int, TkTindex*); +extern int tktanytags(TkTitem*); +extern Rectangle tktbbox(Tk*, TkTindex*); +extern void tktdirty(Tk*); +extern int tktdispwidth(Tk*, TkTtabstop *tabs, TkTitem*, Font*, int, int, int); +extern void tktendind(TkText*, TkTindex*); +extern char* tktextcursor(Tk*, char*, char **); +extern Tk* tktextevent(Tk*, int, void*); +extern Tk* tktinwindow(Tk*, Point*); +extern char* tktextselection(Tk*, char*, char**); +extern void tktextsize(Tk*, int); +extern TkTmarkinfo* tktfindmark(TkTmarkinfo*, char*); +extern int tktfindsubitem(Tk*, TkTindex*); +extern TkTtaginfo* tktfindtag(TkTtaginfo*, char*); +extern char* tktfixgeom(Tk*, TkTline*, TkTline*, int); +extern void tktfreeitems(TkText*, TkTitem*, int); +extern void tktfreelines(TkText*, TkTline*, int); +extern void tktfreemarks(TkTmarkinfo*); +extern void tktfreetabs(TkTtabstop*); +extern void tktfreetags(TkTtaginfo*); +extern int tktindcompare(TkText*, TkTindex*, int, TkTindex*); +extern int tktindbefore(TkTindex*, TkTindex*); +extern int tktindrune(TkTindex*); +extern char* tktinsert(Tk*, TkTindex*, char*, TkTitem*); +extern int tktisbreak(int); +extern void tktitemind(TkTitem*, TkTindex*); +extern char* tktiteminsert(TkText*, TkTindex*, TkTitem*); +extern TkTline* tktitemline(TkTitem*); +extern char* tktindparse(Tk*, char**, TkTindex*); +extern TkTitem* tktlastitem(TkTitem*); +extern int tktlinenum(TkText*, TkTindex*); +extern int tktlinepos(TkText*, TkTindex*); +extern int tktmarkind(Tk*, char*, TkTindex*); +extern char* tktmarkmove(Tk*, TkTmarkinfo*, TkTindex*); +extern char* tktmarkparse(Tk*, char**, TkTmarkinfo**); +extern int tktmaxwid(TkTline*); +extern char* tktnewitem(int, int, TkTitem**); +extern char* tktnewline(int, TkTitem*, TkTline*, TkTline*, TkTline**); +extern int tktposcount(TkTitem*); +extern TkTline* tktprevwrapline(Tk*, TkTline*); +extern void tktremitem(TkText*, TkTindex*); +extern int tktsametags(TkTitem*, TkTitem*); +extern char* tktsplititem(TkTindex*); +extern void tktstartind(TkText*, TkTindex*); +extern char* tkttagchange(Tk*, int, TkTindex*, TkTindex*, int); +extern int tkttagbit(TkTitem*, int, int); +extern void tkttagcomb(TkTitem*, TkTitem*, int); +extern int tkttagind(Tk*, char*, int, TkTindex*); +extern char* tkttagname(TkText*, int); +extern int tkttagnrange(TkText*, int, TkTindex*, TkTindex*, TkTindex*, TkTindex*); +extern void tkttagopts(Tk*, TkTitem*, int*, TkEnv*, TkTtabstop **, int); +extern char* tkttagparse(Tk*, char**, TkTtaginfo**); +extern int tkttagset(TkTitem*, int); +extern int tktxyind(Tk*, int, int, TkTindex*); diff --git a/libtk/tindx.c b/libtk/tindx.c new file mode 100644 index 00000000..8eeffc69 --- /dev/null +++ b/libtk/tindx.c @@ -0,0 +1,609 @@ +#include "lib9.h" +#include "draw.h" +#include "tk.h" +#include "textw.h" + +#define istring u.string +#define iwin u.win +#define imark u.mark +#define iline u.line + +/* debugging */ +extern int tktdbg; +extern void tktprinttext(TkText*); +extern void tktprintindex(TkTindex*); +extern void tktprintitem(TkTitem*); +extern void tktprintline(TkTline*); + +char* +tktindparse(Tk *tk, char **pspec, TkTindex *ans) +{ + int m, n, done, neg, modstart; + char *s, *mod; + TkTline *lend; + TkText *tkt; + char *buf; + + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + + tkt = TKobj(TkText, tk); + lend = &tkt->end; + + *pspec = tkword(tk->env->top, *pspec, buf, buf+Tkmaxitem, nil); + modstart = 0; + for(mod = buf; *mod != '\0'; mod++) + if(*mod == ' ' || *mod == '-' || *mod == '+') { + modstart = *mod; + *mod = '\0'; + break; + } + + /* + * XXX there's a problem here - if either coordinate is negative + * which shouldn't be precluded, then the above scanning code + * will break up the coordinate pair, so @-23,45 for example + * yields a bad index, when it should probably return the index + * of the character at the start of the line containing y=45. + * i've seen this cause wm/sh to crash. + */ + if(strcmp(buf, "end") == 0) + tktendind(tkt, ans); + else + if(*buf == '@') { + /* by coordinates */ + + s = strchr(buf, ','); + if(s == nil) { + free(buf); + return TkBadix; + } + *s = '\0'; + m = atoi(buf+1); + n = atoi(s+1); + tktxyind(tk, m, n, ans); + } + else + if(*buf >= '0' && *buf <= '9') { + /* line.char */ + + s = strchr(buf, '.'); + if(s == nil) { + free(buf); + return TkBadix; + } + *s = '\0'; + m = atoi(buf); + n = atoi(s+1); + + if(m < 1) + m = 1; + + tktstartind(tkt, ans); + + while(--m > 0 && ans->line->next != lend) + tktadjustind(tkt, TkTbyline, ans); + + while(n-- > 0 && ans->item->kind != TkTnewline) + tktadjustind(tkt, TkTbychar, ans); + } + else + if(*buf == '.') { + /* window */ + + tktstartind(tkt, ans); + + while(ans->line != lend) { + if(ans->item->kind == TkTwin && + ans->item->iwin->sub != nil && + ans->item->iwin->sub->name != nil && + strcmp(ans->item->iwin->sub->name->name, buf) == 0) + break; + if(!tktadjustind(tkt, TkTbyitem, ans)) + ans->line = lend; + } + if(ans->line == lend) { + free(buf); + return TkBadix; + } + } + else { + s = strchr(buf, '.'); + if(s == nil) { + if(tktmarkind(tk, buf, ans) == 0) { + free(buf); + return TkBadix; + } + } + else { + /* tag.first or tag.last */ + + *s = '\0'; + if(strcmp(s+1, "first") == 0) { + if(tkttagind(tk, buf, 1, ans) == 0) { + free(buf); + return TkBadix; + } + } + else + if(strcmp(s+1, "last") == 0) { + if(tkttagind(tk, buf, 0, ans) == 0) { + free(buf); + return TkBadix; + } + } + else { + free(buf); + return TkBadix; + } + } + } + + if(modstart == 0) { + free(buf); + return nil; + } + + *mod = modstart; + while(*mod == ' ') + mod++; + + while(*mod != '\0') { + done = 0; + switch(*mod) { + case '+': + case '-': + neg = (*mod == '-'); + mod++; + while(*mod == ' ') + mod++; + n = strtol(mod, &mod, 10); + while(*mod == ' ') + mod++; + while(n-- > 0) { + if(*mod == 'c') + tktadjustind(tkt, neg? TkTbycharback : TkTbychar, ans); + else + if(*mod == 'l') + tktadjustind(tkt, neg? TkTbylineback : TkTbyline, ans); + else + done = 1; + } + break; + case 'l': + if(strncmp(mod, "lines", 5) == 0) + tktadjustind(tkt, TkTbylinestart, ans); + else + if(strncmp(mod, "linee", 5) == 0) + tktadjustind(tkt, TkTbylineend, ans); + else + done = 1; + break; + case 'w': + if(strncmp(mod, "words", 5) == 0) + tktadjustind(tkt, TkTbywordstart, ans); + else + if(strncmp(mod, "worde", 5) == 0) + tktadjustind(tkt, TkTbywordend, ans); + else + done = 1; + break; + default: + done = 1; + } + + if(done) + break; + + while(tkiswordchar(*mod)) + mod++; + while(*mod == ' ') + mod++; + } + + free(buf); + return nil; +} + +int +tktisbreak(int c) +{ + /* unicode rules suggest / as well but that would split dates, and URLs might as well char. wrap */ + return c == ' ' || c == '\t' || c == '\n' || c == '-' || c == ','; + /* previously included . but would probably need more then to handle ." */ +} + +/* + * Adjust the index p by units (one of TkTbyitem, etc.). + * The TkTbychar units mean that the final point should rest on a + * "character" (in text widget index space; i.e., a newline, a rune, + * and an embedded window are each 1 character, but marks and contlines are not). + * + * Indexes may not point in the tkt->start or tkt->end lines (which have + * no items); tktadjustind sticks at the beginning or end of the buffer. + * + * Return 1 if the index changes at all, 0 otherwise. + */ +int +tktadjustind(TkText *tkt, int units, TkTindex *p) +{ + int n, opos, count, c; + TkTitem *i, *it, *oit; + TkTindex q; + + oit = p->item; + opos = p->pos; + count = 1; + + switch(units) { + case TkTbyitemback: + it = p->item; + p->item = p->line->items; + p->pos = 0; + if(it == p->item) { + if(p->line->prev != &tkt->start) { + p->line = p->line->prev; + p->item = tktlastitem(p->line->items); + } + } + else { + while(p->item->next != it) { + p->item = p->item->next; + if(tktdbg && p->item == nil) { + print("tktadjustind: botch 1\n"); + break; + } + } + } + break; + + case TkTbyitem: + p->pos = 0; + i = p->item->next; + if(i == nil) { + if(p->line->next != &tkt->end) { + p->line = p->line->next; + p->item = p->line->items; + } + } + else + p->item = i; + break; + + case TkTbytlineback: + if(p->line->prev != &tkt->start) + p->line = p->line->prev; + p->item = p->line->items; + p->pos = 0; + break; + + case TkTbytline: + if(p->line->next != &tkt->end) + p->line = p->line->next; + p->item = p->line->items; + p->pos = 0; + break; + + case TkTbycharstart: + count = 0; + case TkTbychar: + while(count > 0) { + i = p->item; + n = tktposcount(i) - p->pos; + if(count >= n) { + if(tktadjustind(tkt, TkTbyitem, p)) + count -= n; + else + break; + } + else { + p->pos += count; + break; + } + } + while(p->item->kind == TkTmark || p->item->kind == TkTcontline) + if(!tktadjustind(tkt, TkTbyitem, p)) + break; + break; + case TkTbycharback: + count = -1; + while(count < 0) { + if(p->pos + count >= 0) { + p->pos += count; + count = 0; + } + else { + count += p->pos; + if(!tktadjustind(tkt, TkTbyitemback, p)) + break; + n = tktposcount(p->item); + p->pos = n; + } + } + break; + + case TkTbylineback: + count = -1; + /* fall through */ + case TkTbyline: + n = tktlinepos(tkt, p); + while(count > 0) { + if(p->line->next == &tkt->end) { + count = 0; + break; + } + if(p->line->flags&TkTlast) + count--; + p->line = p->line->next; + } + while(count < 0 && p->line->prev != &tkt->start) { + if(p->line->flags&TkTfirst) + count++; + p->line = p->line->prev; + } + tktadjustind(tkt, TkTbylinestart, p); + while(n > 0) { + if(p->item->kind == TkTnewline) + break; + if(!tktadjustind(tkt, TkTbychar, p)) + break; + n--; + } + break; + + case TkTbylinestart: + /* note: can call this with only p->line set correctly in *p */ + + while(!(p->line->flags&TkTfirst)) + p->line = p->line->prev; + p->item = p->line->items; + p->pos = 0; + break; + + case TkTbylineend: + while(p->item->kind != TkTnewline) + if(!tktadjustind(tkt, TkTbychar, p)) + break; + break; + + case TkTbywordstart: + tktadjustind(tkt, TkTbycharstart, p); + q = *p; + c = tktindrune(p); + while(tkiswordchar(c)) { + q = *p; + if(!tktadjustind(tkt, TkTbycharback, p)) + break; + c = tktindrune(p); + } + *p = q; + break; + + case TkTbywordend: + tktadjustind(tkt, TkTbycharstart, p); + if(p->item->kind == TkTascii || p->item->kind == TkTrune) { + c = tktindrune(p); + if(tkiswordchar(c)) { + do { + if(!tktadjustind(tkt, TkTbychar, p)) + break; + c = tktindrune(p); + } while(tkiswordchar(c)); + } + else + tktadjustind(tkt, TkTbychar, p); + } + else if(!(p->item->kind == TkTnewline && p->line->next == &tkt->end)) + tktadjustind(tkt, TkTbychar, p); + + break; + + case TkTbywrapstart: + tktadjustind(tkt, TkTbycharstart, p); + q = *p; + c = tktindrune(p); + while(!tktisbreak(c)) { + q = *p; + if(!tktadjustind(tkt, TkTbycharback, p)) + break; + c = tktindrune(p); + } + *p = q; + break; + + case TkTbywrapend: + tktadjustind(tkt, TkTbycharstart, p); + if(p->item->kind == TkTascii || p->item->kind == TkTrune) { + c = tktindrune(p); + if(!tktisbreak(c)) { + do { + if(!tktadjustind(tkt, TkTbychar, p)) + break; + c = tktindrune(p); + } while(!tktisbreak(c) && (p->item->kind == TkTascii || p->item->kind == TkTrune)); + while(tktisbreak(c) && tktadjustind(tkt, TkTbychar, p)) + c = tktindrune(p); /* could limit it */ + } + else + tktadjustind(tkt, TkTbychar, p); + } + else if(!(p->item->kind == TkTnewline && p->line->next == &tkt->end)) + tktadjustind(tkt, TkTbychar, p); + + break; + } + return (p->item != oit || p->pos != opos); +} + +/* return 1 if advancing i1 by item eventually hits i2 */ +int +tktindbefore(TkTindex *i1, TkTindex *i2) +{ + int ans; + TkTitem *i; + TkTline *l1, *l2; + + ans = 0; + l1 = i1->line; + l2 = i2->line; + + if(l1 == l2) { + if(i1->item == i2->item) + ans = (i1->pos < i2->pos); + else { + for(i = i1->item; i != nil; i = i->next) + if(i->next == i2->item) { + ans = 1; + break; + } + } + } + else { + if(l1->orig.y < l2->orig.y) + ans = 1; + else + if(l1->orig.y == l2->orig.y) { + for(; l1 != nil; l1 = l1->next) { + if(l1->next == l2) { + ans = 1; + break; + } + if(l1->orig.y > l2->orig.y) + break; + } + } + } + + return ans; +} + +/* + * This comparison only cares which characters the indices are before. + * So two marks should be called "equal" (and not "less" or "greater") + * if they are adjacent. + */ +int +tktindcompare(TkText *tkt, TkTindex *i1, int op, TkTindex *i2) +{ + int eq, ans; + TkTindex x1, x2; + + x1 = *i1; + x2 = *i2; + + /* skip over any marks, contlines, to see if on same character */ + tktadjustind(tkt, TkTbycharstart, &x1); + tktadjustind(tkt, TkTbycharstart, &x2); + eq = (x1.item == x2.item && x1.pos == x2.pos); + + switch(op) { + case TkEq: + ans = eq; + break; + case TkNeq: + ans = !eq; + break; + case TkLte: + ans = eq || tktindbefore(i1, i2); + break; + case TkLt: + ans = !eq && tktindbefore(i1, i2); + break; + case TkGte: + ans = eq || tktindbefore(i2, i1); + break; + case TkGt: + ans = !eq && tktindbefore(i2, i1); + break; + default: + SET(ans); + }; + + return ans; +} + +void +tktstartind(TkText *tkt, TkTindex *ans) +{ + ans->line = tkt->start.next; + ans->item = ans->line->items; + ans->pos = 0; +} + +void +tktendind(TkText *tkt, TkTindex *ans) +{ + ans->line = tkt->end.prev; + ans->item = tktlastitem(ans->line->items); + ans->pos = 0; +} + +void +tktitemind(TkTitem *it, TkTindex *ans) +{ + ans->item = it; + ans->line = tktitemline(it); + ans->pos = 0; +} + +/* + * Fill ans with the item that (x,y) (in V space) is over. + * Return 0 if it is over the first half of the width, + * and 1 if it is over the second half. + */ +int +tktxyind(Tk *tk, int x, int y, TkTindex *ans) +{ + int n, w, secondhalf, k; + Point p, q; + TkTitem *i; + TkText *tkt; + + tkt = TKobj(TkText, tk); + tktstartind(tkt, ans); + secondhalf = 0; + + /* (x,y), p, q in V space */ + p = subpt(ans->line->orig, tkt->deltatv); + q = subpt(ans->line->next->orig, tkt->deltatv); + while(ans->line->next != &tkt->end) { + if(q.y > y) + break; + tktadjustind(tkt, TkTbytline, ans); + p = q; + q = subpt(ans->line->next->orig, tkt->deltatv); + } + if (ans->line->next == &tkt->end) { + Point ep = subpt(tkt->end.orig, tkt->deltatv); + if (ep.y < y) + x = 1000000; + } + + while(ans->item->next != nil) { + i = ans->item; + w = i->width; + if(p.x+w > x) { + n = tktposcount(i); + if(n > 1) { + for(k = 0; k < n; k++) { + /* probably wrong w.r.t tag tabs */ + w = tktdispwidth(tk, nil, i, nil, p.x, k, 1); + if(p.x+w > x) { + ans->pos = k; + break; + } + p.x += w; + } + } + secondhalf = (p.x + w/2 <= x); + break; + } + p.x += w; + if(!tktadjustind(tkt, TkTbyitem, ans)) + break; + } + tktadjustind(tkt, TkTbycharstart, ans); + return secondhalf; +} + diff --git a/libtk/tmark.c b/libtk/tmark.c new file mode 100644 index 00000000..37f5deef --- /dev/null +++ b/libtk/tmark.c @@ -0,0 +1,392 @@ +#include "lib9.h" +#include "draw.h" +#include "tk.h" +#include "textw.h" + +#define istring u.string +#define iwin u.win +#define imark u.mark +#define iline u.line + +static char* tktmarkgravity(Tk*, char*, char**); +static char* tktmarknames(Tk*, char*, char**); +static char* tktmarknext(Tk*, char*, char**); +static char* tktmarkprevious(Tk*, char*, char**); +static char* tktmarkset(Tk*, char*, char**); +static char* tktmarkunset(Tk*, char*, char**); + +TkCmdtab +tktmarkcmd[] = +{ + "gravity", tktmarkgravity, + "names", tktmarknames, + "next", tktmarknext, + "previous", tktmarkprevious, + "set", tktmarkset, + "unset", tktmarkunset, + nil +}; + +char* +tktaddmarkinfo(TkText *tkt, char *name, TkTmarkinfo **ret) +{ + TkTmarkinfo *mi; + + mi = malloc(sizeof(TkTmarkinfo)); + if(mi == nil) + return TkNomem; + + mi->name = strdup(name); + if(mi->name == nil) { + free(mi); + return TkNomem; + } + mi->gravity = Tkright; + mi->cur = nil; + mi->next = tkt->marks; + tkt->marks = mi; + *ret = mi; + return nil; +} + +void +tktfreemarks(TkTmarkinfo *m) +{ + TkTmarkinfo *n; + + while(m != nil) { + n = m->next; + free(m->name); + free(m); + m = n; + } +} + +TkTmarkinfo * +tktfindmark(TkTmarkinfo *m, char *name) +{ + while(m != nil) { + if(strcmp(m->name, name) == 0) + return m; + m = m->next; + } + return nil; +} + +int +tktmarkind(Tk *tk, char *name, TkTindex *ans) +{ + TkTmarkinfo *mk; + TkText *tkt = TKobj(TkText, tk); + + if(strcmp(name, "current") == 0) { + tktxyind(tk, tkt->current.x, tkt->current.y, ans); + return 1; + } + + mk = tktfindmark(tkt->marks, name); + if(mk == nil || mk->cur == nil) + return 0; + + ans->item = mk->cur; + ans->line = tktitemline(ans->item); + ans->pos = 0; + return 1; +} + +char* +tktmarkparse(Tk *tk, char **parg, TkTmarkinfo **ret) +{ + char *e, *buf; + TkText *tkt = TKobj(TkText, tk); + + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + + *parg = tkword(tk->env->top, *parg, buf, buf+Tkmaxitem, nil); + if(*buf == '\0') { + free(buf); + return TkOparg; + } + + *ret = tktfindmark(tkt->marks, buf); + if(*ret == nil) { + e = tktaddmarkinfo(tkt, buf, ret); + if(e != nil) { + free(buf); + return e; + } + } + free(buf); + + return nil; +} + +/* + * Insert mark before ixnew, first removing it from old place, if any. + * Make sure ixnew continues to point after mark. + */ +char* +tktmarkmove(Tk *tk, TkTmarkinfo *m, TkTindex *ixnew) +{ + char *e; + int deleted, split; + TkTitem *i; + TkTindex ix, pix; + TkText *tkt = TKobj(TkText, tk); + + deleted = 0; + if(m->cur != nil) { + if(m->cur == ixnew->item) + return nil; + ix.item = m->cur; + ix.line = tktitemline(m->cur); + ix.pos = 0; + tktremitem(tkt, &ix); + deleted = 1; + } + + /* XXX - Tad: memory leak on 'i' if something fails later? */ + e = tktnewitem(TkTmark, 0, &i); + if(e != nil) + return e; + + i->imark = m; + m->cur = i; + + /* keep adjacent marks sorted: all rights, then all lefts */ + if(m->gravity == Tkright) { + while(ixnew->item->kind == TkTmark && ixnew->item->imark->gravity == Tkleft) + if(!tktadjustind(tkt, TkTbyitem, ixnew)) + break; + } + else { + for(;;) { + pix = *ixnew; + if(!tktadjustind(tkt, TkTbyitemback, &pix)) + break; + if(pix.item->kind == TkTmark && pix.item->imark->gravity == Tkright) + *ixnew = pix; + else + break; + } + } + + split = (ixnew->pos > 0); + e = tktsplititem(ixnew); + if(e != nil) + return e; + + e = tktiteminsert(tkt, ixnew, i); + if(e != nil) + return nil; + + if(strcmp(m->name, "insert") == 0 || split) { + if(deleted && ix.line != ixnew->line) { + tktfixgeom(tk, tktprevwrapline(tk, ix.line), ix.line, 0); + /* + * this is ok only because tktfixgeom cannot + * free mark items, and we know that i is a mark item. + */ + ixnew->item = i; + ixnew->line = tktitemline(i); + ixnew->pos = 0; + } + tktfixgeom(tk, tktprevwrapline(tk, ixnew->line), ixnew->line, 0); + tktextsize(tk, 1); + } + + ixnew->item = i; + ixnew->line = tktitemline(i); + ixnew->pos = 0; + return nil; +} + +/* Text Mark Commands (+ means implemented) + +gravity + +names + +next + +previous + +set + +unset +*/ + +static char* +tktmarkgravity(Tk *tk, char *arg, char **val) +{ + char *e; + TkTmarkinfo *m; + char *buf; + + e = tktmarkparse(tk, &arg, &m); + if(e != nil) + return e; + + if(*arg == '\0') + return tkvalue(val, (m->gravity & Tkleft)? "left" : "right"); + else { + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + tkword(tk->env->top, arg, buf, buf+Tkmaxitem, nil); + if(strcmp(buf, "left") == 0) + m->gravity = Tkleft; + else + if(strcmp(buf, "right") == 0) + m->gravity = Tkright; + else { + free(buf); + return TkBadcm; + } + free(buf); + } + return nil; +} + +static char* +tktmarknames(Tk *tk, char *arg, char **val) +{ + char *r, *fmt; + TkTmarkinfo *m; + TkText *tkt = TKobj(TkText, tk); + + USED(arg); + + fmt = "%s"; + for(m = tkt->marks; m != nil; m = m->next) { + r = tkvalue(val, fmt, m->name); + if(r != nil) + return r; + fmt = " %s"; + } + return nil; +} + +static char* +tktmarknext(Tk *tk, char *arg, char **val) +{ + char *e; + TkTmarkinfo *mix; + TkTindex ix, ixend; + TkText *tkt = TKobj(TkText, tk); + + /* special behavior if specified index is a mark name */ + mix = tktfindmark(tkt->marks, arg); + + e = tktindparse(tk, &arg, &ix); + if(e != nil) + return e; + + if(mix != nil) + tktadjustind(tkt, TkTbyitem, &ix); + + /* special behavior if index is 'end' */ + tktendind(tkt, &ixend); + if(tktindcompare(tkt, &ix, TkEq, &ixend)) { + do { + tktadjustind(tkt, TkTbyitemback, &ix); + } while(ix.item->kind == TkTmark); + } + + do { + if(ix.item->kind == TkTmark) + return tkvalue(val, "%s", ix.item->imark->name); + + } while(tktadjustind(tkt, TkTbyitem, &ix)); + + return nil; +} + +static char* +tktmarkprevious(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; + + while(tktadjustind(tkt, TkTbyitemback, &ix)) { + if(ix.item->kind == TkTmark) + return tkvalue(val, "%s", ix.item->imark->name); + } + + return nil; +} + +/* XXX - Tad: possible memory leak here */ +static char* +tktmarkset(Tk *tk, char *arg, char **val) +{ + char *e; + TkTmarkinfo *m; + TkTindex ixnew; + + USED(val); + + e = tktmarkparse(tk, &arg, &m); + if(e != nil) + return e; + e = tktindparse(tk, &arg, &ixnew); + if(e != nil) + return e; + + return tktmarkmove(tk, m, &ixnew); +} + +static char* +tktmarkunset(Tk *tk, char *arg, char **val) +{ + TkText *tkt; + TkTmarkinfo *m, **p; + TkTindex ix; + char *e; + int resize; + + USED(val); + + tkt = TKobj(TkText, tk); + + e = tktmarkparse(tk, &arg, &m); + if(e != nil) + return e; + + resize = 0; + while(m != nil) { + if(strcmp(m->name, "insert") == 0 || strcmp(m->name, "current") == 0) + return TkBadvl; + + if(m->cur != nil) { + ix.item = m->cur; + ix.line = tktitemline(m->cur); + ix.pos = 0; + tktremitem(tkt, &ix); + tktfixgeom(tk, tktprevwrapline(tk, ix.line), ix.line, 0); + resize = 1; + } + + for(p = &tkt->marks; *p != nil; p = &(*p)->next) { + if(*p == m) { + *p = m->next; + break; + } + } + m->next = nil; + tktfreemarks(m); + + if(*arg != '\0') { + e = tktmarkparse(tk, &arg, &m); + if(e != nil) + return e; + } + else + m = nil; + } + if (resize) + tktextsize(tk, 1); + return nil; +} + diff --git a/libtk/ttags.c b/libtk/ttags.c new file mode 100644 index 00000000..e3e2739e --- /dev/null +++ b/libtk/ttags.c @@ -0,0 +1,1029 @@ +#include "lib9.h" +#include "draw.h" +#include "tk.h" +#include "textw.h" + +#define istring u.string +#define iwin u.win +#define imark u.mark +#define iline u.line + +static char* tkttagadd(Tk*, char*, char**); +static char* tkttagbind(Tk*, char*, char**); +static char* tkttagcget(Tk*, char*, char**); +static char* tkttagconfigure(Tk*, char*, char**); +static char* tkttagdelete(Tk*, char*, char**); +static char* tkttaglower(Tk*, char*, char**); +static char* tkttagnames(Tk*, char*, char**); +static char* tkttagnextrange(Tk*, char*, char**); +static char* tkttagprevrange(Tk*, char*, char**); +static char* tkttagraise(Tk*, char*, char**); +static char* tkttagranges(Tk*, char*, char**); +static char* tkttagremove(Tk*, char*, char**); + +#define O(t, e) ((long)(&((t*)0)->e)) + +#define TKTEO (O(TkTtaginfo, env)) +static +TkOption tagopts[] = +{ + "borderwidth", + OPTnndist, O(TkTtaginfo, opts[TkTborderwidth]), nil, + "justify", + OPTstab, O(TkTtaginfo, opts[TkTjustify]), tkjustify, + "lineheight", + OPTnndist, O(TkTtaginfo, opts[TkTlineheight]), IAUX(TKTEO), + "lmargin1", + OPTdist, O(TkTtaginfo, opts[TkTlmargin1]), IAUX(TKTEO), + "lmargin2", + OPTdist, O(TkTtaginfo, opts[TkTlmargin2]), IAUX(TKTEO), + "lmargin3", + OPTdist, O(TkTtaginfo, opts[TkTlmargin3]), IAUX(TKTEO), + "rmargin", + OPTdist, O(TkTtaginfo, opts[TkTrmargin]), IAUX(TKTEO), + "spacing1", + OPTnndist, O(TkTtaginfo, opts[TkTspacing1]), IAUX(TKTEO), + "spacing2", + OPTnndist, O(TkTtaginfo, opts[TkTspacing2]), IAUX(TKTEO), + "spacing3", + OPTnndist, O(TkTtaginfo, opts[TkTspacing3]), IAUX(TKTEO), + "offset", + OPTdist, O(TkTtaginfo, opts[TkToffset]), IAUX(TKTEO), + "underline", + OPTstab, O(TkTtaginfo, opts[TkTunderline]), tkbool, + "overstrike", + OPTstab, O(TkTtaginfo, opts[TkToverstrike]), tkbool, + "relief", + OPTstab, O(TkTtaginfo, opts[TkTrelief]), tkrelief, + "tabs", + OPTtabs, O(TkTtaginfo, tabs), IAUX(TKTEO), + "wrap", + OPTstab, O(TkTtaginfo, opts[TkTwrap]), tkwrap, + nil, +}; + +static +TkOption tagenvopts[] = +{ + "foreground", OPTcolr, O(TkTtaginfo, env), IAUX(TkCforegnd), + "background", OPTcolr, O(TkTtaginfo, env), IAUX(TkCbackgnd), + "fg", OPTcolr, O(TkTtaginfo, env), IAUX(TkCforegnd), + "bg", OPTcolr, O(TkTtaginfo, env), IAUX(TkCbackgnd), + "font", OPTfont, O(TkTtaginfo, env), nil, + nil +}; + +TkCmdtab +tkttagcmd[] = +{ + "add", tkttagadd, + "bind", tkttagbind, + "cget", tkttagcget, + "configure", tkttagconfigure, + "delete", tkttagdelete, + "lower", tkttaglower, + "names", tkttagnames, + "nextrange", tkttagnextrange, + "prevrange", tkttagprevrange, + "raise", tkttagraise, + "ranges", tkttagranges, + "remove", tkttagremove, + nil +}; + +int +tktanytags(TkTitem *it) +{ + int i; + + if(it->tagextra == 0) + return (it->tags[0] != 0); + for(i = 0; i <= it->tagextra; i++) + if(it->tags[i] != 0) + return 1; + return 0; +} + +int +tktsametags(TkTitem *i1, TkTitem *i2) +{ + int i, j; + + for(i = 0; i <= i1->tagextra && i <= i2->tagextra; i++) + if(i1->tags[i] != i2->tags[i]) + return 0; + for(j = i; j <= i1->tagextra; j++) + if(i1->tags[j] != 0) + return 0; + for(j = i; j <= i2->tagextra; j++) + if(i2->tags[j] != 0) + return 0; + return 1; +} + +int +tkttagset(TkTitem *it, int id) +{ + int i; + + if(it->tagextra == 0 && it->tags[0] == 0) + return 0; + for(i = 0; i <= it->tagextra; i++) { + if(id < 32) + return ((it->tags[i] & (1<<id)) != 0); + id -= 32; + } + return 0; +} + +char * +tkttagname(TkText *tkt, int id) +{ + TkTtaginfo *t; + + for(t = tkt->tags; t != nil; t = t->next) { + if(t->id == id) + return t->name; + } + return ""; +} + +/* return 1 if this actually changes the value */ +int +tkttagbit(TkTitem *it, int id, int val) +{ + int i, changed; + ulong z, b; + + changed = 0; + for(i = 0; i <= it->tagextra; i++) { + if(id < 32) { + b = (1<<id); + z = it->tags[i]; + if(val == 0) { + if(z & b) { + changed = 1; + it->tags[i] = z & (~b); + } + } + else { + if((z & b) == 0) { + changed = 1; + it->tags[i] = z | b; + } + } + break; + } + id -= 32; + } + return changed; +} + +void +tkttagcomb(TkTitem *i1, TkTitem *i2, int add) +{ + int i; + + for(i = 0; i <= i1->tagextra && i <= i2->tagextra; i++) { + if(add == 1) + i1->tags[i] |= i2->tags[i]; + else if(add == 0) + /* intersect */ + i1->tags[i] &= i2->tags[i]; + else + /* subtract */ + i1->tags[i] &= ~i2->tags[i]; + } +} + +char* +tktaddtaginfo(Tk *tk, char *name, TkTtaginfo **ret) +{ + int i, *ntagp; + TkTtaginfo *ti; + TkText *tkt, *tktshare; + + tkt = TKobj(TkText, tk); + ti = malloc(sizeof(TkTtaginfo)); + if(ti == nil) + return TkNomem; + + ntagp = &tkt->nexttag; + if(tkt->tagshare != nil) { + tktshare = TKobj(TkText, tkt->tagshare); + ntagp = &tktshare->nexttag; + } + ti->id = *ntagp; + ti->name = strdup(name); + if(ti->name == nil) { + free(ti); + return TkNomem; + } + ti->env = tknewenv(tk->env->top); + if(ti->env == nil) { + free(ti->name); + free(ti); + return TkNomem; + } + + ti->tabs = nil; + for(i = 0; i < TkTnumopts; i++) + ti->opts[i] = TkTunset; + ti->next = tkt->tags; + tkt->tags = ti; + + (*ntagp)++; + if(tkt->tagshare) + tkt->nexttag = *ntagp; + + *ret = ti; + return nil; +} + +TkTtaginfo * +tktfindtag(TkTtaginfo *t, char *name) +{ + while(t != nil) { + if(strcmp(t->name, name) == 0) + return t; + t = t->next; + } + return nil; +} + +void +tktfreetags(TkTtaginfo *t) +{ + TkTtaginfo *n; + + while(t != nil) { + n = t->next; + free(t->name); + tktfreetabs(t->tabs); + tkputenv(t->env); + tkfreebind(t->binds); + free(t); + t = n; + } +} + +int +tkttagind(Tk *tk, char *name, int first, TkTindex *ans) +{ + int id; + TkTtaginfo *t; + TkText *tkt; + + tkt = TKobj(TkText, tk); + + if(strcmp(name, "sel") == 0) { + if(tkt->selfirst == nil) + return 0; + if(first) + tktitemind(tkt->selfirst, ans); + else + tktitemind(tkt->sellast, ans); + return 1; + } + + t = tktfindtag(tkt->tags, name); + if(t == nil) + return 0; + id = t->id; + + if(first) { + tktstartind(tkt, ans); + while(!tkttagset(ans->item, id)) + if(!tktadjustind(tkt, TkTbyitem, ans)) + return 0; + } + else { + tktendind(tkt, ans); + while(!tkttagset(ans->item, id)) + if(!tktadjustind(tkt, TkTbyitemback, ans)) + return 0; + tktadjustind(tkt, TkTbyitem, ans); + } + + return 1; +} + +/* + * Fill in opts and e, based on info from tags set in it, + * using tags order for priority. + * If dflt != 0, options not set are filled from tk, + * otherwise iInteger options not set by any tag are left 'TkTunset' + * and environment values not set are left nil. + */ +void +tkttagopts(Tk *tk, TkTitem *it, int *opts, TkEnv *e, TkTtabstop **tb, int dflt) +{ + int i; + int colset; + TkEnv *te; + TkTtaginfo *tags; + TkText *tkt = TKobj(TkText, tk); + + if (tb != nil) + *tb = tkt->tabs; + + tags = tkt->tags; + + if(opts != nil) + for(i = 0; i < TkTnumopts; i++) + opts[i] = TkTunset; + + memset(e, 0, sizeof(TkEnv)); + e->top = tk->env->top; + colset = 0; + while(tags != nil) { + if(tkttagset(it, tags->id)) { + if(opts != nil) { + for(i = 0; i < TkTnumopts; i++) { + if(opts[i] == TkTunset && tags->opts[i] != TkTunset) + opts[i] = tags->opts[i]; + } + } + + te = tags->env; + for(i = 0; i < TkNcolor; i++) + if(!(colset & (1<<i)) && te->set & (1<<i)) { + e->colors[i] = te->colors[i]; + colset |= 1<<i; + } + + if(e->font == nil && te->font != nil) + e->font = te->font; + + if (tb != nil && tags->tabs != nil) + *tb = tags->tabs; + } + tags = tags->next; + } + e->set |= colset; + if(dflt) { + if(opts != nil) { + for(i = 0; i < TkTnumopts; i++) + if(opts[i] == TkTunset) + opts[i] = tkt->opts[i]; + } + te = tk->env; + for(i = 0; i < TkNcolor; i++) + if(!(e->set & (1<<i))) { + e->colors[i] = te->colors[i]; + e->set |= 1<<i; + } + if(e->font == nil) + e->font = te->font; + } +} + +char* +tkttagparse(Tk *tk, char **parg, TkTtaginfo **ret) +{ + char *e, *buf; + TkText *tkt = TKobj(TkText, tk); + + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + *parg = tkword(tk->env->top, *parg, buf, buf+Tkmaxitem, nil); + if(*buf == '\0') { + free(buf); + return TkOparg; + } + if(buf[0] >= '0' && buf[0] <= '9'){ + free(buf); + return TkBadtg; + } + + *ret = tktfindtag(tkt->tags, buf); + if(*ret == nil) { + e = tktaddtaginfo(tk, buf, ret); + if(e != nil) { + free(buf); + return e; + } + } + free(buf); + + return nil; +} + +int +tkttagnrange(TkText *tkt, int tid, TkTindex *i1, TkTindex *i2, + TkTindex *istart, TkTindex *iend) +{ + int found; + + found = 0; + while(i1->line != &tkt->end) { + if(i1->item == i2->item && i2->pos == 0) + break; + if(tkttagset(i1->item, tid)) { + if(!found) { + found = 1; + *istart = *i1; + } + if(i1->item == i2->item) { + /* i2->pos > 0 */ + *iend = *i2; + return 1; + } + } + else + if(i1->item == i2->item || (found && i1->item->kind != TkTmark && i1->item->kind != TkTcontline)) + break; + tktadjustind(tkt, TkTbyitem, i1); + } + if(found) + *iend = *i1; + + return found; +} + +static int +tkttagprange(TkText *tkt, int tid, TkTindex *i1, TkTindex *i2, + TkTindex *istart, TkTindex *iend) +{ + int found; + + found = 0; + while(i1->line != &tkt->start && i1->item != i2->item) { + tktadjustind(tkt, TkTbyitemback, i1); + if(tkttagset(i1->item, tid)) { + if(!found) { + found = 1; + *iend = *i1; + } + } + else + if(found && i1->item->kind != TkTmark && i1->item->kind != TkTcontline) + break; + } + if(found) { + tktadjustind(tkt, TkTbyitem, i1); + *istart = *i1; + if(i1->item == i2->item) + istart->pos = i2->pos; + } + + return found; +} + +/* XXX - Tad: potential memory leak on memory allocation failure */ +char * +tkttagchange(Tk *tk, int tid, TkTindex *i1, TkTindex *i2, int add) +{ + char *e; + int samei, nextra, j, changed; + TkTline *lmin, *lmax; + TkTindex ixprev; + TkTitem *nit; + TkText *tkt = TKobj(TkText, tk); + + if(!tktindbefore(i1, i2)) + return nil; + + nextra = tid/32; + lmin = nil; + lmax = nil; + tktadjustind(tkt, TkTbycharstart, i1); + tktadjustind(tkt, TkTbycharstart, i2); + samei = (i1->item == i2->item); + if(i2->pos != 0) { + e = tktsplititem(i2); + if(e != nil) + return e; + if(samei) { + /* split means i1 should now point to previous item */ + ixprev = *i2; + tktadjustind(tkt, TkTbyitemback, &ixprev); + i1->item = ixprev.item; + } + } + if(i1->pos != 0) { + e = tktsplititem(i1); + if(e != nil) + return e; + } + /* now i1 and i2 both point to beginning of non-mark/contline items */ + if(tid == TkTselid) { + /* + * Cache location of selection. + * Note: there can be only one selection range in widget + */ + if(add) { + if(tkt->selfirst != nil) + return TkBadsl; + tkt->selfirst = i1->item; + tkt->sellast = i2->item; + } + else { + tkt->selfirst = nil; + tkt->sellast = nil; + } + } + while(i1->item != i2->item) { + if(i1->item->kind != TkTmark && i1->item->kind != TkTcontline) { + if(tid >= 32 && i1->item->tagextra < nextra) { + nit = realloc(i1->item, sizeof(TkTitem) + nextra * sizeof(long)); + if(nit == nil) + return TkNomem; + for(j = nit->tagextra+1; j <= nextra; j++) + nit->tags[j] = 0; + nit->tagextra = nextra; + if(i1->line->items == i1->item) + i1->line->items = nit; + else { + ixprev = *i1; + tktadjustind(tkt, TkTbyitemback, &ixprev); + ixprev.item->next = nit; + } + /* check nit against cached items */ + if(tkt->selfirst == i1->item) + tkt->selfirst = nit; + if(tkt->sellast == i1->item) + tkt->sellast = nit; + i1->item = nit; + } + changed = tkttagbit(i1->item, tid, add); + if(lmin == nil) { + if(changed) { + lmin = i1->line; + lmax = lmin; + } + } + else { + if(changed) + lmax = i1->line; + } + } + if(!tktadjustind(tkt, TkTbyitem, i1)) + break; + } + if(lmin != nil) { + tktfixgeom(tk, tktprevwrapline(tk, lmin), lmax, 0); + tktextsize(tk, 1); + } + return nil; +} + +static char* +tkttagaddrem(Tk *tk, char *arg, int add) +{ + char *e; + TkText *tkt; + TkTtaginfo *ti; + TkTindex ix1, ix2; + + tkt = TKobj(TkText, tk); + + e = tkttagparse(tk, &arg, &ti); + if(e != nil) + return e; + + while(*arg != '\0') { + 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); + } + if(!tktindbefore(&ix1, &ix2)) + continue; + + e = tkttagchange(tk, ti->id, &ix1, &ix2, add); + if(e != nil) + return e; + } + + return nil; +} + + +/* Text Tag Command (+ means implemented) + +add + +bind + +cget + +configure + +delete + +lower + +names + +nextrange + +prevrange + +raise + +ranges + +remove +*/ + +static char* +tkttagadd(Tk *tk, char *arg, char **val) +{ + USED(val); + + return tkttagaddrem(tk, arg, 1); +} + +static char* +tkttagbind(Tk *tk, char *arg, char **val) +{ + char *e; + Rune r; + TkTtaginfo *ti; + TkAction *a; + int event, mode; + char *cmd, buf[Tkmaxitem]; + + + e = tkttagparse(tk, &arg, &ti); + if(e != nil) + return e; + + arg = tkskip(arg, " \t"); + if (arg[0] == '\0') + return TkBadsq; + arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + if(buf[0] == '<') { + event = tkseqparse(buf+1); + if(event == -1) + return TkBadsq; + } + else { + chartorune(&r, buf); + event = TkKey | r; + } + if(event == 0) + return TkBadsq; + + arg = tkskip(arg, " \t"); + if(*arg == '\0') { + for(a = ti->binds; a; a = a->link) + if(event == a->event) + return tkvalue(val, "%s", a->arg); + return nil; + } + + mode = TkArepl; + if(*arg == '+') { + mode = TkAadd; + arg++; + } + else if(*arg == '-'){ + mode = TkAsub; + arg++; + } + + tkword(tk->env->top, arg, buf, buf+sizeof(buf), nil); + cmd = strdup(buf); + if(cmd == nil) + return TkNomem; + return tkaction(&ti->binds, event, TkDynamic, cmd, mode); +} + +static char* +tkttagcget(Tk *tk, char *arg, char **val) +{ + char *e; + TkTtaginfo *ti; + TkOptab tko[3]; + + e = tkttagparse(tk, &arg, &ti); + if(e != nil) + return e; + + tko[0].ptr = ti; + tko[0].optab = tagopts; + tko[1].ptr = ti; + tko[1].optab = tagenvopts; + tko[2].ptr = nil; + + return tkgencget(tko, arg, val, tk->env->top); +} + +static char* +tkttagconfigure(Tk *tk, char *arg, char **val) +{ + char *e; + TkOptab tko[3]; + TkTtaginfo *ti; + TkTindex ix; + TkText *tkt = TKobj(TkText, tk); + + USED(val); + + e = tkttagparse(tk, &arg, &ti); + if(e != nil) + return e; + + tko[0].ptr = ti; + tko[0].optab = tagopts; + tko[1].ptr = ti; + tko[1].optab = tagenvopts; + tko[2].ptr = nil; + + e = tkparse(tk->env->top, arg, tko, nil); + if(e != nil) + return e; + + if(tkttagind(tk, ti->name, 1, &ix)) { + tktfixgeom(tk, tktprevwrapline(tk, ix.line), tkt->end.prev, 0); + tktextsize(tk, 1); + } + + return nil; +} + +static void +tktunlinktag(TkText *tkt, TkTtaginfo *t) +{ + TkTtaginfo *f, **l; + + l = &tkt->tags; + for(f = *l; f != nil; f = f->next) { + if(f == t) { + *l = t->next; + return; + } + l = &f->next; + } +} + +static char* +tkttagdelete(Tk *tk, char *arg, char **val) +{ + TkText *tkt; + TkTtaginfo *t; + TkTindex ix; + char *e; + int found; + + USED(val); + + tkt = TKobj(TkText, tk); + + e = tkttagparse(tk, &arg, &t); + if(e != nil) + return e; + + found = 0; + while(t != nil) { + if(t->id == TkTselid) + return TkBadvl; + + while(tkttagind(tk, t->name, 1, &ix)) { + found = 1; + tkttagbit(ix.item, t->id, 0); + } + + tktunlinktag(tkt, t); + t->next = nil; + tktfreetags(t); + + if(*arg != '\0') { + e = tkttagparse(tk, &arg, &t); + if(e != nil) + return e; + } + else + t = nil; + } + if (found) { + tktfixgeom(tk, &tkt->start, tkt->end.prev, 0); + tktextsize(tk, 1); + } + + return nil; +} + +static char* +tkttaglower(Tk *tk, char *arg, char **val) +{ + TkText *tkt; + TkTindex ix; + TkTtaginfo *t, *tbelow, *f, **l; + char *e; + + USED(val); + + tkt = TKobj(TkText, tk); + + e = tkttagparse(tk, &arg, &t); + if(e != nil) + return e; + + if(*arg != '\0') { + e = tkttagparse(tk, &arg, &tbelow); + if(e != nil) + return e; + } + else + tbelow = nil; + + tktunlinktag(tkt, t); + + if(tbelow != nil) { + t->next = tbelow->next; + tbelow->next = t; + } + else { + l = &tkt->tags; + for(f = *l; f != nil; f = f->next) + l = &f->next; + *l = t; + t->next = nil; + } + if(tkttagind(tk, t->name, 1, &ix)) { + tktfixgeom(tk, tktprevwrapline(tk, ix.line), tkt->end.prev, 0); + tktextsize(tk, 1); + } + + return nil; +} + + +static char* +tkttagnames(Tk *tk, char *arg, char **val) +{ + char *e, *r, *fmt; + TkTtaginfo *t; + TkTindex i; + TkText *tkt = TKobj(TkText, tk); + TkTitem *tagit; + + if(*arg != '\0') { + e = tktindparse(tk, &arg, &i); + if(e != nil) + return e; + /* make sure we're actually on a character */ + tktadjustind(tkt, TkTbycharstart, &i); + tagit = i.item; + } + else + tagit = nil; + + /* generate in order highest-to-lowest priority (contrary to spec) */ + fmt = "%s"; + for(t = tkt->tags; t != nil; t = t->next) { + if(tagit == nil || tkttagset(tagit, t->id)) { + r = tkvalue(val, fmt, t->name); + if(r != nil) + return r; + fmt = " %s"; + } + } + return nil; +} + +static char* +tkttagnextrange(Tk *tk, char *arg, char **val) +{ + char *e; + TkTtaginfo *t; + TkTindex i1, i2, istart, iend; + TkText *tkt = TKobj(TkText, tk); + + e = tkttagparse(tk, &arg, &t); + if(e != nil) + return e; + e = tktindparse(tk, &arg, &i1); + if(e != nil) + return e; + if(*arg != '\0') { + e = tktindparse(tk, &arg, &i2); + if(e != nil) + return e; + } + else + tktendind(tkt, &i2); + + if(tkttagnrange(tkt, t->id, &i1, &i2, &istart, &iend)) + return tkvalue(val, "%d.%d %d.%d", + tktlinenum(tkt, &istart), tktlinepos(tkt, &istart), + tktlinenum(tkt, &iend), tktlinepos(tkt, &iend)); + + return nil; +} + +static char* +tkttagprevrange(Tk *tk, char *arg, char **val) +{ + char *e; + TkTtaginfo *t; + TkTindex i1, i2, istart, iend; + TkText *tkt = TKobj(TkText, tk); + + e = tkttagparse(tk, &arg, &t); + if(e != nil) + return e; + e = tktindparse(tk, &arg, &i1); + if(e != nil) + return e; + if(*arg != '\0') { + e = tktindparse(tk, &arg, &i2); + if(e != nil) + return e; + } + else + tktstartind(tkt, &i2); + + if(tkttagprange(tkt, t->id, &i1, &i2, &istart, &iend)) + return tkvalue(val, "%d.%d %d.%d", + tktlinenum(tkt, &istart), tktlinepos(tkt, &istart), + tktlinenum(tkt, &iend), tktlinepos(tkt, &iend)); + + return nil; +} + +static char* +tkttagraise(Tk *tk, char *arg, char **val) +{ + TkText *tkt; + TkTindex ix; + TkTtaginfo *t, *tabove, *f, **l; + char *e; + + USED(val); + + tkt = TKobj(TkText, tk); + + e = tkttagparse(tk, &arg, &t); + if(e != nil) + return e; + + if(*arg != '\0') { + e = tkttagparse(tk, &arg, &tabove); + if(e != nil) + return e; + } + else + tabove = nil; + + tktunlinktag(tkt, t); + + if(tabove != nil) { + l = &tkt->tags; + for(f = *l; f != nil; f = f->next) { + if(f == tabove) { + *l = t; + t->next = tabove; + break; + } + l = &f->next; + } + } + else { + t->next = tkt->tags; + tkt->tags = t; + } + + if(tkttagind(tk, t->name, 1, &ix)) { + tktfixgeom(tk, tktprevwrapline(tk, ix.line), tkt->end.prev, 0); + tktextsize(tk, 1); + } + return nil; +} + +static char* +tkttagranges(Tk *tk, char *arg, char **val) +{ + char *e, *fmt; + TkTtaginfo *t; + TkTindex i1, i2, istart, iend; + TkText *tkt = TKobj(TkText, tk); + + e = tkttagparse(tk, &arg, &t); + if(e != nil) + return e; + + tktstartind(tkt, &i1); + tktendind(tkt, &i2); + + fmt = "%d.%d %d.%d"; + while(tkttagnrange(tkt, t->id, &i1, &i2, &istart, &iend)) { + e = tkvalue(val, fmt, + tktlinenum(tkt, &istart), tktlinepos(tkt, &istart), + tktlinenum(tkt, &iend), tktlinepos(tkt, &iend)); + if(e != nil) + return e; + + fmt = " %d.%d %d.%d"; + i1 = iend; + } + + return nil; +} + +static char* +tkttagremove(Tk *tk, char *arg, char **val) +{ + USED(val); + + return tkttagaddrem(tk, arg, 0); +} diff --git a/libtk/twind.c b/libtk/twind.c new file mode 100644 index 00000000..49b9c94b --- /dev/null +++ b/libtk/twind.c @@ -0,0 +1,396 @@ +#include "lib9.h" +#include "draw.h" +#include "tk.h" +#include "textw.h" + +#define istring u.string +#define iwin u.win +#define imark u.mark +#define iline u.line + +#define O(t, e) ((long)(&((t*)0)->e)) + +static char* tktwincget(Tk*, char*, char**); +static char* tktwinconfigure(Tk*, char*, char**); +static char* tktwincreate(Tk*, char*, char**); +static char* tktwinnames(Tk*, char*, char**); +static int winowned(Tk *tk, Tk *sub); + +static +TkStab tkalign[] = +{ + "top", Tktop, + "bottom", Tkbottom, + "center", Tkcenter, + "baseline", Tkbaseline, + nil +}; + +static +TkOption twinopts[] = +{ + "align", OPTstab, O(TkTwind, align), tkalign, + "create", OPTtext, O(TkTwind, create), nil, + "padx", OPTnndist, O(TkTwind, padx), nil, + "pady", OPTnndist, O(TkTwind, pady), nil, + "stretch", OPTstab, O(TkTwind, stretch), tkbool, + "window", OPTwinp, O(TkTwind, sub), nil, + "ascent", OPTdist, O(TkTwind, ascent), nil, + nil +}; + +TkCmdtab +tktwincmd[] = +{ + "cget", tktwincget, + "configure", tktwinconfigure, + "create", tktwincreate, + "names", tktwinnames, + nil +}; + +int +tktfindsubitem(Tk *sub, TkTindex *ix) +{ + Tk *tk, *isub; + TkText *tkt; + + tk = sub->parent; + if(tk != nil) { + tkt = TKobj(TkText, tk); + tktstartind(tkt, ix); + do { + if(ix->item->kind == TkTwin) { + isub = ix->item->iwin->sub; + if(isub != nil && + isub->name != nil && + strcmp(isub->name->name, sub->name->name) == 0) + return 1; + } + } while(tktadjustind(tkt, TkTbyitem, ix)); + } + return 0; +} + +static void +tktwindsize(Tk *tk, TkTindex *ix) +{ + Tk *s; + TkTitem *i; + TkTwind *w; + + + i = ix->item; + /* assert(i->kind == TkTwin); */ + + w = i->iwin; + s = w->sub; + if(s == nil) + return; + + if(w->width != s->act.width || w->height != s->act.height) { + s->act.width = w->width; + s->act.height = w->height; + if(s->slave) { + tkpackqit(s); + tkrunpack(tk->env->top); + } + } + + tktfixgeom(tk, tktprevwrapline(tk, ix->line), ix->line, 0); + tktextsize(tk, 1); +} + +/* + * check that w->focus is a window packed under tk. + * XXX couldn't this be done more simply by traversing + * directly upwards from w->focus and seeing whether + * it hits tk? (same applies to tkcvschkwfocus in cwind.c) + */ +static int +tktchkwfocus(TkTwind *w, Tk *tk) +{ + if(w->focus == tk) + return 1; + for(tk = tk->slave; tk; tk = tk->next) + if(tktchkwfocus(w, tk)) + return 1; + return 0; +} + +static void +tktwingeom(Tk *sub, int x, int y, int w, int h) +{ + TkTindex ix; + Tk *tk; + TkTwind *win; + + USED(x); + USED(y); + + tk = sub->parent; + if(!tktfindsubitem(sub, &ix)) { + print("tktwingeom: %s not found\n", sub->name->name); + return; + } + + win = ix.item->iwin; + + if(win->focus != nil) { + if(tktchkwfocus(win, sub) == 0) + win->focus = nil; + } + + win->width = w; + win->height = h; + + sub->req.width = w; + sub->req.height = h; + tktwindsize(tk, &ix); +} + +static void +tktdestroyed(Tk *sub) +{ + TkTindex ix; + Tk *tk; + + if(tktfindsubitem(sub, &ix)) { + ix.item->iwin->sub = nil; + ix.item->iwin->focus = nil; + if((tk = sub->parent) != nil) { + tktfixgeom(tk, tktprevwrapline(tk, ix.line), ix.line, 0); + tktextsize(tk, 1); + sub->parent = nil; + } + } +} + +void +tktdirty(Tk *sub) +{ + Tk *tk, *parent, *isub; + TkText *tkt; + TkTindex ix; + + parent = nil; + for(tk = sub; tk && parent == nil; tk = tk->master) + parent = tk->parent; + if(tk == nil) + return; + + tkt = TKobj(TkText, parent); + tktstartind(tkt, &ix); + do { + if(ix.item->kind == TkTwin) { + isub = ix.item->iwin->sub; + if(isub != nil) { + tktfixgeom(parent, tktprevwrapline(parent, ix.line), ix.line, 0); + if (sub->flag & Tktransparent) + parent->flag |= Tkrefresh; /* XXX could be more efficient, by drawing the background locally? */ + return; + } + } + } while(tktadjustind(tkt, TkTbyitem, &ix)); + tktextsize(parent, 1); +} + +static char* +tktwinchk(Tk *tk, TkTwind *w, Tk *oldsub) +{ + Tk *sub; + + sub = w->sub; + if (sub != oldsub) { + w->sub = oldsub; + if(sub == nil) + return nil; + + if(sub->flag & Tkwindow) + return TkIstop; + + if(sub->master != nil || sub->parent != nil) + return TkWpack; + + if (oldsub != nil) { + oldsub->parent = nil; + oldsub->geom = nil; + oldsub->destroyed = nil; + } + w->sub = sub; + w->focus = nil; + + sub->parent = tk; + tksetbits(sub, Tksubsub); + sub->geom = tktwingeom; + sub->destroyed = tktdestroyed; + + w->width = sub->req.width; + w->height = sub->req.height; + w->owned = winowned(tk, sub); + } + + return nil; +} + + +/* Text Window Command (+ means implemented) + +cget + +configure + +create + +names +*/ + +static char* +tktwincget(Tk *tk, char *arg, char **val) +{ + char *e; + TkTindex ix; + TkOptab tko[2]; + + e = tktindparse(tk, &arg, &ix); + if(e != nil) + return e; + if(ix.item->kind != TkTwin) + return TkBadwp; + + tko[0].ptr = ix.item->iwin; + tko[0].optab = twinopts; + tko[1].ptr = nil; + + return tkgencget(tko, arg, val, tk->env->top); +} + +static char* +tktwinconfigure(Tk *tk, char *arg, char **val) +{ + char *e; + TkTindex ix; + TkOptab tko[2]; + Tk *oldsub; + + USED(val); + + e = tktindparse(tk, &arg, &ix); + if(e != nil) + return e; + if(ix.item->kind != TkTwin) + return TkBadwp; + + oldsub = ix.item->iwin->sub; + + tko[0].ptr = ix.item->iwin; + tko[0].optab = twinopts; + tko[1].ptr = nil; + + e = tkparse(tk->env->top, arg, tko, nil); + if(e != nil) + return e; + + e = tktwinchk(tk, ix.item->iwin, oldsub); + if(e != nil) + return e; + + tktwindsize(tk, &ix); + return nil; +} + +/* + * return true if tk is an ancestor of sub + */ +static int +winowned(Tk *tk, Tk *sub) +{ + int len; + if (tk->name == nil || sub->name == nil) + return 0; + len = strlen(tk->name->name); + if (strncmp(tk->name->name, sub->name->name, len) == 0 && + sub->name->name[len] == '.') + return 1; + return 0; +} + +static char* +tktwincreate(Tk *tk, char *arg, char **val) +{ + char *e; + TkTindex ix; + TkTitem *i; + TkText *tkt; + TkOptab tko[2]; + + USED(val); + + tkt = TKobj(TkText, tk); + + e = tktindparse(tk, &arg, &ix); + if(e != nil) + return e; + + e = tktnewitem(TkTwin, 0, &i); + if(e != nil) + return e; + + i->iwin = malloc(sizeof(TkTwind)); + if(i->iwin == nil) { + tktfreeitems(tkt, i, 1); + return TkNomem; + } + + memset(i->iwin, 0, sizeof(TkTwind)); + i->iwin->align = Tkcenter; + i->iwin->ascent = -1; + + tko[0].ptr = i->iwin; + tko[0].optab = twinopts; + tko[1].ptr = nil; + + e = tkparse(tk->env->top, arg, tko, nil); + if(e != nil) { + err1: + tktfreeitems(tkt, i, 1); + return e; + } + + e = tktwinchk(tk, i->iwin, nil); + if(e != nil) + goto err1; + + e = tktsplititem(&ix); + if(e != nil) + goto err1; + + tktiteminsert(tkt, &ix, i); + if(e != nil) + goto err1; + + tktadjustind(tkt, TkTbyitemback, &ix); + tktwindsize(tk, &ix); + + return nil; +} + +static char* +tktwinnames(Tk *tk, char *arg, char **val) +{ + char *e, *fmt; + TkTindex ix; + TkText *tkt = TKobj(TkText, tk); + + USED(arg); + + tktstartind(tkt, &ix); + fmt = "%s"; + do { + if(ix.item->kind == TkTwin + && ix.item->iwin->sub != nil + && (ix.item->iwin->sub->name != nil)) { + e = tkvalue(val, fmt, ix.item->iwin->sub->name->name); + if(e != nil) + return e; + fmt = " %s"; + } + } while(tktadjustind(tkt, TkTbyitem, &ix)); + return nil; +} diff --git a/libtk/utils.c b/libtk/utils.c new file mode 100644 index 00000000..4e86e40c --- /dev/null +++ b/libtk/utils.c @@ -0,0 +1,1951 @@ +#include "lib9.h" +#include "draw.h" +#include "tk.h" + +extern void rptwakeup(void*, void*); +extern void* rptproc(char*, int, void*, int (*)(void*), int (*)(void*,int), void (*)(void*)); + +typedef struct Cmd Cmd; +struct Cmd +{ + char* name; + char* (*fn)(TkTop*, char*, char**); +}; +static struct Cmd cmdmain[] = +{ + "bind", tkbind, + "button", tkbutton, + "canvas", tkcanvas, + "checkbutton", tkcheckbutton, + "choicebutton", tkchoicebutton, + "cursor", tkcursorcmd, + "destroy", tkdestroy, + "entry", tkentry, + "focus", tkfocus, + "frame", tkframe, + "grab", tkgrab, + "grid", tkgrid, + "image", tkimage, + "label", tklabel, + "listbox", tklistbox, + "lower", tklower, + "menu", tkmenu, + "menubutton", tkmenubutton, + "pack", tkpack, + "panel", tkpanel, + "puts", tkputs, + "radiobutton", tkradiobutton, + "raise", tkraise, + "scale", tkscale, + "scrollbar", tkscrollbar, + "see", tkseecmd, + "send", tksend, + "text", tktext, + "update", tkupdatecmd, + "variable", tkvariable, + "winfo", tkwinfo, +}; + +char* tkfont; + +/* auto-repeating support + * should perhaps be one rptproc per TkCtxt + * This is not done for the moment as there isn't + * a mechanism for terminating the rptproc + */ +static void *autorpt; +static int rptid; +static Tk *rptw; +static void *rptnote; +static void (*rptcb)(Tk*, void*, int); +static long rptto; +static int rptint; + +/* blinking carets - should be per TkCtxt */ +static void *blinkrpt; +static Tk *blinkw; +static void (*blinkcb)(Tk*, int); +static int blinkignore; +static int blinkon; + + +ulong +tkrgba(int r, int g, int b, int a) +{ + ulong p; + + if(r < 0) + r = 0; + else if(r > 255) + r = 255; + if(g < 0) + g = 0; + else if(g > 255) + g = 255; + if(b < 0) + b = 0; + else if(b > 255) + b = 255; + p = (r<<24)|(g<<16)|(b<<8)|0xFF; + if(a == 255) + return p; + return setalpha(p, a); +} + +/* to be replaced */ +static int +revalpha(int c, int a) +{ + if (a == 0) + return 0; + return (c & 0xff) * 255 / a; +} + +void +tkrgbavals(ulong rgba, int *R, int *G, int *B, int *A) +{ + int a; + + a = rgba & 0xff; + *A = a; + if (a != 0xff) { + *R = revalpha(rgba>>24, a); + *G = revalpha((rgba>>16) & 0xFF, a); + *B = revalpha((rgba >> 8) & 0xFF, a); + } else { + *R = (rgba>>24); + *G = ((rgba>>16) & 0xFF); + *B = ((rgba >> 8) & 0xFF); + } +} + +void +tkfreecolcache(TkCtxt *c) +{ + TkCol *cc; + + if(c == nil) + return; + while((cc = c->chead) != nil){ + c->chead = cc->forw; + freeimage(cc->i); + free(cc); + } + c->ctail = nil; + c->ncol = 0; +} + +Image* +tkcolor(TkCtxt *c, ulong pix) +{ + Image *i; + TkCol *cc, **l; + Display *d; + Rectangle r; + + for(l = &c->chead; (cc = *l) != nil; l = &cc->forw) + if(cc->rgba == pix){ + /* move it up in the list */ + *l = cc->forw; + cc->forw = c->chead; + c->chead = cc; + /* we assume it will be used right away and not stored */ + return cc->i; + } + d = c->display; + if(pix == DWhite) + return d->white; + if(pix == DBlack) + return d->black; + r.min = ZP; + r.max.x = 1; + r.max.y = 1; + if ((pix & 0xff) == 0xff) + i = allocimage(d, r, RGB24, 1, pix); + else + i = allocimage(d, r, RGBA32, 1, pix); + if(i == nil) + return d->black; + cc = malloc(sizeof(*cc)); + if(cc == nil){ + freeimage(i); + return d->black; + } + cc->rgba = pix; + cc->i = i; + cc->forw = c->chead; + c->chead = cc; + c->ncol++; + /* we'll do LRU management at some point */ + if(c->ncol > TkColcachesize){ + static int warn; + if(warn == 0){ + warn = 1; + print("tk: %d colours cached\n", TkColcachesize); + } + } + return i; +} + +/* + * XXX should be in libdraw? + */ +int +tkchanhastype(ulong c, int t) +{ + for(; c; c>>=8) + if(TYPE(c) == t) + return 1; + return 0; +} + +void +tksettransparent(Tk *tk, int transparent) +{ + if (transparent) + tk->flag |= Tktransparent; + else + tk->flag &= ~Tktransparent; +} + +int +tkhasalpha(TkEnv *e, int col) +{ + return (e->colors[col] & 0xff) != 0xff; +} + +Image* +tkgc(TkEnv *e, int col) +{ + return tkcolor(e->top->ctxt, e->colors[col]); +} + + +/* + * Todo: improve the fixed-point code + * the 255 scale factor is used because RGB ranges 0-255 + */ +static void +rgb2hsv(int r, int g, int b, int *h, int *s, int *v) +{ + int min, max, delta; + + max = r; + if(g > max) + max = g; + if(b > max) + max = b; + min = r; + if(g < min) + min = g; + if(b < min) + min = b; + *v = max; + if (max != 0) + *s = ((max - min)*255) / max; + else + *s = 0; + + if (*s == 0) { + *h = 0; /* undefined */ + } else { + delta = max - min; + if (r == max) + *h = (g - b)*255 / delta; + else if (g == max) + *h = (2*255) + ((b - r)*255) / delta; + else if (b == max) + *h = (4*255) + ((r - g)*255)/ delta; + *h *= 60; + if (*h < 0) + *h += 360*255; + *h /= 255; + } +} + +static void +hsv2rgb(int h, int s, int v, int *r, int *g, int *b) +{ + int i; + int f,p,q,t; + + if (s == 0 && h == 0) { + *r = *g = *b = v; /* achromatic case */ + } else { + if (h >= 360) + h = 0; + i = h / 60; + h *= 255; + h /= 60; + + f = h % 255; + p = v * (255 - s); + q = v * (255 - ((s * f)/255)); + t = v * (255- ((s * (255 - f))/255)); + p /= 255; + q /= 255; + t /= 255; + switch (i) { + case 0: *r = v; *g = t; *b = p; break; + case 1: *r = q; *g = v; *b = p; break; + case 2: *r = p; *g = v; *b = t; break; + case 3: *r = p; *g = q; *b = v; break; + case 4: *r = t; *g = p; *b = v; break; + case 5: *r = v; *g = p; *b = q; break; + } + } +} + +enum { + MINDELTA = 0x10, + DELTA = 0x30, +}; + +ulong +tkrgbashade(ulong rgba, int shade) +{ + int R, G, B, A, h, s, v, vl, vd; + + if (shade == TkSameshade) + return rgba; + + tkrgbavals(rgba, &R, &G, &B, &A); + rgb2hsv(R, G, B, &h, &s, &v); + + if (v < MINDELTA) { + vd = v+DELTA; + vl = vd+DELTA; + } else if (v > 255-MINDELTA) { + vl = v-DELTA; + vd = vl-DELTA; + } else { + vl = v+DELTA; + vd = v-DELTA; + } + + v = (shade == TkLightshade)?vl:vd; + if (v < 0) + v = 0; + if (v > 255) + v = 255; + hsv2rgb(h, s, v, &R, &G, &B); + + return tkrgba(R, G, B, A); +} + +Image* +tkgshade(TkEnv *e, int col, int shade) +{ + ulong rgba; + + if (col == TkCbackgnd || col == TkCselectbgnd || col == TkCactivebgnd) + return tkgc(e, col+shade); + rgba = tkrgbashade(e->colors[col], shade); + return tkcolor(e->top->ctxt, rgba); +} + +TkEnv* +tknewenv(TkTop *t) +{ + TkEnv *e; + + e = malloc(sizeof(TkEnv)); + if(e == nil) + return nil; + + e->ref = 1; + e->top = t; + return e; +} + +TkEnv* +tkdefaultenv(TkTop *t) +{ + int locked; + TkEnv *env; + Display *d; + + if(t->env != nil) { + t->env->ref++; + return t->env; + } + t->env = malloc(sizeof(TkEnv)); + if(t->env == nil) + return nil; + + env = t->env; + env->ref = 1; + env->top = t; + + if(tkfont == nil) + tkfont = "/fonts/pelm/unicode.8.font"; + + d = t->display; + env->font = font_open(d, tkfont); + if(env->font == nil) { + static int warn; + if(warn == 0) { + warn = 1; + print("tk: font not found: %s\n", tkfont); + } + env->font = font_open(d, "*default*"); + if(env->font == nil) { + free(t->env); + t->env = nil; + return nil; + } + } + + locked = lockdisplay(d); + env->wzero = stringwidth(env->font, "0"); + if ( env->wzero <= 0 ) + env->wzero = env->font->height / 2; + if(locked) + unlockdisplay(d); + + tksetenvcolours(env); + return env; +} + +void +tkputenv(TkEnv *env) +{ + Display *d; + int locked; + + if(env == nil) + return; + + env->ref--; + if(env->ref != 0) + return; + + d = env->top->display; + locked = lockdisplay(d); + + if(env->font != nil) + font_close(env->font); + + if(locked) + unlockdisplay(d); + + free(env); +} + +TkEnv* +tkdupenv(TkEnv **env) +{ + Display *d; + TkEnv *e, *ne; + + e = *env; + if(e->ref == 1) + return e; + + ne = malloc(sizeof(TkEnv)); + if(ne == nil) + return nil; + + ne->ref = 1; + ne->top = e->top; + + d = e->top->display; + memmove(ne->colors, e->colors, sizeof(e->colors)); + ne->set = e->set; + ne->font = font_open(d, e->font->name); + ne->wzero = e->wzero; + + e->ref--; + *env = ne; + return ne; +} + +Tk* +tknewobj(TkTop *t, int type, int n) +{ + Tk *tk; + + tk = malloc(n); + if(tk == 0) + return 0; + + tk->type = type; /* Defaults */ + tk->flag = Tktop; + tk->relief = TKflat; + tk->env = tkdefaultenv(t); + if(tk->env == nil) { + free(tk); + return nil; + } + + return tk; +} + +void +tkfreebind(TkAction *a) +{ + TkAction *next; + + while(a != nil) { + next = a->link; + if((a->type & 0xff) == TkDynamic) + free(a->arg); + free(a); + a = next; + } +} + +void +tkfreename(TkName *f) +{ + TkName *n; + + while(f != nil) { + n = f->link; + free(f); + f = n; + } +} + +void +tkfreeobj(Tk *tk) +{ + TkCtxt *c; + + c = tk->env->top->ctxt; + if(c != nil) { + if(c->tkkeygrab == tk) + c->tkkeygrab = nil; + if(c->mgrab == tk) + tksetmgrab(tk->env->top, nil); + if(c->mfocus == tk) + c->mfocus = nil; + if(c->entered == tk) + c->entered = nil; + } + + if (tk == rptw) { + /* cancel the autorepeat without notifying the widget */ + rptid++; + rptw = nil; + } + if (tk == blinkw) + blinkw = nil; + tkextnfreeobj(tk); + tkmethod[tk->type]->free(tk); + tkputenv(tk->env); + tkfreebind(tk->binds); + if(tk->name != nil) + free(tk->name); + free(tk); +} + +char* +tkaddchild(TkTop *t, Tk *tk, TkName **names) +{ + TkName *n; + Tk *f, **l; + int found, len; + char *s, *ep; + + n = *names; + if(n == nil || n->name[0] != '.'){ + if(n != nil) + tkerr(t, n->name); + return TkBadwp; + } + + if (n->name[1] == '\0') + return TkDupli; + + /* + * check that the name is well-formed. + * ep will point to end of parent component of the name. + */ + ep = nil; + for (s = n->name + 1; *s; s++) { + if (*s == '.'){ + tkerr(t, n->name); + return TkBadwp; + } + for (; *s && *s != '.'; s++) + ; + if (*s == '\0') + break; + ep = s; + } + if (ep == s - 1){ + tkerr(t, n->name); + return TkBadwp; + } + if (ep == nil) + ep = n->name + 1; + len = ep - n->name; + + found = 0; + l = &t->root; + for(f = *l; f; f = f->siblings) { + if (f->name != nil) { + if (strcmp(n->name, f->name->name) == 0) + return TkDupli; + if (!found && + strncmp(n->name, f->name->name, len) == 0 && + f->name->name[len] == '\0') + found = 1; + } + l = &f->siblings; + } + if (0) { /* don't enable this until a reasonably major release... if ever */ + /* + * parent widget must already exist + */ + if (!found){ + tkerr(t, n->name); + return TkBadwp; + } + } + *l = tk; + tk->name = n; + *names = n->link; + + return nil; +} + +Tk* +tklook(TkTop *t, char *wp, int parent) +{ + Tk *f; + char *p, *q; + + if(wp == nil) + return nil; + + if(parent) { + p = strdup(wp); + if(p == nil) + return nil; + q = strrchr(p, '.'); + if(q == nil) + abort(); + if(q == p) { + free(p); + return t->root; + } + *q = '\0'; + } else + p = wp; + + for(f = t->root; f; f = f->siblings) + if ((f->name != nil) && (strcmp(f->name->name, p) == 0)) + break; + + if(f != nil && (f->flag & Tkdestroy)) + f = nil; + + if (parent) + free(p); + return f; +} + +void +tktextsdraw(Image *img, Rectangle r, TkEnv *e, int sbw) +{ + Image *l, *d; + Rectangle s; + + draw(img, r, tkgc(e, TkCselectbgnd), nil, ZP); + s.min = r.min; + s.min.x -= sbw; + s.min.y -= sbw; + s.max.x = r.max.x; + s.max.y = r.min.y; + l = tkgc(e, TkCselectbgndlght); + draw(img, s, l, nil, ZP); + s.max.x = s.min.x + sbw; + s.max.y = r.max.y + sbw; + draw(img, s, l, nil, ZP); + s.max = r.max; + s.max.x += sbw; + s.max.y += sbw; + s.min.x = r.min.x; + s.min.y = r.max.y; + d = tkgc(e, TkCselectbgnddark); + draw(img, s, d, nil, ZP); + s.min.x = r.max.x; + s.min.y = r.min.y - sbw; + draw(img, s, d, nil, ZP); +} + +void +tkbox(Image *i, Rectangle r, int bd, Image *fill) +{ + if (bd > 0) { + draw(i, Rect(r.min.x, r.min.y, r.max.x, r.min.y+bd), fill, nil, ZP); + draw(i, Rect(r.min.x, r.min.y+bd, r.min.x+bd, r.max.y-bd), fill, nil, ZP); + draw(i, Rect(r.min.x, r.max.y-bd, r.max.x, r.max.y), fill, nil, ZP); + draw(i, Rect(r.max.x-bd, r.min.y+bd, r.max.x, r.max.y), fill, nil, ZP); + } +} + +void +tkbevel(Image *i, Point o, int w, int h, int bw, Image *top, Image *bottom) +{ + Rectangle r; + int x, border; + + border = 2 * bw; + + r.min = o; + r.max.x = r.min.x + w + border; + r.max.y = r.min.y + bw; + draw(i, r, top, nil, ZP); + + r.max.x = r.min.x + bw; + r.max.y = r.min.y + h + border; + draw(i, r, top, nil, ZP); + + r.max.x = o.x + w + border; + r.max.y = o.y + h + border; + r.min.x = o.x + bw; + r.min.y = r.max.y - bw; + for(x = 0; x < bw; x++) { + draw(i, r, bottom, nil, ZP); + r.min.x--; + r.min.y++; + } + r.min.x = o.x + bw + w; + r.min.y = o.y + bw; + for(x = bw; x >= 0; x--) { + draw(i, r, bottom, nil, ZP); + r.min.x++; + r.min.y--; + } +} + +/* + * draw a relief border. + * color is an index into tk->env->colors and assumes + * light and dark versions following immediately after + * that index + */ +void +tkdrawrelief(Image *i, Tk *tk, Point o, int color, int rlf) +{ + TkEnv *e; + Image *l, *d, *t; + int h, w, bd, bd1, bd2; + + if(tk->borderwidth == 0) + return; + + h = tk->act.height; + w = tk->act.width; + + e = tk->env; + if (color == TkCbackgnd || color == TkCselectbgnd || color == TkCactivebgnd) { + l = tkgc(e, color+TkLightshade); + d = tkgc(e, color+TkDarkshade); + } else { + l = tkgshade(e, color, TkLightshade); + d = tkgshade(e, color, TkDarkshade); + } + bd = tk->borderwidth; + if(rlf < 0) + rlf = TKraised; + switch(rlf) { + case TKflat: + break; + case TKsunken: + tkbevel(i, o, w, h, bd, d, l); + break; + case TKraised: + tkbevel(i, o, w, h, bd, l, d); + break; + case TKgroove: + t = d; + d = l; + l = t; + /* fall through */ + case TKridge: + bd1 = bd/2; + bd2 = bd - bd1; + if(bd1 > 0) + tkbevel(i, o, w + 2*bd2, h + 2*bd2, bd1, l, d); + o.x += bd1; + o.y += bd1; + tkbevel(i, o, w, h, bd2, d, l); + break; + } +} + +Point +tkstringsize(Tk *tk, char *text) +{ + char *q; + int locked; + Display *d; + Point p, t; + + if(text == nil) { + p.x = 0; + p.y = tk->env->font->height; + return p; + } + + d = tk->env->top->display; + locked = lockdisplay(d); + + p = ZP; + while(*text) { + q = strchr(text, '\n'); + if(q != nil) + *q = '\0'; + t = stringsize(tk->env->font, text); + p.y += t.y; + if(p.x < t.x) + p.x = t.x; + if(q == nil) + break; + text = q+1; + *q = '\n'; + } + if(locked) + unlockdisplay(d); + + return p; +} + +static char* +tkul(Image *i, Point o, Image *col, int ul, Font *f, char *text) +{ + char c, *v; + Rectangle r; + + v = text+ul+1; + c = *v; + *v = '\0'; + r.max = stringsize(f, text); + r.max = addpt(r.max, o); + r.min = stringsize(f, v-1); + *v = c; + r.min.x = r.max.x - r.min.x; + r.min.y = r.max.y - 1; + r.max.y += 2; + draw(i, r, col, nil, ZP); + + return nil; +} + +char* +tkdrawstring(Tk *tk, Image *i, Point o, char *text, int ul, Image *col, int j) +{ + int n, l, maxl, sox; + char *q, *txt; + Point p; + TkEnv *e; + + e = tk->env; + sox = maxl = 0; + if(j != Tkleft){ + maxl = 0; + txt = text; + while(*txt){ + q = strchr(txt, '\n'); + if(q != nil) + *q = '\0'; + l = stringwidth(e->font, txt); + if(l > maxl) + maxl = l; + if(q == nil) + break; + txt = q+1; + *q = '\n'; + } + sox = o.x; + } + while(*text) { + q = strchr(text, '\n'); + if(q != nil) + *q = '\0'; + if(j != Tkleft){ + o.x = sox; + l = stringwidth(e->font, text); + if(j == Tkcenter) + o.x += (maxl-l)/2; + else + o.x += maxl-l; + } + p = string(i, o, col, o, e->font, text); + if(ul >= 0) { + n = strlen(text); + if(ul < n) { + char *r; + + r = tkul(i, o, col, ul, e->font, text); + if(r != nil) + return r; + ul = -1; + } + ul -= n; + } + o.y += e->font->height; + if(q == nil) + break; + text = q+1; + *q = '\n'; + } + return nil; +} + +/* for debugging */ +char* +tkname(Tk *tk) +{ + return tk ? (tk->name ? tk->name->name : "(noname)") : "(nil)"; +} + +Tk* +tkdeliver(Tk *tk, int event, void *data) +{ + Tk *dest; +//print("tkdeliver %v to %s\n", event, tkname(tk)); + if(tk == nil || ((tk->flag&Tkdestroy) && event != TkDestroy)) + return tk; + + if(event&(TkFocusin|TkFocusout) && (tk->flag&Tktakefocus)) + tk->dirty = tkrect(tk, 1); + + if (tkmethod[tk->type]->deliver != nil) { + dest = tkmethod[tk->type]->deliver(tk, event, data); + if (dest == nil) + return tk; + tkdirty(tk); + return dest; + } + + if((tk->flag & Tkdisabled) == 0) + tksubdeliver(tk, tk->binds, event, data, 0); + tkdirty(tk); + return tk; +} + +static int +nullop(char *fmt, ...) +{ + USED(fmt); + return 0; +} + +int +tksubdeliver(Tk *tk, TkAction *binds, int event, void *data, int extn) +{ + + TkAction *a; + int delivered, genkey, delivered2, iskey; +//int (*debug)(char *fmt, ...); + if (!extn) + return tkextndeliver(tk, binds, event, data); + +//debug = (tk->name && !strcmp(tk->name->name, ".cd")) ? print : nullop; +//debug("subdeliver %v\n", event); + + if (event & TkTakefocus) { + if (tk->flag & Tktakefocus) + tksetkeyfocus(tk->env->top, tk, 0); + return TkDdelivered; + } + + delivered = TkDnone; + genkey = 0; + for(a = binds; a != nil; a = a->link) { + if(event == a->event) { +//debug(" exact match on %v\n", a->event); + tkcmdbind(tk, event, a->arg, data); + delivered = TkDdelivered; + } else if (a->event == TkKey && (a->type>>8)==TkAadd) + genkey = 1; + } + if(delivered != TkDnone && !((event & TkKey) && genkey)) + return delivered; + + delivered2 = delivered; + for(a = binds; a != nil; a = a->link) { + /* + * only bind to non-specific key events; if a specific + * key event has already been delivered, only deliver event if + * the non-specific binding was added. (TkAadd) + */ + if (a->event & TkExtns) + continue; + iskey = (a->event & TkKey); + if (iskey ^ (event & TkKey)) + continue; + if(iskey && (TKKEY(a->event) != 0 + || ((a->type>>8) != TkAadd && delivered != TkDnone))) + continue; + if(!iskey && (a->event & TkMotion) && (a->event&TkEpress) != 0) + continue; + if(!(event & TkDouble) && (a->event & TkDouble)) + continue; + if((event & ~TkDouble) & a->event) { +//debug(" partial match on %v\n", a->event); + tkcmdbind(tk, event, a->arg, data); + delivered2 = TkDdelivered; + } + } + return delivered2; +} + +void +tkcancel(TkAction **l, int event) +{ + TkAction *a; + + for(a = *l; a; a = *l) { + if(a->event == event) { + *l = a->link; + a->link = nil; + tkfreebind(a); + continue; + } + l = &a->link; + } +} + +static void +tkcancela(TkAction **l, int event, int type, char *arg) +{ + TkAction *a; + + for(a = *l; a; a = *l) { + if(a->event == event && strcmp(a->arg, arg) == 0 && (a->type&0xff) == type){ + *l = a->link; + a->link = nil; + tkfreebind(a); + continue; + } + l = &a->link; + } +} + +char* +tkaction(TkAction **l, int event, int type, char *arg, int how) +{ + TkAction *a; + + if(arg == nil) + return nil; + if(how == TkArepl) + tkcancel(l, event); + else if(how == TkAadd){ + for(a = *l; a; a = a->link) + if(a->event == event && strcmp(a->arg, arg) == 0 && (a->type&0xff) == type){ + a->type = type + (how << 8); + return nil; + } + } + else if(how == TkAsub){ + tkcancela(l, event, type, arg); + if(type == TkDynamic) /* should always be the case */ + free(arg); + return nil; + } + + a = malloc(sizeof(TkAction)); + if(a == nil) { + if(type == TkDynamic) + free(arg); + return TkNomem; + } + + a->event = event; + a->arg = arg; + a->type = type + (how << 8); + + a->link = *l; + *l = a; + + return nil; +} + +char* +tkitem(char *buf, char *a) +{ + char *e; + + while(*a && (*a == ' ' || *a == '\t')) + a++; + + e = buf + Tkmaxitem - 1; + while(*a && *a != ' ' && *a != '\t' && buf < e) + *buf++ = *a++; + + *buf = '\0'; + while(*a && (*a == ' ' || *a == '\t')) + a++; + return a; +} + +int +tkismapped(Tk *tk) +{ + while(tk->master) + tk = tk->master; + + /* We need subwindows of text & canvas to appear mapped always + * so that the geom function update are seen by the parent + * widget + */ + if((tk->flag & Tkwindow) == 0) + return 1; + + return tk->flag & Tkmapped; +} + +/* + * Return absolute screen position of tk (just outside its top-left border). + * When a widget is embedded in a text or canvas widget, we need to + * use the text or canvas's relpos() function instead of act{x,y}, and we + * need to folow up the parent pointer rather than the master one. + */ +Point +tkposn(Tk *tk) +{ + Tk *f, *last; + Point g; + + last = tk; + if(tk->parent != nil) { + g = tkmethod[tk->parent->type]->relpos(tk); + f = tk->parent; + } + else { + g.x = tk->act.x; + g.y = tk->act.y; + f = tk->master; + } + while(f) { + g.x += f->borderwidth; + g.y += f->borderwidth; + last = f; + if(f->parent != nil) { + g = addpt(g, tkmethod[f->parent->type]->relpos(f)); + f = f->parent; + } + else { + g.x += f->act.x; + g.y += f->act.y; + f = f->master; + } + } + if (last->flag & Tkwindow) + g = addpt(g, TKobj(TkWin, last)->req); + return g; +} + +/* + * convert screen coords to local widget coords + */ +Point +tkscrn2local(Tk *tk, Point p) +{ + p = subpt(p, tkposn(tk)); + p.x -= tk->borderwidth; + p.y -= tk->borderwidth; + return p; +} + +int +tkvisiblerect(Tk *tk, Rectangle *rr) +{ + Rectangle r; + Point g; + Tk *f, *last; + g = Pt(tk->borderwidth, tk->borderwidth); + last = tk; + if(tk->parent != nil) { + g = addpt(g, tkmethod[tk->parent->type]->relpos(tk)); + f = tk->parent; + } else { + g.x += tk->act.x; + g.y += tk->act.y; + f = tk->master; + } + if (f == nil) { + *rr = tkrect(tk, 1); + return 1; + } + r = rectaddpt(tkrect(tk, 1), g); + while (f) { + if (!rectclip(&r, tkrect(f, 0))) + return 0; + g.x = f->borderwidth; + g.y = f->borderwidth; + last = f; + if (f->parent != nil) { + g = addpt(g, tkmethod[f->parent->type]->relpos(f)); + f = f->parent; + } else { + g.x += f->act.x; + g.y += f->act.y; + f = f->master; + } + r = rectaddpt(r, g); + } + if (last->flag & Tkwindow) + r = rectaddpt(r, TKobj(TkWin, last)->act); + /* + * now we have the visible rectangle in screen coords; + * subtract actx+borderwidth and we've got it back in + * widget-local coords again + */ + r = rectsubpt(r, tkposn(tk)); + *rr = rectsubpt(r, Pt(tk->borderwidth, tk->borderwidth)); + return 1; +} + +Point +tkanchorpoint(Rectangle r, Point size, int anchor) +{ + int dx, dy; + Point p; + + p = r.min; + dx = Dx(r) - size.x; + dy = Dy(r) - size.y; + if((anchor & (Tknorth|Tksouth)) == 0) + p.y += dy/2; + else + if(anchor & Tksouth) + p.y += dy; + + if((anchor & (Tkeast|Tkwest)) == 0) + p.x += dx/2; + else + if(anchor & Tkeast) + p.x += dx; + return p; +} + +static char* +tkunits(char c, int *d, TkEnv *e) +{ + switch(c) { + default: + if(c >= '0' || c <= '9' || c == '.') + break; + return TkBadvl; + case '\0': + break; + case 'c': /* Centimeters */ + *d *= (Tkdpi*100)/254; + break; + case 'm': /* Millimeters */ + *d *= (Tkdpi*10)/254; + break; + case 'i': /* Inches */ + *d *= Tkdpi; + break; + case 'p': /* Points */ + *d = (*d*Tkdpi)/72; + break; + case 'w': /* Character width */ + if(e == nil) + return TkBadvl; + *d = *d * e->wzero; + break; + case 'h': /* Character height */ + if(e == nil) + return TkBadvl; + *d = *d * e->font->height; + break; + } + return nil; +} + +int +TKF2I(int f) +{ + if (f >= 0) + return (f + Tkfpscalar/2) / Tkfpscalar; + return (f - Tkfpscalar/2) / Tkfpscalar; +} + +/* + * Parse a floating point number into a decimal fixed point representation + */ +char* +tkfrac(char **arg, int *f, TkEnv *env) +{ + int c, minus, i, fscale, seendigit; + char *p, *e; + + seendigit = 0; + + p = *arg; + p = tkskip(p, " \t"); + + minus = 0; + if(*p == '-') { + minus = 1; + p++; + } + i = 0; + while(*p) { + c = *p; + if(c == '.') + break; + if(c < '0' || c > '9') + break; + i = i*10 + (c - '0'); + seendigit = 1; + p++; + } + i *= Tkfpscalar; + if(*p == '.') + p++; + fscale = Tkfpscalar; + while(*p && *p >= '0' && *p <= '9') { + fscale /= 10; + i += fscale * (*p++ - '0'); + seendigit = 1; + } + + if(minus) + i = -i; + + if(!seendigit) + return TkBadvl; + e = tkunits(*p, &i, env); + if (e != nil) + return e; + while (*p && *p != ' ' && *p != '\t') + p++; + *arg = p; + *f = i; + return nil; +} + +char* +tkfracword(TkTop *t, char **arg, int *f, TkEnv *env) +{ + char *p; + char buf[Tkminitem]; + + *arg = tkword(t, *arg, buf, buf+sizeof(buf), nil); + p = buf; + return tkfrac(&p, f, env); +} + +char* +tkfprint(char *v, int frac) +{ + int fscale; + + if(frac < 0) { + *v++ = '-'; + frac = -frac; + } + v += sprint(v, "%d", frac/Tkfpscalar); + frac = frac%Tkfpscalar; + if(frac != 0) + *v++ = '.'; + fscale = Tkfpscalar/10; + while(frac) { + *v++ = '0' + frac/fscale; + frac %= fscale; + fscale /= 10; + } + *v = '\0'; + return v; +} + +char* +tkvalue(char **val, char *fmt, ...) +{ + va_list arg; + Fmt fmtx; + + if(val == nil) + return nil; + + fmtstrinit(&fmtx); + if(*val != nil) + if(fmtprint(&fmtx, "%s", *val) < 0) + return TkNomem; + va_start(arg, fmt); + fmtvprint(&fmtx, fmt, arg); + va_end(arg); + free(*val); + *val = fmtstrflush(&fmtx); + if(*val == nil) + return TkNomem; + return nil; +} + +static char* +tkwidgetcmd(TkTop *t, Tk *tk, char *arg, char **val) +{ + TkMethod *cm; + TkCmdtab *ct; + int bot, top, new, r; + char *e, *buf; + + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + + arg = tkword(t, arg, buf, buf+Tkmaxitem, nil); + if(val != nil) + *val = nil; + + cm = tkmethod[tk->type]; + + e = TkBadcm; + bot = 0; + top = cm->ncmd - 1; + + while(bot <= top) { + new = (bot + top)/2; + ct = &cm->cmd[new]; + r = strcmp(ct->name, buf); + if(r == 0) { + e = ct->fn(tk, arg, val); + break; + } + if(r < 0) + bot = new + 1; + else + top = new - 1; + } + free(buf); + tkdirty(tk); + return e; +} + +Rectangle +tkrect(Tk *tk, int withborder) +{ + Rectangle r; + int bd; + bd = withborder ? tk->borderwidth : 0; + r.min.x = -bd; + r.min.y = -bd; + r.max.x = tk->act.width + bd; + r.max.y = tk->act.height + bd; + return r; +} + +void +tkdirty(Tk *tk) +{ + Tk *sub; + Point rel; + Rectangle dirty; + int isdirty, transparent; + + /* + * mark as dirty all views underneath a dirty transparent widget + * down to the first opaque widget. + * inform parents about any dirtiness. + + * XXX as Tksubsub never gets reset, testing against Tksubsub doesn't *exactly* test + * whether we're in a canvas/text widget, but merely + * whether it has ever been. Tksubsub should probably be reset on unpack. + */ + isdirty = Dx(tk->dirty) > 0; + transparent = tk->flag & Tktransparent; + sub = tk; + while (isdirty && ((tk->flag&Tksubsub) || transparent)) { + if (tk->master != nil) { + if (transparent) { + rel.x = tk->act.x + tk->borderwidth; + rel.y = tk->act.y + tk->borderwidth; + dirty = rectaddpt(sub->dirty, rel); + sub = tk->master; + combinerect(&sub->dirty, dirty); + transparent = sub->flag & Tktransparent; + } + tk = tk->master; + } else if (tk->parent != nil) { + tkmethod[tk->parent->type]->dirtychild(sub); + tk = sub = tk->parent; + isdirty = Dx(sub->dirty) > 0; + transparent = sub->flag & Tktransparent; + } else + break; + } +} + +static int +qcmdcmp(const void *a, const void *b) +{ + return strcmp(((TkCmdtab*)a)->name, ((TkCmdtab*)b)->name); +} + +void +tksorttable(void) +{ + int i; + TkMethod *c; + TkCmdtab *cmd; + + for(i = 0; i < TKwidgets; i++) { + c = tkmethod[i]; + if(c->cmd == nil) + continue; + + for(cmd = c->cmd; cmd->name != nil; cmd++) + ; + c->ncmd = cmd - c->cmd; + + qsort(c->cmd, c->ncmd, sizeof(TkCmdtab), qcmdcmp); + } +} + +static char* +tksinglecmd(TkTop *t, char *arg, char **val) +{ + Tk *tk; + int bot, top, new; + char *e, *buf; + + if(t->debug) + print("tk: '%s'\n", arg); + + buf = mallocz(Tkmaxitem, 0); + if(buf == nil) + return TkNomem; + + arg = tkword(t, arg, buf, buf+Tkmaxitem, nil); + switch(buf[0]) { + case '\0': + free(buf); + return nil; + case '.': + tk = tklook(t, buf, 0); + if(tk == nil){ + tkerr(t, buf); + free(buf); + return TkBadwp; + } + e = tkwidgetcmd(t, tk, arg, val); + free(buf); + return e; + } + + bot = 0; + top = nelem(cmdmain) - 1; + e = TkBadcm; + while(bot <= top) { + int rc; + new = (bot + top)/2; + rc = strcmp(cmdmain[new].name, buf); + if(!rc) { + e = cmdmain[new].fn(t, arg, val); + break; + } + + if(rc < 0) + bot = new + 1; + else + top = new - 1; + } + free(buf); + return e; +} + +static char* +tkmatch(int inc, int dec, char *p) +{ + int depth, esc, c; + + esc = 0; + depth = 1; + while(*p) { + c = *p; + if(esc == 0) { + if(c == inc) + depth++; + if(c == dec) + depth--; + if(depth == 0) + return p; + } + if(c == '\\' && esc == 0) + esc = 1; + else + esc = 0; + p++; + } + return nil; +} + +char* +tkexec(TkTop *t, char *arg, char **val) +{ + int cmdsz, n; + char *p, *cmd, *e, *c; + + if(t->execdepth >= 0 && ++t->execdepth > 128) + return TkDepth; + + cmd = nil; + cmdsz = 0; + + p = arg; + for(;;) { + switch(*p++) { + case '[': + p = tkmatch('[', ']', p); + if(p == nil){ + free(cmd); + return TkSyntx; + } + break; + case '{': + p = tkmatch('{', '}', p); + if(p == nil){ + free(cmd); + return TkSyntx; + } + break; + case ';': + n = p - arg - 1; + if(cmdsz < n) + cmdsz = n; + c = realloc(cmd, cmdsz+1); + if(c == nil){ + free(cmd); + return TkNomem; + } + cmd = c; + memmove(cmd, arg, n); + cmd[n] = '\0'; + e = tksinglecmd(t, cmd, nil); + if(e != nil) { + t->err = e; + strncpy(t->errcmd, cmd, sizeof(t->errcmd)); + t->errcmd[sizeof(t->errcmd)-1] = '\0'; + free(cmd); + return e; + } + arg = p; + break; + case '\0': + case '\'': + free(cmd); + e = tksinglecmd(t, arg, val); + if(e != nil) { + t->err = e; + strncpy(t->errcmd, arg, sizeof(t->errcmd)); + t->errcmd[sizeof(t->errcmd)-1] = '\0'; + } + return e; + } + } +} + +static struct { + char *name; + int mask; +} events[] = { + "Button1P", TkButton1P, + "Button1R", TkButton1R, + "Button2P", TkButton2P, + "Button2R", TkButton2R, + "Button3P", TkButton3P, + "Button3R", TkButton3R, + "Button4P", TkButton4P, + "Button4R", TkButton4R, + "Button5P", TkButton5P, + "Button5R", TkButton5R, + "Button6P", TkButton6P, + "Button6R", TkButton6R, + "Extn1", TkExtn1, + "Extn2", TkExtn2, + "Takefocus", TkTakefocus, + "Destroy", TkDestroy, + "Enter", TkEnter, + "Leave", TkLeave, + "Motion", TkMotion, + "Map", TkMap, + "Unmap", TkUnmap, + "Key", TkKey, + "Focusin", TkFocusin, + "Focusout", TkFocusout, + "Configure", TkConfigure, + "Double", TkDouble, + 0 +}; + +int +tkeventfmt(Fmt *f) +{ + int k, i, d; + int e; + + e = va_arg(f->args, int); + + if ((f->flags & FmtSharp) && e == TkMotion) + return 0; + fmtprint(f, "<"); + k = -1; + if (e & TkKey) { + k = e & 0xffff; + e &= ~0xffff; + } + d = 0; + for (i = 0; events[i].name; i++) { + if (e & events[i].mask) { + if (d++) + fmtprint(f, "|"); + fmtprint(f, "%s", events[i].name); + } + } + if (k != -1) { + fmtprint(f, "[%c]", k); + } else if (e == 0) + fmtprint(f, "Noevent"); + fmtprint(f, ">"); + return 0; +} + +void +tkerr(TkTop *t, char *e) +{ + if(t != nil && e != nil){ + strncpy(t->errx, e, sizeof(t->errx)); + t->errx[sizeof(t->errx)-1] = '\0'; + } +} + +char* +tkerrstr(TkTop *t, char *e) +{ + char *s = malloc(strlen(e)+1+strlen(t->errx)+1); + + if(s == nil) + return nil; + strcpy(s, e); + if(*e == '!'){ + strcat(s, " "); + strcat(s, t->errx); + } + t->errx[0] = '\0'; + return s; +} + +char* +tksetmgrab(TkTop *t, Tk *tk) +{ + Tk *omgrab; + TkCtxt *c; + c = t->ctxt; + if (tk == nil) { + omgrab = c->mgrab; + c->mgrab = nil; + /* + * don't enterleave if grab reset would cause no leave event + */ + if (!(omgrab != nil && (omgrab->flag & Tknograb) && + c->entered != nil && (c->entered->flag & Tknograb))) + tkenterleave(t); + } else { + if (c->focused && c->mfocus != nil && c->mfocus->env->top != tk->env->top) + return "!grab already taken on another toplevel"; + c->mgrab = tk; + if (tk->flag & Tknograb) { + if (c->focused) { + c->focused = 0; + c->mfocus = nil; + } + } else if (c->focused || c->mstate.b != 0) { + c->focused = 1; + c->mfocus = tk; + } +//print("setmgrab(%s) focus now %s\n", tkname(tk), tkname(c->mfocus)); + tkenterleave(t); + } + return nil; +} + +int +tkinsidepoly(Point *poly, int np, int winding, Point p) +{ + Point pi, pj; + int i, j, hit; + + hit = 0; + j = np - 1; + for (i = 0; i < np; j = i++) { + pi = poly[i]; + pj = poly[j]; + if ((pi.y <= p.y && p.y < pj.y || pj.y <= p.y && p.y < pi.y) && + p.x < (pj.x - pi.x) * (p.y - pi.y) / (pj.y - pi.y) + pi.x) { + if (winding == 1 || pi.y > p.y) + hit++; + else + hit--; + } + } + return (hit & winding) != 0; +} + +int +tklinehit(Point *a, int np, int w, Point p) +{ + Point *b; + int z, nx, ny, nrm; + while(np-- > 1) { + b = a+1; + nx = a->y - b->y; + ny = b->x - a->x; + nrm = (nx < 0? -nx : nx) + (ny < 0? -ny : ny); + if(nrm) + z = (p.x-b->x)*nx/nrm + (p.y-b->y)*ny/nrm; + else + z = (p.x-b->x) + (p.y-b->y); + if(z < 0) + z = -z; + if(z < w) + return 1; + a++; + } + return 0; +} + +int +tkiswordchar(int c) +{ + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c >= 0xA0; +} + +int +tkhaskeyfocus(Tk *tk) +{ + if (tk == nil || tk->env->top->focused == 0) + return 0; + return tk == tk->env->top->ctxt->tkkeygrab; +} + +static int +rptactive(void *v) +{ + int id = (int)v; + if (id == rptid) + return 1; + return 0; +} + +static int +ckrpt(void *v, int interval) +{ + int id = (int)v; + if (id != rptid) + return -1; + if (interval < rptto) + return 0; + return 1; +} + +static void +dorpt(void *v) +{ + int id = (int)v; + + if (id == rptid) { + rptto = rptint; + (*rptcb)(rptw, rptnote, 0); + if (rptint <= 0) { + rptid++; + rptw = nil; + } + } +} + +void +tkcancelrepeat(Tk *tk) +{ + if (tk == rptw) { + rptid++; + rptw = nil; + } +} + +void +tkrepeat(Tk *tk, void (*callback)(Tk*, void*, int), void *note, int pause, int interval) +{ + rptid++; + if (tk != rptw && rptw != nil) + /* existing callback being replaced- report to owner */ + (*rptcb)(rptw, rptnote, 1); + rptw = tk; + if (tk == nil || callback == nil) + return; + rptnote = note; + rptcb = callback; + rptto = pause; + rptint = interval; + if (!autorpt) + autorpt = rptproc("autorepeat", TkRptclick, (void*)rptid, rptactive, ckrpt, dorpt); + else + rptwakeup((void*)rptid, autorpt); +} + +static int +blinkactive(void *v) +{ + USED(v); + return blinkw != nil; +} + +static int +ckblink(void *v, int interval) +{ + USED(v); + USED(interval); + + if (blinkw == nil) + return -1; + if (blinkignore) { + blinkignore = 0; + return 0; + } + return 1; +} + +static void +doblink(void *v) +{ + USED(v); + + if (blinkw == nil) + return; + blinkcb(blinkw, blinkon++ & 1); + tkupdate(blinkw->env->top); +} + +void +tkblinkreset(Tk *tk) +{ + if (blinkw == tk) { + blinkignore = 1; + blinkon = 0; + } +} + +void +tkblink(Tk *tk, void (*callback)(Tk*, int)) +{ + if (tk == nil || callback == nil) { + blinkw = nil; + return; + } + blinkw = tk; + blinkcb = callback; + if (!blinkrpt) + blinkrpt = rptproc("blinker", TkBlinkinterval, nil, blinkactive, ckblink, doblink); + else + rptwakeup(nil, blinkrpt); +} diff --git a/libtk/windw.c b/libtk/windw.c new file mode 100644 index 00000000..4c9e608c --- /dev/null +++ b/libtk/windw.c @@ -0,0 +1,716 @@ +#include "lib9.h" +#include "draw.h" +#include "tk.h" +#include "canvs.h" +#include "textw.h" +#include "kernel.h" + +TkCtxt* +tknewctxt(Display *d) +{ + TkCtxt *c; + c = malloc(sizeof(TkCtxt)); + if(c == nil) + return nil; + c->lock = libqlalloc(); + if(c->lock == nil){ + free(c); + return nil; + } + if (tkextnnewctxt(c) != 0) { + free(c->lock); + free(c); + return nil; + } + c->display = d; + return c; +} + +void +tkfreectxt(TkCtxt *c) +{ + int locked; + Display *d; + + if(c == nil) + return; + + tkextnfreectxt(c); + + d = c->display; + locked = lockdisplay(d); + tkfreecolcache(c); + freeimage(c->i); + freeimage(c->ia); + if(locked) + unlockdisplay(d); + libqlfree(c->lock); + free(c); +} + +Image* +tkitmp(TkEnv *e, Point p, int fillcol) +{ + Image *i, **ip; + TkTop *t; + TkCtxt *ti; + Display *d; + Rectangle r; + ulong pix; + int alpha; + + t = e->top; + ti = t->ctxt; + d = t->display; + + pix = e->colors[fillcol]; + alpha = (pix & 0xff) != 0xff; + ip = alpha ? &ti->ia : &ti->i; + + if(*ip != nil) { + i = *ip; + if(p.x <= i->r.max.x && p.y <= i->r.max.y) { + r.min = ZP; + r.max = p; + if (alpha) + drawop(i, r, nil, nil, ZP, Clear); + draw(i, r, tkgc(e, fillcol), nil, ZP); + return i; + } + r = i->r; + freeimage(i); + if(p.x < r.max.x) + p.x = r.max.x; + if(p.y < r.max.y) + p.y = r.max.y; + } + + r.min = ZP; + r.max = p; + *ip = allocimage(d, r, alpha?RGBA32:d->image->chan, 0, pix); + + return *ip; +} + +void +tkgeomchg(Tk *tk, TkGeom *g, int bd) +{ + int w, h; + void (*geomfn)(Tk*); + if(memcmp(&tk->req, g, sizeof(TkGeom)) == 0 && bd == tk->borderwidth) + return; + + geomfn = tkmethod[tk->type]->geom; + if(geomfn != nil) + geomfn(tk); + + if(tk->master != nil) { + tkpackqit(tk->master); + tkrunpack(tk->env->top); + } + else + if(tk->geom != nil) { + w = tk->req.width; + h = tk->req.height; + tk->req.width = 0; + tk->req.height = 0; + tk->geom(tk, tk->act.x, tk->act.y, w, h); + if (tk->slave) { + tkpackqit(tk); + tkrunpack(tk->env->top); + } + } + tkdeliver(tk, TkConfigure, g); +} + +/* + * return the widget within tk with by point p (in widget coords) + */ +Tk* +tkinwindow(Tk *tk, Point p, int descend) +{ + Tk *f; + Point q; + if (ptinrect(p, tkrect(tk, 1)) == 0) + return nil; + for (;;) { + if (descend && tkmethod[tk->type]->inwindow != nil) + f = tkmethod[tk->type]->inwindow(tk, &p); + else { + for (f = tk->slave; f; f = f->next) { + q.x = p.x - (f->act.x + f->borderwidth); + q.y = p.y - (f->act.y + f->borderwidth); + if (ptinrect(q, tkrect(f, 1))) + break; + } + p = q; + } + if (f == nil || f == tk) + return tk; + tk = f; + } + return nil; /* for compiler */ +} + +Tk* +tkfindfocus(TkTop *t, int x, int y, int descend) +{ + Point p, q; + Tk *tk, *f; + TkWin *tkw; + p.x = x; + p.y = y; + for(f = t->windows; f != nil; f = TKobj(TkWin, f)->next) { + assert(f->flag&Tkwindow); + if(f->flag & Tkmapped) { + tkw = TKobj(TkWin, f); + q.x = p.x - (tkw->act.x+f->borderwidth); + q.y = p.y - (tkw->act.y+f->borderwidth); + tk = tkinwindow(f, q, descend); + if(tk != nil) + return tk; + } + } + return nil; +} + +void +tkmovewin(Tk *tk, Point p) +{ + TkWin *tkw; + if((tk->flag & Tkwindow) == 0) + return; + tkw = TKobj(TkWin, tk); + if(! eqpt(p, tkw->req)){ + tkw->req = p; + tkw->changed = 1; + } +} + +void +tkmoveresize(Tk *tk, int x, int y, int w, int h) +{ + TkWin *tkw; + USED(x); + USED(y); + assert(tk->flag&Tkwindow); + tkw = TKobj(TkWin, tk); + if(w < 0) + w = 0; + if(h < 0) + h = 0; +//print("moveresize %s %d %d +[%d %d], callerpc %lux\n", tk->name->name, x, y, w, h, getcallerpc(&tk)); + tk->req.width = w; + tk->req.height = h; + tk->act = tk->req; + /* XXX perhaps should actually suspend the window here? */ + tkw->changed = 1; +} + +static void +tkexterncreatewin(Tk *tk, Rectangle r) +{ + TkWin *tkw; + TkTop *top; + char *name; + + top = tk->env->top; + tkw = TKobj(TkWin, tk); + + /* + * for a choicebutton menu, use the name of the choicebutton which created it + */ + if(tk->name == nil){ + name = tkw->cbname; + assert(name != nil); + } else + name = tk->name->name; + + tkw->reqid++; + tkwreq(top, "!reshape %s %d %d %d %d %d", name, tkw->reqid, r.min.x, r.min.y, r.max.x, r.max.y); + tkw->changed = 0; + tk->flag |= Tksuspended; +} + +/* + * return non-zero if the window size has changed (XXX choose better return value/function name!) + */ +int +tkupdatewinsize(Tk *tk) +{ + TkWin *tkw; + Image *previ; + Rectangle r, or; + int bw2; + + tkw = TKobj(TkWin, tk); + bw2 = 2*tk->borderwidth; + r.min.x = tkw->req.x; + r.min.y = tkw->req.y; + r.max.x = r.min.x + tk->act.width + bw2; + r.max.y = r.min.y + tk->act.height + bw2; + previ = tkw->image; + if(previ != nil){ + or.min.x = tkw->act.x; + or.min.y = tkw->act.y; + or.max.x = tkw->act.x + Dx(previ->r); + or.max.y = tkw->act.y + Dy(previ->r); + if(eqrect(or, r)) + return 0; + } + tkexterncreatewin(tk, r); + return 1; +} + +static char* +tkdrawslaves1(Tk *tk, Point orig, Image *dst, int *dirty) +{ + Tk *f; + char *e = nil; + Point worig; + Rectangle r, oclip; + + worig.x = orig.x + tk->act.x + tk->borderwidth; + worig.y = orig.y + tk->act.y + tk->borderwidth; + + r = rectaddpt(tk->dirty, worig); + if (Dx(r) > 0 && rectXrect(r, dst->clipr)) { + e = tkmethod[tk->type]->draw(tk, orig); + tk->dirty = bbnil; + *dirty = 1; + } + if(e != nil) + return e; + + /* + * grids need clipping + * XXX BUG: they can't, 'cos text widgets don't clip appropriately. + */ + if (tk->grid != nil) { + r = rectaddpt(tkrect(tk, 0), worig); + if (rectclip(&r, dst->clipr) == 0) + return nil; + oclip = dst->clipr; + replclipr(dst, 0, r); + } + for(f = tk->slave; e == nil && f; f = f->next) + e = tkdrawslaves1(f, worig, dst, dirty); + if (tk->grid != nil) + replclipr(dst, 0, oclip); + return e; +} + +char* +tkdrawslaves(Tk *tk, Point orig, int *dirty) +{ + Image *i; + char *e; + i = tkimageof(tk); + if (i == nil) + return nil; + e = tkdrawslaves1(tk, orig, i, dirty); + return e; +} + +char* +tkupdate(TkTop *t) +{ + Tk* tk; + int locked; + TkWin *tkw; + Display *d; + char *e; + int dirty = 0; + if(t->noupdate) + return nil; + + d = t->display; + locked = lockdisplay(d); + tk = t->windows; + while(tk) { + tkw = TKobj(TkWin, tk); + if((tk->flag & (Tkmapped|Tksuspended)) == Tkmapped) { + if (tkupdatewinsize(tk) == 0){ + e = tkdrawslaves(tk, ZP, &dirty); + if(e != nil) + return e; + } + } + tk = tkw->next; + } + if (dirty || t->dirty) { + flushimage(d, 1); + t->dirty = 0; + } + if(locked) + unlockdisplay(d); + return nil; +} + +int +tkischild(Tk *tk, Tk *child) +{ + while(child != nil && child != tk){ + if(child->master) + child = child->master; + else + child = child->parent; + } + return child == tk; +} + +void +tksetbits(Tk *tk, int mask) +{ + tk->flag |= mask; + for(tk = tk->slave; tk; tk = tk->next) + tksetbits(tk, mask); +} + +char* +tkmap(Tk *tk) +{ +/* + is this necessary? + tkw = TKobj(TkWin, tk); + if(tkw->image != nil) + tkwreq(tk->env->top, "raise %s", tk->name->name); +*/ + + if(tk->flag & Tkmapped) + return nil; + + tk->flag |= Tkmapped; + tkmoveresize(tk, 0, 0, tk->act.width, tk->act.height); + tkdeliver(tk, TkMap, nil); + return nil; +//tkupdate(tk->env->top); +} + +void +tkclrfocus(Tk *master) +{ + TkCtxt *c; + Tk *tk; + TkTop *top; + + if(master == nil) + return; + top = master->env->top; + c = top->ctxt; + + tk = c->mgrab; + if(tkischild(master, tk)) + tksetmgrab(top, nil); + + tk = c->entered; + if(tkischild(master, tk)){ + c->entered = nil; + tkdeliver(tk, TkLeave, nil); + } +} + +void +tkunmap(Tk *tk) +{ + TkTop *t; + TkCtxt *c; + + while(tk->master) + tk = tk->master; + + if((tk->flag & Tkmapped) == 0) + return; + + t = tk->env->top; + c = t->ctxt; + + if(tkischild(tk, c->mgrab)) + tksetmgrab(t, nil); + if(tkischild(tk, c->entered)){ + tkdeliver(c->entered, TkLeave, nil); + c->entered = nil; + } + if(tk == t->root) + tksetglobalfocus(t, 0); + + tk->flag &= ~(Tkmapped|Tksuspended); + + tkdestroywinimage(tk); + tkdeliver(tk, TkUnmap, nil); + tkenterleave(t); + /* XXX should unmap menus too */ +} + +Image* +tkimageof(Tk *tk) +{ + while(tk) { + if(tk->flag & Tkwindow) + return TKobj(TkWin, tk)->image; + if(tk->parent != nil) { + tk = tk->parent; + switch(tk->type) { + case TKmenu: + return TKobj(TkWin, tk)->image; + case TKcanvas: + return TKobj(TkCanvas, tk)->image; + case TKtext: + return TKobj(TkText, tk)->image; + } + abort(); + } + tk = tk->master; + } + return nil; +} + +void +tktopopt(Tk *tk, char *opt) +{ + TkTop *t; + TkWin *tkw; + TkOptab tko[4]; + + tkw = TKobj(TkWin, tk); + + t = tk->env->top; + + tko[0].ptr = tkw; + tko[0].optab = tktop; + tko[1].ptr = tk; + tko[1].optab = tkgeneric; + tko[2].ptr = t; + tko[2].optab = tktopdbg; + tko[3].ptr = nil; + + tkparse(t, opt, tko, nil); +} + +/* general compare - compare top-left corners, y takes priority */ +static int +tkfcmpgen(void *ap, void *bp) +{ + TkWinfo *a = ap, *b = bp; + + if (a->r.min.y > b->r.min.y) + return 1; + if (a->r.min.y < b->r.min.y) + return -1; + if (a->r.min.x > b->r.min.x) + return 1; + if (a->r.min.x < b->r.min.x) + return -1; + return 0; +} + +/* compare x-coords only */ +static int +tkfcmpx(void *ap, void *bp) +{ + TkWinfo *a = ap, *b = bp; + return a->r.min.x - b->r.min.x; +} + +/* compare y-coords only */ +static int +tkfcmpy(void *ap, void *bp) +{ + TkWinfo *a = ap, *b = bp; + return a->r.min.y - b->r.min.y; +} + +static void +tkfintervalintersect(int min1, int max1, int min2, int max2, int *min, int *max) +{ + if (min1 < min2) + min1 = min2; + if (max1 > max2) + max1 = max2; + if (max1 > min1) { + *min = min1; + *max = max1; + } else + *max = *min; /* no intersection */ +} + +void +tksortfocusorder(TkWinfo *inf, int n) +{ + int i; + Rectangle overlap, r; + int (*cmpfn)(void*, void*); + + overlap = inf[0].r; + for (i = 0; i < n; i++) { + r = inf[i].r; + tkfintervalintersect(overlap.min.x, overlap.max.x, + r.min.x, r.max.x, &overlap.min.x, &overlap.max.x); + tkfintervalintersect(overlap.min.y, overlap.max.y, + r.min.y, r.max.y, &overlap.min.y, &overlap.max.y); + } + + if (Dx(overlap) > 0) + cmpfn = tkfcmpy; + else if (Dy(overlap) > 0) + cmpfn = tkfcmpx; + else + cmpfn = tkfcmpgen; + + qsort(inf, n, sizeof(*inf), cmpfn); +} + +void +tkappendfocusorder(Tk *tk) +{ + TkTop *tkt; + tkt = tk->env->top; + if (tk->flag & Tktakefocus) + tkt->focusorder[tkt->nfocus++] = tk; + if (tkmethod[tk->type]->focusorder != nil) + tkmethod[tk->type]->focusorder(tk); +} + +void +tkbuildfocusorder(TkTop *tkt) +{ + Tk *tk; + int n; + + if (tkt->focusorder != nil) + free(tkt->focusorder); + n = 0; + for (tk = tkt->root; tk != nil; tk = tk->siblings) + if (tk->flag & Tktakefocus) + n++; + if (n == 0) { + tkt->focusorder = nil; + return; + } + + tkt->focusorder = malloc(sizeof(*tkt->focusorder) * n); + tkt->nfocus = 0; + if (tkt->focusorder == nil) + return; + + tkappendfocusorder(tkt->root); +} + +void +tkdirtyfocusorder(TkTop *tkt) +{ + free(tkt->focusorder); + tkt->focusorder = nil; + tkt->nfocus = 0; +} + +#define O(t, e) ((long)(&((t*)0)->e)) +#define OA(t, e) ((long)(((t*)0)->e)) + +typedef struct TkSee TkSee; +struct TkSee { + int r[4]; + int p[2]; + int query; +}; + +static +TkOption seeopts[] = { + "rectangle", OPTfrac, OA(TkSee, r), IAUX(4), + "point", OPTfrac, OA(TkSee, p), IAUX(2), + "where", OPTbool, O(TkSee, query), nil, + nil +}; + +char* +tkseecmd(TkTop *t, char *arg, char **ret) +{ + TkOptab tko[2]; + TkSee opts; + TkName *names; + Tk *tk; + char *e; + Rectangle vr; + Point vp; + + opts.r[0] = bbnil.min.x; + opts.r[1] = bbnil.min.y; + opts.r[2] = bbnil.max.x; + opts.r[3] = bbnil.max.y; + opts.p[0] = bbnil.max.x; + opts.p[1] = bbnil.max.y; + opts.query = 0; + + tko[0].ptr = &opts; + tko[0].optab = seeopts; + tko[1].ptr = nil; + names = nil; + e = tkparse(t, arg, tko, &names); + if (e != nil) + return e; + if (names == nil) + return TkBadwp; + tk = tklook(t, names->name, 0); + tkfreename(names); + if (tk == nil) + return TkBadwp; + if (opts.query) { + if (!tkvisiblerect(tk, &vr)) + return nil; + /* XXX should this be converted into screen coords? */ + return tkvalue(ret, "%d %d %d %d", vr.min.x, vr.min.y, vr.max.x, vr.max.y); + } + vr.min.x = opts.r[0]; + vr.min.y = opts.r[1]; + vr.max.x = opts.r[2]; + vr.max.y = opts.r[3]; + vp.x = opts.p[0]; + vp.y = opts.p[1]; + + if (eqrect(vr, bbnil)) + vr = tkrect(tk, 1); + if (eqpt(vp, bbnil.max)) + vp = vr.min; + tksee(tk, vr, vp); + return nil; +} + +/* + * make rectangle r in widget tk visible if possible; + * if not possible, at least make point p visible. + */ +void +tksee(Tk *tk, Rectangle r, Point p) +{ + Point g; +//print("tksee %R, %P in %s\n", r, p, tk->name->name); + g = Pt(tk->borderwidth, tk->borderwidth); + if(tk->parent != nil) { + g = addpt(g, tkmethod[tk->parent->type]->relpos(tk)); + tk = tk->parent; + } else { + g.x += tk->act.x; + g.y += tk->act.y; + tk = tk->master; + } + r = rectaddpt(r, g); + p = addpt(p, g); + while (tk != nil) { + if (tkmethod[tk->type]->see != nil){ +//print("see r %R, p %P in %s\n", r, p, tk->name->name); + tkmethod[tk->type]->see(tk, &r, &p); +//print("now r %R, p %P\n", r, p); + } + g = Pt(tk->borderwidth, tk->borderwidth); + if (tk->parent != nil) { + g = addpt(g, tkmethod[tk->parent->type]->relpos(tk)); + tk = tk->parent; + } else { + g.x += tk->act.x; + g.y += tk->act.y; + tk = tk->master; + } + r = rectaddpt(r, g); + p = addpt(p, g); + } +} diff --git a/libtk/xdata.c b/libtk/xdata.c new file mode 100644 index 00000000..1c968f9b --- /dev/null +++ b/libtk/xdata.c @@ -0,0 +1,217 @@ +#include "lib9.h" +#include "draw.h" +#include "tk.h" + +#define O(t, e) ((long)(&((t*)0)->e)) + +TkStab tkorient[] = +{ + "vertical", Tkvertical, + "horizontal", Tkhorizontal, + nil +}; + +#define RGB(r,g,b) ((r<<24)|(g<<16)|(b<<8)|0xff) + +TkStab tkcolortab[] = +{ + "black", RGB(0,0,0), + "blue", RGB(0,0,204), + "darkblue", RGB(93,0,187), + "red", RGB(255,0,0), + "yellow", RGB(255,255,0), + "green", RGB(0,128,0), + "white", RGB(255,255,255), + "orange", RGB(255,170,0), + "aqua", RGB(0,255,255), + "fuchsia", RGB(255,0,255), + "gray", RGB(128,128,128), + "grey", RGB(128,128,128), + "lime", RGB(0,255,0), + "maroon", RGB(128,0,0), + "navy", RGB(0,0,128), + "olive", RGB(128,128,0), + "purple", RGB(128,0,128), + "silver", RGB(192,192,192), + "teal", RGB(0,128,128), + "transparent", DTransparent, + nil +}; + +TkStab tkrelief[] = +{ + "raised", TKraised, + "sunken", TKsunken, + "flat", TKflat, + "groove", TKgroove, + "ridge", TKridge, + nil +}; + +TkStab tkbool[] = +{ + "0", BoolF, + "no", BoolF, + "off", BoolF, + "false", BoolF, + "1", BoolT, + "yes", BoolT, + "on", BoolT, + "true", BoolT, + nil +}; + +TkStab tkanchor[] = +{ + "center", Tkcenter, + "c", Tkcenter, + "n", Tknorth, + "ne", Tknorth|Tkeast, + "e", Tkeast, + "se", Tksouth|Tkeast, + "s", Tksouth, + "sw", Tksouth|Tkwest, + "w", Tkwest, + "nw", Tknorth|Tkwest, + nil +}; + +static +TkStab tkstate[] = +{ + "normal", 0, + "active", Tkactive, + "disabled", Tkdisabled, + nil +}; + +static +TkStab tktakefocus[] = +{ + "0", 0, + "1", Tktakefocus, + nil +}; + +TkStab tktabjust[] = +{ + "left", Tkleft, + "right", Tkright, + "center", Tkcenter, + "numeric", Tknumeric, + nil +}; + +TkStab tkwrap[] = +{ + "none", Tkwrapnone, + "word", Tkwrapword, + "char", Tkwrapchar, + nil +}; + +TkStab tkjustify[] = +{ + "left", Tkleft, + "right", Tkright, + "center", Tkcenter, + nil +}; + +TkOption tkgeneric[] = +{ + "actx", OPTact, 0, IAUX(0), + "acty", OPTact, 0, IAUX(1), + "actwidth", OPTdist, O(Tk, act.width), IAUX(O(Tk, env)), + "actheight", OPTdist, O(Tk, act.height), IAUX(O(Tk, env)), + "bd", OPTnndist, O(Tk, borderwidth), nil, + "borderwidth", OPTnndist, O(Tk, borderwidth), nil, + "highlightthickness", OPTnndist, O(Tk, highlightwidth), nil, + "height", OPTsize, 0, IAUX(O(Tk, env)), + "width", OPTsize, 0, IAUX(O(Tk, env)), + "relief", OPTstab, O(Tk, relief), tkrelief, + "state", OPTflag, O(Tk, flag), tkstate, + "font", OPTfont, O(Tk, env), nil, + "foreground", OPTcolr, O(Tk, env), IAUX(TkCforegnd), + "background", OPTcolr, O(Tk, env), IAUX(TkCbackgnd), + "fg", OPTcolr, O(Tk, env), IAUX(TkCforegnd), + "bg", OPTcolr, O(Tk, env), IAUX(TkCbackgnd), + "selectcolor", OPTcolr, O(Tk, env), IAUX(TkCselect), + "selectforeground", OPTcolr, O(Tk, env), IAUX(TkCselectfgnd), + "selectbackground", OPTcolr, O(Tk, env), IAUX(TkCselectbgnd), + "activeforeground", OPTcolr, O(Tk, env), IAUX(TkCactivefgnd), + "activebackground", OPTcolr, O(Tk, env), IAUX(TkCactivebgnd), + "highlightcolor", OPTcolr, O(Tk, env), IAUX(TkChighlightfgnd), + "disabledcolor", OPTcolr, O(Tk, env), IAUX(TkCdisablefgnd), + "padx", OPTnndist, O(Tk, pad.x), nil, + "pady", OPTnndist, O(Tk, pad.y), nil, + "takefocus", OPTflag, O(Tk, flag), tktakefocus, + nil +}; + +TkOption tktop[] = +{ + "x", OPTdist, O(TkWin, req.x), nil, + "y", OPTdist, O(TkWin, req.y), nil, + nil +}; + +TkOption tktopdbg[] = +{ + "debug", OPTbool, O(TkTop, debug), nil, + nil +}; + +TkMethod *tkmethod[] = +{ + &framemethod, /* TKframe */ + &labelmethod, /* TKlabel */ + &checkbuttonmethod, /* TKcheckbutton */ + &buttonmethod, /* TKbutton */ + &menubuttonmethod, /* TKmenubutton */ + &menumethod, /* TKmenu */ + &separatormethod, /* TKseparator */ + &cascademethod, /* TKcascade */ + &listboxmethod, /* TKlistbox */ + &scrollbarmethod, /* TKscrollbar */ + &textmethod, /* TKtext */ + &canvasmethod, /* TKcanvas */ + &entrymethod, /* TKentry */ + &radiobuttonmethod, /* TKradiobutton */ + &scalemethod, /* TKscale */ + &panelmethod, /* TKpanel */ + &choicebuttonmethod, /*TKchoicebutton */ +}; + +char TkNomem[] = "!out of memory"; +char TkBadop[] = "!bad option"; +char TkOparg[] = "!arg requires option"; +char TkBadvl[] = "!bad value"; +char TkBadwp[] = "!bad window path"; +char TkWpack[] = "!window is already packed"; +char TkNotop[] = "!no toplevel"; +char TkDupli[] = "!window path already exists"; +char TkNotpk[] = "!window not packed"; +char TkBadcm[] = "!bad command"; +char TkIstop[] = "!can't pack top level"; +char TkBadbm[] = "!failed to load bitmap"; +char TkBadft[] = "!failed to open font"; +char TkBadit[] = "!bad item type"; +char TkBadtg[] = "!bad/no matching tag"; +char TkFewpt[] = "!wrong number of points"; +char TkBadsq[] = "!bad event sequence"; +char TkBadix[] = "!bad index"; +char TkNotwm[] = "!not a window"; +char TkBadvr[] = "!variable does not exist"; +char TkNotvt[] = "!variable is wrong type"; +char TkMovfw[] = "!too many events buffered"; +char TkBadsl[] = "!selection already exists"; +char TkSyntx[] = "!bad [] or {} syntax"; +char TkRecur[] = "!cannot pack recursively"; +char TkDepth[] = "!execution stack too big"; +char TkNomaster[] = "!no master given"; +char TkNotgrid[] = "!not a grid"; +char TkIsgrid[] = "!cannot use pack inside a grid"; +char TkBadgridcell[] = "!grid cell in use"; +char TkBadspan[] = "!bad grid span"; +char TkBadcursor[] = "!bad cursor image"; |
