diff options
Diffstat (limited to 'generic/ttk/ttkScale.c')
-rw-r--r-- | generic/ttk/ttkScale.c | 516 |
1 files changed, 516 insertions, 0 deletions
diff --git a/generic/ttk/ttkScale.c b/generic/ttk/ttkScale.c new file mode 100644 index 0000000..0745ebd --- /dev/null +++ b/generic/ttk/ttkScale.c @@ -0,0 +1,516 @@ +/* + * Copyright (C) 2004 Pat Thoyts <patthoyts@users.sourceforge.net> + * + * ttk::scale widget. + */ + +#include <tk.h> +#include <string.h> +#include <stdio.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)) + +/* + * 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; + +} ScalePart; + +typedef struct +{ + WidgetCore core; + ScalePart scale; +} Scale; + +static Tk_OptionSpec ScaleOptionSpecs[] = +{ + WIDGET_TAKES_FOCUS, + + {TK_OPTION_STRING, "-command", "command", "Command", "", + Tk_Offset(Scale,scale.commandObj), -1, + TK_OPTION_NULL_OK,0,0}, + {TK_OPTION_STRING, "-variable", "variable", "Variable", "", + Tk_Offset(Scale,scale.variableObj), -1, + 0,0,0}, + {TK_OPTION_STRING_TABLE, "-orient", "orient", "Orient", "horizontal", + Tk_Offset(Scale,scale.orientObj), + Tk_Offset(Scale,scale.orient), 0, + (ClientData)ttkOrientStrings, STYLE_CHANGED }, + + {TK_OPTION_DOUBLE, "-from", "from", "From", "0", + Tk_Offset(Scale,scale.fromObj), -1, 0, 0, 0}, + {TK_OPTION_DOUBLE, "-to", "to", "To", "1.0", + Tk_Offset(Scale,scale.toObj), -1, 0, 0, 0}, + {TK_OPTION_DOUBLE, "-value", "value", "Value", "0", + Tk_Offset(Scale,scale.valueObj), -1, 0, 0, 0}, + {TK_OPTION_PIXELS, "-length", "length", "Length", + DEF_SCALE_LENGTH, Tk_Offset(Scale,scale.lengthObj), -1, 0, 0, + GEOMETRY_CHANGED}, + + 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 = 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 *interp, void *recordPtr) +{ + Scale *scalePtr = recordPtr; + TtkTrackElementState(&scalePtr->core); +} + +static void ScaleCleanup(void *recordPtr) +{ + 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 = 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; + + return TCL_OK; +} + +/* ScalePostConfigure -- + * Post-configuration hook. + */ +static int ScalePostConfigure( + Tcl_Interp *interp, void *recordPtr, int mask) +{ + Scale *scale = recordPtr; + int status = TCL_OK; + + 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 = 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 = 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 = 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 = 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_NewIntObj(pt.x); + point[1] = Tcl_NewIntObj(pt.y); + Tcl_SetObjResult(interp, Tcl_NewListObj(2, point)); + } + return r; +} + +static void ScaleDoLayout(void *clientData) +{ + WidgetCore *corePtr = 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 = 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 = clientData; + Scale *scalePtr = 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 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); +} + |