/* * Copyright (C) 2004 Pat Thoyts * * ttk::scale widget. */ #include "tkInt.h" #include "ttkTheme.h" #include "ttkWidget.h" #define DEF_SCALE_LENGTH "100" #define MAX(a,b) ((a) > (b) ? (a) : (b)) #define MIN(a,b) ((a) < (b) ? (a) : (b)) /* Bit fields for OptionSpec mask field: */ #define STATE_CHANGED (0x100) /* -state option changed */ /* * Scale widget record */ typedef struct { /* slider element options */ Tcl_Obj *fromObj; /* minimum value */ Tcl_Obj *toObj; /* maximum value */ Tcl_Obj *valueObj; /* current value */ Tcl_Obj *lengthObj; /* length of the long axis of the scale */ Tcl_Obj *orientObj; /* widget orientation */ int orient; /* widget options */ Tcl_Obj *commandObj; Tcl_Obj *variableObj; /* internal state */ Ttk_TraceHandle *variableTrace; /* * Compatibility/legacy options: */ Tcl_Obj *stateObj; } ScalePart; typedef struct { WidgetCore core; ScalePart scale; } Scale; static const Tk_OptionSpec ScaleOptionSpecs[] = { {TK_OPTION_STRING, "-command", "command", "Command", "", offsetof(Scale,scale.commandObj), TCL_INDEX_NONE, TK_OPTION_NULL_OK,0,0}, {TK_OPTION_STRING, "-variable", "variable", "Variable", "", offsetof(Scale,scale.variableObj), TCL_INDEX_NONE, 0, 0, 0}, {TK_OPTION_STRING_TABLE, "-orient", "orient", "Orient", "horizontal", offsetof(Scale,scale.orientObj), offsetof(Scale,scale.orient), 0, (void *)ttkOrientStrings, STYLE_CHANGED }, {TK_OPTION_DOUBLE, "-from", "from", "From", "0", offsetof(Scale,scale.fromObj), TCL_INDEX_NONE, 0, 0, 0}, {TK_OPTION_DOUBLE, "-to", "to", "To", "1.0", offsetof(Scale,scale.toObj), TCL_INDEX_NONE, 0, 0, 0}, {TK_OPTION_DOUBLE, "-value", "value", "Value", "0", offsetof(Scale,scale.valueObj), TCL_INDEX_NONE, 0, 0, 0}, {TK_OPTION_PIXELS, "-length", "length", "Length", DEF_SCALE_LENGTH, offsetof(Scale,scale.lengthObj), TCL_INDEX_NONE, 0, 0, GEOMETRY_CHANGED}, {TK_OPTION_STRING, "-state", "state", "State", "normal", offsetof(Scale,scale.stateObj), TCL_INDEX_NONE, 0, 0, STATE_CHANGED}, WIDGET_TAKEFOCUS_TRUE, WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs) }; static XPoint ValueToPoint(Scale *scalePtr, double value); static double PointToValue(Scale *scalePtr, int x, int y); /* ScaleVariableChanged -- * Variable trace procedure for scale -variable; * Updates the scale's value. * If the linked variable is not a valid double, * sets the 'invalid' state. */ static void ScaleVariableChanged(void *recordPtr, const char *value) { Scale *scale = (Scale *)recordPtr; double v; if (value == NULL || Tcl_GetDouble(0, value, &v) != TCL_OK) { TtkWidgetChangeState(&scale->core, TTK_STATE_INVALID, 0); } else { Tcl_Obj *valueObj = Tcl_NewDoubleObj(v); Tcl_IncrRefCount(valueObj); Tcl_DecrRefCount(scale->scale.valueObj); scale->scale.valueObj = valueObj; TtkWidgetChangeState(&scale->core, 0, TTK_STATE_INVALID); } TtkRedisplayWidget(&scale->core); } /* ScaleInitialize -- * Scale widget initialization hook. */ static void ScaleInitialize(Tcl_Interp *dummy, void *recordPtr) { Scale *scalePtr = (Scale *)recordPtr; (void)dummy; TtkTrackElementState(&scalePtr->core); } static void ScaleCleanup(void *recordPtr) { Scale *scale = (Scale *)recordPtr; if (scale->scale.variableTrace) { Ttk_UntraceVariable(scale->scale.variableTrace); scale->scale.variableTrace = 0; } } /* ScaleConfigure -- * Configuration hook. */ static int ScaleConfigure(Tcl_Interp *interp, void *recordPtr, int mask) { Scale *scale = (Scale *)recordPtr; Tcl_Obj *varName = scale->scale.variableObj; Ttk_TraceHandle *vt = 0; if (varName != NULL && *Tcl_GetString(varName) != '\0') { vt = Ttk_TraceVariable(interp,varName, ScaleVariableChanged,recordPtr); if (!vt) return TCL_ERROR; } if (TtkCoreConfigure(interp, recordPtr, mask) != TCL_OK) { if (vt) Ttk_UntraceVariable(vt); return TCL_ERROR; } if (scale->scale.variableTrace) { Ttk_UntraceVariable(scale->scale.variableTrace); } scale->scale.variableTrace = vt; if (mask & STATE_CHANGED) { TtkCheckStateOption(&scale->core, scale->scale.stateObj); } return TCL_OK; } /* ScalePostConfigure -- * Post-configuration hook. */ static int ScalePostConfigure( Tcl_Interp *dummy, void *recordPtr, int mask) { Scale *scale = (Scale *)recordPtr; int status = TCL_OK; (void)dummy; (void)mask; if (scale->scale.variableTrace) { status = Ttk_FireTrace(scale->scale.variableTrace); if (WidgetDestroyed(&scale->core)) { return TCL_ERROR; } if (status != TCL_OK) { /* Unset -variable: */ Ttk_UntraceVariable(scale->scale.variableTrace); Tcl_DecrRefCount(scale->scale.variableObj); scale->scale.variableTrace = 0; scale->scale.variableObj = NULL; status = TCL_ERROR; } } return status; } /* ScaleGetLayout -- * getLayout hook. */ static Ttk_Layout ScaleGetLayout(Tcl_Interp *interp, Ttk_Theme theme, void *recordPtr) { Scale *scalePtr = (Scale *)recordPtr; return TtkWidgetGetOrientedLayout( interp, theme, recordPtr, scalePtr->scale.orientObj); } /* * TroughBox -- * Returns the inner area of the trough element. */ static Ttk_Box TroughBox(Scale *scalePtr) { return Ttk_ClientRegion(scalePtr->core.layout, "trough"); } /* * TroughRange -- * Return the value area of the trough element, adjusted * for slider size. */ static Ttk_Box TroughRange(Scale *scalePtr) { Ttk_Box troughBox = TroughBox(scalePtr); Ttk_Element slider = Ttk_FindElement(scalePtr->core.layout,"slider"); /* * If this is a scale widget, adjust range for slider: */ if (slider) { Ttk_Box sliderBox = Ttk_ElementParcel(slider); if (scalePtr->scale.orient == TTK_ORIENT_HORIZONTAL) { troughBox.x += sliderBox.width / 2; troughBox.width -= sliderBox.width; } else { troughBox.y += sliderBox.height / 2; troughBox.height -= sliderBox.height; } } return troughBox; } /* * ScaleFraction -- */ static double ScaleFraction(Scale *scalePtr, double value) { double from = 0, to = 1, fraction; Tcl_GetDoubleFromObj(NULL, scalePtr->scale.fromObj, &from); Tcl_GetDoubleFromObj(NULL, scalePtr->scale.toObj, &to); if (from == to) { return 1.0; } fraction = (value - from) / (to - from); return fraction < 0 ? 0 : fraction > 1 ? 1 : fraction; } /* $scale get ?x y? -- * Returns the current value of the scale widget, or if $x and * $y are specified, the value represented by point @x,y. */ static int ScaleGetCommand( void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Scale *scalePtr = (Scale *)recordPtr; int x, y, r = TCL_OK; double value = 0; if ((objc != 2) && (objc != 4)) { Tcl_WrongNumArgs(interp, 1, objv, "get ?x y?"); return TCL_ERROR; } if (objc == 2) { Tcl_SetObjResult(interp, scalePtr->scale.valueObj); } else { r = Tcl_GetIntFromObj(interp, objv[2], &x); if (r == TCL_OK) r = Tcl_GetIntFromObj(interp, objv[3], &y); if (r == TCL_OK) { value = PointToValue(scalePtr, x, y); Tcl_SetObjResult(interp, Tcl_NewDoubleObj(value)); } } return r; } /* $scale set $newValue */ static int ScaleSetCommand( void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Scale *scalePtr = (Scale *)recordPtr; double from = 0.0, to = 1.0, value; int result = TCL_OK; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "set value"); return TCL_ERROR; } if (Tcl_GetDoubleFromObj(interp, objv[2], &value) != TCL_OK) { return TCL_ERROR; } if (scalePtr->core.state & TTK_STATE_DISABLED) { return TCL_OK; } /* ASSERT: fromObj and toObj are valid doubles. */ Tcl_GetDoubleFromObj(interp, scalePtr->scale.fromObj, &from); Tcl_GetDoubleFromObj(interp, scalePtr->scale.toObj, &to); /* Limit new value to between 'from' and 'to': */ if (from < to) { value = value < from ? from : value > to ? to : value; } else { value = value < to ? to : value > from ? from : value; } /* * Set value: */ Tcl_DecrRefCount(scalePtr->scale.valueObj); scalePtr->scale.valueObj = Tcl_NewDoubleObj(value); Tcl_IncrRefCount(scalePtr->scale.valueObj); TtkRedisplayWidget(&scalePtr->core); /* * Set attached variable, if any: */ if (scalePtr->scale.variableObj != NULL) { Tcl_ObjSetVar2(interp, scalePtr->scale.variableObj, NULL, scalePtr->scale.valueObj, TCL_GLOBAL_ONLY); } if (WidgetDestroyed(&scalePtr->core)) { return TCL_ERROR; } /* * Invoke -command, if any: */ if (scalePtr->scale.commandObj != NULL) { Tcl_Obj *cmdObj = Tcl_DuplicateObj(scalePtr->scale.commandObj); Tcl_IncrRefCount(cmdObj); Tcl_AppendToObj(cmdObj, " ", 1); Tcl_AppendObjToObj(cmdObj, scalePtr->scale.valueObj); result = Tcl_EvalObjEx(interp, cmdObj, TCL_EVAL_GLOBAL); Tcl_DecrRefCount(cmdObj); } return result; } static int ScaleCoordsCommand( void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Scale *scalePtr = (Scale *)recordPtr; double value; int r = TCL_OK; if (objc < 2 || objc > 3) { Tcl_WrongNumArgs(interp, 1, objv, "coords ?value?"); return TCL_ERROR; } if (objc == 3) { r = Tcl_GetDoubleFromObj(interp, objv[2], &value); } else { r = Tcl_GetDoubleFromObj(interp, scalePtr->scale.valueObj, &value); } if (r == TCL_OK) { Tcl_Obj *point[2]; XPoint pt = ValueToPoint(scalePtr, value); point[0] = Tcl_NewWideIntObj(pt.x); point[1] = Tcl_NewWideIntObj(pt.y); Tcl_SetObjResult(interp, Tcl_NewListObj(2, point)); } return r; } static void ScaleDoLayout(void *clientData) { WidgetCore *corePtr = (WidgetCore *)clientData; Ttk_Element slider = Ttk_FindElement(corePtr->layout, "slider"); Ttk_PlaceLayout(corePtr->layout,corePtr->state,Ttk_WinBox(corePtr->tkwin)); /* Adjust the slider position: */ if (slider) { Scale *scalePtr = (Scale *)clientData; Ttk_Box troughBox = TroughBox(scalePtr); Ttk_Box sliderBox = Ttk_ElementParcel(slider); double value = 0.0; double fraction; int range; Tcl_GetDoubleFromObj(NULL, scalePtr->scale.valueObj, &value); fraction = ScaleFraction(scalePtr, value); if (scalePtr->scale.orient == TTK_ORIENT_HORIZONTAL) { range = troughBox.width - sliderBox.width; sliderBox.x += (int)(fraction * range); } else { range = troughBox.height - sliderBox.height; sliderBox.y += (int)(fraction * range); } Ttk_PlaceElement(corePtr->layout, slider, sliderBox); } } /* * ScaleSize -- * Compute requested size of scale. */ static int ScaleSize(void *clientData, int *widthPtr, int *heightPtr) { WidgetCore *corePtr = (WidgetCore *)clientData; Scale *scalePtr = (Scale *)clientData; int length; Ttk_LayoutSize(corePtr->layout, corePtr->state, widthPtr, heightPtr); /* Assert the -length configuration option */ Tk_GetPixelsFromObj(NULL, corePtr->tkwin, scalePtr->scale.lengthObj, &length); if (scalePtr->scale.orient == TTK_ORIENT_VERTICAL) { *heightPtr = MAX(*heightPtr, length); } else { *widthPtr = MAX(*widthPtr, length); } return 1; } static double PointToValue(Scale *scalePtr, int x, int y) { Ttk_Box troughBox = TroughRange(scalePtr); double from = 0, to = 1, fraction; Tcl_GetDoubleFromObj(NULL, scalePtr->scale.fromObj, &from); Tcl_GetDoubleFromObj(NULL, scalePtr->scale.toObj, &to); if (scalePtr->scale.orient == TTK_ORIENT_HORIZONTAL) { fraction = (double)(x - troughBox.x) / (double)troughBox.width; } else { fraction = (double)(y - troughBox.y) / (double)troughBox.height; } fraction = fraction < 0 ? 0 : fraction > 1 ? 1 : fraction; return from + fraction * (to-from); } /* * Return the center point in the widget corresponding to the given * value. This point can be used to center the slider. */ static XPoint ValueToPoint(Scale *scalePtr, double value) { Ttk_Box troughBox = TroughRange(scalePtr); double fraction = ScaleFraction(scalePtr, value); XPoint pt = {0, 0}; if (scalePtr->scale.orient == TTK_ORIENT_HORIZONTAL) { pt.x = troughBox.x + (int)(fraction * troughBox.width); pt.y = troughBox.y + troughBox.height / 2; } else { pt.x = troughBox.x + troughBox.width / 2; pt.y = troughBox.y + (int)(fraction * troughBox.height); } return pt; } static const Ttk_Ensemble ScaleCommands[] = { { "configure", TtkWidgetConfigureCommand,0 }, { "cget", TtkWidgetCgetCommand,0 }, { "state", TtkWidgetStateCommand,0 }, { "instate", TtkWidgetInstateCommand,0 }, { "identify", TtkWidgetIdentifyCommand,0 }, { "set", ScaleSetCommand,0 }, { "get", ScaleGetCommand,0 }, { "coords", ScaleCoordsCommand,0 }, { 0,0,0 } }; static const WidgetSpec ScaleWidgetSpec = { "TScale", /* Class name */ sizeof(Scale), /* record size */ ScaleOptionSpecs, /* option specs */ ScaleCommands, /* widget commands */ ScaleInitialize, /* initialization proc */ ScaleCleanup, /* cleanup proc */ ScaleConfigure, /* configure proc */ ScalePostConfigure, /* postConfigure */ ScaleGetLayout, /* getLayoutProc */ ScaleSize, /* sizeProc */ ScaleDoLayout, /* layoutProc */ TtkWidgetDisplay /* displayProc */ }; TTK_BEGIN_LAYOUT(VerticalScaleLayout) TTK_GROUP("Vertical.Scale.trough", TTK_FILL_BOTH, TTK_NODE("Vertical.Scale.slider", TTK_PACK_TOP) ) TTK_END_LAYOUT TTK_BEGIN_LAYOUT(HorizontalScaleLayout) TTK_GROUP("Horizontal.Scale.trough", TTK_FILL_BOTH, TTK_NODE("Horizontal.Scale.slider", TTK_PACK_LEFT) ) TTK_END_LAYOUT /* * Initialization. */ MODULE_SCOPE void TtkScale_Init(Tcl_Interp *interp) { Ttk_Theme theme = Ttk_GetDefaultTheme(interp); Ttk_RegisterLayout(theme, "Vertical.TScale", VerticalScaleLayout); Ttk_RegisterLayout(theme, "Horizontal.TScale", HorizontalScaleLayout); RegisterWidget(interp, "ttk::scale", &ScaleWidgetSpec); }