/* scale.c - Copyright (C) 2004 Pat Thoyts <patthoyts@users.sourceforge.net>
*
* Themed scale widget implementation.
*
* This is EXPERIMENTAL code.
*
* $Id: scale.c,v 1.35 2004/09/09 01:49:11 jenglish Exp $
*
*/
#include <tk.h>
#include <string.h>
#include <stdio.h>
#include "tkTheme.h"
#include "widget.h"
#define DEF_SCALE_SLIDER_RELIEF "raised"
#define DEF_SCALE_WIDTH "15"
#define DEF_SCALE_LENGTH "100"
#define DEF_SCALE_SHOW_VALUE "1"
#ifndef MAX
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#endif
/*
* 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 *widthObj; /* size of the short axis of the scale trough */
Tcl_Obj *orientObj; /* widget orientation */
/* widget options */
Tcl_Obj *commandObj;
Tcl_Obj *varNameObj;
Tcl_Obj *fontObj;
Tcl_Obj *foregroundObj;
int showValue;
/* internal state */
int orient;
} 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", NULL,
Tk_Offset(Scale,scale.varNameObj), -1, TK_OPTION_NULL_OK,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_FONT, "-font", "font", "Font", DEFAULT_FONT,
Tk_Offset(Scale,scale.fontObj), -1,
0/*NOTE: TK_OPTION_NULL*NOT*OK */, 0, GEOMETRY_CHANGED},
{TK_OPTION_COLOR, "-foreground", "foreground", "Foreground",
DEFAULT_FOREGROUND, Tk_Offset(Scale,scale.foregroundObj), -1, 0,0,0 },
{TK_OPTION_BOOLEAN, "-showvalue", "showValue", "ShowValue",
DEF_SCALE_SHOW_VALUE, -1, Tk_Offset(Scale,scale.showValue), 0, 0, 0},
{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_PIXELS, "-width", "width", "Width",
DEF_SCALE_WIDTH, Tk_Offset(Scale,scale.widthObj), -1, 0, 0,
GEOMETRY_CHANGED},
WIDGET_INHERIT_OPTIONS(CoreOptionSpecs)
};
static int ScaleValueSpace(Scale *scalePtr);
static XPoint ValueToPoint(Scale *scalePtr, double value);
static double PointToValue(Scale *scalePtr, int x, int y);
static int
ScaleInitialize(Tcl_Interp *interp, void *recordPtr)
{
Scale *scalePtr = recordPtr;
TrackElementState(&scalePtr->core);
return TCL_OK;
}
static TTK_Layout ScaleGetLayout(
Tcl_Interp *interp, TTK_Theme theme, void *recordPtr)
{
Scale *scalePtr = recordPtr;
const char *layoutName;
if (scalePtr->scale.orient == TTK_ORIENT_HORIZONTAL) {
layoutName = "Horizontal.TScale";
} else {
layoutName = "Vertical.TScale";
}
return TTK_CreateLayout(interp, theme, layoutName,
recordPtr, scalePtr->core.optionTable, scalePtr->core.tkwin);
}
/*
* TroughBox --
* Returns the inner area of the trough element.
*/
static TTK_Box TroughBox(Scale *scalePtr)
{
WidgetCore *corePtr = &scalePtr->core;
TTK_LayoutNode *node = TTK_LayoutFindNode(corePtr->layout, "trough");
if (node) {
return TTK_LayoutNodeInternalParcel(corePtr->layout, node);
} else {
return TTK_MakeBox(
0,0, Tk_Width(corePtr->tkwin), Tk_Height(corePtr->tkwin));
}
}
/*
* 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_LayoutNode *slider=TTK_LayoutFindNode(scalePtr->core.layout,"slider");
/*
* If this is a scale widget, adjust range for slider:
*/
if (slider) {
TTK_Box sliderBox = TTK_LayoutNodeParcel(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 'identify' method:
* $widget identify $x $y
*
* Returns: name of element at $x, $y
*/
static int
ScaleIdentifyCommand(
Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], void *recordPtr)
{
Scale *scalePtr = recordPtr;
TTK_LayoutNode *node;
int x, y;
if (objc != 4) {
Tcl_WrongNumArgs(interp, 2, objv, "x y");
return TCL_ERROR;
}
if (Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK
|| Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK)
return TCL_ERROR;
node = TTK_LayoutIdentify(scalePtr->core.layout, x, y);
if (node) {
Tcl_SetObjResult(interp,Tcl_NewStringObj(TTK_LayoutNodeName(node),-1));
}
return TCL_OK;
}
static int
ScaleGetCommand(
Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], void *recordPtr)
{
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;
}
static int
ScaleSetCommand(
Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], void *recordPtr)
{
Scale *scalePtr = recordPtr;
double value, min, max;
int r = TCL_OK;
if (objc != 3) {
Tcl_WrongNumArgs(interp, 1, objv, "set value");
return TCL_ERROR;
}
r = Tcl_GetDoubleFromObj(interp, objv[2], &value);
if (r == TCL_OK)
r = Tcl_GetDoubleFromObj(interp, scalePtr->scale.fromObj, &min);
if (r == TCL_OK)
r = Tcl_GetDoubleFromObj(interp, scalePtr->scale.toObj, &max);
if (r != TCL_OK)
return r;
if (scalePtr->core.state & TTK_STATE_DISABLED)
return r;
if (value < min) value = min;
if (value > max) value = max;
/*
* Set value:
*/
Tcl_DecrRefCount(scalePtr->scale.valueObj);
scalePtr->scale.valueObj = Tcl_NewDoubleObj(value);
Tcl_IncrRefCount(scalePtr->scale.valueObj);
WidgetChanged(&scalePtr->core, REDISPLAY_REQUIRED);
/*
* Set attached variable, if any:
*/
if (scalePtr->scale.varNameObj != NULL) {
Tcl_ObjSetVar2(interp, scalePtr->scale.varNameObj, NULL,
scalePtr->scale.valueObj, TCL_GLOBAL_ONLY);
}
if (WidgetDestroyed(&scalePtr->core)) {
return TCL_ERROR;
}
/*
* Invoke -command, if any:
*/
if (r == TCL_OK && 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);
r = Tcl_EvalObjEx(interp, cmdObj, TCL_EVAL_GLOBAL);
Tcl_DecrRefCount(cmdObj);
}
return r;
}
static int
ScaleCoordsCommand(
Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], void *recordPtr)
{
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)
{
Scale *scalePtr = clientData;
Tk_Window tkwin = scalePtr->core.tkwin;
int hextra = 0, wextra = 0;
/* If -showvalue, offset the elements.*/
if (scalePtr->scale.showValue) {
if (scalePtr->scale.orient == TTK_ORIENT_VERTICAL) {
wextra = ScaleValueSpace(scalePtr);
} else {
hextra = ScaleValueSpace(scalePtr);
}
}
if (wextra >= Tk_Width(tkwin)) wextra = 0;
if (hextra >= Tk_Height(tkwin)) hextra = 0;
TTK_LayoutGeometry(
scalePtr->core.layout, scalePtr->core.state,
0,hextra, Tk_Width(tkwin)-wextra, Tk_Height(tkwin)-hextra);
}
static void ScaleDisplay(void *clientData, Drawable d)
{
WidgetCore *corePtr = clientData;
Scale *scalePtr = clientData;
Tk_Window tkwin = corePtr->tkwin;
TTK_LayoutNode *sliderNode;
TTK_Box troughBox, textBox;
double value = 0.0;
/* Now adjust the slider offset (the size will be ok)
*/
Tcl_GetDoubleFromObj(NULL, scalePtr->scale.valueObj, &value);
troughBox = TroughBox(scalePtr);
sliderNode = TTK_LayoutFindNode(corePtr->layout, "slider");
if (sliderNode) {
TTK_Box sliderBox = TTK_LayoutNodeParcel(sliderNode);
double fraction = ScaleFraction(scalePtr, value);
int range;
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_LayoutNodeSetParcel(sliderNode, sliderBox);
if (scalePtr->scale.showValue) {
int valuespace = ScaleValueSpace(scalePtr);
textBox = troughBox;
if (scalePtr->scale.orient == TTK_ORIENT_VERTICAL) {
textBox.x = troughBox.x + troughBox.width;
textBox.y = (int)(troughBox.y + fraction * range + sliderBox.height / 2);
textBox.width = valuespace;
} else {
textBox.x = (int)(troughBox.x + fraction * range + sliderBox.width / 2);
textBox.y = troughBox.y - valuespace;
textBox.height = valuespace;
}
}
}
TTK_LayoutDraw(corePtr->layout, d, corePtr->state);
/*
* While testing, draw the value in the middle of the trough
*
* FIX ME: Add space for this. Need to calculate the right amount
* from the display format.
*/
if (scalePtr->scale.showValue)
{
Tk_Font font = Tk_GetFontFromObj(tkwin, scalePtr->scale.fontObj);
XColor *color = Tk_GetColorFromObj(tkwin, scalePtr->scale.foregroundObj);
char str[TCL_DOUBLE_SPACE];
int len;
XGCValues gcValues;
GC gc;
Tk_TextLayout textlayout;
int labelpad = 4;
int labelwidth, labelheight;
gcValues.font = Tk_FontId(font);
gcValues.foreground = color->pixel;
gc = Tk_GetGC(tkwin, GCFont | GCForeground, &gcValues);
sprintf(str, "%.0f", value);
len = strlen(str);
textlayout = Tk_ComputeTextLayout(font, str, len, -1, TK_JUSTIFY_LEFT,
TK_IGNORE_TABS | TK_IGNORE_NEWLINES, &labelwidth, &labelheight);
if (scalePtr->scale.orient == TTK_ORIENT_HORIZONTAL) {
Tk_DrawChars(Tk_Display(tkwin), d, gc, font,
str, len, textBox.x - labelwidth/2,
textBox.y + textBox.height - labelpad);
} else {
Tk_DrawChars(Tk_Display(tkwin), d, gc, font,
str, len, textBox.x + labelpad, textBox.y + labelheight/2);
}
Tk_FreeTextLayout(textlayout);
Tk_FreeGC(Tk_Display(tkwin), gc);
}
}
static void ProgressDoLayout(void *clientData)
{
WidgetCore *corePtr = clientData;
Scale *scalePtr = clientData;
Tk_Window tkwin = corePtr->tkwin;
TTK_LayoutNode *node;
TTK_Box box;
double value;
TTK_LayoutGeometry(corePtr->layout, corePtr->state,
0,0, Tk_Width(tkwin), Tk_Height(tkwin));
/* Adjust the bar size:
*/
box = TroughBox(scalePtr);
Tcl_GetDoubleFromObj(NULL, scalePtr->scale.valueObj, &value);
node = TTK_LayoutFindNode(corePtr->layout, "bar");
if (node) {
double fraction = ScaleFraction(scalePtr, value);
if (scalePtr->scale.orient == TTK_ORIENT_HORIZONTAL) {
box.width = (int)(box.width * fraction);
} else {
box.height = (int)(box.height * fraction);
}
TTK_LayoutNodeSetParcel(node, box);
}
}
/*
* ScaleSize --
* Compute requested size of scale.
*/
static int ScaleSize(void *clientData, int *widthPtr, int *heightPtr)
{
WidgetCore *corePtr = clientData;
Scale *scalePtr = clientData;
int length;
if (corePtr && corePtr->layout && corePtr->tkwin) {
TTK_LayoutReqSize(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);
}
/* Allow extra space for the value */
if (scalePtr->scale.showValue) {
if (scalePtr->scale.orient == TTK_ORIENT_VERTICAL) {
*widthPtr += ScaleValueSpace(scalePtr);
} else {
*heightPtr += ScaleValueSpace(scalePtr);
}
}
}
return 1;
}
static int ScaleValueSpace(Scale *scalePtr)
{
return 20; /* FIX ME: Calculate this from the font + number of places. */
}
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;
}
/*
* Progress widget overrides.
*/
static int
ProgressInitialize(Tcl_Interp *interp, void *recordPtr)
{
Scale *scalePtr = recordPtr;
scalePtr->scale.showValue = 0;
return TCL_OK;
}
static TTK_Layout ProgressGetLayout(
Tcl_Interp *interp, TTK_Theme theme, void *recordPtr)
{
Scale *scalePtr = recordPtr;
const char *layoutName;
if (scalePtr->scale.orient == TTK_ORIENT_HORIZONTAL) {
layoutName = "Horizontal.TProgress";
} else {
layoutName = "Vertical.TProgress";
}
return TTK_CreateLayout(
interp, theme, layoutName,
recordPtr, scalePtr->core.optionTable, scalePtr->core.tkwin);
}
static WidgetCommandSpec ScaleCommands[] =
{
{ "configure", WidgetConfigureCommand },
{ "cget", WidgetCgetCommand },
{ "state", WidgetStateCommand },
{ "instate", WidgetInstateCommand },
{ "identify", ScaleIdentifyCommand },
{ "set", ScaleSetCommand },
{ "get", ScaleGetCommand },
{ "coords", ScaleCoordsCommand },
{ 0, 0 }
};
WidgetSpec ScaleWidgetSpec =
{
"TScale", /* Class name */
sizeof(Scale), /* record size */
ScaleOptionSpecs, /* option specs */
ScaleCommands, /* widget commands */
ScaleInitialize, /* initialization proc */
NullCleanup, /* cleanup proc */
CoreConfigure, /* configure proc */
NullPostConfigure, /* postConfigure */
ScaleGetLayout, /* getLayoutProc */
ScaleSize, /* sizeProc */
ScaleDoLayout, /* layoutProc */
ScaleDisplay, /* display proc */
WIDGET_SPEC_END /* sentinel */
};
WidgetSpec ProgressWidgetSpec =
{
"TProgress", /* Class name */
sizeof(Scale), /* record size */
ScaleOptionSpecs, /* option specs */
ScaleCommands, /* widget commands */
ProgressInitialize, /* initialization proc */
NullCleanup, /* cleanup proc */
CoreConfigure, /* configure proc */
NullPostConfigure, /* postConfigureProc */
ProgressGetLayout, /* getLayoutProc */
ScaleSize, /* sizeProc */
ProgressDoLayout, /* layoutProc */
WidgetDisplay, /* displayProc */
WIDGET_SPEC_END /* sentinel */
};