summaryrefslogtreecommitdiff
path: root/libtk
diff options
context:
space:
mode:
Diffstat (limited to 'libtk')
-rw-r--r--libtk/NOTICE25
-rw-r--r--libtk/buton.c609
-rw-r--r--libtk/canvs.c2220
-rw-r--r--libtk/canvs.h238
-rw-r--r--libtk/canvu.c506
-rw-r--r--libtk/carcs.c280
-rw-r--r--libtk/cbits.c211
-rw-r--r--libtk/cimag.c211
-rw-r--r--libtk/cline.c268
-rw-r--r--libtk/colrs.c90
-rw-r--r--libtk/coval.c248
-rw-r--r--libtk/cpoly.c270
-rw-r--r--libtk/crect.c252
-rw-r--r--libtk/ctext.c666
-rw-r--r--libtk/cwind.c410
-rw-r--r--libtk/ebind.c1033
-rw-r--r--libtk/entry.c1379
-rw-r--r--libtk/extns.c37
-rw-r--r--libtk/frame.c277
-rw-r--r--libtk/frame.h2
-rw-r--r--libtk/grids.c1552
-rw-r--r--libtk/image.c380
-rw-r--r--libtk/label.c539
-rw-r--r--libtk/label.h73
-rw-r--r--libtk/listb.c1065
-rw-r--r--libtk/listb.h1
-rw-r--r--libtk/mail.tk224
-rw-r--r--libtk/menu.tk49
-rw-r--r--libtk/menus.c1840
-rw-r--r--libtk/mkfile26
-rw-r--r--libtk/mkfile-std44
-rw-r--r--libtk/packr.c689
-rw-r--r--libtk/panel.c408
-rw-r--r--libtk/parse.c1165
-rw-r--r--libtk/radio.tk4
-rw-r--r--libtk/scale.c958
-rw-r--r--libtk/scrol.c781
-rw-r--r--libtk/textu.c1076
-rw-r--r--libtk/textw.c3693
-rw-r--r--libtk/textw.h229
-rw-r--r--libtk/tindx.c609
-rw-r--r--libtk/tmark.c392
-rw-r--r--libtk/ttags.c1029
-rw-r--r--libtk/twind.c396
-rw-r--r--libtk/utils.c1951
-rw-r--r--libtk/windw.c716
-rw-r--r--libtk/xdata.c217
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 = &param;
+ 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 &lt;)
+ * 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, "&lt;");
+ 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";