From 9a046cd669466105bb9bd4d137b8b227ff958448 Mon Sep 17 00:00:00 2001 From: jenglish Date: Tue, 16 Aug 2011 17:03:20 +0000 Subject: ttk::range widget implementation, contributed by Goodwin Lawlor [SF#3391905] --- generic/ttk/ttkClamTheme.c | 2 + generic/ttk/ttkDefaultTheme.c | 2 + generic/ttk/ttkElements.c | 2 + generic/ttk/ttkInit.c | 2 + generic/ttk/ttkRange.c | 762 ++++++++++++++++++++++++++++++++++++++++++ library/ttk/altTheme.tcl | 3 + library/ttk/classicTheme.tcl | 3 + library/ttk/defaults.tcl | 2 + library/ttk/range.tcl | 135 ++++++++ library/ttk/ttk.tcl | 1 + library/ttk/vistaTheme.tcl | 32 ++ library/ttk/winTheme.tcl | 2 + unix/Makefile.in | 8 +- win/makefile.vc | 1 + win/ttkWinXPTheme.c | 32 ++ 15 files changed, 987 insertions(+), 2 deletions(-) create mode 100644 generic/ttk/ttkRange.c create mode 100644 library/ttk/range.tcl diff --git a/generic/ttk/ttkClamTheme.c b/generic/ttk/ttkClamTheme.c index 572f630..c182f01 100644 --- a/generic/ttk/ttkClamTheme.c +++ b/generic/ttk/ttkClamTheme.c @@ -955,6 +955,8 @@ TtkClamTheme_Init(Tcl_Interp *interp) Ttk_RegisterElement(interp, theme, "client", &ClientElementSpec, NULL); Ttk_RegisterElement(interp, theme, "slider", &SliderElementSpec, NULL); + Ttk_RegisterElement(interp, theme, "minslider", &SliderElementSpec, NULL); + Ttk_RegisterElement(interp, theme, "maxslider", &SliderElementSpec, NULL); Ttk_RegisterElement(interp, theme, "bar", &PbarElementSpec, NULL); Ttk_RegisterElement(interp, theme, "pbar", &PbarElementSpec, NULL); diff --git a/generic/ttk/ttkDefaultTheme.c b/generic/ttk/ttkDefaultTheme.c index d2deee8..850a476 100644 --- a/generic/ttk/ttkDefaultTheme.c +++ b/generic/ttk/ttkDefaultTheme.c @@ -1104,6 +1104,8 @@ MODULE_SCOPE int TtkAltTheme_Init(Tcl_Interp *interp) Ttk_RegisterElement(interp, theme, "trough", &TroughElementSpec, NULL); Ttk_RegisterElement(interp, theme, "thumb", &ThumbElementSpec, NULL); Ttk_RegisterElement(interp, theme, "slider", &SliderElementSpec, NULL); + Ttk_RegisterElement(interp, theme, "minslider", &SliderElementSpec, NULL); + Ttk_RegisterElement(interp, theme, "maxslider", &SliderElementSpec, NULL); Ttk_RegisterElement(interp, theme, "uparrow", &ArrowElementSpec, &ArrowElements[0]); diff --git a/generic/ttk/ttkElements.c b/generic/ttk/ttkElements.c index 22af1d6..7d0497a 100644 --- a/generic/ttk/ttkElements.c +++ b/generic/ttk/ttkElements.c @@ -1258,6 +1258,8 @@ void TtkElements_Init(Tcl_Interp *interp) Ttk_RegisterElement(interp, theme, "trough", &TroughElementSpec, NULL); Ttk_RegisterElement(interp, theme, "thumb", &ThumbElementSpec, NULL); Ttk_RegisterElement(interp, theme, "slider", &SliderElementSpec, NULL); + Ttk_RegisterElement(interp, theme, "minslider", &SliderElementSpec, NULL); + Ttk_RegisterElement(interp, theme, "maxslider", &SliderElementSpec, NULL); Ttk_RegisterElement(interp, theme, "pbar", &PbarElementSpec, NULL); Ttk_RegisterElement(interp, theme, "separator", diff --git a/generic/ttk/ttkInit.c b/generic/ttk/ttkInit.c index be30ee7..59d8705 100644 --- a/generic/ttk/ttkInit.c +++ b/generic/ttk/ttkInit.c @@ -213,6 +213,7 @@ extern void TtkFrame_Init(Tcl_Interp *); extern void TtkNotebook_Init(Tcl_Interp *); extern void TtkPanedwindow_Init(Tcl_Interp *); extern void TtkProgressbar_Init(Tcl_Interp *); +extern void TtkRange_Init(Tcl_Interp *); extern void TtkScale_Init(Tcl_Interp *); extern void TtkScrollbar_Init(Tcl_Interp *); extern void TtkSeparator_Init(Tcl_Interp *); @@ -230,6 +231,7 @@ static void RegisterWidgets(Tcl_Interp *interp) TtkNotebook_Init(interp); TtkPanedwindow_Init(interp); TtkProgressbar_Init(interp); + TtkRange_Init(interp); TtkScale_Init(interp); TtkScrollbar_Init(interp); TtkSeparator_Init(interp); diff --git a/generic/ttk/ttkRange.c b/generic/ttk/ttkRange.c new file mode 100644 index 0000000..618b432 --- /dev/null +++ b/generic/ttk/ttkRange.c @@ -0,0 +1,762 @@ +/* + * Copyright (C) 2011 Goodwin Lawlor goodwin.lawlor@gmail.com + * + * ttk::range widget. + */ + +#include +#include +#include +#include "ttkTheme.h" +#include "ttkWidget.h" + +#define DEF_RANGE_LENGTH "100" + +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + +/* + * Range widget record + */ +typedef struct +{ + /* slider element options */ + Tcl_Obj *fromObj; /* from value - lower bound*/ + Tcl_Obj *toObj; /* to value - upper bound*/ + Tcl_Obj *minvalueObj; /* min value */ + Tcl_Obj *maxvalueObj; /* max value */ + Tcl_Obj *lengthObj; /* length of the long axis of the range */ + Tcl_Obj *orientObj; /* widget orientation */ + int orient; + + /* widget options */ + Tcl_Obj *commandObj; + Tcl_Obj *minvariableObj; + Tcl_Obj *maxvariableObj; + + /* internal state */ + Ttk_TraceHandle *minvariableTrace; + Ttk_TraceHandle *maxvariableTrace; + +} RangePart; + +typedef struct +{ + WidgetCore core; + RangePart range; +} Range; + +static Tk_OptionSpec RangeOptionSpecs[] = +{ + WIDGET_TAKES_FOCUS, + + {TK_OPTION_STRING, "-command", "command", "Command", "", + Tk_Offset(Range,range.commandObj), -1, + TK_OPTION_NULL_OK,0,0}, + {TK_OPTION_STRING, "-minvariable", "minvariable", "Minvariable", "", + Tk_Offset(Range,range.minvariableObj), -1, + 0,0,0}, + {TK_OPTION_STRING, "-maxvariable", "maxvariable", "Maxvariable", "", + Tk_Offset(Range,range.maxvariableObj), -1, + 0,0,0}, + {TK_OPTION_STRING_TABLE, "-orient", "orient", "Orient", "horizontal", + Tk_Offset(Range,range.orientObj), + Tk_Offset(Range,range.orient), 0, + (ClientData)ttkOrientStrings, STYLE_CHANGED }, + + {TK_OPTION_DOUBLE, "-from", "from", "From", "0", + Tk_Offset(Range,range.fromObj), -1, 0, 0, 0}, + {TK_OPTION_DOUBLE, "-to", "to", "To", "1.0", + Tk_Offset(Range,range.toObj), -1, 0, 0, 0}, + {TK_OPTION_DOUBLE, "-minvalue", "minvalue", "Minvalue", "0", + Tk_Offset(Range,range.minvalueObj), -1, 0, 0, 0}, + {TK_OPTION_DOUBLE, "-maxvalue", "maxvalue", "Maxvalue", "1.0", + Tk_Offset(Range,range.maxvalueObj), -1, 0, 0, 0}, + {TK_OPTION_PIXELS, "-length", "length", "Length", + DEF_RANGE_LENGTH, Tk_Offset(Range,range.lengthObj), -1, 0, 0, + GEOMETRY_CHANGED}, + + WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs) +}; + +static XPoint ValueToPoint(Range *rangePtr, double value); +static double PointToValue(Range *rangePtr, int x, int y); + +/* RangeMinVariableChanged -- + * Variable trace procedure for range -variable; + * Updates the range's value. + * If the linked variable is not a valid double, + * sets the 'invalid' state. + */ +static void RangeMinVariableChanged(void *recordPtr, const char *minvalue) +{ + Range *range = recordPtr; + double v; + + if (minvalue == NULL || Tcl_GetDouble(0, minvalue, &v) != TCL_OK) { + TtkWidgetChangeState(&range->core, TTK_STATE_INVALID, 0); + } else { + Tcl_Obj *minvalueObj = Tcl_NewDoubleObj(v); + Tcl_IncrRefCount(minvalueObj); + Tcl_DecrRefCount(range->range.minvalueObj); + range->range.minvalueObj = minvalueObj; + TtkWidgetChangeState(&range->core, 0, TTK_STATE_INVALID); + } + TtkRedisplayWidget(&range->core); +} + +/* RangeMaxVariableChanged -- + * Variable trace procedure for range -variable; + * Updates the range's value. + * If the linked variable is not a valid double, + * sets the 'invalid' state. + */ +static void RangeMaxVariableChanged(void *recordPtr, const char *maxvalue) +{ + Range *range = recordPtr; + double v; + + if (maxvalue == NULL || Tcl_GetDouble(0, maxvalue, &v) != TCL_OK) { + TtkWidgetChangeState(&range->core, TTK_STATE_INVALID, 0); + } else { + Tcl_Obj *maxvalueObj = Tcl_NewDoubleObj(v); + Tcl_IncrRefCount(maxvalueObj); + Tcl_DecrRefCount(range->range.maxvalueObj); + range->range.maxvalueObj = maxvalueObj; + TtkWidgetChangeState(&range->core, 0, TTK_STATE_INVALID); + } + TtkRedisplayWidget(&range->core); +} + +/* RangeInitialize -- + * Range widget initialization hook. + */ +static void RangeInitialize(Tcl_Interp *interp, void *recordPtr) +{ + Range *rangePtr = recordPtr; + TtkTrackElementState(&rangePtr->core); + +} + +static void RangeCleanup(void *recordPtr) +{ + Range *range = recordPtr; + + if (range->range.minvariableTrace) { + Ttk_UntraceVariable(range->range.minvariableTrace); + range->range.minvariableTrace = 0; + } + + if (range->range.maxvariableTrace) { + Ttk_UntraceVariable(range->range.maxvariableTrace); + range->range.maxvariableTrace = 0; + } +} + +/* RangeConfigure -- + * Configuration hook. + */ +static int RangeConfigure(Tcl_Interp *interp, void *recordPtr, int mask) +{ + Range *range = recordPtr; + Tcl_Obj *minvarName = range->range.minvariableObj; + Tcl_Obj *maxvarName = range->range.maxvariableObj; + Ttk_TraceHandle *minvt = 0; + Ttk_TraceHandle *maxvt = 0; + + if (minvarName != NULL && *Tcl_GetString(minvarName) != '\0') { + minvt = Ttk_TraceVariable(interp,minvarName, RangeMinVariableChanged,recordPtr); + if (!minvt) return TCL_ERROR; + } + + if (maxvarName != NULL && *Tcl_GetString(maxvarName) != '\0') { + maxvt = Ttk_TraceVariable(interp,maxvarName, RangeMaxVariableChanged,recordPtr); + if (!maxvt) return TCL_ERROR; + } + + if (TtkCoreConfigure(interp, recordPtr, mask) != TCL_OK) { + if (minvt) Ttk_UntraceVariable(minvt); + if (maxvt) Ttk_UntraceVariable(maxvt); + return TCL_ERROR; + } + + if (range->range.minvariableTrace) { + Ttk_UntraceVariable(range->range.minvariableTrace); + } + range->range.minvariableTrace = minvt; + + if (range->range.maxvariableTrace) { + Ttk_UntraceVariable(range->range.maxvariableTrace); + } + range->range.maxvariableTrace = maxvt; + + return TCL_OK; +} + +/* RangePostConfigure -- + * Post-configuration hook. + */ +static int RangePostConfigure( + Tcl_Interp *interp, void *recordPtr, int mask) +{ + Range *range = recordPtr; + int minstatus = TCL_OK; + int maxstatus = TCL_OK; + + if (range->range.minvariableTrace) { + minstatus = Ttk_FireTrace(range->range.minvariableTrace); + if (WidgetDestroyed(&range->core)) { + return TCL_ERROR; + } + if (minstatus != TCL_OK) { + /* Unset -variable: */ + Ttk_UntraceVariable(range->range.minvariableTrace); + Tcl_DecrRefCount(range->range.minvariableObj); + range->range.minvariableTrace = 0; + range->range.minvariableObj = NULL; + minstatus = TCL_ERROR; + } + } + + if (range->range.maxvariableTrace) { + maxstatus = Ttk_FireTrace(range->range.maxvariableTrace); + if (WidgetDestroyed(&range->core)) { + return TCL_ERROR; + } + if (maxstatus != TCL_OK) { + /* Unset -variable: */ + Ttk_UntraceVariable(range->range.maxvariableTrace); + Tcl_DecrRefCount(range->range.maxvariableObj); + range->range.maxvariableTrace = 0; + range->range.maxvariableObj = NULL; + maxstatus = TCL_ERROR; + } + } + if (minstatus != TCL_OK || maxstatus != TCL_OK) { + return TCL_ERROR; + } else { + return TCL_OK; + } + +} + +/* RangeGetLayout -- + * getLayout hook. + */ +static Ttk_Layout +RangeGetLayout(Tcl_Interp *interp, Ttk_Theme theme, void *recordPtr) +{ + Range *rangePtr = recordPtr; + return TtkWidgetGetOrientedLayout( + interp, theme, recordPtr, rangePtr->range.orientObj); +} + +/* + * TroughBox -- + * Returns the inner area of the trough element. + */ +static Ttk_Box TroughBox(Range *rangePtr) +{ + return Ttk_ClientRegion(rangePtr->core.layout, "trough"); +} + +/* + * TroughRange -- + * Return the value area of the trough element, adjusted + * for slider size. + */ +static Ttk_Box TroughRange(Range *rangePtr) +{ + Ttk_Box troughBox = TroughBox(rangePtr); + Ttk_Element slider = Ttk_FindElement(rangePtr->core.layout,"minslider"); + + /* + * If this is a range widget, adjust range for slider: + */ + if (slider) { + Ttk_Box sliderBox = Ttk_ElementParcel(slider); + if (rangePtr->range.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; +} + +/* + * RangeFraction -- + */ +static double RangeFraction(Range *rangePtr, double value) +{ + double from = 0, to = 1, fraction; + + Tcl_GetDoubleFromObj(NULL, rangePtr->range.fromObj, &from); + Tcl_GetDoubleFromObj(NULL, rangePtr->range.toObj, &to); + + if (from == to) { + return 1.0; + } + + fraction = (value - from) / (to - from); + + return fraction < 0 ? 0 : fraction > 1 ? 1 : fraction; +} + +/* $range get ?x y? -- + * Returns the current value of the range widget, or if $x and + * $y are specified, the value represented by point @x,y. + */ +static int +RangeGetCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Range *rangePtr = recordPtr; + int x, y, r = TCL_OK; + double value = 0; + + if (objc != 4) { + Tcl_WrongNumArgs(interp, 1, objv, "get ?x y?"); + return TCL_ERROR; + } + + r = Tcl_GetIntFromObj(interp, objv[2], &x); + if (r == TCL_OK) + r = Tcl_GetIntFromObj(interp, objv[3], &y); + if (r == TCL_OK) { + value = PointToValue(rangePtr, x, y); + Tcl_SetObjResult(interp, Tcl_NewDoubleObj(value)); + } + + return r; +} + +static int +RangeGetMinCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Range *rangePtr = recordPtr; + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 1, objv, "getmin"); + return TCL_ERROR; + } + + Tcl_SetObjResult(interp, rangePtr->range.minvalueObj); + + return TCL_OK; +} + +static int +RangeGetMaxCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Range *rangePtr = recordPtr; + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 1, objv, "getmax"); + return TCL_ERROR; + } + + Tcl_SetObjResult(interp, rangePtr->range.maxvalueObj); + + return TCL_OK; +} + +/* $range setmin $newValue + */ +static int +RangeSetMinCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Range *rangePtr = recordPtr; + double from = 0.0, to = 1.0, minvalue, maxvalue; + int result = TCL_OK; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 1, objv, "setmin value"); + return TCL_ERROR; + } + + if (Tcl_GetDoubleFromObj(interp, objv[2], &minvalue) != TCL_OK) { + return TCL_ERROR; + } + + if (rangePtr->core.state & TTK_STATE_DISABLED) { + return TCL_OK; + } + + /* ASSERT: fromObj and toObj are valid doubles. + */ + Tcl_GetDoubleFromObj(interp, rangePtr->range.fromObj, &from); + Tcl_GetDoubleFromObj(interp, rangePtr->range.toObj, &to); + + /* Limit new value to between 'from' and 'to': + */ + if (from < to) { + minvalue = minvalue < from ? from : minvalue > to ? to : minvalue; + } else { + minvalue = minvalue < to ? to : minvalue > from ? from : minvalue; + } + + // Must be <= maxvalue too. + Tcl_GetDoubleFromObj(NULL, rangePtr->range.maxvalueObj, &maxvalue); + minvalue = minvalue < maxvalue ? minvalue : maxvalue; + + /* + * Set value: + */ + Tcl_DecrRefCount(rangePtr->range.minvalueObj); + rangePtr->range.minvalueObj = Tcl_NewDoubleObj(minvalue); + Tcl_IncrRefCount(rangePtr->range.minvalueObj); + TtkRedisplayWidget(&rangePtr->core); + + /* + * Set attached variable, if any: + */ + if (rangePtr->range.minvariableObj != NULL) { + Tcl_ObjSetVar2(interp, rangePtr->range.minvariableObj, NULL, + rangePtr->range.minvalueObj, TCL_GLOBAL_ONLY); + } + if (WidgetDestroyed(&rangePtr->core)) { + return TCL_ERROR; + } + + /* + * Invoke -command, if any: + */ + if (rangePtr->range.commandObj != NULL) { + Tcl_Obj *cmdObj = Tcl_DuplicateObj(rangePtr->range.commandObj); + Tcl_IncrRefCount(cmdObj); + Tcl_AppendToObj(cmdObj, " ", 1); + Tcl_AppendObjToObj(cmdObj, rangePtr->range.minvalueObj); + Tcl_AppendToObj(cmdObj, " ", 1); + Tcl_AppendObjToObj(cmdObj, rangePtr->range.maxvalueObj); + result = Tcl_EvalObjEx(interp, cmdObj, TCL_EVAL_GLOBAL); + Tcl_DecrRefCount(cmdObj); + } + + return result; +} + +/* $range setmax $newValue + */ +static int +RangeSetMaxCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Range *rangePtr = recordPtr; + double from = 0.0, to = 1.0, minvalue, maxvalue; + int result = TCL_OK; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 1, objv, "setmax value"); + return TCL_ERROR; + } + + if (Tcl_GetDoubleFromObj(interp, objv[2], &maxvalue) != TCL_OK) { + return TCL_ERROR; + } + + if (rangePtr->core.state & TTK_STATE_DISABLED) { + return TCL_OK; + } + + /* ASSERT: fromObj and toObj are valid doubles. + */ + Tcl_GetDoubleFromObj(interp, rangePtr->range.fromObj, &from); + Tcl_GetDoubleFromObj(interp, rangePtr->range.toObj, &to); + + /* Limit new value to between 'from' and 'to': + */ + if (from < to) { + maxvalue = maxvalue < from ? from : maxvalue > to ? to : maxvalue; + } else { + maxvalue = maxvalue < to ? to : maxvalue > from ? from : maxvalue; + } + + // Must be >= minvalue too. + Tcl_GetDoubleFromObj(NULL, rangePtr->range.minvalueObj, &minvalue); + maxvalue = maxvalue > minvalue ? maxvalue : minvalue; + + /* + * Set value: + */ + Tcl_DecrRefCount(rangePtr->range.maxvalueObj); + rangePtr->range.maxvalueObj = Tcl_NewDoubleObj(maxvalue); + Tcl_IncrRefCount(rangePtr->range.maxvalueObj); + TtkRedisplayWidget(&rangePtr->core); + + /* + * Set attached variable, if any: + */ + if (rangePtr->range.maxvariableObj != NULL) { + Tcl_ObjSetVar2(interp, rangePtr->range.maxvariableObj, NULL, + rangePtr->range.maxvalueObj, TCL_GLOBAL_ONLY); + } + if (WidgetDestroyed(&rangePtr->core)) { + return TCL_ERROR; + } + + /* + * Invoke -command, if any: + */ + if (rangePtr->range.commandObj != NULL) { + Tcl_Obj *cmdObj = Tcl_DuplicateObj(rangePtr->range.commandObj); + Tcl_IncrRefCount(cmdObj); + Tcl_AppendToObj(cmdObj, " ", 1); + Tcl_AppendObjToObj(cmdObj, rangePtr->range.minvalueObj); + Tcl_AppendToObj(cmdObj, " ", 1); + Tcl_AppendObjToObj(cmdObj, rangePtr->range.maxvalueObj); + result = Tcl_EvalObjEx(interp, cmdObj, TCL_EVAL_GLOBAL); + Tcl_DecrRefCount(cmdObj); + } + + return result; +} + +static int +RangeCoordsCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Range *rangePtr = recordPtr; + double value; + int r = TCL_OK; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 1, objv, "coords ?value?"); + return TCL_ERROR; + } + + r = Tcl_GetDoubleFromObj(interp, objv[2], &value); + + if (r == TCL_OK) { + Tcl_Obj *point[2]; + XPoint pt = ValueToPoint(rangePtr, value); + point[0] = Tcl_NewIntObj(pt.x); + point[1] = Tcl_NewIntObj(pt.y); + Tcl_SetObjResult(interp, Tcl_NewListObj(2, point)); + } + return r; +} + +static int +RangeMinCoordsCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Range *rangePtr = recordPtr; + double value; + int r = TCL_OK; + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 1, objv, "mincoords"); + return TCL_ERROR; + } + + r = Tcl_GetDoubleFromObj(interp, rangePtr->range.minvalueObj, &value); + + if (r == TCL_OK) { + Tcl_Obj *point[2]; + XPoint pt = ValueToPoint(rangePtr, value); + point[0] = Tcl_NewIntObj(pt.x); + point[1] = Tcl_NewIntObj(pt.y); + Tcl_SetObjResult(interp, Tcl_NewListObj(2, point)); + } + return r; +} + +static int +RangeMaxCoordsCommand( + void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + Range *rangePtr = recordPtr; + double value; + int r = TCL_OK; + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 1, objv, "maxcoords"); + return TCL_ERROR; + } + + r = Tcl_GetDoubleFromObj(interp, rangePtr->range.maxvalueObj, &value); + + if (r == TCL_OK) { + Tcl_Obj *point[2]; + XPoint pt = ValueToPoint(rangePtr, value); + point[0] = Tcl_NewIntObj(pt.x); + point[1] = Tcl_NewIntObj(pt.y); + Tcl_SetObjResult(interp, Tcl_NewListObj(2, point)); + } + return r; +} + +static void RangeDoLayout(void *clientData) +{ + WidgetCore *corePtr = clientData; + Ttk_Element minslider = Ttk_FindElement(corePtr->layout, "minslider"); + Ttk_Element maxslider = Ttk_FindElement(corePtr->layout, "maxslider"); + + Ttk_PlaceLayout(corePtr->layout,corePtr->state,Ttk_WinBox(corePtr->tkwin)); + + /* Adjust the slider position: + */ + if (minslider && maxslider) { + Range *rangePtr = clientData; + Ttk_Box troughBox = TroughBox(rangePtr); + Ttk_Box minsliderBox = Ttk_ElementParcel(minslider); + Ttk_Box maxsliderBox = Ttk_ElementParcel(maxslider); + double minvalue = 0.0, maxvalue = 1.0; + double minfraction, maxfraction; + int range, offset; + + Tcl_GetDoubleFromObj(NULL, rangePtr->range.minvalueObj, &minvalue); + Tcl_GetDoubleFromObj(NULL, rangePtr->range.maxvalueObj, &maxvalue); + minfraction = RangeFraction(rangePtr, minvalue); + maxfraction = RangeFraction(rangePtr, maxvalue); + + if (rangePtr->range.orient == TTK_ORIENT_HORIZONTAL) { + range = troughBox.width - minsliderBox.width; + offset = minsliderBox.x; + minsliderBox.x = offset + (int)(minfraction * range); + maxsliderBox.x = offset + (int)(maxfraction * range); + } else { + range = troughBox.height - minsliderBox.height; + offset = minsliderBox.y; + minsliderBox.y = offset + (int)(minfraction * range); + maxsliderBox.y = offset + (int)(maxfraction * range); + } + + Ttk_PlaceElement(corePtr->layout, minslider, minsliderBox); + Ttk_PlaceElement(corePtr->layout, maxslider, maxsliderBox); + + } +} + +/* + * RangeSize -- + * Compute requested size of range. + */ +static int RangeSize(void *clientData, int *widthPtr, int *heightPtr) +{ + WidgetCore *corePtr = clientData; + Range *rangePtr = clientData; + int length; + + Ttk_LayoutSize(corePtr->layout, corePtr->state, widthPtr, heightPtr); + + /* Assert the -length configuration option */ + Tk_GetPixelsFromObj(NULL, corePtr->tkwin, + rangePtr->range.lengthObj, &length); + if (rangePtr->range.orient == TTK_ORIENT_VERTICAL) { + *heightPtr = MAX(*heightPtr, length); + } else { + *widthPtr = MAX(*widthPtr, length); + } + + return 1; +} + +static double +PointToValue(Range *rangePtr, int x, int y) +{ + Ttk_Box troughBox = TroughRange(rangePtr); + double from = 0, to = 1, fraction; + + Tcl_GetDoubleFromObj(NULL, rangePtr->range.fromObj, &from); + Tcl_GetDoubleFromObj(NULL, rangePtr->range.toObj, &to); + + if (rangePtr->range.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(Range *rangePtr, double value) +{ + Ttk_Box troughBox = TroughRange(rangePtr); + double fraction = RangeFraction(rangePtr, value); + XPoint pt = {0, 0}; + + if (rangePtr->range.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 RangeCommands[] = { + { "configure", TtkWidgetConfigureCommand,0 }, + { "cget", TtkWidgetCgetCommand,0 }, + { "state", TtkWidgetStateCommand,0 }, + { "instate", TtkWidgetInstateCommand,0 }, + { "identify", TtkWidgetIdentifyCommand,0 }, + { "setmin", RangeSetMinCommand,0 }, + { "setmax", RangeSetMaxCommand,0 }, + { "get", RangeGetCommand,0 }, + { "getmin", RangeGetMinCommand,0 }, + { "getmax", RangeGetMaxCommand,0 }, + { "coords", RangeCoordsCommand,0 }, + { "mincoords", RangeMinCoordsCommand,0 }, + { "maxcoords", RangeMaxCoordsCommand,0 }, + { 0,0,0 } +}; + +static WidgetSpec RangeWidgetSpec = +{ + "TRange", /* Class name */ + sizeof(Range), /* record size */ + RangeOptionSpecs, /* option specs */ + RangeCommands, /* widget commands */ + RangeInitialize, /* initialization proc */ + RangeCleanup, /* cleanup proc */ + RangeConfigure, /* configure proc */ + RangePostConfigure, /* postConfigure */ + RangeGetLayout, /* getLayoutProc */ + RangeSize, /* sizeProc */ + RangeDoLayout, /* layoutProc */ + TtkWidgetDisplay /* displayProc */ +}; + +TTK_BEGIN_LAYOUT(VerticalRangeLayout) + TTK_GROUP("Vertical.Range.trough", TTK_FILL_BOTH, + TTK_NODE("Vertical.Range.minslider", TTK_PACK_TOP) + TTK_NODE("Vertical.Range.maxslider", TTK_PACK_BOTTOM) ) +TTK_END_LAYOUT + +TTK_BEGIN_LAYOUT(HorizontalRangeLayout) + TTK_GROUP("Horizontal.Range.trough", TTK_FILL_BOTH, + TTK_NODE("Horizontal.Range.minslider", TTK_PACK_LEFT) + TTK_NODE("Horizontal.Range.maxslider", TTK_PACK_RIGHT) ) +TTK_END_LAYOUT + +/* + * Initialization. + */ +MODULE_SCOPE +void TtkRange_Init(Tcl_Interp *interp) +{ + Ttk_Theme theme = Ttk_GetDefaultTheme(interp); + + Ttk_RegisterLayout(theme, "Vertical.TRange", VerticalRangeLayout); + Ttk_RegisterLayout(theme, "Horizontal.TRange", HorizontalRangeLayout); + + RegisterWidget(interp, "ttk::range", &RangeWidgetSpec); +} + diff --git a/library/ttk/altTheme.tcl b/library/ttk/altTheme.tcl index d57227c..20bde48 100644 --- a/library/ttk/altTheme.tcl +++ b/library/ttk/altTheme.tcl @@ -95,6 +95,9 @@ namespace eval ttk::theme::alt { ttk::style configure TScale \ -groovewidth 4 -troughrelief sunken \ -sliderwidth raised -borderwidth 2 + ttk::style configure TRange \ + -groovewidth 4 -troughrelief sunken \ + -sliderwidth raised -borderwidth 2 ttk::style configure TProgressbar \ -background $colors(-selectbg) -borderwidth 0 } diff --git a/library/ttk/classicTheme.tcl b/library/ttk/classicTheme.tcl index 7e3eff5..5c1f91c 100644 --- a/library/ttk/classicTheme.tcl +++ b/library/ttk/classicTheme.tcl @@ -82,6 +82,9 @@ namespace eval ttk::theme::classic { ttk::style configure TScale -sliderrelief raised ttk::style map TScale -sliderrelief {{pressed !disabled} sunken} + + ttk::style configure TRange -sliderrelief raised + ttk::style map TRange -sliderrelief {{pressed !disabled} sunken} ttk::style configure TProgressbar -background SteelBlue ttk::style configure TNotebook.Tab \ diff --git a/library/ttk/defaults.tcl b/library/ttk/defaults.tcl index 05a46bd..42032b4 100644 --- a/library/ttk/defaults.tcl +++ b/library/ttk/defaults.tcl @@ -79,6 +79,8 @@ namespace eval ttk::theme::default { ttk::style configure TScale \ -sliderrelief raised + ttk::style configure TRange \ + -sliderrelief raised ttk::style configure TProgressbar \ -background $colors(-selectbg) diff --git a/library/ttk/range.tcl b/library/ttk/range.tcl new file mode 100644 index 0000000..1608dfb --- /dev/null +++ b/library/ttk/range.tcl @@ -0,0 +1,135 @@ +# range.tcl - Copyright (C) 2011 Goodwin Lawlor +# +# Bindings for the TRange widget + +namespace eval ttk::range { + variable State + array set State { + dragging 0 + } +} + +bind TRange { ttk::range::Press %W %x %y } +bind TRange { ttk::range::Drag %W %x %y } +bind TRange { ttk::range::Release %W %x %y } + +bind TRange { ttk::range::Jump %W %x %y } +bind TRange { ttk::range::Drag %W %x %y } +bind TRange { ttk::range::Release %W %x %y } + +bind TRange { ttk::range::Jump %W %x %y } +bind TRange { ttk::range::Drag %W %x %y } +bind TRange { ttk::range::Release %W %x %y } + +bind TRange { ttk::range::Increment %W -1 } +bind TRange { ttk::range::Increment %W -1 } +bind TRange { ttk::range::Increment %W 1 } +bind TRange { ttk::range::Increment %W 1 } +bind TRange { ttk::range::Increment %W -10 } +bind TRange { ttk::range::Increment %W -10 } +bind TRange { ttk::range::Increment %W 10 } +bind TRange { ttk::range::Increment %W 10 } +bind TRange { %W set [%W cget -from] } +bind TRange { %W set [%W cget -to] } + +proc ttk::range::Press {w x y} { + variable State + set State(dragging) 0 + + switch -glob -- [$w identify $x $y] { + *track - + *trough { + set val [$w get $x $y] + set min [$w getmin] + set max [$w getmax] + if {$val < $min} { + #increment min + ttk::Repeatedly IncrementMin $w -0.1 + } elseif {$val > $max} { + #increment max + ttk::Repeatedly IncrementMax $w +0.1 + } else { + set State(dragging) 3 + set State(initial) [$w get $x $y] + } + } + *minslider { + set State(dragging) 1 + set State(initial) [$w getmin] + } + *maxslider { + # hack to prevent minslider hidding under the maxslider + # when both are at the "to" value + if {[$w getmin] == [$w cget -to]} { + set State(dragging) 1 + } else { + set State(dragging) 2 + } + + set State(initial) [$w getmax] + } + } +} + +# range::Jump -- ButtonPress-2/3 binding for range acts like +# Press except that clicking in the trough jumps to the +# clicked position. +proc ttk::range::Jump {w x y} { + variable State + set State(dragging) 0 + + switch -glob -- [$w identify $x $y] { + *track - + *trough { + set val [$w get $x $y] + set min [$w getmin] + set max [$w getmax] + if {$val < $min} { + #jump min + $w setmin $val + set State(initial) [$w get $x $y] + } elseif {$val > $max} { + #jump max + $w setmax $val + set State(initial) [$w get $x $y] + } else { + set State(dragging) 3 + set State(initial) [$w get $x $y] + } + } + *slider { + Press $w $x $y + } + } +} + +proc ttk::range::Drag {w x y} { + variable State + if {$State(dragging) eq 1} { + $w setmin [$w get $x $y] + } elseif {$State(dragging) eq 2} { + $w setmax [$w get $x $y] + } else { + set v [$w get $x $y] + set dv [expr $v - $State(initial)] + $w setmin [expr [$w getmin] + $dv] + $w setmax [expr [$w getmax] + $dv] + set State(initial) $v + } +} + +proc ttk::range::Release {w x y} { + variable State + set State(dragging) 0 + ttk::CancelRepeat +} + +proc ttk::range::IncrementMin {w delta} { + if {![winfo exists $w]} return + $w setmin [expr {[$w getmin] + $delta}] +} + +proc ttk::range::IncrementMax {w delta} { + if {![winfo exists $w]} return + $w setmax [expr {[$w getmax] + $delta}] +} diff --git a/library/ttk/ttk.tcl b/library/ttk/ttk.tcl index 7bae211..04ac959 100644 --- a/library/ttk/ttk.tcl +++ b/library/ttk/ttk.tcl @@ -109,6 +109,7 @@ source [file join $::ttk::library combobox.tcl] ;# dependency: entry.tcl source [file join $::ttk::library spinbox.tcl] ;# dependency: entry.tcl source [file join $::ttk::library treeview.tcl] source [file join $::ttk::library sizegrip.tcl] +source [file join $::ttk::library range.tcl] ## Label and Labelframe bindings: # (not enough to justify their own file...) diff --git a/library/ttk/vistaTheme.tcl b/library/ttk/vistaTheme.tcl index 99410cb..749042f 100644 --- a/library/ttk/vistaTheme.tcl +++ b/library/ttk/vistaTheme.tcl @@ -215,6 +215,38 @@ namespace eval ttk::theme::vista { } } } + + # Range + ttk::style element create Horizontal.Range.minslider vsapi \ + TRACKBAR 3 {disabled 5 focus 4 pressed 3 active 2 {} 1} \ + -width 6 -height 12 + ttk::style element create Horizontal.Range.maxslider vsapi \ + TRACKBAR 3 {disabled 5 focus 4 pressed 3 active 2 {} 1} \ + -width 6 -height 12 + ttk::style layout Horizontal.TRange { + Range.focus -expand 1 -sticky nswe -children { + Horizontal.Range.trough -expand 1 -sticky nswe -children { + Horizontal.Range.track -sticky we + Horizontal.Range.minslider -side left -sticky {} + Horizontal.Range.maxslider -side right -sticky {} + } + } + } + ttk::style element create Vertical.Range.minslider vsapi \ + TRACKBAR 6 {disabled 5 focus 4 pressed 3 active 2 {} 1} \ + -width 12 -height 6 + ttk::style element create Vertical.Range.maxslider vsapi \ + TRACKBAR 6 {disabled 5 focus 4 pressed 3 active 2 {} 1} \ + -width 12 -height 6 + ttk::style layout Vertical.TRange { + Range.focus -expand 1 -sticky nswe -children { + Vertical.Range.trough -expand 1 -sticky nswe -children { + Vertical.Range.track -sticky ns + Vertical.Range.maxslider -side top -sticky {} + Vertical.Range.minslider -side bottom -sticky {} + } + } + } # Treeview ttk::style configure Item -padding {4 0 0 0} diff --git a/library/ttk/winTheme.tcl b/library/ttk/winTheme.tcl index 55367bc..909e82f 100644 --- a/library/ttk/winTheme.tcl +++ b/library/ttk/winTheme.tcl @@ -62,6 +62,8 @@ namespace eval ttk::theme::winnative { {disabled flat selected sunken pressed sunken active raised} ttk::style configure TScale -groovewidth 4 + + ttk::style configure TRange groovewidth 4 ttk::style configure TNotebook -tabmargins {2 2 2 0} ttk::style configure TNotebook.Tab -padding {3 1} -borderwidth 1 diff --git a/unix/Makefile.in b/unix/Makefile.in index a38ff74..23d6056 100644 --- a/unix/Makefile.in +++ b/unix/Makefile.in @@ -374,8 +374,8 @@ TTK_OBJS = \ ttkBlink.o ttkButton.o ttkCache.o ttkClamTheme.o ttkClassicTheme.o \ ttkDefaultTheme.o ttkElements.o ttkEntry.o ttkFrame.o ttkImage.o \ ttkInit.o ttkLabel.o ttkLayout.o ttkManager.o ttkNotebook.o \ - ttkPanedwindow.o ttkProgress.o ttkScale.o ttkScrollbar.o ttkScroll.o \ - ttkSeparator.o ttkSquare.o ttkState.o \ + ttkPanedwindow.o ttkProgress.o ttkRange.o ttkScale.o ttkScrollbar.o \ + ttkScroll.o ttkSeparator.o ttkSquare.o ttkState.o \ ttkTagSet.o ttkTheme.o ttkTrace.o ttkTrack.o ttkTreeview.o \ ttkWidget.o ttkStubInit.o @@ -475,6 +475,7 @@ TTK_SRCS = \ $(TTK_DIR)/ttkNotebook.c \ $(TTK_DIR)/ttkPanedwindow.c \ $(TTK_DIR)/ttkProgress.c \ + $(TTK_DIR)/ttkRange.c \ $(TTK_DIR)/ttkScale.c \ $(TTK_DIR)/ttkScrollbar.c \ $(TTK_DIR)/ttkScroll.c \ @@ -1383,6 +1384,9 @@ ttkPanedwindow.o: $(TTK_DIR)/ttkPanedwindow.c ttkProgress.o: $(TTK_DIR)/ttkProgress.c $(CC) -c $(CC_SWITCHES) $(TTK_DIR)/ttkProgress.c +ttkRange.o: $(TTK_DIR)/ttkRange.c + $(CC) -c $(CC_SWITCHES) $(TTK_DIR)/ttkRange.c + ttkScale.o: $(TTK_DIR)/ttkScale.c $(CC) -c $(CC_SWITCHES) $(TTK_DIR)/ttkScale.c diff --git a/win/makefile.vc b/win/makefile.vc index 14dc2d0..3e76985 100644 --- a/win/makefile.vc +++ b/win/makefile.vc @@ -385,6 +385,7 @@ TTK_OBJS = \ $(TMP_DIR)\ttkNotebook.obj \ $(TMP_DIR)\ttkPanedwindow.obj \ $(TMP_DIR)\ttkProgress.obj \ + $(TMP_DIR)\ttkRange.obj \ $(TMP_DIR)\ttkScale.obj \ $(TMP_DIR)\ttkScrollbar.obj \ $(TMP_DIR)\ttkScroll.obj \ diff --git a/win/ttkWinXPTheme.c b/win/ttkWinXPTheme.c index af6103b..33f44d0 100644 --- a/win/ttkWinXPTheme.c +++ b/win/ttkWinXPTheme.c @@ -339,6 +339,15 @@ static Ttk_StateTable scale_statemap[] = { TUS_NORMAL, 0, 0 } }; +static Ttk_StateTable range_statemap[] = +{ + { TUS_DISABLED, TTK_STATE_DISABLED, 0 }, + { TUS_PRESSED, TTK_STATE_PRESSED, 0 }, + { TUS_FOCUSED, TTK_STATE_FOCUS, 0 }, + { TUS_HOT, TTK_STATE_ACTIVE, 0 }, + { TUS_NORMAL, 0, 0 } +}; + static Ttk_StateTable tabitem_statemap[] = { { TIS_DISABLED, TTK_STATE_DISABLED, 0 }, @@ -932,6 +941,20 @@ TTK_LAYOUT("Vertical.TScale", TTK_NODE("Vertical.Scale.track", TTK_FILL_Y) TTK_NODE("Vertical.Scale.slider", TTK_PACK_TOP) ))) +TTK_LAYOUT("Horizontal.TRange", + TTK_GROUP("Range.focus", TTK_EXPAND|TTK_FILL_BOTH, + TTK_GROUP("Horizontal.Range.trough", TTK_EXPAND|TTK_FILL_BOTH, + TTK_NODE("Horizontal.Range.track", TTK_FILL_X) + TTK_NODE("Horizontal.Range.minslider", TTK_PACK_LEFT) + TTK_NODE("Horizontal.Range.maxslider", TTK_PACK_RIGHT) ))) + +TTK_LAYOUT("Vertical.TRange", + TTK_GROUP("Range.focus", TTK_EXPAND|TTK_FILL_BOTH, + TTK_GROUP("Vertical.Range.trough", TTK_EXPAND|TTK_FILL_BOTH, + TTK_NODE("Vertical.Range.track", TTK_FILL_Y) + TTK_NODE("Vertical.Range.minslider", TTK_PACK_TOP) + TTK_NODE("Vertical.Range.maxslider", TTK_PACK_BOTTOM) ))) + TTK_END_LAYOUT_TABLE /*---------------------------------------------------------------------- @@ -991,6 +1014,14 @@ static ElementInfo ElementInfoTable[] = { TKP_TRACK, scale_statemap, NOPAD, 0 }, { "Vertical.Scale.track", &GenericElementSpec, L"TRACKBAR", TKP_TRACKVERT, scale_statemap, NOPAD, 0 }, + { "Horizontal.Range.slider", &GenericElementSpec, L"TRACKBAR", + TKP_THUMB, range_statemap, NOPAD, 0 }, + { "Vertical.Range.slider", &GenericElementSpec, L"TRACKBAR", + TKP_THUMBVERT, range_statemap, NOPAD, 0 }, + { "Horizontal.Range.track", &GenericElementSpec, L"TRACKBAR", + TKP_TRACK, range_statemap, NOPAD, 0 }, + { "Vertical.Range.track", &GenericElementSpec, L"TRACKBAR", + TKP_TRACKVERT, range_statemap, NOPAD, 0 }, /* ttk::progressbar elements */ { "Horizontal.Progressbar.pbar", &PbarElementSpec, L"PROGRESS", PP_CHUNK, null_statemap, NOPAD, 0 }, @@ -1309,6 +1340,7 @@ MODULE_SCOPE int TtkXPTheme_Init(Tcl_Interp *interp, HWND hwnd) } Ttk_RegisterElementSpec(themePtr, "Scale.trough", &ttkNullElementSpec, 0); + Ttk_RegisterElementSpec(themePtr, "Range.trough", &ttkNullElementSpec, 0); /* * Layouts: -- cgit v0.12