xref: /inferno-os/libtk/scale.c (revision 06155dbb53eb0585800b239acd6e45b946c6e0cf)
1 #include <lib9.h>
2 #include <kernel.h>
3 #include "draw.h"
4 #include "tk.h"
5 #include "keyboard.h"
6 
7 #define	O(t, e)		((long)(&((t*)0)->e))
8 
9 typedef struct TkScale TkScale;
10 struct TkScale
11 {
12 	int	value;
13 	int	bigi;
14 	int	digits;
15 	int	digwidth;
16 	int	from;		/* Base of range */
17 	int	to;		/* Limit of range */
18 	int	len;		/* Length of groove */
19 	int	res;		/* Resolution */
20 	int	sv;		/* Show value */
21 	int	sl;		/* Slider length */
22 	int	sw;		/* Slider width div 2 */
23 	int	relief;
24 	int	tick;
25 	int	orient;
26 	char*	command;
27 	char*	label;
28 	int	pixmin;
29 	int	pixmax;
30 	int	pixpos;
31 	int	center;
32 	int	pix;
33 	int	base;
34 	int	flag;
35 	int	jump;
36 };
37 
38 enum {
39 	Dragging = (1<<0),
40 	Autorepeat = (1<<1),
41 };
42 
43 static
44 TkOption opts[] =
45 {
46 	"bigincrement",		OPTnnfrac,	O(TkScale, bigi),	nil,
47 	"digits",		OPTdist,	O(TkScale, digits),	nil,
48 	"from",			OPTfrac,	O(TkScale, from),	nil,
49 	"to",			OPTfrac,	O(TkScale, to),		nil,
50 	"length",		OPTdist,	O(TkScale, len),	nil,
51 	"resolution",		OPTnnfrac,	O(TkScale, res),	nil,
52 	"showrange",	OPTignore,	0,	nil,
53 	"showvalue",		OPTstab,	O(TkScale, sv),		tkbool,
54 	"jump",		OPTstab, O(TkScale, jump),	tkbool,
55 	"sliderlength",		OPTdist,	O(TkScale, sl),		nil,
56 	"sliderrelief",		OPTstab,	O(TkScale, relief),	tkrelief,
57 	"tickinterval",		OPTfrac,	O(TkScale, tick),	nil,
58 	"tick",		OPTfrac,	O(TkScale, tick),	nil,
59 	"label",		OPTtext,	O(TkScale, label),	nil,
60 	"command",		OPTtext,	O(TkScale, command),	nil,
61 	"orient",		OPTstab,	O(TkScale, orient),	tkorient,
62 	nil
63 };
64 
65 static char trough1[] = "trough1";
66 static char trough2[] = "trough2";
67 static char slider[]  = "slider";
68 
69 static
70 TkEbind b[] =
71 {
72 	{TkMotion,		"%W tkScaleMotion %x %y"},
73 	{TkButton1P|TkMotion,	"%W tkScaleDrag %x %y"},
74 	{TkButton1P,		"%W tkScaleMotion %x %y; %W tkScaleBut1P %x %y"},
75 	{TkButton1P|TkDouble,	"%W tkScaleMotion %x %y; %W tkScaleBut1P %x %y"},
76 	{TkButton1R,		"%W tkScaleDrag %x %y; %W tkScaleBut1R; %W tkScaleMotion %x %y"},
77 	{TkKey,		"%W tkScaleKey 0x%K"},
78 };
79 
80 enum
81 {
82 	Scalewidth	= 18,
83 	ScalePad	= 2,
84 	ScaleBW		= 1,
85 	ScaleSlider	= 16,
86 	ScaleLen	= 80,
87 
88 };
89 
90 static int
91 maximum(int a, int b)
92 {
93 	if (a > b)
94 		return a;
95 	return b;
96 }
97 
98 void
99 tksizescale(Tk *tk)
100 {
101 	Point p;
102 	char buf[32];
103 	TkScale *tks;
104 	int fh, w, h, digits, digits2;
105 
106 	tks = TKobj(TkScale, tk);
107 
108 	digits = tks->digits;
109 	if(digits <= 0) {
110 		digits = tkfprint(buf, tks->from) - buf;
111 		digits2 = tkfprint(buf, tks->to) - buf;
112 		digits = maximum(digits, digits2);
113 		if (tks->res > 0) {
114 			digits2 = tkfprint(buf, tks->from + tks->res) - buf;
115 			digits = maximum(digits, digits2);
116 			digits2 = tkfprint(buf, tks->to - tks->res) - buf;
117 			digits = maximum(digits, digits2);
118 		}
119 	}
120 
121 	digits *= tk->env->wzero;
122 	if(tks->sv != BoolT)
123 		digits = 0;
124 
125 	tks->digwidth = digits;
126 
127 	p = tkstringsize(tk, tks->label);
128 	if(tks->orient == Tkvertical) {
129 		h = tks->len + 2*ScaleBW + 2*ScalePad;
130 		w = Scalewidth + 2*ScalePad + 2*ScaleBW;
131 		if (p.x)
132 			w += p.x + ScalePad;
133 		if (tks->sv == BoolT)
134 			w += digits + ScalePad;
135 	} else {
136 		w = maximum(p.x, tks->len + ScaleBW + 2*ScalePad);
137 		h = Scalewidth + 2*ScalePad + 2*ScaleBW;
138 		fh = tk->env->font->height;
139 		if(tks->label != nil)
140 			h += fh + ScalePad;
141 		if(tks->sv == BoolT)
142 			h += fh + ScalePad;
143 	}
144 	w += 2*tk->highlightwidth;
145 	h += 2*tk->highlightwidth;
146 	if(!(tk->flag & Tksetwidth))
147 		tk->req.width = w;
148 	if(!(tk->flag & Tksetheight))
149 		tk->req.height = h;
150 }
151 
152 static int
153 tkscalecheckvalue(Tk *tk)
154 {
155 	int v;
156 	TkScale *tks = TKobj(TkScale, tk);
157 	int limit = 1;
158 
159 	v = tks->value;
160 	if (tks->res > 0)
161 		v = (v / tks->res) * tks->res;
162 	if (tks->to >= tks->from) {
163 		if (v < tks->from)
164 			v = tks->from;
165 		else if (v > tks->to)
166 			v = tks->to;
167 		else
168 			limit = 0;
169 	} else {
170 		if (v < tks->to)
171 			v = tks->to;
172 		else if (v > tks->from)
173 			v = tks->from;
174 		else
175 			limit = 0;
176 	}
177 	/*
178 	 *  it's possible for the value to end up as a non-whole
179 	 * multiple of resolution here, if the end points aren't
180 	 * themselves such a multiple. if so, tough - that's
181 	 * what you asked for! (it does mean that the endpoints
182 	 * are always accessible however, which could be a good thing).
183 	 */
184 	tks->value = v;
185 	return limit;
186 }
187 
188 char*
189 tkscale(TkTop *t, char *arg, char **ret)
190 {
191 	Tk *tk;
192 	char *e;
193 	TkName *names;
194 	TkScale *tks;
195 	TkOptab tko[3];
196 
197 	tk = tknewobj(t, TKscale, sizeof(Tk)+sizeof(TkScale));
198 	if(tk == nil)
199 		return TkNomem;
200 
201 	tk->flag |= Tktakefocus;
202 	tks = TKobj(TkScale, tk);
203 	tks->res = TKI2F(1);
204 	tks->to = TKI2F(100);
205 	tks->len = ScaleLen;
206 	tks->orient = Tkvertical;
207 	tks->relief = TKraised;
208 	tks->sl = ScaleSlider;
209 	tks->sv = BoolT;
210 	tks->bigi = 0;
211 
212 	tko[0].ptr = tk;
213 	tko[0].optab = tkgeneric;
214 	tko[1].ptr = tks;
215 	tko[1].optab = opts;
216 	tko[2].ptr = nil;
217 
218 	names = nil;
219 	e = tkparse(t, arg, tko, &names);
220 	if(e != nil) {
221 		tkfreeobj(tk);
222 		return e;
223 	}
224 	tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd));
225 	tkscalecheckvalue(tk);
226 	tksizescale(tk);
227 	if (tks->bigi == 0)
228 		tks->bigi = TKI2F(TKF2I(tks->to - tks->from) / 10);
229 	e = tkbindings(t, tk, b, nelem(b));
230 
231 	if(e != nil) {
232 		tkfreeobj(tk);
233 		return e;
234 	}
235 
236 	e = tkaddchild(t, tk, &names);
237 	tkfreename(names);
238 	if(e != nil) {
239 		tkfreeobj(tk);
240 		return e;
241 	}
242 	tk->name->link = nil;
243 
244 	return tkvalue(ret, "%s", tk->name->name);
245 }
246 
247 static char*
248 tkscalecget(Tk *tk, char *arg, char **val)
249 {
250 	TkOptab tko[3];
251 	TkScale *tks = TKobj(TkScale, tk);
252 
253 	tko[0].ptr = tk;
254 	tko[0].optab = tkgeneric;
255 	tko[1].ptr = tks;
256 	tko[1].optab = opts;
257 	tko[2].ptr = nil;
258 
259 	return tkgencget(tko, arg, val, tk->env->top);
260 }
261 
262 void
263 tkfreescale(Tk *tk)
264 {
265 	TkScale *tks = TKobj(TkScale, tk);
266 
267 	if(tks->command != nil)
268 		free(tks->command);
269 	if(tks->label != nil)
270 		free(tks->label);
271 }
272 
273 static void
274 tkscalehoriz(Tk *tk, Image *i)
275 {
276 	TkEnv *e;
277 	char sv[32];
278 	TkScale *tks;
279 	Image *d, *l;
280 	Rectangle r, r2, sr;
281 	Point p, q;
282 	int fh, sh, gh, sl, v, w, h, len;
283 	int fgnd;
284 
285 	e = tk->env;
286 	tks = TKobj(TkScale, tk);
287 
288 
289 	fh = e->font->height;
290 	fgnd = TkCforegnd;
291 	if (tk->flag & Tkdisabled)
292 		fgnd = TkCdisablefgnd;
293 
294 	r = Rect(0, 0, tk->act.width, tk->act.height);
295 	r = rectaddpt(r, Pt(tk->borderwidth, tk->borderwidth));
296 	r = insetrect(r, tk->highlightwidth);
297 	r = insetrect(r, ScalePad);
298 
299 	if(tks->label != nil) {
300 		string(i, r.min, tkgc(e, fgnd), ZP, e->font, tks->label);
301 		r.min.y += fh + ScalePad;
302 	}
303 	if(tks->sv == BoolT)
304 		r.min.y += fh + ScalePad;
305 
306 	sr = insetrect(r, ScaleBW);
307 	w = Dx(sr);
308 	h = Dy(sr);
309 	sl = tks->sl + 2*ScaleBW;
310 
311 	l = tkgc(e, TkCbackgndlght);
312 	d = tkgc(e, TkCbackgnddark);
313 	tkbevel(i, r.min, w, h, ScaleBW, d, l);
314 
315 	tks->pixmin = sr.min.x;
316 	tks->pixmax = sr.max.x;
317 
318 	sh = h - 2*ScaleBW;
319 	tks->sw = sh/2;
320 
321 	w -= sl;
322 	if (w <= 0)
323 		w = 1;
324 	p.x = sr.min.x;
325 	p.y = sr.max.y;
326 	if(tks->tick > 0){
327 		int j, t, l;
328 		t = tks->tick;
329 		l = tks->to-tks->from;
330 		if (l < 0)
331 			l = -l;
332 		if (l == 0)
333 			l = 1;
334 		r2.min.y = p.y;
335 		r2.max.y = p.y + ScaleBW + ScalePad;
336 		for(j = 0; j <= l; j += t){
337 			r2.min.x = p.x+((vlong)j*w)/l+sl/2;
338 			r2.max.x = r2.min.x+1;
339 			draw(i, r2, tkgc(e, fgnd), nil, ZP);
340 		}
341 	}
342 	v = tks->value-tks->from;
343 	len = tks->to-tks->from;
344 	if (len != 0)
345 		p.x += ((vlong)v*w)/len;
346 	p.y = sr.min.y;
347 	q = p;
348 	q.x += tks->sl/2 + 1;
349 	if(ScaleBW > 1) {
350 		gh = sh;
351 		q.y++;
352 	} else
353 		gh = sh-1;
354 	if(tk->flag & Tkactivated) {
355 		r2.min = p;
356 		r2.max.x = p.x+sl;
357 		r2.max.y = sr.max.y;
358 		draw(i, r2, tkgc(e, TkCactivebgnd), nil, ZP);
359 	}
360 	switch(tks->relief) {
361 	case TKsunken:
362 		tkbevel(i, p, tks->sl, sh, ScaleBW, d, l);
363 		tkbevel(i, q, 0, gh, 1, l, d);
364 		break;
365 	case TKraised:
366 		tkbevel(i, p, tks->sl, sh, ScaleBW, l, d);
367 		tkbevel(i, q, 0, gh, 1, d, l);
368 		break;
369 	}
370 	tks->pixpos = p.x;
371 	tks->center = p.y + sh/2 + ScaleBW;
372 
373 	if(tks->sv != BoolT)
374 		return;
375 
376 	tkfprint(sv, tks->value);
377 	if(tks->digits > 0 && tks->digits < strlen(sv))
378 		sv[tks->digits] = '\0';
379 
380 	w = stringwidth(e->font, sv);
381 	p.x = q.x;
382 	p.x -= w/2;
383 	p.y = r.min.y - fh - ScalePad;
384 	if(p.x < tks->pixmin)
385 		p.x = tks->pixmin;
386 	if(p.x+w > tks->pixmax)
387 		p.x = tks->pixmax - w;
388 
389 	string(i, p, tkgc(e, fgnd), ZP, e->font, sv);
390 }
391 
392 static void
393 tkscalevert(Tk *tk, Image *i)
394 {
395 	TkEnv *e;
396 	TkScale *tks;
397 	char sv[32];
398 	Image *d, *l;
399 	Rectangle r, r2, sr;
400 	Point p, q;
401 	int fh, v, sw, gw, w, h, len, sl;
402 	int fgnd;
403 
404 	e = tk->env;
405 	tks = TKobj(TkScale, tk);
406 
407 	fh = e->font->height;
408 	fgnd = TkCforegnd;
409 	if (tk->flag & Tkdisabled)
410 		fgnd = TkCdisablefgnd;
411 
412 	r = Rect(0, 0, tk->act.width, tk->act.height);
413 	r = rectaddpt(r, Pt(tk->borderwidth, tk->borderwidth));
414 	r = insetrect(r, tk->highlightwidth);
415 	r = insetrect(r, ScalePad);
416 
417 	if (tks->sv)
418 		r.min.x += tks->digwidth + ScalePad;
419 
420 	if(tks->label != nil) {
421 		p =  stringsize(e->font, tks->label);
422 		r.max.x -= p.x;
423 		string(i, Pt(r.max.x, r.min.y), tkgc(e, fgnd), ZP, e->font, tks->label);
424 		r.max.x -= ScalePad;
425 	}
426 
427 	sr = insetrect(r, ScaleBW);
428 	h = Dy(sr);
429 	w = Dx(sr);
430 	sl = tks->sl + 2*ScaleBW;
431 
432 	l = tkgc(e, TkCbackgndlght);
433 	d = tkgc(e, TkCbackgnddark);
434 	tkbevel(i, r.min, w, h, ScaleBW, d, l);
435 
436 	tks->pixmin = sr.min.y;
437 	tks->pixmax = sr.max.y;
438 
439 	sw = w - 2*ScaleBW;
440 	tks->sw = sw/2;
441 
442 	h -= sl;
443 	if (h <= 0)
444 		h = 1;
445 	p.x = sr.max.x;
446 	p.y = sr.min.y;
447 	if(tks->tick > 0){
448 		int j, t, l;
449 		t = tks->tick;
450 		l = tks->to-tks->from;
451 		if (l < 0)
452 			l = -l;
453 		if (l == 0)
454 			l = 1;
455 		r2.min = p;
456 		r2.max.x = p.x + ScaleBW + ScalePad;
457 		for(j = 0; j <= l; j += t){
458 			r2.min.y = p.y+((vlong)j*h)/l+sl/2;
459 			r2.max.y = r2.min.y+1;
460 			draw(i, r2, tkgc(e, fgnd), nil, ZP);
461 		}
462 	}
463 
464 	v = tks->value-tks->from;
465 	len  = tks->to-tks->from;
466 	if (len != 0)
467 		p.y += ((vlong)v*h)/len;
468 	p.x = sr.min.x;
469 	q = p;
470 	if(ScaleBW > 1) {
471 		q.x++;
472 		gw = sw;
473 	} else
474 		gw = sw-1;
475 	q.y += tks->sl/2 + 1;
476 	if(tk->flag & Tkactivated) {
477 		r2.min = p;
478 		r2.max.x = sr.max.x;
479 		r2.max.y = p.y+sl;
480 		draw(i, r2, tkgc(e, TkCactivebgnd), nil, ZP);
481 	}
482 	switch(tks->relief) {
483 	case TKsunken:
484 		tkbevel(i, p, sw, tks->sl, ScaleBW, d, l);
485 		tkbevel(i, q, gw, 0, 1, l, d);
486 		break;
487 	case TKraised:
488 		tkbevel(i, p, sw, tks->sl, ScaleBW, l, d);
489 		tkbevel(i, q, gw, 0, 1, d, l);
490 		break;
491 	}
492 	tks->pixpos = p.y;
493 	tks->center = p.x + sw/2 + ScaleBW;
494 
495 	if(tks->sv != BoolT)
496 		return;
497 
498 	tkfprint(sv, tks->value);
499 	if(tks->digits > 0 && tks->digits < strlen(sv))
500 		sv[tks->digits] = '\0';
501 
502 	p.x = r.min.x - ScalePad - stringwidth(e->font, sv);
503 	p.y = q.y;
504 	p.y -= fh/2;
505 	if (p.y < tks->pixmin)
506 		p.y = tks->pixmin;
507 	if (p.y + fh > tks->pixmax)
508 		p.y = tks->pixmax - fh;
509 	string(i, p, tkgc(e, fgnd), ZP, e->font, sv);
510 }
511 
512 char*
513 tkdrawscale(Tk *tk, Point orig)
514 {
515 	Point p;
516 	TkEnv *env;
517 	TkScale *tks;
518 	Rectangle r, fr;
519 	Image *i;
520 
521 	tks = TKobj(TkScale, tk);
522 	env = tk->env;
523 
524 	r.min = ZP;
525 	r.max.x = tk->act.width + 2*tk->borderwidth;
526 	r.max.y = tk->act.height + 2*tk->borderwidth;
527 	i = tkitmp(env, r.max, TkCbackgnd);
528 	if(i == nil)
529 		return nil;
530 
531 	if(tks->orient == Tkvertical)
532 		tkscalevert(tk, i);
533 	else
534 		tkscalehoriz(tk, i);
535 
536 	tkdrawrelief(i, tk, ZP, TkCbackgnd, tk->relief);
537 	if (tkhaskeyfocus(tk)) {
538 		fr = insetrect(r, tk->borderwidth);
539 		tkbox(i, fr, tk->highlightwidth, tkgc(env, TkChighlightfgnd));
540 	}
541 
542 	p.x = tk->act.x + orig.x;
543 	p.y = tk->act.y + orig.y;
544 	r = rectaddpt(r, p);
545 	draw(tkimageof(tk), r, i, nil, ZP);
546 
547 	return nil;
548 }
549 
550 /* Widget Commands (+ means implemented)
551 	+cget
552 	+configure
553 	+coords
554 	+get
555 	+identify
556 	+set
557 */
558 
559 static char*
560 tkscaleconf(Tk *tk, char *arg, char **val)
561 {
562 	char *e;
563 	TkGeom g;
564 	int bd;
565 	TkOptab tko[3];
566 	TkScale *tks = TKobj(TkScale, tk);
567 
568 	tko[0].ptr = tk;
569 	tko[0].optab = tkgeneric;
570 	tko[1].ptr = tks;
571 	tko[1].optab = opts;
572 	tko[2].ptr = nil;
573 
574 	if(*arg == '\0')
575 		return tkconflist(tko, val);
576 
577 	g = tk->req;
578 	bd = tk->borderwidth;
579 	e = tkparse(tk->env->top, arg, tko, nil);
580 	tksettransparent(tk, tkhasalpha(tk->env, TkCbackgnd));
581 	tkscalecheckvalue(tk);
582 	tksizescale(tk);
583 	tkgeomchg(tk, &g, bd);
584 
585 	tk->dirty = tkrect(tk, 1);
586 	return e;
587 }
588 
589 char*
590 tkscaleposn(TkEnv *env, Tk *tk, char *arg, int *z)
591 {
592 	int x, y;
593 	TkScale *tks = TKobj(TkScale, tk);
594 	char *e;
595 
596 	e = tkfracword(env->top, &arg, &x, env);
597 	if(e != nil)
598 		return e;
599 	e = tkfracword(env->top, &arg, &y, env);
600 	if(e != nil)
601 		return e;
602 
603 	x = TKF2I(x) + tk->borderwidth;
604 	y = TKF2I(y) + tk->borderwidth;
605 
606 	if(tks->orient == Tkvertical) {
607 		if(z != nil) {
608 			z[0] = x;
609 			z[1] = y;
610 		}
611 		x = y;
612 	}
613 	else {
614 		if(z != nil) {
615 			z[0] = y;
616 			z[1] = x;
617 		}
618 	}
619 	if(x > tks->pixmin && x < tks->pixpos)
620 		return trough1;
621 	else if(x >= tks->pixpos && x < tks->pixpos+tks->sl+2*ScaleBW)
622 		return slider;
623 	else if(x >= tks->pixpos+tks->sl+2*ScaleBW && x < tks->pixmax)
624 		return trough2;
625 
626 	return "";
627 }
628 
629 static char*
630 tkscaleident(Tk *tk, char *arg, char **val)
631 {
632 	char *v;
633 
634 	v = tkscaleposn(tk->env, tk, arg, nil);
635 	if(v == nil)
636 		return TkBadvl;
637 	return tkvalue(val, "%s", v);
638 }
639 
640 static char*
641 tkscalecoords(Tk *tk, char *arg, char **val)
642 {
643 	int p, x, y, l, value;
644 	TkScale *tks = TKobj(TkScale, tk);
645 	char *e;
646 
647 	value = tks->value;
648 	if(arg != nil && arg[0] != '\0') {
649 		e = tkfracword(tk->env->top, &arg, &value, tk->env);
650 		if (e != nil)
651 			return e;
652 	}
653 
654 	value -= tks->from;
655 	p = tks->pixmax - tks->pixmin;
656 	l = TKF2I(tks->to-tks->from);
657 	if (l==0)
658 		p /= 2;
659 	else
660 		p = TKF2I(value*p/l);
661 	p += tks->pixmin;
662 	if(tks->orient == Tkvertical) {
663 		x = tks->center;
664 		y = p;
665 	}
666 	else {
667 		x = p;
668 		y = tks->center;
669 	}
670 	return tkvalue(val, "%d %d", x, y);
671 }
672 
673 static char*
674 tkscaleget(Tk *tk, char *arg, char **val)
675 {
676 	int x, y, value, v, l;
677 	char buf[Tkminitem], *e;
678 	TkScale *tks = TKobj(TkScale, tk);
679 
680 	value = tks->value;
681 	if(arg[0] != '\0') {
682 		e = tkfracword(tk->env->top, &arg, &x, tk->env);
683 		if (e != nil)
684 			return e;
685 		e = tkfracword(tk->env->top, &arg, &y, tk->env);
686 		if (e != nil)
687 			return e;
688 		if(tks->orient == Tkvertical)
689 			v = TKF2I(y) + tk->borderwidth;
690 		else
691 			v = TKF2I(x) + tk->borderwidth;
692 
693 		if(v < tks->pixmin)
694 			value = tks->from;
695 		else
696 		if(v > tks->pixmax)
697 			value = tks->to;
698 		else {
699 			l = tks->pixmax-tks->pixmin;
700 			value = 0;
701 			if (l!=0)
702 				value = v * ((tks->to-tks->from)/l);
703 			value += tks->from;
704 		}
705 		if(tks->res > 0)
706 			value = (value/tks->res)*tks->res;
707 	}
708 	tkfprint(buf, value);
709 	return tkvalue(val, "%s", buf);
710 }
711 
712 static char*
713 tkscaleset(Tk *tk, char *arg, char **val)
714 {
715 	TkScale *tks = TKobj(TkScale, tk);
716 	char *e;
717 
718 	USED(val);
719 
720 	e = tkfracword(tk->env->top, &arg, &tks->value, tk->env);
721 	if (e != nil)
722 		return e;
723 	tkscalecheckvalue(tk);
724 	tk->dirty = tkrect(tk, 1);
725 	return nil;
726 }
727 
728 /* tkScaleMotion %x %y */
729 static char*
730 tkscalemotion(Tk *tk, char *arg, char **val)
731 {
732 	int o, z[2];
733 	char *v;
734 	TkScale *tks = TKobj(TkScale, tk);
735 	extern int tkstylus;
736 
737 	USED(val);
738 	v = tkscaleposn(tk->env, tk, arg, z);
739 	if(v == nil)
740 		return TkBadvl;
741 
742 	o = tk->flag;
743 	if(v != slider || z[0] < tks->center-tks->sw || z[0] > tks->center+tks->sw)
744 		tk->flag &= ~Tkactivated;
745 	else if(tkstylus == 0 || tk->env->top->ctxt->mstate.b != 0)
746 		tk->flag |= Tkactivated;
747 
748 	if((o & Tkactivated) != (tk->flag & Tkactivated))
749 		tk->dirty = tkrect(tk, 1);
750 
751 	return nil;
752 }
753 
754 static char*
755 tkscaledrag(Tk *tk, char *arg, char **val)
756 {
757 	int x, y, v;
758 	char *e, buf[Tkmaxitem], f[32];
759 	TkScale *tks = TKobj(TkScale, tk);
760 
761 	USED(val);
762 	if((tks->flag & Dragging) == 0)
763 		return nil;
764 	if(tks->flag & Autorepeat)
765 		return nil;
766 
767 	e = tkfracword(tk->env->top, &arg, &x, tk->env);
768 	if(e != nil)
769 		return e;
770 	e = tkfracword(tk->env->top, &arg, &y, tk->env);
771 	if(e != nil)
772 		return e;
773 
774 	if(tks->orient == Tkvertical)
775 		v = TKF2I(y) + tk->borderwidth;
776 	else
777 		v = TKF2I(x) + tk->borderwidth;
778 
779 	v -= tks->pix;
780 	x = tks->pixmax-tks->pixmin;
781 	if (x!=tks->sl)
782 		v = tks->base + (vlong)v * (tks->to-tks->from)/(x-tks->sl);
783 	else
784 		v = tks->base;
785 	if(tks->res > 0) {
786 		int a = tks->res / 2;
787 		if (v < 0)
788 			a = -a;
789 		v = ((v+a)/tks->res)*tks->res;
790 	}
791 
792 	tks->value = v;
793 	tkscalecheckvalue(tk);
794 
795 	if(tks->command != nil && tks->jump != BoolT) {
796 		tkfprint(f, tks->value);
797 		snprint(buf, sizeof(buf), "%s %s", tks->command, f);
798 		e = tkexec(tk->env->top, buf, nil);
799 	}
800 	tk->dirty = tkrect(tk, 1);
801 	return e;
802 }
803 
804 static int
805 sgn(int v)
806 {
807 	return v >= 0 ? 1 : -1;
808 }
809 
810 static char*
811 stepscale(Tk *tk, char *pos, int *end)
812 {
813 	TkScale *tks = TKobj(TkScale, tk);
814 	char *e, buf[Tkmaxitem], f[32];
815 	int s;
816 
817 	s = sgn(tks->to - tks->from);
818 	if(pos == trough1) {
819 		tks->value -= s * tks->bigi;
820 	} else {
821 		/* trough2 */
822 		tks->value += s * tks->bigi;
823 	}
824 	s = !tkscalecheckvalue(tk);
825 	if (end != nil)
826 		*end = s;
827 	e = nil;
828 	if(tks->command != nil) {
829 		/* XXX perhaps should only send command if value has actually changed */
830 		tkfprint(f, tks->value);
831 		snprint(buf, sizeof(buf), "%s %s", tks->command, f);
832 		e = tkexec(tk->env->top, buf, nil);
833 	}
834 	return e;
835 }
836 
837 static void
838 screpeat(Tk *tk, void *v, int cancelled)
839 {
840 	char *e, *pos;
841 	int repeat;
842 	TkScale *tks = TKobj(TkScale, tk);
843 
844 	pos = v;
845 	if (cancelled) {
846 		tks->flag &= ~Autorepeat;
847 		return;
848 	}
849 	e = stepscale(tk, pos, &repeat);
850 	if(e != nil || !repeat) {
851 		tks->flag &= ~Autorepeat;
852 		tkcancelrepeat(tk);
853 	}
854 	tk->dirty = tkrect(tk, 1);
855 	tkupdate(tk->env->top);
856 }
857 
858 static char*
859 tkscalebut1p(Tk *tk, char *arg, char **val)
860 {
861 	int z[2];
862 	char *v, *e;
863 	TkScale *tks = TKobj(TkScale, tk);
864 	int repeat;
865 
866 	USED(val);
867 	v = tkscaleposn(tk->env, tk, arg, z);
868 	if(v == nil)
869 		return TkBadvl;
870 
871 	e = nil;
872 	if(v[0] == '\0' || z[0] < tks->center-tks->sw || z[0] > tks->center+tks->sw)
873 		return nil;
874 	if(v == slider) {
875 		tks->flag |= Dragging;
876 		tks->relief = TKsunken;
877 		tks->pix = z[1];
878 		tks->base = tks->value;
879 		tkscalecheckvalue(tk);
880 	} else  {
881 		e = stepscale(tk, v, &repeat);
882 		if (e == nil && repeat) {
883 			tks->flag |= Autorepeat;
884 			tkrepeat(tk, screpeat, v, TkRptpause, TkRptinterval);
885 		}
886 	}
887 
888 	tk->dirty = tkrect(tk, 1);
889 	return e;
890 }
891 
892 static char*
893 tkscalebut1r(Tk *tk, char *arg, char **val)
894 {
895 	TkScale *tks = TKobj(TkScale, tk);
896 	char *e, buf[Tkmaxitem], f[32];
897 	USED(val);
898 	USED(arg);
899 	if(tks->flag & Autorepeat) {
900 		tkcancelrepeat(tk);
901 		tks->flag &= ~Autorepeat;
902 	}
903 	e = nil;
904 	if (tks->flag & Dragging) {
905 		if (tks->command != nil && tks->jump == BoolT && (tks->flag & Dragging)) {
906 			tkfprint(f, tks->value);
907 			snprint(buf, sizeof(buf), "%s %s", tks->command, f);
908 			e = tkexec(tk->env->top, buf, nil);
909 		}
910 		tks->relief = TKraised;
911 		tks->flag &= ~Dragging;
912 		tk->dirty = tkrect(tk, 1);
913 	}
914 	return e;
915 }
916 
917 static char*
918 tkscalekey(Tk *tk, char *arg, char **val)
919 {
920 	char *e;
921 	int key;
922 	char *pos = nil;
923 	USED(arg);
924 	USED(val);
925 
926 	if(tk->flag & Tkdisabled)
927 		return nil;
928 
929 	key = strtol(arg, nil, 0);
930 	if (key == Up || key == Left)
931 		pos = trough1;
932 	else if (key == Down || key == Right)
933 		pos = trough2;
934 	if (pos != nil) {
935 		e = stepscale(tk, pos, nil);
936 		tk->dirty = tkrect(tk, 1);
937 		return e;
938 	}
939 	return nil;
940 }
941 
942 TkCmdtab tkscalecmd[] =
943 {
944 	"cget",			tkscalecget,
945 	"configure",		tkscaleconf,
946 	"set",			tkscaleset,
947 	"identify",		tkscaleident,
948 	"get",			tkscaleget,
949 	"coords",		tkscalecoords,
950 	"tkScaleMotion",	tkscalemotion,
951 	"tkScaleDrag",		tkscaledrag,
952 	"tkScaleBut1P",		tkscalebut1p,
953 	"tkScaleBut1R",		tkscalebut1r,
954 	"tkScaleKey",		tkscalekey,
955 	nil
956 };
957 
958 TkMethod scalemethod = {
959 	"scale",
960 	tkscalecmd,
961 	tkfreescale,
962 	tkdrawscale
963 };
964