Unnamed Fossil Project

Artifact [6d5207af25]
Login

Artifact [6d5207af25]

Artifact 6d5207af2579fb4ec6b476686faff2c5a8a2576695672bc74a9344d218ce43dd:


/* 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 */
};