1 /*
2  * Copyright (C) 2004 Pat Thoyts <patthoyts@users.sourceforge.net>
3  *
4  * ttk::scale widget.
5  */
6 
7 #include <tk.h>
8 #include <string.h>
9 #include <stdio.h>
10 #include "ttkTheme.h"
11 #include "ttkWidget.h"
12 
13 #define DEF_SCALE_LENGTH "100"
14 
15 #define MAX(a,b) ((a) > (b) ? (a) : (b))
16 #define MIN(a,b) ((a) < (b) ? (a) : (b))
17 
18 /*
19  * Scale widget record
20  */
21 typedef struct
22 {
23     /* slider element options */
24     Tcl_Obj *fromObj;         /* minimum value */
25     Tcl_Obj *toObj;           /* maximum value */
26     Tcl_Obj *valueObj;        /* current value */
27     Tcl_Obj *lengthObj;       /* length of the long axis of the scale */
28     Tcl_Obj *orientObj;       /* widget orientation */
29     int orient;
30 
31     /* widget options */
32     Tcl_Obj *commandObj;
33     Tcl_Obj *variableObj;
34 
35     /* internal state */
36     Ttk_TraceHandle *variableTrace;
37 
38 } ScalePart;
39 
40 typedef struct
41 {
42     WidgetCore core;
43     ScalePart  scale;
44 } Scale;
45 
46 static Tk_OptionSpec ScaleOptionSpecs[] =
47 {
48     {TK_OPTION_STRING, "-command", "command", "Command", "",
49 	Tk_Offset(Scale,scale.commandObj), -1,
50 	TK_OPTION_NULL_OK,0,0},
51     {TK_OPTION_STRING, "-variable", "variable", "Variable", "",
52 	Tk_Offset(Scale,scale.variableObj), -1,
53 	0,0,0},
54     {TK_OPTION_STRING_TABLE, "-orient", "orient", "Orient", "horizontal",
55 	Tk_Offset(Scale,scale.orientObj),
56 	Tk_Offset(Scale,scale.orient), 0,
57 	(ClientData)ttkOrientStrings, STYLE_CHANGED },
58 
59     {TK_OPTION_DOUBLE, "-from", "from", "From", "0",
60 	Tk_Offset(Scale,scale.fromObj), -1, 0, 0, 0},
61     {TK_OPTION_DOUBLE, "-to", "to", "To", "1.0",
62 	Tk_Offset(Scale,scale.toObj), -1, 0, 0, 0},
63     {TK_OPTION_DOUBLE, "-value", "value", "Value", "0",
64 	Tk_Offset(Scale,scale.valueObj), -1, 0, 0, 0},
65     {TK_OPTION_PIXELS, "-length", "length", "Length",
66 	DEF_SCALE_LENGTH, Tk_Offset(Scale,scale.lengthObj), -1, 0, 0,
67     	GEOMETRY_CHANGED},
68 
69     WIDGET_TAKEFOCUS_TRUE,
70     WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs)
71 };
72 
73 static XPoint ValueToPoint(Scale *scalePtr, double value);
74 static double PointToValue(Scale *scalePtr, int x, int y);
75 
76 /* ScaleVariableChanged --
77  * 	Variable trace procedure for scale -variable;
78  * 	Updates the scale's value.
79  * 	If the linked variable is not a valid double,
80  * 	sets the 'invalid' state.
81  */
ScaleVariableChanged(void * recordPtr,const char * value)82 static void ScaleVariableChanged(void *recordPtr, const char *value)
83 {
84     Scale *scale = recordPtr;
85     double v;
86 
87     if (value == NULL || Tcl_GetDouble(0, value, &v) != TCL_OK) {
88 	TtkWidgetChangeState(&scale->core, TTK_STATE_INVALID, 0);
89     } else {
90 	Tcl_Obj *valueObj = Tcl_NewDoubleObj(v);
91 	Tcl_IncrRefCount(valueObj);
92 	Tcl_DecrRefCount(scale->scale.valueObj);
93 	scale->scale.valueObj = valueObj;
94 	TtkWidgetChangeState(&scale->core, 0, TTK_STATE_INVALID);
95     }
96     TtkRedisplayWidget(&scale->core);
97 }
98 
99 /* ScaleInitialize --
100  * 	Scale widget initialization hook.
101  */
ScaleInitialize(Tcl_Interp * interp,void * recordPtr)102 static void ScaleInitialize(Tcl_Interp *interp, void *recordPtr)
103 {
104     Scale *scalePtr = recordPtr;
105     TtkTrackElementState(&scalePtr->core);
106 }
107 
ScaleCleanup(void * recordPtr)108 static void ScaleCleanup(void *recordPtr)
109 {
110     Scale *scale = recordPtr;
111 
112     if (scale->scale.variableTrace) {
113 	Ttk_UntraceVariable(scale->scale.variableTrace);
114 	scale->scale.variableTrace = 0;
115     }
116 }
117 
118 /* ScaleConfigure --
119  * 	Configuration hook.
120  */
ScaleConfigure(Tcl_Interp * interp,void * recordPtr,int mask)121 static int ScaleConfigure(Tcl_Interp *interp, void *recordPtr, int mask)
122 {
123     Scale *scale = recordPtr;
124     Tcl_Obj *varName = scale->scale.variableObj;
125     Ttk_TraceHandle *vt = 0;
126 
127     if (varName != NULL && *Tcl_GetString(varName) != '\0') {
128 	vt = Ttk_TraceVariable(interp,varName, ScaleVariableChanged,recordPtr);
129 	if (!vt) return TCL_ERROR;
130     }
131 
132     if (TtkCoreConfigure(interp, recordPtr, mask) != TCL_OK) {
133 	if (vt) Ttk_UntraceVariable(vt);
134 	return TCL_ERROR;
135     }
136 
137     if (scale->scale.variableTrace) {
138 	Ttk_UntraceVariable(scale->scale.variableTrace);
139     }
140     scale->scale.variableTrace = vt;
141 
142     return TCL_OK;
143 }
144 
145 /* ScalePostConfigure --
146  * 	Post-configuration hook.
147  */
ScalePostConfigure(Tcl_Interp * interp,void * recordPtr,int mask)148 static int ScalePostConfigure(
149     Tcl_Interp *interp, void *recordPtr, int mask)
150 {
151     Scale *scale = recordPtr;
152     int status = TCL_OK;
153 
154     if (scale->scale.variableTrace) {
155 	status = Ttk_FireTrace(scale->scale.variableTrace);
156 	if (WidgetDestroyed(&scale->core)) {
157 	    return TCL_ERROR;
158 	}
159 	if (status != TCL_OK) {
160 	    /* Unset -variable: */
161 	    Ttk_UntraceVariable(scale->scale.variableTrace);
162 	    Tcl_DecrRefCount(scale->scale.variableObj);
163 	    scale->scale.variableTrace = 0;
164 	    scale->scale.variableObj = NULL;
165 	    status = TCL_ERROR;
166 	}
167     }
168 
169     return status;
170 }
171 
172 /* ScaleGetLayout --
173  *	getLayout hook.
174  */
175 static Ttk_Layout
ScaleGetLayout(Tcl_Interp * interp,Ttk_Theme theme,void * recordPtr)176 ScaleGetLayout(Tcl_Interp *interp, Ttk_Theme theme, void *recordPtr)
177 {
178     Scale *scalePtr = recordPtr;
179     return TtkWidgetGetOrientedLayout(
180 	interp, theme, recordPtr, scalePtr->scale.orientObj);
181 }
182 
183 /*
184  * TroughBox --
185  * 	Returns the inner area of the trough element.
186  */
TroughBox(Scale * scalePtr)187 static Ttk_Box TroughBox(Scale *scalePtr)
188 {
189     return Ttk_ClientRegion(scalePtr->core.layout, "trough");
190 }
191 
192 /*
193  * TroughRange --
194  * 	Return the value area of the trough element, adjusted
195  * 	for slider size.
196  */
TroughRange(Scale * scalePtr)197 static Ttk_Box TroughRange(Scale *scalePtr)
198 {
199     Ttk_Box troughBox = TroughBox(scalePtr);
200     Ttk_Element slider = Ttk_FindElement(scalePtr->core.layout,"slider");
201 
202     /*
203      * If this is a scale widget, adjust range for slider:
204      */
205     if (slider) {
206 	Ttk_Box sliderBox = Ttk_ElementParcel(slider);
207 	if (scalePtr->scale.orient == TTK_ORIENT_HORIZONTAL) {
208 	    troughBox.x += sliderBox.width / 2;
209 	    troughBox.width -= sliderBox.width;
210 	} else {
211 	    troughBox.y += sliderBox.height / 2;
212 	    troughBox.height -= sliderBox.height;
213 	}
214     }
215 
216     return troughBox;
217 }
218 
219 /*
220  * ScaleFraction --
221  */
ScaleFraction(Scale * scalePtr,double value)222 static double ScaleFraction(Scale *scalePtr, double value)
223 {
224     double from = 0, to = 1, fraction;
225 
226     Tcl_GetDoubleFromObj(NULL, scalePtr->scale.fromObj, &from);
227     Tcl_GetDoubleFromObj(NULL, scalePtr->scale.toObj, &to);
228 
229     if (from == to) {
230 	return 1.0;
231     }
232 
233     fraction = (value - from) / (to - from);
234 
235     return fraction < 0 ? 0 : fraction > 1 ? 1 : fraction;
236 }
237 
238 /* $scale get ?x y? --
239  * 	Returns the current value of the scale widget, or if $x and
240  * 	$y are specified, the value represented by point @x,y.
241  */
242 static int
ScaleGetCommand(void * recordPtr,Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])243 ScaleGetCommand(
244     void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
245 {
246     Scale *scalePtr = recordPtr;
247     int x, y, r = TCL_OK;
248     double value = 0;
249 
250     if ((objc != 2) && (objc != 4)) {
251 	Tcl_WrongNumArgs(interp, 1, objv, "get ?x y?");
252 	return TCL_ERROR;
253     }
254     if (objc == 2) {
255 	Tcl_SetObjResult(interp, scalePtr->scale.valueObj);
256     } else {
257 	r = Tcl_GetIntFromObj(interp, objv[2], &x);
258 	if (r == TCL_OK)
259 	    r = Tcl_GetIntFromObj(interp, objv[3], &y);
260 	if (r == TCL_OK) {
261 	    value = PointToValue(scalePtr, x, y);
262 	    Tcl_SetObjResult(interp, Tcl_NewDoubleObj(value));
263 	}
264     }
265     return r;
266 }
267 
268 /* $scale set $newValue
269  */
270 static int
ScaleSetCommand(void * recordPtr,Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])271 ScaleSetCommand(
272     void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
273 {
274     Scale *scalePtr = recordPtr;
275     double from = 0.0, to = 1.0, value;
276     int result = TCL_OK;
277 
278     if (objc != 3) {
279 	Tcl_WrongNumArgs(interp, 1, objv, "set value");
280 	return TCL_ERROR;
281     }
282 
283     if (Tcl_GetDoubleFromObj(interp, objv[2], &value) != TCL_OK) {
284 	return TCL_ERROR;
285     }
286 
287     if (scalePtr->core.state & TTK_STATE_DISABLED) {
288 	return TCL_OK;
289     }
290 
291     /* ASSERT: fromObj and toObj are valid doubles.
292      */
293     Tcl_GetDoubleFromObj(interp, scalePtr->scale.fromObj, &from);
294     Tcl_GetDoubleFromObj(interp, scalePtr->scale.toObj, &to);
295 
296     /* Limit new value to between 'from' and 'to':
297      */
298     if (from < to) {
299 	value = value < from ? from : value > to ? to : value;
300     } else {
301 	value = value < to ? to : value > from ? from : value;
302     }
303 
304     /*
305      * Set value:
306      */
307     Tcl_DecrRefCount(scalePtr->scale.valueObj);
308     scalePtr->scale.valueObj = Tcl_NewDoubleObj(value);
309     Tcl_IncrRefCount(scalePtr->scale.valueObj);
310     TtkRedisplayWidget(&scalePtr->core);
311 
312     /*
313      * Set attached variable, if any:
314      */
315     if (scalePtr->scale.variableObj != NULL) {
316 	Tcl_ObjSetVar2(interp, scalePtr->scale.variableObj, NULL,
317 	    scalePtr->scale.valueObj, TCL_GLOBAL_ONLY);
318     }
319     if (WidgetDestroyed(&scalePtr->core)) {
320 	return TCL_ERROR;
321     }
322 
323     /*
324      * Invoke -command, if any:
325      */
326     if (scalePtr->scale.commandObj != NULL) {
327 	Tcl_Obj *cmdObj = Tcl_DuplicateObj(scalePtr->scale.commandObj);
328 	Tcl_IncrRefCount(cmdObj);
329 	Tcl_AppendToObj(cmdObj, " ", 1);
330 	Tcl_AppendObjToObj(cmdObj, scalePtr->scale.valueObj);
331 	result = Tcl_EvalObjEx(interp, cmdObj, TCL_EVAL_GLOBAL);
332 	Tcl_DecrRefCount(cmdObj);
333     }
334 
335     return result;
336 }
337 
338 static int
ScaleCoordsCommand(void * recordPtr,Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])339 ScaleCoordsCommand(
340     void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
341 {
342     Scale *scalePtr = recordPtr;
343     double value;
344     int r = TCL_OK;
345 
346     if (objc < 2 || objc > 3) {
347 	Tcl_WrongNumArgs(interp, 1, objv, "coords ?value?");
348 	return TCL_ERROR;
349     }
350 
351     if (objc == 3) {
352 	r = Tcl_GetDoubleFromObj(interp, objv[2], &value);
353     } else {
354 	r = Tcl_GetDoubleFromObj(interp, scalePtr->scale.valueObj, &value);
355     }
356 
357     if (r == TCL_OK) {
358 	Tcl_Obj *point[2];
359 	XPoint pt = ValueToPoint(scalePtr, value);
360 	point[0] = Tcl_NewIntObj(pt.x);
361 	point[1] = Tcl_NewIntObj(pt.y);
362 	Tcl_SetObjResult(interp, Tcl_NewListObj(2, point));
363     }
364     return r;
365 }
366 
ScaleDoLayout(void * clientData)367 static void ScaleDoLayout(void *clientData)
368 {
369     WidgetCore *corePtr = clientData;
370     Ttk_Element slider = Ttk_FindElement(corePtr->layout, "slider");
371 
372     Ttk_PlaceLayout(corePtr->layout,corePtr->state,Ttk_WinBox(corePtr->tkwin));
373 
374     /* Adjust the slider position:
375      */
376     if (slider) {
377 	Scale *scalePtr = clientData;
378 	Ttk_Box troughBox = TroughBox(scalePtr);
379 	Ttk_Box sliderBox = Ttk_ElementParcel(slider);
380 	double value = 0.0;
381 	double fraction;
382 	int range;
383 
384 	Tcl_GetDoubleFromObj(NULL, scalePtr->scale.valueObj, &value);
385 	fraction = ScaleFraction(scalePtr, value);
386 
387 	if (scalePtr->scale.orient == TTK_ORIENT_HORIZONTAL) {
388 	    range = troughBox.width - sliderBox.width;
389 	    sliderBox.x += (int)(fraction * range);
390 	} else {
391 	    range = troughBox.height - sliderBox.height;
392 	    sliderBox.y += (int)(fraction * range);
393 	}
394 	Ttk_PlaceElement(corePtr->layout, slider, sliderBox);
395     }
396 }
397 
398 /*
399  * ScaleSize --
400  * 	Compute requested size of scale.
401  */
ScaleSize(void * clientData,int * widthPtr,int * heightPtr)402 static int ScaleSize(void *clientData, int *widthPtr, int *heightPtr)
403 {
404     WidgetCore *corePtr = clientData;
405     Scale *scalePtr = clientData;
406     int length;
407 
408     Ttk_LayoutSize(corePtr->layout, corePtr->state, widthPtr, heightPtr);
409 
410     /* Assert the -length configuration option */
411     Tk_GetPixelsFromObj(NULL, corePtr->tkwin,
412 	    scalePtr->scale.lengthObj, &length);
413     if (scalePtr->scale.orient == TTK_ORIENT_VERTICAL) {
414 	*heightPtr = MAX(*heightPtr, length);
415     } else {
416 	*widthPtr = MAX(*widthPtr, length);
417     }
418 
419     return 1;
420 }
421 
422 static double
PointToValue(Scale * scalePtr,int x,int y)423 PointToValue(Scale *scalePtr, int x, int y)
424 {
425     Ttk_Box troughBox = TroughRange(scalePtr);
426     double from = 0, to = 1, fraction;
427 
428     Tcl_GetDoubleFromObj(NULL, scalePtr->scale.fromObj, &from);
429     Tcl_GetDoubleFromObj(NULL, scalePtr->scale.toObj, &to);
430 
431     if (scalePtr->scale.orient == TTK_ORIENT_HORIZONTAL) {
432 	fraction = (double)(x - troughBox.x) / (double)troughBox.width;
433     } else {
434 	fraction = (double)(y - troughBox.y) / (double)troughBox.height;
435     }
436 
437     fraction = fraction < 0 ? 0 : fraction > 1 ? 1 : fraction;
438 
439     return from + fraction * (to-from);
440 }
441 
442 /*
443  * Return the center point in the widget corresponding to the given
444  * value. This point can be used to center the slider.
445  */
446 
447 static XPoint
ValueToPoint(Scale * scalePtr,double value)448 ValueToPoint(Scale *scalePtr, double value)
449 {
450     Ttk_Box troughBox = TroughRange(scalePtr);
451     double fraction = ScaleFraction(scalePtr, value);
452     XPoint pt = {0, 0};
453 
454     if (scalePtr->scale.orient == TTK_ORIENT_HORIZONTAL) {
455 	pt.x = troughBox.x + (int)(fraction * troughBox.width);
456 	pt.y = troughBox.y + troughBox.height / 2;
457     } else {
458 	pt.x = troughBox.x + troughBox.width / 2;
459 	pt.y = troughBox.y + (int)(fraction * troughBox.height);
460     }
461     return pt;
462 }
463 
464 static const Ttk_Ensemble ScaleCommands[] = {
465     { "configure",   TtkWidgetConfigureCommand,0 },
466     { "cget",        TtkWidgetCgetCommand,0 },
467     { "state",       TtkWidgetStateCommand,0 },
468     { "instate",     TtkWidgetInstateCommand,0 },
469     { "identify",    TtkWidgetIdentifyCommand,0 },
470     { "set",         ScaleSetCommand,0 },
471     { "get",         ScaleGetCommand,0 },
472     { "coords",      ScaleCoordsCommand,0 },
473     { 0,0,0 }
474 };
475 
476 static WidgetSpec ScaleWidgetSpec =
477 {
478     "TScale",			/* Class name */
479     sizeof(Scale),		/* record size */
480     ScaleOptionSpecs,		/* option specs */
481     ScaleCommands,		/* widget commands */
482     ScaleInitialize,		/* initialization proc */
483     ScaleCleanup,		/* cleanup proc */
484     ScaleConfigure,		/* configure proc */
485     ScalePostConfigure,		/* postConfigure */
486     ScaleGetLayout, 		/* getLayoutProc */
487     ScaleSize,			/* sizeProc */
488     ScaleDoLayout,		/* layoutProc */
489     TtkWidgetDisplay		/* displayProc */
490 };
491 
492 TTK_BEGIN_LAYOUT(VerticalScaleLayout)
493     TTK_GROUP("Vertical.Scale.trough", TTK_FILL_BOTH,
494 	TTK_NODE("Vertical.Scale.slider", TTK_PACK_TOP) )
495 TTK_END_LAYOUT
496 
TTK_BEGIN_LAYOUT(HorizontalScaleLayout)497 TTK_BEGIN_LAYOUT(HorizontalScaleLayout)
498     TTK_GROUP("Horizontal.Scale.trough", TTK_FILL_BOTH,
499 	TTK_NODE("Horizontal.Scale.slider", TTK_PACK_LEFT) )
500 TTK_END_LAYOUT
501 
502 /*
503  * Initialization.
504  */
505 MODULE_SCOPE
506 void TtkScale_Init(Tcl_Interp *interp)
507 {
508     Ttk_Theme theme = Ttk_GetDefaultTheme(interp);
509 
510     Ttk_RegisterLayout(theme, "Vertical.TScale", VerticalScaleLayout);
511     Ttk_RegisterLayout(theme, "Horizontal.TScale", HorizontalScaleLayout);
512 
513     RegisterWidget(interp, "ttk::scale", &ScaleWidgetSpec);
514 }
515 
516