/* * tkScale.c -- * * This module implements a scale widgets for the Tk toolkit. * A scale displays a slider that can be adjusted to change a * value; it also displays numeric labels and a textual label, * if desired. * * The modifications to use floating-point values are based on * an implementation by Paul Mackerras. The -variable option * is due to Henning Schulzrinne. All of these are used with * permission. * * Copyright (c) 1990-1994 The Regents of the University of California. * Copyright (c) 1994-1997 Sun Microsystems, Inc. * Copyright (c) 1998-2000 by Scriptics Corporation. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * * RCS: @(#) $Id: tkScale.c,v 1.17.2.1 2006/06/14 22:15:22 dgp Exp $ */ #include "tkPort.h" #include "default.h" #include "tkInt.h" #include "tclMath.h" #include "tkScale.h" /* * The following table defines the legal values for the -orient option. * It is used together with the "enum orient" declaration in tkScale.h. */ static char *orientStrings[] = { "horizontal", "vertical", (char *) NULL }; /* * The following table defines the legal values for the -state option. * It is used together with the "enum state" declaration in tkScale.h. */ static char *stateStrings[] = { "active", "disabled", "normal", (char *) NULL }; static Tk_OptionSpec optionSpecs[] = { {TK_OPTION_BORDER, "-activebackground", "activeBackground", "Foreground", DEF_SCALE_ACTIVE_BG_COLOR, -1, Tk_Offset(TkScale, activeBorder), 0, (ClientData) DEF_SCALE_ACTIVE_BG_MONO, 0}, {TK_OPTION_BORDER, "-background", "background", "Background", DEF_SCALE_BG_COLOR, -1, Tk_Offset(TkScale, bgBorder), 0, (ClientData) DEF_SCALE_BG_MONO, 0}, {TK_OPTION_DOUBLE, "-bigincrement", "bigIncrement", "BigIncrement", DEF_SCALE_BIG_INCREMENT, -1, Tk_Offset(TkScale, bigIncrement), 0, 0, 0}, {TK_OPTION_SYNONYM, "-bd", (char *) NULL, (char *) NULL, (char *) NULL, 0, -1, 0, (ClientData) "-borderwidth", 0}, {TK_OPTION_SYNONYM, "-bg", (char *) NULL, (char *) NULL, (char *) NULL, 0, -1, 0, (ClientData) "-background", 0}, {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth", DEF_SCALE_BORDER_WIDTH, -1, Tk_Offset(TkScale, borderWidth), 0, 0, 0}, {TK_OPTION_STRING, "-command", "command", "Command", DEF_SCALE_COMMAND, -1, Tk_Offset(TkScale, command), TK_OPTION_NULL_OK, 0, 0}, {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor", DEF_SCALE_CURSOR, -1, Tk_Offset(TkScale, cursor), TK_OPTION_NULL_OK, 0, 0}, {TK_OPTION_INT, "-digits", "digits", "Digits", DEF_SCALE_DIGITS, -1, Tk_Offset(TkScale, digits), 0, 0, 0}, {TK_OPTION_SYNONYM, "-fg", "foreground", (char *) NULL, (char *) NULL, 0, -1, 0, (ClientData) "-foreground", 0}, {TK_OPTION_FONT, "-font", "font", "Font", DEF_SCALE_FONT, -1, Tk_Offset(TkScale, tkfont), 0, 0, 0}, {TK_OPTION_COLOR, "-foreground", "foreground", "Foreground", DEF_SCALE_FG_COLOR, -1, Tk_Offset(TkScale, textColorPtr), 0, (ClientData) DEF_SCALE_FG_MONO, 0}, {TK_OPTION_DOUBLE, "-from", "from", "From", DEF_SCALE_FROM, -1, Tk_Offset(TkScale, fromValue), 0, 0, 0}, {TK_OPTION_BORDER, "-highlightbackground", "highlightBackground", "HighlightBackground", DEF_SCALE_HIGHLIGHT_BG_COLOR, -1, Tk_Offset(TkScale, highlightBorder), 0, (ClientData) DEF_SCALE_HIGHLIGHT_BG_MONO, 0}, {TK_OPTION_COLOR, "-highlightcolor", "highlightColor", "HighlightColor", DEF_SCALE_HIGHLIGHT, -1, Tk_Offset(TkScale, highlightColorPtr), 0, 0, 0}, {TK_OPTION_PIXELS, "-highlightthickness", "highlightThickness", "HighlightThickness", DEF_SCALE_HIGHLIGHT_WIDTH, -1, Tk_Offset(TkScale, highlightWidth), 0, 0, 0}, {TK_OPTION_STRING, "-label", "label", "Label", DEF_SCALE_LABEL, -1, Tk_Offset(TkScale, label), TK_OPTION_NULL_OK, 0, 0}, {TK_OPTION_PIXELS, "-length", "length", "Length", DEF_SCALE_LENGTH, -1, Tk_Offset(TkScale, length), 0, 0, 0}, {TK_OPTION_STRING_TABLE, "-orient", "orient", "Orient", DEF_SCALE_ORIENT, -1, Tk_Offset(TkScale, orient), 0, (ClientData) orientStrings, 0}, {TK_OPTION_RELIEF, "-relief", "relief", "Relief", DEF_SCALE_RELIEF, -1, Tk_Offset(TkScale, relief), 0, 0, 0}, {TK_OPTION_INT, "-repeatdelay", "repeatDelay", "RepeatDelay", DEF_SCALE_REPEAT_DELAY, -1, Tk_Offset(TkScale, repeatDelay), 0, 0, 0}, {TK_OPTION_INT, "-repeatinterval", "repeatInterval", "RepeatInterval", DEF_SCALE_REPEAT_INTERVAL, -1, Tk_Offset(TkScale, repeatInterval), 0, 0, 0}, {TK_OPTION_DOUBLE, "-resolution", "resolution", "Resolution", DEF_SCALE_RESOLUTION, -1, Tk_Offset(TkScale, resolution), 0, 0, 0}, {TK_OPTION_BOOLEAN, "-showvalue", "showValue", "ShowValue", DEF_SCALE_SHOW_VALUE, -1, Tk_Offset(TkScale, showValue), 0, 0, 0}, {TK_OPTION_PIXELS, "-sliderlength", "sliderLength", "SliderLength", DEF_SCALE_SLIDER_LENGTH, -1, Tk_Offset(TkScale, sliderLength), 0, 0, 0}, {TK_OPTION_RELIEF, "-sliderrelief", "sliderRelief", "SliderRelief", DEF_SCALE_SLIDER_RELIEF, -1, Tk_Offset(TkScale, sliderRelief), 0, 0, 0}, {TK_OPTION_STRING_TABLE, "-state", "state", "State", DEF_SCALE_STATE, -1, Tk_Offset(TkScale, state), 0, (ClientData) stateStrings, 0}, {TK_OPTION_STRING, "-takefocus", "takeFocus", "TakeFocus", DEF_SCALE_TAKE_FOCUS, Tk_Offset(TkScale, takeFocusPtr), -1, TK_OPTION_NULL_OK, 0, 0}, {TK_OPTION_DOUBLE, "-tickinterval", "tickInterval", "TickInterval", DEF_SCALE_TICK_INTERVAL, -1, Tk_Offset(TkScale, tickInterval), 0, 0, 0}, {TK_OPTION_DOUBLE, "-to", "to", "To", DEF_SCALE_TO, -1, Tk_Offset(TkScale, toValue), 0, 0, 0}, {TK_OPTION_COLOR, "-troughcolor", "troughColor", "Background", DEF_SCALE_TROUGH_COLOR, -1, Tk_Offset(TkScale, troughColorPtr), 0, (ClientData) DEF_SCALE_TROUGH_MONO, 0}, {TK_OPTION_STRING, "-variable", "variable", "Variable", DEF_SCALE_VARIABLE, Tk_Offset(TkScale, varNamePtr), -1, TK_OPTION_NULL_OK, 0, 0}, {TK_OPTION_PIXELS, "-width", "width", "Width", DEF_SCALE_WIDTH, -1, Tk_Offset(TkScale, width), 0, 0, 0}, {TK_OPTION_END, (char *) NULL, (char *) NULL, (char *) NULL, (char *) NULL, 0, -1, 0, 0, 0} }; /* * The following tables define the scale widget commands and map the * indexes into the string tables into a single enumerated type used * to dispatch the scale widget command. */ static CONST char *commandNames[] = { "cget", "configure", "coords", "get", "identify", "set", (char *) NULL }; enum command { COMMAND_CGET, COMMAND_CONFIGURE, COMMAND_COORDS, COMMAND_GET, COMMAND_IDENTIFY, COMMAND_SET }; /* * Forward declarations for procedures defined later in this file: */ static void ComputeFormat _ANSI_ARGS_((TkScale *scalePtr)); static void ComputeScaleGeometry _ANSI_ARGS_((TkScale *scalePtr)); static int ConfigureScale _ANSI_ARGS_((Tcl_Interp *interp, TkScale *scalePtr, int objc, Tcl_Obj *CONST objv[])); static void DestroyScale _ANSI_ARGS_((char *memPtr)); static void ScaleCmdDeletedProc _ANSI_ARGS_(( ClientData clientData)); static void ScaleEventProc _ANSI_ARGS_((ClientData clientData, XEvent *eventPtr)); static char * ScaleVarProc _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp, CONST char *name1, CONST char *name2, int flags)); static int ScaleWidgetObjCmd _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); static void ScaleWorldChanged _ANSI_ARGS_(( ClientData instanceData)); static void ScaleSetVariable _ANSI_ARGS_((TkScale *scalePtr)); /* * The structure below defines scale class behavior by means of procedures * that can be invoked from generic window code. */ static Tk_ClassProcs scaleClass = { sizeof(Tk_ClassProcs), /* size */ ScaleWorldChanged, /* worldChangedProc */ }; /* *-------------------------------------------------------------- * * Tk_ScaleObjCmd -- * * This procedure is invoked to process the "scale" Tcl * command. See the user documentation for details on what * it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *-------------------------------------------------------------- */ int Tk_ScaleObjCmd(clientData, interp, objc, objv) ClientData clientData; /* NULL. */ Tcl_Interp *interp; /* Current interpreter. */ int objc; /* Number of arguments. */ Tcl_Obj *CONST objv[]; /* Argument values. */ { register TkScale *scalePtr; Tk_OptionTable optionTable; Tk_Window tkwin; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "pathName ?options?"); return TCL_ERROR; } tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp), Tcl_GetString(objv[1]), (char *) NULL); if (tkwin == NULL) { return TCL_ERROR; } /* * Create the option table for this widget class. If it has already * been created, the cached pointer will be returned. */ optionTable = Tk_CreateOptionTable(interp, optionSpecs); Tk_SetClass(tkwin, "Scale"); scalePtr = TkpCreateScale(tkwin); /* * Initialize fields that won't be initialized by ConfigureScale, * or which ConfigureScale expects to have reasonable values * (e.g. resource pointers). */ scalePtr->tkwin = tkwin; scalePtr->display = Tk_Display(tkwin); scalePtr->interp = interp; scalePtr->widgetCmd = Tcl_CreateObjCommand(interp, Tk_PathName(scalePtr->tkwin), ScaleWidgetObjCmd, (ClientData) scalePtr, ScaleCmdDeletedProc); scalePtr->optionTable = optionTable; scalePtr->orient = ORIENT_VERTICAL; scalePtr->width = 0; scalePtr->length = 0; scalePtr->value = 0.0; scalePtr->varNamePtr = NULL; scalePtr->fromValue = 0.0; scalePtr->toValue = 0.0; scalePtr->tickInterval = 0.0; scalePtr->resolution = 1.0; scalePtr->digits = 0; scalePtr->bigIncrement = 0.0; scalePtr->command = NULL; scalePtr->repeatDelay = 0; scalePtr->repeatInterval = 0; scalePtr->label = NULL; scalePtr->labelLength = 0; scalePtr->state = STATE_NORMAL; scalePtr->borderWidth = 0; scalePtr->bgBorder = NULL; scalePtr->activeBorder = NULL; scalePtr->sliderRelief = TK_RELIEF_RAISED; scalePtr->troughColorPtr = NULL; scalePtr->troughGC = None; scalePtr->copyGC = None; scalePtr->tkfont = NULL; scalePtr->textColorPtr = NULL; scalePtr->textGC = None; scalePtr->relief = TK_RELIEF_FLAT; scalePtr->highlightWidth = 0; scalePtr->highlightBorder = NULL; scalePtr->highlightColorPtr = NULL; scalePtr->inset = 0; scalePtr->sliderLength = 0; scalePtr->showValue = 0; scalePtr->horizLabelY = 0; scalePtr->horizValueY = 0; scalePtr->horizTroughY = 0; scalePtr->horizTickY = 0; scalePtr->vertTickRightX = 0; scalePtr->vertValueRightX = 0; scalePtr->vertTroughX = 0; scalePtr->vertLabelX = 0; scalePtr->fontHeight = 0; scalePtr->cursor = None; scalePtr->takeFocusPtr = NULL; scalePtr->flags = NEVER_SET; Tk_SetClassProcs(scalePtr->tkwin, &scaleClass, (ClientData) scalePtr); Tk_CreateEventHandler(scalePtr->tkwin, ExposureMask|StructureNotifyMask|FocusChangeMask, ScaleEventProc, (ClientData) scalePtr); if ((Tk_InitOptions(interp, (char *) scalePtr, optionTable, tkwin) != TCL_OK) || (ConfigureScale(interp, scalePtr, objc - 2, objv + 2) != TCL_OK)) { Tk_DestroyWindow(scalePtr->tkwin); return TCL_ERROR; } Tcl_SetResult(interp, Tk_PathName(scalePtr->tkwin), TCL_STATIC); return TCL_OK; } /* *-------------------------------------------------------------- * * ScaleWidgetObjCmd -- * * This procedure is invoked to process the Tcl command * that corresponds to a widget managed by this module. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *-------------------------------------------------------------- */ static int ScaleWidgetObjCmd(clientData, interp, objc, objv) ClientData clientData; /* Information about scale * widget. */ Tcl_Interp *interp; /* Current interpreter. */ int objc; /* Number of arguments. */ Tcl_Obj *CONST objv[]; /* Argument strings. */ { TkScale *scalePtr = (TkScale *) clientData; Tcl_Obj *objPtr; int index, result; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?"); return TCL_ERROR; } result = Tcl_GetIndexFromObj(interp, objv[1], commandNames, "option", 0, &index); if (result != TCL_OK) { return result; } Tcl_Preserve((ClientData) scalePtr); switch (index) { case COMMAND_CGET: { if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "cget option"); goto error; } objPtr = Tk_GetOptionValue(interp, (char *) scalePtr, scalePtr->optionTable, objv[2], scalePtr->tkwin); if (objPtr == NULL) { goto error; } else { Tcl_SetObjResult(interp, objPtr); } break; } case COMMAND_CONFIGURE: { if (objc <= 3) { objPtr = Tk_GetOptionInfo(interp, (char *) scalePtr, scalePtr->optionTable, (objc == 3) ? objv[2] : (Tcl_Obj *) NULL, scalePtr->tkwin); if (objPtr == NULL) { goto error; } else { Tcl_SetObjResult(interp, objPtr); } } else { result = ConfigureScale(interp, scalePtr, objc-2, objv+2); } break; } case COMMAND_COORDS: { int x, y ; double value; char buf[TCL_INTEGER_SPACE * 2]; if ((objc != 2) && (objc != 3)) { Tcl_WrongNumArgs(interp, 1, objv, "coords ?value?"); goto error; } if (objc == 3) { if (Tcl_GetDoubleFromObj(interp, objv[2], &value) != TCL_OK) { goto error; } } else { value = scalePtr->value; } if (scalePtr->orient == ORIENT_VERTICAL) { x = scalePtr->vertTroughX + scalePtr->width/2 + scalePtr->borderWidth; y = TkScaleValueToPixel(scalePtr, value); } else { x = TkScaleValueToPixel(scalePtr, value); y = scalePtr->horizTroughY + scalePtr->width/2 + scalePtr->borderWidth; } sprintf(buf, "%d %d", x, y); Tcl_SetResult(interp, buf, TCL_VOLATILE); break; } case COMMAND_GET: { double value; int x, y; char buf[TCL_DOUBLE_SPACE]; if ((objc != 2) && (objc != 4)) { Tcl_WrongNumArgs(interp, 1, objv, "get ?x y?"); goto error; } if (objc == 2) { value = scalePtr->value; } else { if ((Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK) || (Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK)) { goto error; } value = TkScalePixelToValue(scalePtr, x, y); } sprintf(buf, scalePtr->format, value); Tcl_SetResult(interp, buf, TCL_VOLATILE); break; } case COMMAND_IDENTIFY: { int x, y, thing; if (objc != 4) { Tcl_WrongNumArgs(interp, 1, objv, "identify x y"); goto error; } if ((Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK) || (Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK)) { goto error; } thing = TkpScaleElement(scalePtr, x,y); switch (thing) { case TROUGH1: Tcl_SetResult(interp, "trough1", TCL_STATIC); break; case SLIDER: Tcl_SetResult(interp, "slider", TCL_STATIC); break; case TROUGH2: Tcl_SetResult(interp, "trough2", TCL_STATIC); break; } break; } case COMMAND_SET: { double value; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "set value"); goto error; } if (Tcl_GetDoubleFromObj(interp, objv[2], &value) != TCL_OK) { goto error; } if (scalePtr->state != STATE_DISABLED) { TkScaleSetValue(scalePtr, value, 1, 1); } break; } } Tcl_Release((ClientData) scalePtr); return result; error: Tcl_Release((ClientData) scalePtr); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * DestroyScale -- * * This procedure is invoked by Tcl_EventuallyFree or Tcl_Release * to clean up the internal structure of a button at a safe time * (when no-one is using it anymore). * * Results: * None. * * Side effects: * Everything associated with the scale is freed up. * *---------------------------------------------------------------------- */ static void DestroyScale(memPtr) char *memPtr; /* Info about scale widget. */ { register TkScale *scalePtr = (TkScale *) memPtr; scalePtr->flags |= SCALE_DELETED; Tcl_DeleteCommandFromToken(scalePtr->interp, scalePtr->widgetCmd); if (scalePtr->flags & REDRAW_PENDING) { Tcl_CancelIdleCall(TkpDisplayScale, (ClientData) scalePtr); } /* * Free up all the stuff that requires special handling, then * let Tk_FreeOptions handle all the standard option-related * stuff. */ if (scalePtr->varNamePtr != NULL) { Tcl_UntraceVar(scalePtr->interp, Tcl_GetString(scalePtr->varNamePtr), TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, ScaleVarProc, (ClientData) scalePtr); } if (scalePtr->troughGC != None) { Tk_FreeGC(scalePtr->display, scalePtr->troughGC); } if (scalePtr->copyGC != None) { Tk_FreeGC(scalePtr->display, scalePtr->copyGC); } if (scalePtr->textGC != None) { Tk_FreeGC(scalePtr->display, scalePtr->textGC); } Tk_FreeConfigOptions((char *) scalePtr, scalePtr->optionTable, scalePtr->tkwin); scalePtr->tkwin = NULL; TkpDestroyScale(scalePtr); } /* *---------------------------------------------------------------------- * * ConfigureScale -- * * This procedure is called to process an argv/argc list, plus * the Tk option database, in order to configure (or * reconfigure) a scale widget. * * Results: * The return value is a standard Tcl result. If TCL_ERROR is * returned, then the interp's result contains an error message. * * Side effects: * Configuration information, such as colors, border width, * etc. get set for scalePtr; old resources get freed, * if there were any. * *---------------------------------------------------------------------- */ static int ConfigureScale(interp, scalePtr, objc, objv) Tcl_Interp *interp; /* Used for error reporting. */ register TkScale *scalePtr; /* Information about widget; may or may * not already have values for some fields. */ int objc; /* Number of valid entries in objv. */ Tcl_Obj *CONST objv[]; /* Argument values. */ { Tk_SavedOptions savedOptions; Tcl_Obj *errorResult = NULL; int error; double varValue; /* * Eliminate any existing trace on a variable monitored by the scale. */ if (scalePtr->varNamePtr != NULL) { Tcl_UntraceVar(interp, Tcl_GetString(scalePtr->varNamePtr), TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, ScaleVarProc, (ClientData) scalePtr); } for (error = 0; error <= 1; error++) { if (!error) { /* * First pass: set options to new values. */ if (Tk_SetOptions(interp, (char *) scalePtr, scalePtr->optionTable, objc, objv, scalePtr->tkwin, &savedOptions, (int *) NULL) != TCL_OK) { continue; } } else { /* * Second pass: restore options to old values. */ errorResult = Tcl_GetObjResult(interp); Tcl_IncrRefCount(errorResult); Tk_RestoreSavedOptions(&savedOptions); } /* * If the scale is tied to the value of a variable, then set * the scale's value from the value of the variable, if it exists * and it holds a valid double value. */ if (scalePtr->varNamePtr != NULL) { double value; Tcl_Obj *valuePtr; valuePtr = Tcl_ObjGetVar2(interp, scalePtr->varNamePtr, NULL, TCL_GLOBAL_ONLY); if ((valuePtr != NULL) && (Tcl_GetDoubleFromObj(NULL, valuePtr, &value) == TCL_OK)) { scalePtr->value = TkRoundToResolution(scalePtr, value); } } /* * Several options need special processing, such as parsing the * orientation and creating GCs. */ scalePtr->fromValue = TkRoundToResolution(scalePtr, scalePtr->fromValue); scalePtr->toValue = TkRoundToResolution(scalePtr, scalePtr->toValue); scalePtr->tickInterval = TkRoundToResolution(scalePtr, scalePtr->tickInterval); /* * Make sure that the tick interval has the right sign so that * addition moves from fromValue to toValue. */ if ((scalePtr->tickInterval < 0) ^ ((scalePtr->toValue - scalePtr->fromValue) < 0)) { scalePtr->tickInterval = -scalePtr->tickInterval; } ComputeFormat(scalePtr); scalePtr->labelLength = scalePtr->label ? strlen(scalePtr->label) : 0; Tk_SetBackgroundFromBorder(scalePtr->tkwin, scalePtr->bgBorder); if (scalePtr->highlightWidth < 0) { scalePtr->highlightWidth = 0; } scalePtr->inset = scalePtr->highlightWidth + scalePtr->borderWidth; break; } if (!error) { Tk_FreeSavedOptions(&savedOptions); } /* * Set the scale value to itself; all this does is to make sure * that the scale's value is within the new acceptable range for * the scale. We don't set the var here because we need to make * special checks for possibly changed varNamePtr. */ TkScaleSetValue(scalePtr, scalePtr->value, 0, 1); /* * Reestablish the variable trace, if it is needed. */ if (scalePtr->varNamePtr != NULL) { Tcl_Obj *valuePtr; /* * Set the associated variable only when the new value differs * from the current value, or the variable doesn't yet exist */ valuePtr = Tcl_ObjGetVar2(interp, scalePtr->varNamePtr, NULL, TCL_GLOBAL_ONLY); if ((valuePtr == NULL) || (Tcl_GetDoubleFromObj(NULL, valuePtr, &varValue) != TCL_OK)) { ScaleSetVariable(scalePtr); } else { char varString[TCL_DOUBLE_SPACE]; char scaleString[TCL_DOUBLE_SPACE]; sprintf(varString, scalePtr->format, varValue); sprintf(scaleString, scalePtr->format, scalePtr->value); if (strcmp(varString, scaleString)) { ScaleSetVariable(scalePtr); } } Tcl_TraceVar(interp, Tcl_GetString(scalePtr->varNamePtr), TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, ScaleVarProc, (ClientData) scalePtr); } ScaleWorldChanged((ClientData) scalePtr); if (error) { Tcl_SetObjResult(interp, errorResult); Tcl_DecrRefCount(errorResult); return TCL_ERROR; } else { return TCL_OK; } } /* *--------------------------------------------------------------------------- * * ScaleWorldChanged -- * * This procedure is called when the world has changed in some * way and the widget needs to recompute all its graphics contexts * and determine its new geometry. * * Results: * None. * * Side effects: * Scale will be relayed out and redisplayed. * *--------------------------------------------------------------------------- */ static void ScaleWorldChanged(instanceData) ClientData instanceData; /* Information about widget. */ { XGCValues gcValues; GC gc; TkScale *scalePtr; scalePtr = (TkScale *) instanceData; gcValues.foreground = scalePtr->troughColorPtr->pixel; gc = Tk_GetGC(scalePtr->tkwin, GCForeground, &gcValues); if (scalePtr->troughGC != None) { Tk_FreeGC(scalePtr->display, scalePtr->troughGC); } scalePtr->troughGC = gc; gcValues.font = Tk_FontId(scalePtr->tkfont); gcValues.foreground = scalePtr->textColorPtr->pixel; gc = Tk_GetGC(scalePtr->tkwin, GCForeground | GCFont, &gcValues); if (scalePtr->textGC != None) { Tk_FreeGC(scalePtr->display, scalePtr->textGC); } scalePtr->textGC = gc; if (scalePtr->copyGC == None) { gcValues.graphics_exposures = False; scalePtr->copyGC = Tk_GetGC(scalePtr->tkwin, GCGraphicsExposures, &gcValues); } scalePtr->inset = scalePtr->highlightWidth + scalePtr->borderWidth; /* * Recompute display-related information, and let the geometry * manager know how much space is needed now. */ ComputeScaleGeometry(scalePtr); TkEventuallyRedrawScale(scalePtr, REDRAW_ALL); } /* *---------------------------------------------------------------------- * * ComputeFormat -- * * This procedure is invoked to recompute the "format" field * of a scale's widget record, which determines how the value * of the scale is converted to a string. * * Results: * None. * * Side effects: * The format field of scalePtr is modified. * *---------------------------------------------------------------------- */ static void ComputeFormat(scalePtr) TkScale *scalePtr; /* Information about scale widget. */ { double maxValue, x; int mostSigDigit, numDigits, leastSigDigit, afterDecimal; int eDigits, fDigits; /* * Compute the displacement from the decimal of the most significant * digit required for any number in the scale's range. */ maxValue = fabs(scalePtr->fromValue); x = fabs(scalePtr->toValue); if (x > maxValue) { maxValue = x; } if (maxValue == 0) { maxValue = 1; } mostSigDigit = (int) floor(log10(maxValue)); /* * If the number of significant digits wasn't specified explicitly, * compute it. It's the difference between the most significant * digit needed to represent any number on the scale and the * most significant digit of the smallest difference between * numbers on the scale. In other words, display enough digits so * that at least one digit will be different between any two adjacent * positions of the scale. */ numDigits = scalePtr->digits; if (numDigits <= 0) { if (scalePtr->resolution > 0) { /* * A resolution was specified for the scale, so just use it. */ leastSigDigit = (int) floor(log10(scalePtr->resolution)); } else { /* * No resolution was specified, so compute the difference * in value between adjacent pixels and use it for the least * significant digit. */ x = fabs(scalePtr->fromValue - scalePtr->toValue); if (scalePtr->length > 0) { x /= scalePtr->length; } if (x > 0){ leastSigDigit = (int) floor(log10(x)); } else { leastSigDigit = 0; } } numDigits = mostSigDigit - leastSigDigit + 1; if (numDigits < 1) { numDigits = 1; } } /* * Compute the number of characters required using "e" format and * "f" format, and then choose whichever one takes fewer characters. */ eDigits = numDigits + 4; if (numDigits > 1) { eDigits++; /* Decimal point. */ } afterDecimal = numDigits - mostSigDigit - 1; if (afterDecimal < 0) { afterDecimal = 0; } fDigits = (mostSigDigit >= 0) ? mostSigDigit + afterDecimal : afterDecimal; if (afterDecimal > 0) { fDigits++; /* Decimal point. */ } if (mostSigDigit < 0) { fDigits++; /* Zero to left of decimal point. */ } if (fDigits <= eDigits) { sprintf(scalePtr->format, "%%.%df", afterDecimal); } else { sprintf(scalePtr->format, "%%.%de", numDigits-1); } } /* *---------------------------------------------------------------------- * * ComputeScaleGeometry -- * * This procedure is called to compute various geometrical * information for a scale, such as where various things get * displayed. It's called when the window is reconfigured. * * Results: * None. * * Side effects: * Display-related numbers get changed in *scalePtr. The * geometry manager gets told about the window's preferred size. * *---------------------------------------------------------------------- */ static void ComputeScaleGeometry(scalePtr) register TkScale *scalePtr; /* Information about widget. */ { char valueString[PRINT_CHARS]; int tmp, valuePixels, x, y, extraSpace; Tk_FontMetrics fm; Tk_GetFontMetrics(scalePtr->tkfont, &fm); scalePtr->fontHeight = fm.linespace + SPACING; /* * Horizontal scales are simpler than vertical ones because * all sizes are the same (the height of a line of text); * handle them first and then quit. */ if (scalePtr->orient == ORIENT_HORIZONTAL) { y = scalePtr->inset; extraSpace = 0; if (scalePtr->labelLength != 0) { scalePtr->horizLabelY = y + SPACING; y += scalePtr->fontHeight; extraSpace = SPACING; } if (scalePtr->showValue) { scalePtr->horizValueY = y + SPACING; y += scalePtr->fontHeight; extraSpace = SPACING; } else { scalePtr->horizValueY = y; } y += extraSpace; scalePtr->horizTroughY = y; y += scalePtr->width + 2*scalePtr->borderWidth; if (scalePtr->tickInterval != 0) { scalePtr->horizTickY = y + SPACING; y += scalePtr->fontHeight + SPACING; } Tk_GeometryRequest(scalePtr->tkwin, scalePtr->length + 2*scalePtr->inset, y + scalePtr->inset); Tk_SetInternalBorder(scalePtr->tkwin, scalePtr->inset); return; } /* * Vertical scale: compute the amount of space needed to display * the scales value by formatting strings for the two end points; * use whichever length is longer. */ sprintf(valueString, scalePtr->format, scalePtr->fromValue); valuePixels = Tk_TextWidth(scalePtr->tkfont, valueString, -1); sprintf(valueString, scalePtr->format, scalePtr->toValue); tmp = Tk_TextWidth(scalePtr->tkfont, valueString, -1); if (valuePixels < tmp) { valuePixels = tmp; } /* * Assign x-locations to the elements of the scale, working from * left to right. */ x = scalePtr->inset; if ((scalePtr->tickInterval != 0) && (scalePtr->showValue)) { scalePtr->vertTickRightX = x + SPACING + valuePixels; scalePtr->vertValueRightX = scalePtr->vertTickRightX + valuePixels + fm.ascent/2; x = scalePtr->vertValueRightX + SPACING; } else if (scalePtr->tickInterval != 0) { scalePtr->vertTickRightX = x + SPACING + valuePixels; scalePtr->vertValueRightX = scalePtr->vertTickRightX; x = scalePtr->vertTickRightX + SPACING; } else if (scalePtr->showValue) { scalePtr->vertTickRightX = x; scalePtr->vertValueRightX = x + SPACING + valuePixels; x = scalePtr->vertValueRightX + SPACING; } else { scalePtr->vertTickRightX = x; scalePtr->vertValueRightX = x; } scalePtr->vertTroughX = x; x += 2*scalePtr->borderWidth + scalePtr->width; if (scalePtr->labelLength == 0) { scalePtr->vertLabelX = 0; } else { scalePtr->vertLabelX = x + fm.ascent/2; x = scalePtr->vertLabelX + fm.ascent/2 + Tk_TextWidth(scalePtr->tkfont, scalePtr->label, scalePtr->labelLength); } Tk_GeometryRequest(scalePtr->tkwin, x + scalePtr->inset, scalePtr->length + 2*scalePtr->inset); Tk_SetInternalBorder(scalePtr->tkwin, scalePtr->inset); } /* *-------------------------------------------------------------- * * ScaleEventProc -- * * This procedure is invoked by the Tk dispatcher for various * events on scales. * * Results: * None. * * Side effects: * When the window gets deleted, internal structures get * cleaned up. When it gets exposed, it is redisplayed. * *-------------------------------------------------------------- */ static void ScaleEventProc(clientData, eventPtr) ClientData clientData; /* Information about window. */ XEvent *eventPtr; /* Information about event. */ { TkScale *scalePtr = (TkScale *) clientData; if ((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0)) { TkEventuallyRedrawScale(scalePtr, REDRAW_ALL); } else if (eventPtr->type == DestroyNotify) { DestroyScale((char *) clientData); } else if (eventPtr->type == ConfigureNotify) { ComputeScaleGeometry(scalePtr); TkEventuallyRedrawScale(scalePtr, REDRAW_ALL); } else if (eventPtr->type == FocusIn) { if (eventPtr->xfocus.detail != NotifyInferior) { scalePtr->flags |= GOT_FOCUS; if (scalePtr->highlightWidth > 0) { TkEventuallyRedrawScale(scalePtr, REDRAW_ALL); } } } else if (eventPtr->type == FocusOut) { if (eventPtr->xfocus.detail != NotifyInferior) { scalePtr->flags &= ~GOT_FOCUS; if (scalePtr->highlightWidth > 0) { TkEventuallyRedrawScale(scalePtr, REDRAW_ALL); } } } } /* *---------------------------------------------------------------------- * * ScaleCmdDeletedProc -- * * This procedure is invoked when a widget command is deleted. If * the widget isn't already in the process of being destroyed, * this command destroys it. * * Results: * None. * * Side effects: * The widget is destroyed. * *---------------------------------------------------------------------- */ static void ScaleCmdDeletedProc(clientData) ClientData clientData; /* Pointer to widget record for widget. */ { TkScale *scalePtr = (TkScale *) clientData; Tk_Window tkwin = scalePtr->tkwin; /* * This procedure could be invoked either because the window was * destroyed and the command was then deleted (in which case tkwin * is NULL) or because the command was deleted, and then this procedure * destroys the widget. */ if (!(scalePtr->flags & SCALE_DELETED)) { scalePtr->flags |= SCALE_DELETED; Tk_DestroyWindow(tkwin); } } /* *-------------------------------------------------------------- * * TkEventuallyRedrawScale -- * * Arrange for part or all of a scale widget to redrawn at * the next convenient time in the future. * * Results: * None. * * Side effects: * If "what" is REDRAW_SLIDER then just the slider and the * value readout will be redrawn; if "what" is REDRAW_ALL * then the entire widget will be redrawn. * *-------------------------------------------------------------- */ void TkEventuallyRedrawScale(scalePtr, what) register TkScale *scalePtr; /* Information about widget. */ int what; /* What to redraw: REDRAW_SLIDER * or REDRAW_ALL. */ { if ((what == 0) || (scalePtr->tkwin == NULL) || !Tk_IsMapped(scalePtr->tkwin)) { return; } if (!(scalePtr->flags & REDRAW_PENDING)) { scalePtr->flags |= REDRAW_PENDING; Tcl_DoWhenIdle(TkpDisplayScale, (ClientData) scalePtr); } scalePtr->flags |= what; } /* *-------------------------------------------------------------- * * TkRoundToResolution -- * * Round a given floating-point value to the nearest multiple * of the scale's resolution. * * Results: * The return value is the rounded result. * * Side effects: * None. * *-------------------------------------------------------------- */ double TkRoundToResolution(scalePtr, value) TkScale *scalePtr; /* Information about scale widget. */ double value; /* Value to round. */ { double rem, new, tick; if (scalePtr->resolution <= 0) { return value; } tick = floor(value/scalePtr->resolution); new = scalePtr->resolution * tick; rem = value - new; if (rem < 0) { if (rem <= -scalePtr->resolution/2) { new = (tick - 1.0) * scalePtr->resolution; } } else { if (rem >= scalePtr->resolution/2) { new = (tick + 1.0) * scalePtr->resolution; } } return new; } /* *---------------------------------------------------------------------- * * ScaleVarProc -- * * This procedure is invoked by Tcl whenever someone modifies a * variable associated with a scale widget. * * Results: * NULL is always returned. * * Side effects: * The value displayed in the scale will change to match the * variable's new value. If the variable has a bogus value then * it is reset to the value of the scale. * *---------------------------------------------------------------------- */ /* ARGSUSED */ static char * ScaleVarProc(clientData, interp, name1, name2, flags) ClientData clientData; /* Information about button. */ Tcl_Interp *interp; /* Interpreter containing variable. */ CONST char *name1; /* Name of variable. */ CONST char *name2; /* Second part of variable name. */ int flags; /* Information about what happened. */ { register TkScale *scalePtr = (TkScale *) clientData; char *resultStr; double value; Tcl_Obj *valuePtr; int result; /* * If the variable is unset, then immediately recreate it unless * the whole interpreter is going away. */ if (flags & TCL_TRACE_UNSETS) { if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) { Tcl_TraceVar(interp, Tcl_GetString(scalePtr->varNamePtr), TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, ScaleVarProc, clientData); scalePtr->flags |= NEVER_SET; TkScaleSetValue(scalePtr, scalePtr->value, 1, 0); } return (char *) NULL; } /* * If we came here because we updated the variable (in TkScaleSetValue), * then ignore the trace. Otherwise update the scale with the value * of the variable. */ if (scalePtr->flags & SETTING_VAR) { return (char *) NULL; } resultStr = NULL; valuePtr = Tcl_ObjGetVar2(interp, scalePtr->varNamePtr, NULL, TCL_GLOBAL_ONLY); result = Tcl_GetDoubleFromObj(interp, valuePtr, &value); if (result != TCL_OK) { resultStr = "can't assign non-numeric value to scale variable"; ScaleSetVariable(scalePtr); } else { scalePtr->value = TkRoundToResolution(scalePtr, value); /* * This code is a bit tricky because it sets the scale's value before * calling TkScaleSetValue. This way, TkScaleSetValue won't bother * to set the variable again or to invoke the -command. However, it * also won't redisplay the scale, so we have to ask for that * explicitly. */ TkScaleSetValue(scalePtr, scalePtr->value, 1, 0); } TkEventuallyRedrawScale(scalePtr, REDRAW_SLIDER); return resultStr; } /* *-------------------------------------------------------------- * * TkScaleSetValue -- * * This procedure changes the value of a scale and invokes * a Tcl command to reflect the current position of a scale * * Results: * None. * * Side effects: * A Tcl command is invoked, and an additional error-processing * command may also be invoked. The scale's slider is redrawn. * *-------------------------------------------------------------- */ void TkScaleSetValue(scalePtr, value, setVar, invokeCommand) register TkScale *scalePtr; /* Info about widget. */ double value; /* New value for scale. Gets adjusted * if it's off the scale. */ int setVar; /* Non-zero means reflect new value through * to associated variable, if any. */ int invokeCommand; /* Non-zero means invoked -command option * to notify of new value, 0 means don't. */ { value = TkRoundToResolution(scalePtr, value); if ((value < scalePtr->fromValue) ^ (scalePtr->toValue < scalePtr->fromValue)) { value = scalePtr->fromValue; } if ((value > scalePtr->toValue) ^ (scalePtr->toValue < scalePtr->fromValue)) { value = scalePtr->toValue; } if (scalePtr->flags & NEVER_SET) { scalePtr->flags &= ~NEVER_SET; } else if (scalePtr->value == value) { return; } scalePtr->value = value; if (invokeCommand) { scalePtr->flags |= INVOKE_COMMAND; } TkEventuallyRedrawScale(scalePtr, REDRAW_SLIDER); if (setVar && scalePtr->varNamePtr) { ScaleSetVariable(scalePtr); } } /* *-------------------------------------------------------------- * * ScaleSetVariable -- * * This procedure sets the variable associated with a scale, if any. * * Results: * None. * * Side effects: * Other write traces on the variable will trigger. * *-------------------------------------------------------------- */ static void ScaleSetVariable(scalePtr) register TkScale *scalePtr; /* Info about widget. */ { if (scalePtr->varNamePtr != NULL) { char string[PRINT_CHARS]; sprintf(string, scalePtr->format, scalePtr->value); scalePtr->flags |= SETTING_VAR; Tcl_ObjSetVar2(scalePtr->interp, scalePtr->varNamePtr, NULL, Tcl_NewStringObj(string, -1), TCL_GLOBAL_ONLY); scalePtr->flags &= ~SETTING_VAR; } } /* *---------------------------------------------------------------------- * * TkScalePixelToValue -- * * Given a pixel within a scale window, return the scale * reading corresponding to that pixel. * * Results: * A double-precision scale reading. If the value is outside * the legal range for the scale then it's rounded to the nearest * end of the scale. * * Side effects: * None. * *---------------------------------------------------------------------- */ double TkScalePixelToValue(scalePtr, x, y) register TkScale *scalePtr; /* Information about widget. */ int x, y; /* Coordinates of point within * window. */ { double value, pixelRange; if (scalePtr->orient == ORIENT_VERTICAL) { pixelRange = Tk_Height(scalePtr->tkwin) - scalePtr->sliderLength - 2*scalePtr->inset - 2*scalePtr->borderWidth; value = y; } else { pixelRange = Tk_Width(scalePtr->tkwin) - scalePtr->sliderLength - 2*scalePtr->inset - 2*scalePtr->borderWidth; value = x; } if (pixelRange <= 0) { /* * Not enough room for the slider to actually slide: just return * the scale's current value. */ return scalePtr->value; } value -= scalePtr->sliderLength/2 + scalePtr->inset + scalePtr->borderWidth; value /= pixelRange; if (value < 0) { value = 0; } if (value > 1) { value = 1; } value = scalePtr->fromValue + value * (scalePtr->toValue - scalePtr->fromValue); return TkRoundToResolution(scalePtr, value); } /* *---------------------------------------------------------------------- * * TkScaleValueToPixel -- * * Given a reading of the scale, return the x-coordinate or * y-coordinate corresponding to that reading, depending on * whether the scale is vertical or horizontal, respectively. * * Results: * An integer value giving the pixel location corresponding * to reading. The value is restricted to lie within the * defined range for the scale. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TkScaleValueToPixel(scalePtr, value) register TkScale *scalePtr; /* Information about widget. */ double value; /* Reading of the widget. */ { int y, pixelRange; double valueRange; valueRange = scalePtr->toValue - scalePtr->fromValue; pixelRange = ((scalePtr->orient == ORIENT_VERTICAL) ? Tk_Height(scalePtr->tkwin) : Tk_Width(scalePtr->tkwin)) - scalePtr->sliderLength - 2*scalePtr->inset - 2*scalePtr->borderWidth; if (valueRange == 0) { y = 0; } else { y = (int) ((value - scalePtr->fromValue) * pixelRange / valueRange + 0.5); if (y < 0) { y = 0; } else if (y > pixelRange) { y = pixelRange; } } y += scalePtr->sliderLength/2 + scalePtr->inset + scalePtr->borderWidth; return y; }