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