535 lines
14 KiB
C
535 lines
14 KiB
C
/*
|
|
* Copyright (C) 2004 Pat Thoyts <patthoyts@users.sourceforge.net>
|
|
*
|
|
* 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 Tk_OptionSpec ScaleOptionSpecs[] =
|
|
{
|
|
{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,
|
|
(void *)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},
|
|
|
|
{TK_OPTION_STRING, "-state", "state", "State",
|
|
"normal", Tk_Offset(Scale,scale.stateObj), -1,
|
|
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_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 = (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 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);
|
|
}
|
|
|