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