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