ref: ad5a80bfb081dc954be03836cc65090e0f6c7e4f
dir: /libtk/buton.c/
#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 = 1;
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;
switch (btype) {
case TKbutton:
e = tkbindings(t, tk, bb, nelem(bb));
break;
case TKcheckbutton:
e = tkbindings(t, tk, cb, nelem(cb));
break;
case TKradiobutton:
tkl->variable = strdup(tkselbut);
e = tkbindings(t, tk, cb, nelem(cb));
break;
}
if (e != nil) {
print("tkmkbutton internal error: %s\n", e);
tkfreeobj(tk);
return nil;
}
return tk;
}
/*
* draw TKbutton, TKcheckbutton, TKradiobutton
*/
char*
tkdrawbutton(Tk *tk, Point orig)
{
TkEnv *e;
TkLabel *tkl;
Rectangle r, s, mainr, focusr;
int dx, dy, h;
Point p, u, v, pp[4];
Image *i, *dst, *cd, *cl, *ct, *img;
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 = v;
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);
tkbevel(i, u, CheckButton, CheckButton, CheckButtonBW, cd, cl);
if(tkl->check) {
u.x += CheckButtonBW+1;
u.y += CheckButtonBW+1;
pp[0] = u;
pp[0].y += CheckButton/2-1;
pp[1] = pp[0];
pp[1].x += 2;
pp[1].y += 2;
pp[2] = u;
pp[2].x += CheckButton/4;
pp[2].y += CheckButton-2;
pp[3] = u;
pp[3].x += CheckButton-2;
pp[3].y++;
bezspline(i, pp, 4, Enddisc, Enddisc, 1, tkgc(e, TkCforegnd), ZP);
}
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;
v = Pt(u.x+CheckButton/2,u.y+CheckButton/2);
ellipse(i, v, CheckButton/2, CheckButton/2, CheckButtonBW-1, tkgc(e, bgnd+TkDarkshade), ZP);
if(tkl->check)
fillellipse(i, v, CheckButton/2-2, CheckButton/2-2, tkgc(e, TkCforegnd), ZP); /* could be TkCselect */
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;
tkdrawstring(tk, i, addpt(u, p), tkl->text, tkl->ul, ct, tkl->justify);
}
// 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;
}
void
tksizebutton(Tk *tk)
{
int w, h;
TkLabel *tkl;
tkl = TKobj(TkLabel, tk);
if(tkl->anchor == 0)
tkl->anchor = Tkcenter;
tksizelabel(tk); /* text, bitmap or image, and highlight */
w = tkl->w;
h = tkl->h;
if((tk->type == TKcheckbutton || tk->type == TKradiobutton) && tkl->indicator != BoolF) {
w += CheckSpace;
if(h < CheckSpace)
h = CheckSpace;
}
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
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 tklabelmargin(tk);
}
void
tkfreebutton(Tk *tk)
{
tkfreelabel(tk);
}
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 = strtol(arg, nil, 0);
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,
tkfreebutton,
tkdrawbutton,
nil,
tklabelgetimgs
};
TkMethod checkbuttonmethod = {
"checkbutton",
tkchkbuttoncmd,
tkfreebutton,
tkdrawbutton,
nil,
tklabelgetimgs,
nil,
nil,
nil,
nil,
nil,
nil,
tkvarchanged
};
TkMethod radiobuttonmethod = {
"radiobutton",
tkradbuttoncmd,
tkfreebutton,
tkdrawbutton,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
tkvarchanged
};