1 /*
2  * tkScale.c --
3  *
4  *	This module implements a scale widgets for the Tk toolkit.
5  *	A scale displays a slider that can be adjusted to change a
6  *	value;  it also displays numeric labels and a textual label,
7  *	if desired.
8  *
9  *	The modifications to use floating-point values are based on
10  *	an implementation by Paul Mackerras.  The -variable option
11  *	is due to Henning Schulzrinne.  All of these are used with
12  *	permission.
13  *
14  * Copyright (c) 1990-1994 The Regents of the University of California.
15  * Copyright (c) 1994-1997 Sun Microsystems, Inc.
16  * Copyright (c) 1998-2000 by Scriptics Corporation.
17  *
18  * See the file "license.terms" for information on usage and redistribution
19  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
20  *
21  * RCS: @(#) $Id: tkScale.c,v 1.17 2002/08/05 04:30:40 dgp Exp $
22  */
23 
24 #include "tkPort.h"
25 #include "default.h"
26 #include "tkInt.h"
27 #include <math.h>
28 #include "tkVMacro.h"
29 #include "tkScale.h"
30 
31 /*
32  * The following table defines the legal values for the -orient option.
33  * It is used together with the "enum orient" declaration in tkScale.h.
34  */
35 
36 static char *orientStrings[] = {
37     "horizontal", "vertical", (char *) NULL
38 };
39 
40 /*
41  * The following table defines the legal values for the -state option.
42  * It is used together with the "enum state" declaration in tkScale.h.
43  */
44 
45 static char *stateStrings[] = {
46     "active", "disabled", "normal", (char *) NULL
47 };
48 
49 static Tk_OptionSpec optionSpecs[] = {
50     {TK_OPTION_BORDER, "-activebackground", "activeBackground", "Foreground",
51 	DEF_SCALE_ACTIVE_BG_COLOR, -1, Tk_Offset(TkScale, activeBorder),
52 	0, (ClientData) DEF_SCALE_ACTIVE_BG_MONO, 0},
53     {TK_OPTION_BORDER, "-background", "background", "Background",
54 	DEF_SCALE_BG_COLOR, -1, Tk_Offset(TkScale, bgBorder),
55 	0, (ClientData) DEF_SCALE_BG_MONO, 0},
56     {TK_OPTION_DOUBLE, "-bigincrement", "bigIncrement", "BigIncrement",
57         DEF_SCALE_BIG_INCREMENT, -1, Tk_Offset(TkScale, bigIncrement),
58         0, 0, 0},
59     {TK_OPTION_SYNONYM, "-bd", (char *) NULL, (char *) NULL,
60 	(char *) NULL, 0, -1, 0, (ClientData) "-borderwidth", 0},
61     {TK_OPTION_SYNONYM, "-bg", (char *) NULL, (char *) NULL,
62 	(char *) NULL, 0, -1, 0, (ClientData) "-background", 0},
63     {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
64 	DEF_SCALE_BORDER_WIDTH, -1, Tk_Offset(TkScale, borderWidth),
65         0, 0, 0},
66     {TK_OPTION_CALLBACK, "-command", "command", "Command",
67 	DEF_SCALE_COMMAND, -1, Tk_Offset(TkScale, command),
68 	TK_OPTION_NULL_OK, 0, 0},
69     {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor",
70 	DEF_SCALE_CURSOR, -1, Tk_Offset(TkScale, cursor),
71 	TK_OPTION_NULL_OK, 0, 0},
72     {TK_OPTION_INT, "-digits", "digits", "Digits",
73 	DEF_SCALE_DIGITS, -1, Tk_Offset(TkScale, digits),
74         0, 0, 0},
75     {TK_OPTION_SYNONYM, "-fg", "foreground", (char *) NULL,
76 	(char *) NULL, 0, -1, 0, (ClientData) "-foreground", 0},
77     {TK_OPTION_FONT, "-font", "font", "Font",
78 	DEF_SCALE_FONT, -1, Tk_Offset(TkScale, tkfont), 0, 0, 0},
79     {TK_OPTION_COLOR, "-foreground", "foreground", "Foreground",
80 	DEF_SCALE_FG_COLOR, -1, Tk_Offset(TkScale, textColorPtr), 0,
81         (ClientData) DEF_SCALE_FG_MONO, 0},
82     {TK_OPTION_DOUBLE, "-from", "from", "From", DEF_SCALE_FROM, -1,
83         Tk_Offset(TkScale, fromValue), 0, 0, 0},
84     {TK_OPTION_BORDER, "-highlightbackground", "highlightBackground",
85 	"HighlightBackground", DEF_SCALE_HIGHLIGHT_BG_COLOR,
86 	-1, Tk_Offset(TkScale, highlightBorder),
87         0, (ClientData) DEF_SCALE_HIGHLIGHT_BG_MONO, 0},
88     {TK_OPTION_COLOR, "-highlightcolor", "highlightColor", "HighlightColor",
89 	DEF_SCALE_HIGHLIGHT, -1, Tk_Offset(TkScale, highlightColorPtr),
90 	0, 0, 0},
91     {TK_OPTION_PIXELS, "-highlightthickness", "highlightThickness",
92 	"HighlightThickness", DEF_SCALE_HIGHLIGHT_WIDTH, -1,
93 	Tk_Offset(TkScale, highlightWidth), 0, 0, 0},
94     {TK_OPTION_STRING, "-label", "label", "Label",
95 	DEF_SCALE_LABEL, -1, Tk_Offset(TkScale, label),
96 	TK_OPTION_NULL_OK, 0, 0},
97     {TK_OPTION_PIXELS, "-length", "length", "Length",
98 	DEF_SCALE_LENGTH, -1, Tk_Offset(TkScale, length), 0, 0, 0},
99     {TK_OPTION_STRING_TABLE, "-orient", "orient", "Orient",
100         DEF_SCALE_ORIENT, -1, Tk_Offset(TkScale, orient),
101         0, (ClientData) orientStrings, 0},
102     {TK_OPTION_RELIEF, "-relief", "relief", "Relief",
103 	DEF_SCALE_RELIEF, -1, Tk_Offset(TkScale, relief), 0, 0, 0},
104     {TK_OPTION_INT, "-repeatdelay", "repeatDelay", "RepeatDelay",
105         DEF_SCALE_REPEAT_DELAY, -1, Tk_Offset(TkScale, repeatDelay),
106         0, 0, 0},
107     {TK_OPTION_INT, "-repeatinterval", "repeatInterval", "RepeatInterval",
108         DEF_SCALE_REPEAT_INTERVAL, -1, Tk_Offset(TkScale, repeatInterval),
109         0, 0, 0},
110     {TK_OPTION_DOUBLE, "-resolution", "resolution", "Resolution",
111         DEF_SCALE_RESOLUTION, -1, Tk_Offset(TkScale, resolution),
112         0, 0, 0},
113     {TK_OPTION_BOOLEAN, "-showvalue", "showValue", "ShowValue",
114         DEF_SCALE_SHOW_VALUE, -1, Tk_Offset(TkScale, showValue),
115         0, 0, 0},
116     {TK_OPTION_PIXELS, "-sliderlength", "sliderLength", "SliderLength",
117         DEF_SCALE_SLIDER_LENGTH, -1, Tk_Offset(TkScale, sliderLength),
118         0, 0, 0},
119     {TK_OPTION_RELIEF, "-sliderrelief", "sliderRelief", "SliderRelief",
120 	DEF_SCALE_SLIDER_RELIEF, -1, Tk_Offset(TkScale, sliderRelief),
121         0, 0, 0},
122     {TK_OPTION_STRING_TABLE, "-state", "state", "State",
123         DEF_SCALE_STATE, -1, Tk_Offset(TkScale, state),
124         0, (ClientData) stateStrings, 0},
125     {TK_OPTION_STRING, "-takefocus", "takeFocus", "TakeFocus",
126 	DEF_SCALE_TAKE_FOCUS, Tk_Offset(TkScale, takeFocusPtr), -1,
127 	TK_OPTION_NULL_OK, 0, 0},
128     {TK_OPTION_DOUBLE, "-tickinterval", "tickInterval", "TickInterval",
129         DEF_SCALE_TICK_INTERVAL, -1, Tk_Offset(TkScale, tickInterval),
130         0, 0, 0},
131     {TK_OPTION_DOUBLE, "-to", "to", "To",
132         DEF_SCALE_TO, -1, Tk_Offset(TkScale, toValue), 0, 0, 0},
133     {TK_OPTION_COLOR, "-troughcolor", "troughColor", "Background",
134         DEF_SCALE_TROUGH_COLOR, -1, Tk_Offset(TkScale, troughColorPtr),
135         0, (ClientData) DEF_SCALE_TROUGH_MONO, 0},
136     {TK_OPTION_STRING, "-variable", "variable", "Variable",
137 	DEF_SCALE_VARIABLE, Tk_Offset(TkScale, varNamePtr), -1,
138 	TK_OPTION_NULL_OK, 0, 0},
139     {TK_OPTION_PIXELS, "-width", "width", "Width",
140 	DEF_SCALE_WIDTH, -1, Tk_Offset(TkScale, width), 0, 0, 0},
141     {TK_OPTION_END, (char *) NULL, (char *) NULL, (char *) NULL,
142 	(char *) NULL, 0, -1, 0, 0, 0}
143 };
144 
145 /*
146  * The following tables define the scale widget commands and map the
147  * indexes into the string tables into a single enumerated type used
148  * to dispatch the scale widget command.
149  */
150 
151 static CONST char *commandNames[] = {
152     "cget", "configure", "coords", "get", "identify", "set", (char *) NULL
153 };
154 
155 enum command {
156     COMMAND_CGET, COMMAND_CONFIGURE, COMMAND_COORDS, COMMAND_GET,
157     COMMAND_IDENTIFY, COMMAND_SET
158 };
159 
160 /*
161  * Forward declarations for procedures defined later in this file:
162  */
163 
164 static void		ComputeFormat _ANSI_ARGS_((TkScale *scalePtr));
165 static void		ComputeScaleGeometry _ANSI_ARGS_((TkScale *scalePtr));
166 static int		ConfigureScale _ANSI_ARGS_((Tcl_Interp *interp,
167 			    TkScale *scalePtr, int objc,
168 			    Tcl_Obj *CONST objv[]));
169 static void		DestroyScale _ANSI_ARGS_((char *memPtr));
170 static void		ScaleCmdDeletedProc _ANSI_ARGS_((
171 			    ClientData clientData));
172 static void		ScaleEventProc _ANSI_ARGS_((ClientData clientData,
173 			    XEvent *eventPtr));
174 static char *		ScaleVarProc _ANSI_ARGS_((ClientData clientData,
175 			    Tcl_Interp *interp, Var name1,
176 			    CONST char *name2, int flags));
177 static int		ScaleWidgetObjCmd _ANSI_ARGS_((ClientData clientData,
178 			    Tcl_Interp *interp, int objc,
179 			    Tcl_Obj *CONST objv[]));
180 static void		ScaleWorldChanged _ANSI_ARGS_((
181 			    ClientData instanceData));
182 static void		ScaleSetVariable _ANSI_ARGS_((TkScale *scalePtr));
183 
184 /*
185  * The structure below defines scale class behavior by means of procedures
186  * that can be invoked from generic window code.
187  */
188 
189 static Tk_ClassProcs scaleClass = {
190     sizeof(Tk_ClassProcs),	/* size */
191     ScaleWorldChanged,		/* worldChangedProc */
192 };
193 
194 
195 /*
196  *--------------------------------------------------------------
197  *
198  * Tk_ScaleObjCmd --
199  *
200  *	This procedure is invoked to process the "scale" Tcl
201  *	command.  See the user documentation for details on what
202  *	it does.
203  *
204  * Results:
205  *	A standard Tcl result.
206  *
207  * Side effects:
208  *	See the user documentation.
209  *
210  *--------------------------------------------------------------
211  */
212 
213 int
Tk_ScaleObjCmd(clientData,interp,objc,objv)214 Tk_ScaleObjCmd(clientData, interp, objc, objv)
215     ClientData clientData;	/* NULL. */
216     Tcl_Interp *interp;		/* Current interpreter. */
217     int objc;			/* Number of arguments. */
218     Tcl_Obj *CONST objv[];	/* Argument values. */
219 {
220     register TkScale *scalePtr;
221     Tk_OptionTable optionTable;
222     Tk_Window tkwin;
223 
224     if (objc < 2) {
225 	Tcl_WrongNumArgs(interp, 1, objv, "pathName ?options?");
226 	return TCL_ERROR;
227     }
228 
229     tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp),
230             Tcl_GetString(objv[1]), (char *) NULL);
231     if (tkwin == NULL) {
232 	return TCL_ERROR;
233     }
234 
235     /*
236      * Create the option table for this widget class.  If it has already
237      * been created, the cached pointer will be returned.
238      */
239 
240     optionTable = Tk_CreateOptionTable(interp, optionSpecs);
241 
242     Tk_SetClass(tkwin, "Scale");
243     scalePtr = TkpCreateScale(tkwin);
244 
245     /*
246      * Initialize fields that won't be initialized by ConfigureScale,
247      * or which ConfigureScale expects to have reasonable values
248      * (e.g. resource pointers).
249      */
250 
251     scalePtr->tkwin		= tkwin;
252     scalePtr->display		= Tk_Display(tkwin);
253     scalePtr->interp		= interp;
254     scalePtr->widgetCmd		= Tcl_CreateObjCommand(interp,
255 	    Tk_PathName(scalePtr->tkwin), ScaleWidgetObjCmd,
256 	    (ClientData) scalePtr, ScaleCmdDeletedProc);
257     scalePtr->optionTable	= optionTable;
258     scalePtr->orient		= ORIENT_VERTICAL;
259     scalePtr->width		= 0;
260     scalePtr->length		= 0;
261     scalePtr->value		= 0.0;
262     scalePtr->varNamePtr	= NULL;
263     scalePtr->fromValue		= 0.0;
264     scalePtr->toValue		= 0.0;
265     scalePtr->tickInterval	= 0.0;
266     scalePtr->resolution	= 1.0;
267     scalePtr->digits		= 0;
268     scalePtr->bigIncrement	= 0.0;
269     scalePtr->command		= NULL;
270     scalePtr->repeatDelay	= 0;
271     scalePtr->repeatInterval	= 0;
272     scalePtr->label		= NULL;
273     scalePtr->labelLength	= 0;
274     scalePtr->state		= STATE_NORMAL;
275     scalePtr->borderWidth	= 0;
276     scalePtr->bgBorder		= NULL;
277     scalePtr->activeBorder	= NULL;
278     scalePtr->sliderRelief	= TK_RELIEF_RAISED;
279     scalePtr->troughColorPtr	= NULL;
280     scalePtr->troughGC		= None;
281     scalePtr->copyGC		= None;
282     scalePtr->tkfont		= NULL;
283     scalePtr->textColorPtr	= NULL;
284     scalePtr->textGC		= None;
285     scalePtr->relief		= TK_RELIEF_FLAT;
286     scalePtr->highlightWidth	= 0;
287     scalePtr->highlightBorder	= NULL;
288     scalePtr->highlightColorPtr	= NULL;
289     scalePtr->inset		= 0;
290     scalePtr->sliderLength	= 0;
291     scalePtr->showValue		= 0;
292     scalePtr->horizLabelY	= 0;
293     scalePtr->horizValueY	= 0;
294     scalePtr->horizTroughY	= 0;
295     scalePtr->horizTickY	= 0;
296     scalePtr->vertTickRightX	= 0;
297     scalePtr->vertValueRightX	= 0;
298     scalePtr->vertTroughX	= 0;
299     scalePtr->vertLabelX	= 0;
300     scalePtr->fontHeight	= 0;
301     scalePtr->cursor		= None;
302     scalePtr->takeFocusPtr	= NULL;
303     scalePtr->flags		= NEVER_SET;
304 
305     Tk_SetClassProcs(scalePtr->tkwin, &scaleClass, (ClientData) scalePtr);
306     Tk_CreateEventHandler(scalePtr->tkwin,
307 	    ExposureMask|StructureNotifyMask|FocusChangeMask,
308 	    ScaleEventProc, (ClientData) scalePtr);
309 
310     if ((Tk_InitOptions(interp, (char *) scalePtr, optionTable, tkwin)
311 	    != TCL_OK) ||
312 	    (ConfigureScale(interp, scalePtr, objc - 2, objv + 2) != TCL_OK)) {
313 	Tk_DestroyWindow(scalePtr->tkwin);
314 	return TCL_ERROR;
315     }
316 
317     Tcl_SetResult(interp, Tk_PathName(scalePtr->tkwin), TCL_STATIC);
318     return TCL_OK;
319 }
320 
321 /*
322  *--------------------------------------------------------------
323  *
324  * ScaleWidgetObjCmd --
325  *
326  *	This procedure is invoked to process the Tcl command
327  *	that corresponds to a widget managed by this module.
328  *	See the user documentation for details on what it does.
329  *
330  * Results:
331  *	A standard Tcl result.
332  *
333  * Side effects:
334  *	See the user documentation.
335  *
336  *--------------------------------------------------------------
337  */
338 
339 static int
ScaleWidgetObjCmd(clientData,interp,objc,objv)340 ScaleWidgetObjCmd(clientData, interp, objc, objv)
341     ClientData clientData;		/* Information about scale
342 					 * widget. */
343     Tcl_Interp *interp;			/* Current interpreter. */
344     int objc;				/* Number of arguments. */
345     Tcl_Obj *CONST objv[];		/* Argument strings. */
346 {
347     TkScale *scalePtr = (TkScale *) clientData;
348     Tcl_Obj *objPtr;
349     int index, result;
350 
351     if (objc < 2) {
352         Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?");
353 	return TCL_ERROR;
354     }
355     result = Tcl_GetIndexFromObj(interp, objv[1], commandNames,
356             "option", 0, &index);
357     if (result != TCL_OK) {
358 	return result;
359     }
360     Tcl_Preserve((ClientData) scalePtr);
361 
362     switch (index) {
363         case COMMAND_CGET: {
364   	    if (objc != 3) {
365 	        Tcl_WrongNumArgs(interp, 1, objv, "cget option");
366 		goto error;
367 	    }
368 	    objPtr = Tk_GetOptionValue(interp, (char *) scalePtr,
369 		    scalePtr->optionTable, objv[2], scalePtr->tkwin);
370 	    if (objPtr == NULL) {
371 		 goto error;
372 	    } else {
373 		Tcl_SetObjResult(interp, objPtr);
374 	    }
375 	    break;
376 	}
377         case COMMAND_CONFIGURE: {
378 	    if (objc <= 3) {
379 		objPtr = Tk_GetOptionInfo(interp, (char *) scalePtr,
380 			scalePtr->optionTable,
381 			(objc == 3) ? objv[2] : (Tcl_Obj *) NULL,
382 			scalePtr->tkwin);
383 		if (objPtr == NULL) {
384 		    goto error;
385 		} else {
386 		    Tcl_SetObjResult(interp, objPtr);
387 		}
388 	    } else {
389 		result = ConfigureScale(interp, scalePtr, objc-2, objv+2);
390 	    }
391 	    break;
392 	}
393         case COMMAND_COORDS: {
394 	    int x, y ;
395 	    double value;
396 	    char buf[TCL_INTEGER_SPACE * 2];
397 
398 	    if ((objc != 2) && (objc != 3)) {
399 	        Tcl_WrongNumArgs(interp, 1, objv, "coords ?value?");
400 		goto error;
401 	    }
402 	    if (objc == 3) {
403 	        if (Tcl_GetDoubleFromObj(interp, objv[2], &value)
404                         != TCL_OK) {
405 		    goto error;
406 		}
407 	    } else {
408 	        value = scalePtr->value;
409 	    }
410 	    if (scalePtr->orient == ORIENT_VERTICAL) {
411 	        x = scalePtr->vertTroughX + scalePtr->width/2
412 		        + scalePtr->borderWidth;
413 		y = TkScaleValueToPixel(scalePtr, value);
414 	    } else {
415 	        x = TkScaleValueToPixel(scalePtr, value);
416 		y = scalePtr->horizTroughY + scalePtr->width/2
417                         + scalePtr->borderWidth;
418 	    }
419 	    Tcl_IntResults(interp,2,0, x, y);
420             break;
421         }
422         case COMMAND_GET: {
423 	    double value;
424 	    int x, y;
425 	    char buf[TCL_DOUBLE_SPACE];
426 
427 	    if ((objc != 2) && (objc != 4)) {
428 	        Tcl_WrongNumArgs(interp, 1, objv, "get ?x y?");
429 		goto error;
430 	    }
431 	    if (objc == 2) {
432 	        value = scalePtr->value;
433 	    } else {
434 	        if ((Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK)
435 		        || (Tcl_GetIntFromObj(interp, objv[3], &y)
436                         != TCL_OK)) {
437 		    goto error;
438 		}
439 		value = TkScalePixelToValue(scalePtr, x, y);
440 	    }
441 	    sprintf(buf, scalePtr->format, value);
442 	    Tcl_SetResult(interp, buf, TCL_VOLATILE);
443             break;
444         }
445         case COMMAND_IDENTIFY: {
446 	    int x, y, thing;
447 
448 	    if (objc != 4) {
449 	        Tcl_WrongNumArgs(interp, 1, objv, "identify x y");
450 		goto error;
451 	    }
452 	    if ((Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK)
453                     || (Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK)) {
454 	        goto error;
455 	    }
456 	    thing = TkpScaleElement(scalePtr, x,y);
457 	    switch (thing) {
458 	        case TROUGH1:
459 		    Tcl_SetResult(interp, "trough1", TCL_STATIC);
460 		    break;
461 	        case SLIDER:
462 		    Tcl_SetResult(interp, "slider", TCL_STATIC);
463 		    break;
464 	        case TROUGH2:
465 		    Tcl_SetResult(interp, "trough2", TCL_STATIC);
466 		    break;
467 	    }
468             break;
469         }
470         case COMMAND_SET: {
471 	    double value;
472 
473 	    if (objc != 3) {
474 	        Tcl_WrongNumArgs(interp, 1, objv, "set value");
475 		goto error;
476 	    }
477 	    if (Tcl_GetDoubleFromObj(interp, objv[2], &value) != TCL_OK) {
478 	        goto error;
479 	    }
480 	    if (scalePtr->state != STATE_DISABLED) {
481 	      TkScaleSetValue(scalePtr, value, 1, 1);
482 	    }
483 	    break;
484         }
485     }
486     Tcl_Release((ClientData) scalePtr);
487     return result;
488 
489     error:
490     Tcl_Release((ClientData) scalePtr);
491     return TCL_ERROR;
492 }
493 
494 /*
495  *----------------------------------------------------------------------
496  *
497  * DestroyScale --
498  *
499  *	This procedure is invoked by Tcl_EventuallyFree or Tcl_Release
500  *	to clean up the internal structure of a button at a safe time
501  *	(when no-one is using it anymore).
502  *
503  * Results:
504  *	None.
505  *
506  * Side effects:
507  *	Everything associated with the scale is freed up.
508  *
509  *----------------------------------------------------------------------
510  */
511 
512 static void
DestroyScale(memPtr)513 DestroyScale(memPtr)
514     char *memPtr;	/* Info about scale widget. */
515 {
516     register TkScale *scalePtr = (TkScale *) memPtr;
517 
518     scalePtr->flags |= SCALE_DELETED;
519 
520     Tcl_DeleteCommandFromToken(scalePtr->interp, scalePtr->widgetCmd);
521     if (scalePtr->flags & REDRAW_PENDING) {
522 	Tcl_CancelIdleCall(TkpDisplayScale, (ClientData) scalePtr);
523     }
524 
525     /*
526      * Free up all the stuff that requires special handling, then
527      * let Tk_FreeOptions handle all the standard option-related
528      * stuff.
529      */
530 
531     if (scalePtr->varNamePtr != NULL) {
532 	Lang_UntraceVar(scalePtr->interp, scalePtr->varNamePtr,
533 		TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
534 		ScaleVarProc, (ClientData) scalePtr);
535     }
536     if (scalePtr->troughGC != None) {
537 	Tk_FreeGC(scalePtr->display, scalePtr->troughGC);
538     }
539     if (scalePtr->copyGC != None) {
540 	Tk_FreeGC(scalePtr->display, scalePtr->copyGC);
541     }
542     if (scalePtr->textGC != None) {
543 	Tk_FreeGC(scalePtr->display, scalePtr->textGC);
544     }
545     Tk_FreeConfigOptions((char *) scalePtr, scalePtr->optionTable,
546 	    scalePtr->tkwin);
547     scalePtr->tkwin = NULL;
548     TkpDestroyScale(scalePtr);
549 }
550 
551 /*
552  *----------------------------------------------------------------------
553  *
554  * ConfigureScale --
555  *
556  *	This procedure is called to process an argv/argc list, plus
557  *	the Tk option database, in order to configure (or
558  *	reconfigure) a scale widget.
559  *
560  * Results:
561  *	The return value is a standard Tcl result.  If TCL_ERROR is
562  *	returned, then the interp's result contains an error message.
563  *
564  * Side effects:
565  *	Configuration information, such as colors, border width,
566  *	etc. get set for scalePtr;  old resources get freed,
567  *	if there were any.
568  *
569  *----------------------------------------------------------------------
570  */
571 
572 static int
ConfigureScale(interp,scalePtr,objc,objv)573 ConfigureScale(interp, scalePtr, objc, objv)
574     Tcl_Interp *interp;		/* Used for error reporting. */
575     register TkScale *scalePtr;	/* Information about widget;  may or may
576 				 * not already have values for some fields. */
577     int objc;			/* Number of valid entries in objv. */
578     Tcl_Obj *CONST objv[];	/* Argument values. */
579 {
580     Tk_SavedOptions savedOptions;
581     Tcl_Obj *errorResult = NULL;
582     int error;
583     double oldValue = scalePtr->value;
584 
585     /*
586      * Eliminate any existing trace on a variable monitored by the scale.
587      */
588 
589     if (scalePtr->varNamePtr != NULL) {
590 	Lang_UntraceVar(interp, scalePtr->varNamePtr,
591 		TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
592 		ScaleVarProc, (ClientData) scalePtr);
593     }
594 
595     for (error = 0; error <= 1; error++) {
596 	if (!error) {
597 	    /*
598 	     * First pass: set options to new values.
599 	     */
600 
601 	    if (Tk_SetOptions(interp, (char *) scalePtr,
602 		    scalePtr->optionTable, objc, objv,
603 		    scalePtr->tkwin, &savedOptions, (int *) NULL) != TCL_OK) {
604 		continue;
605 	    }
606 	} else {
607 	    /*
608 	     * Second pass: restore options to old values.
609 	     */
610 
611 	    errorResult = Tcl_GetObjResult(interp);
612 	    Tcl_IncrRefCount(errorResult);
613 	    Tk_RestoreSavedOptions(&savedOptions);
614 	}
615 
616 	/*
617 	 * If the scale is tied to the value of a variable, then set
618 	 * the scale's value from the value of the variable, if it exists
619 	 * and it holds a valid double value.
620 	 */
621 
622 	if (scalePtr->varNamePtr != NULL) {
623 	    double value;
624 	    Tcl_Obj *valuePtr;
625 
626 	    valuePtr = Tcl_ObjGetVar2(interp, scalePtr->varNamePtr, NULL,
627 		    TCL_GLOBAL_ONLY);
628 	    if ((valuePtr != NULL) &&
629 		    (Tcl_GetDoubleFromObj(NULL, valuePtr, &value) == TCL_OK)) {
630 		scalePtr->value = TkRoundToResolution(scalePtr, value);
631 	    }
632 	}
633 
634 	/*
635 	 * Several options need special processing, such as parsing the
636 	 * orientation and creating GCs.
637 	 */
638 
639 	scalePtr->fromValue = TkRoundToResolution(scalePtr,
640                 scalePtr->fromValue);
641 	scalePtr->toValue = TkRoundToResolution(scalePtr, scalePtr->toValue);
642 	scalePtr->tickInterval = TkRoundToResolution(scalePtr,
643 	        scalePtr->tickInterval);
644 
645 	/*
646 	 * Make sure that the tick interval has the right sign so that
647 	 * addition moves from fromValue to toValue.
648 	 */
649 
650 	if ((scalePtr->tickInterval < 0)
651 		^ ((scalePtr->toValue - scalePtr->fromValue) <  0)) {
652 	  scalePtr->tickInterval = -scalePtr->tickInterval;
653 	}
654 
655 	ComputeFormat(scalePtr);
656 
657 	scalePtr->labelLength = scalePtr->label ? strlen(scalePtr->label) : 0;
658 
659 	Tk_SetBackgroundFromBorder(scalePtr->tkwin, scalePtr->bgBorder);
660 
661 	if (scalePtr->highlightWidth < 0) {
662 	    scalePtr->highlightWidth = 0;
663 	}
664 	scalePtr->inset = scalePtr->highlightWidth + scalePtr->borderWidth;
665 	break;
666     }
667     if (!error) {
668         Tk_FreeSavedOptions(&savedOptions);
669     }
670 
671     /*
672      * Set the scale value to itself;  all this does is to make sure
673      * that the scale's value is within the new acceptable range for
674      * the scale.  We don't set the var here because we need to make
675      * special checks for possibly changed varNamePtr.
676      */
677 
678     TkScaleSetValue(scalePtr, scalePtr->value, 0, 1);
679 
680     /*
681      * Reestablish the variable trace, if it is needed.
682      */
683 
684     if (scalePtr->varNamePtr != NULL) {
685 	Tcl_Obj *valuePtr;
686 
687 	/*
688 	 * Set the associated variable only when the new value differs
689 	 * from the current value, or the variable doesn't yet exist
690 	 */
691 	valuePtr = Tcl_ObjGetVar2(interp, scalePtr->varNamePtr, NULL,
692 		TCL_GLOBAL_ONLY);
693 	if ((valuePtr == NULL) || (scalePtr->value != oldValue)
694 		|| (Tcl_GetDoubleFromObj(NULL, valuePtr, &oldValue) != TCL_OK)
695 		|| (scalePtr->value != oldValue)) {
696 	    ScaleSetVariable(scalePtr);
697 	}
698         Lang_TraceVar(interp, scalePtr->varNamePtr,
699 	        TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
700 	        ScaleVarProc, (ClientData) scalePtr);
701     }
702 
703     ScaleWorldChanged((ClientData) scalePtr);
704     if (error) {
705         Tcl_SetObjResult(interp, errorResult);
706 	Tcl_DecrRefCount(errorResult);
707 	return TCL_ERROR;
708     } else {
709 	return TCL_OK;
710     }
711 }
712 
713 /*
714  *---------------------------------------------------------------------------
715  *
716  * ScaleWorldChanged --
717  *
718  *      This procedure is called when the world has changed in some
719  *      way and the widget needs to recompute all its graphics contexts
720  *	and determine its new geometry.
721  *
722  * Results:
723  *      None.
724  *
725  * Side effects:
726  *      Scale will be relayed out and redisplayed.
727  *
728  *---------------------------------------------------------------------------
729  */
730 
731 static void
ScaleWorldChanged(instanceData)732 ScaleWorldChanged(instanceData)
733     ClientData instanceData;	/* Information about widget. */
734 {
735     XGCValues gcValues;
736     GC gc;
737     TkScale *scalePtr;
738 
739     scalePtr = (TkScale *) instanceData;
740 
741     gcValues.foreground = scalePtr->troughColorPtr->pixel;
742     gc = Tk_GetGC(scalePtr->tkwin, GCForeground, &gcValues);
743     if (scalePtr->troughGC != None) {
744 	Tk_FreeGC(scalePtr->display, scalePtr->troughGC);
745     }
746     scalePtr->troughGC = gc;
747 
748     gcValues.font = Tk_FontId(scalePtr->tkfont);
749     gcValues.foreground = scalePtr->textColorPtr->pixel;
750     gc = Tk_GetGC(scalePtr->tkwin, GCForeground | GCFont, &gcValues);
751     if (scalePtr->textGC != None) {
752 	Tk_FreeGC(scalePtr->display, scalePtr->textGC);
753     }
754     scalePtr->textGC = gc;
755 
756     if (scalePtr->copyGC == None) {
757 	gcValues.graphics_exposures = False;
758 	scalePtr->copyGC = Tk_GetGC(scalePtr->tkwin, GCGraphicsExposures,
759 	    &gcValues);
760     }
761     scalePtr->inset = scalePtr->highlightWidth + scalePtr->borderWidth;
762 
763     /*
764      * Recompute display-related information, and let the geometry
765      * manager know how much space is needed now.
766      */
767 
768     ComputeScaleGeometry(scalePtr);
769 
770     TkEventuallyRedrawScale(scalePtr, REDRAW_ALL);
771 }
772 
773 /*
774  *----------------------------------------------------------------------
775  *
776  * ComputeFormat --
777  *
778  *	This procedure is invoked to recompute the "format" field
779  *	of a scale's widget record, which determines how the value
780  *	of the scale is converted to a string.
781  *
782  * Results:
783  *	None.
784  *
785  * Side effects:
786  *	The format field of scalePtr is modified.
787  *
788  *----------------------------------------------------------------------
789  */
790 
791 static void
ComputeFormat(scalePtr)792 ComputeFormat(scalePtr)
793     TkScale *scalePtr;			/* Information about scale widget. */
794 {
795     double maxValue, x;
796     int mostSigDigit, numDigits, leastSigDigit, afterDecimal;
797     int eDigits, fDigits;
798 
799     /*
800      * Compute the displacement from the decimal of the most significant
801      * digit required for any number in the scale's range.
802      */
803 
804     maxValue = fabs(scalePtr->fromValue);
805     x = fabs(scalePtr->toValue);
806     if (x > maxValue) {
807 	maxValue = x;
808     }
809     if (maxValue == 0) {
810 	maxValue = 1;
811     }
812     mostSigDigit = (int) floor(log10(maxValue));
813 
814     /*
815      * If the number of significant digits wasn't specified explicitly,
816      * compute it. It's the difference between the most significant
817      * digit needed to represent any number on the scale and the
818      * most significant digit of the smallest difference between
819      * numbers on the scale.  In other words, display enough digits so
820      * that at least one digit will be different between any two adjacent
821      * positions of the scale.
822      */
823 
824     numDigits = scalePtr->digits;
825     if (numDigits <= 0) {
826 	if  (scalePtr->resolution > 0) {
827 	    /*
828 	     * A resolution was specified for the scale, so just use it.
829 	     */
830 
831 	    leastSigDigit = (int) floor(log10(scalePtr->resolution));
832 	} else {
833 	    /*
834 	     * No resolution was specified, so compute the difference
835 	     * in value between adjacent pixels and use it for the least
836 	     * significant digit.
837 	     */
838 
839 	    x = fabs(scalePtr->fromValue - scalePtr->toValue);
840 	    if (scalePtr->length > 0) {
841 		x /= scalePtr->length;
842 	    }
843 	    if (x > 0){
844 		leastSigDigit = (int) floor(log10(x));
845 	    } else {
846 		leastSigDigit = 0;
847 	    }
848 	}
849 	numDigits = mostSigDigit - leastSigDigit + 1;
850 	if (numDigits < 1) {
851 	    numDigits = 1;
852 	}
853     }
854 
855     /*
856      * Compute the number of characters required using "e" format and
857      * "f" format, and then choose whichever one takes fewer characters.
858      */
859 
860     eDigits = numDigits + 4;
861     if (numDigits > 1) {
862 	eDigits++;			/* Decimal point. */
863     }
864     afterDecimal = numDigits - mostSigDigit - 1;
865     if (afterDecimal < 0) {
866 	afterDecimal = 0;
867     }
868     fDigits = (mostSigDigit >= 0) ? mostSigDigit + afterDecimal : afterDecimal;
869     if (afterDecimal > 0) {
870 	fDigits++;			/* Decimal point. */
871     }
872     if (mostSigDigit < 0) {
873 	fDigits++;			/* Zero to left of decimal point. */
874     }
875     if (fDigits <= eDigits) {
876 	sprintf(scalePtr->format, "%%.%df", afterDecimal);
877     } else {
878 	sprintf(scalePtr->format, "%%.%de", numDigits-1);
879     }
880 }
881 
882 /*
883  *----------------------------------------------------------------------
884  *
885  * ComputeScaleGeometry --
886  *
887  *	This procedure is called to compute various geometrical
888  *	information for a scale, such as where various things get
889  *	displayed.  It's called when the window is reconfigured.
890  *
891  * Results:
892  *	None.
893  *
894  * Side effects:
895  *	Display-related numbers get changed in *scalePtr.  The
896  *	geometry manager gets told about the window's preferred size.
897  *
898  *----------------------------------------------------------------------
899  */
900 
901 static void
ComputeScaleGeometry(scalePtr)902 ComputeScaleGeometry(scalePtr)
903     register TkScale *scalePtr;		/* Information about widget. */
904 {
905     char valueString[PRINT_CHARS];
906     int tmp, valuePixels, x, y, extraSpace;
907     Tk_FontMetrics fm;
908 
909     Tk_GetFontMetrics(scalePtr->tkfont, &fm);
910     scalePtr->fontHeight = fm.linespace + SPACING;
911 
912     /*
913      * Horizontal scales are simpler than vertical ones because
914      * all sizes are the same (the height of a line of text);
915      * handle them first and then quit.
916      */
917 
918     if (scalePtr->orient == ORIENT_HORIZONTAL) {
919 	y = scalePtr->inset;
920 	extraSpace = 0;
921 	if (scalePtr->labelLength != 0) {
922 	    scalePtr->horizLabelY = y + SPACING;
923 	    y += scalePtr->fontHeight;
924 	    extraSpace = SPACING;
925 	}
926 	if (scalePtr->showValue) {
927 	    scalePtr->horizValueY = y + SPACING;
928 	    y += scalePtr->fontHeight;
929 	    extraSpace = SPACING;
930 	} else {
931 	    scalePtr->horizValueY = y;
932 	}
933 	y += extraSpace;
934 	scalePtr->horizTroughY = y;
935 	y += scalePtr->width + 2*scalePtr->borderWidth;
936 	if (scalePtr->tickInterval != 0) {
937 	    scalePtr->horizTickY = y + SPACING;
938 	    y += scalePtr->fontHeight + SPACING;
939 	}
940 	Tk_GeometryRequest(scalePtr->tkwin,
941 		scalePtr->length + 2*scalePtr->inset, y + scalePtr->inset);
942 	Tk_SetInternalBorder(scalePtr->tkwin, scalePtr->inset);
943 	return;
944     }
945 
946     /*
947      * Vertical scale:  compute the amount of space needed to display
948      * the scales value by formatting strings for the two end points;
949      * use whichever length is longer.
950      */
951 
952     sprintf(valueString, scalePtr->format, scalePtr->fromValue);
953     valuePixels = Tk_TextWidth(scalePtr->tkfont, valueString, -1);
954 
955     sprintf(valueString, scalePtr->format, scalePtr->toValue);
956     tmp = Tk_TextWidth(scalePtr->tkfont, valueString, -1);
957     if (valuePixels < tmp) {
958 	valuePixels = tmp;
959     }
960 
961     /*
962      * Assign x-locations to the elements of the scale, working from
963      * left to right.
964      */
965 
966     x = scalePtr->inset;
967     if ((scalePtr->tickInterval != 0) && (scalePtr->showValue)) {
968 	scalePtr->vertTickRightX = x + SPACING + valuePixels;
969 	scalePtr->vertValueRightX = scalePtr->vertTickRightX + valuePixels
970 		+ fm.ascent/2;
971 	x = scalePtr->vertValueRightX + SPACING;
972     } else if (scalePtr->tickInterval != 0) {
973 	scalePtr->vertTickRightX = x + SPACING + valuePixels;
974 	scalePtr->vertValueRightX = scalePtr->vertTickRightX;
975 	x = scalePtr->vertTickRightX + SPACING;
976     } else if (scalePtr->showValue) {
977 	scalePtr->vertTickRightX = x;
978 	scalePtr->vertValueRightX = x + SPACING + valuePixels;
979 	x = scalePtr->vertValueRightX + SPACING;
980     } else {
981 	scalePtr->vertTickRightX = x;
982 	scalePtr->vertValueRightX = x;
983     }
984     scalePtr->vertTroughX = x;
985     x += 2*scalePtr->borderWidth + scalePtr->width;
986     if (scalePtr->labelLength == 0) {
987 	scalePtr->vertLabelX = 0;
988     } else {
989 	scalePtr->vertLabelX = x + fm.ascent/2;
990 	x = scalePtr->vertLabelX + fm.ascent/2
991 	    + Tk_TextWidth(scalePtr->tkfont, scalePtr->label,
992 		    scalePtr->labelLength);
993     }
994     Tk_GeometryRequest(scalePtr->tkwin, x + scalePtr->inset,
995 	    scalePtr->length + 2*scalePtr->inset);
996     Tk_SetInternalBorder(scalePtr->tkwin, scalePtr->inset);
997 }
998 
999 /*
1000  *--------------------------------------------------------------
1001  *
1002  * ScaleEventProc --
1003  *
1004  *	This procedure is invoked by the Tk dispatcher for various
1005  *	events on scales.
1006  *
1007  * Results:
1008  *	None.
1009  *
1010  * Side effects:
1011  *	When the window gets deleted, internal structures get
1012  *	cleaned up.  When it gets exposed, it is redisplayed.
1013  *
1014  *--------------------------------------------------------------
1015  */
1016 
1017 static void
ScaleEventProc(clientData,eventPtr)1018 ScaleEventProc(clientData, eventPtr)
1019     ClientData clientData;	/* Information about window. */
1020     XEvent *eventPtr;		/* Information about event. */
1021 {
1022     TkScale *scalePtr = (TkScale *) clientData;
1023 
1024     if ((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0)) {
1025 	TkEventuallyRedrawScale(scalePtr, REDRAW_ALL);
1026     } else if (eventPtr->type == DestroyNotify) {
1027 	DestroyScale((char *) clientData);
1028     } else if (eventPtr->type == ConfigureNotify) {
1029 	ComputeScaleGeometry(scalePtr);
1030 	TkEventuallyRedrawScale(scalePtr, REDRAW_ALL);
1031     } else if (eventPtr->type == FocusIn) {
1032 	if (eventPtr->xfocus.detail != NotifyInferior) {
1033 	    scalePtr->flags |= GOT_FOCUS;
1034 	    if (scalePtr->highlightWidth > 0) {
1035 		TkEventuallyRedrawScale(scalePtr, REDRAW_ALL);
1036 	    }
1037 	}
1038     } else if (eventPtr->type == FocusOut) {
1039 	if (eventPtr->xfocus.detail != NotifyInferior) {
1040 	    scalePtr->flags &= ~GOT_FOCUS;
1041 	    if (scalePtr->highlightWidth > 0) {
1042 		TkEventuallyRedrawScale(scalePtr, REDRAW_ALL);
1043 	    }
1044 	}
1045     }
1046 }
1047 
1048 /*
1049  *----------------------------------------------------------------------
1050  *
1051  * ScaleCmdDeletedProc --
1052  *
1053  *	This procedure is invoked when a widget command is deleted.  If
1054  *	the widget isn't already in the process of being destroyed,
1055  *	this command destroys it.
1056  *
1057  * Results:
1058  *	None.
1059  *
1060  * Side effects:
1061  *	The widget is destroyed.
1062  *
1063  *----------------------------------------------------------------------
1064  */
1065 
1066 static void
ScaleCmdDeletedProc(clientData)1067 ScaleCmdDeletedProc(clientData)
1068     ClientData clientData;	/* Pointer to widget record for widget. */
1069 {
1070     TkScale *scalePtr = (TkScale *) clientData;
1071     Tk_Window tkwin = scalePtr->tkwin;
1072 
1073     /*
1074      * This procedure could be invoked either because the window was
1075      * destroyed and the command was then deleted (in which case tkwin
1076      * is NULL) or because the command was deleted, and then this procedure
1077      * destroys the widget.
1078      */
1079 
1080     if (!(scalePtr->flags & SCALE_DELETED)) {
1081 	scalePtr->flags |= SCALE_DELETED;
1082 	Tk_DestroyWindow(tkwin);
1083     }
1084 }
1085 
1086 /*
1087  *--------------------------------------------------------------
1088  *
1089  * TkEventuallyRedrawScale --
1090  *
1091  *	Arrange for part or all of a scale widget to redrawn at
1092  *	the next convenient time in the future.
1093  *
1094  * Results:
1095  *	None.
1096  *
1097  * Side effects:
1098  *	If "what" is REDRAW_SLIDER then just the slider and the
1099  *	value readout will be redrawn;  if "what" is REDRAW_ALL
1100  *	then the entire widget will be redrawn.
1101  *
1102  *--------------------------------------------------------------
1103  */
1104 
1105 void
TkEventuallyRedrawScale(scalePtr,what)1106 TkEventuallyRedrawScale(scalePtr, what)
1107     register TkScale *scalePtr;	/* Information about widget. */
1108     int what;			/* What to redraw:  REDRAW_SLIDER
1109 				 * or REDRAW_ALL. */
1110 {
1111     if ((what == 0) || (scalePtr->tkwin == NULL)
1112 	    || !Tk_IsMapped(scalePtr->tkwin)) {
1113 	return;
1114     }
1115     if (!(scalePtr->flags & REDRAW_PENDING)) {
1116 	scalePtr->flags |= REDRAW_PENDING;
1117 	Tcl_DoWhenIdle(TkpDisplayScale, (ClientData) scalePtr);
1118     }
1119     scalePtr->flags |= what;
1120 }
1121 
1122 /*
1123  *--------------------------------------------------------------
1124  *
1125  * TkRoundToResolution --
1126  *
1127  *	Round a given floating-point value to the nearest multiple
1128  *	of the scale's resolution.
1129  *
1130  * Results:
1131  *	The return value is the rounded result.
1132  *
1133  * Side effects:
1134  *	None.
1135  *
1136  *--------------------------------------------------------------
1137  */
1138 
1139 double
TkRoundToResolution(scalePtr,value)1140 TkRoundToResolution(scalePtr, value)
1141     TkScale *scalePtr;		/* Information about scale widget. */
1142     double value;		/* Value to round. */
1143 {
1144     double rem, new, tick;
1145 
1146     if (scalePtr->resolution <= 0) {
1147 	return value;
1148     }
1149     tick = floor(value/scalePtr->resolution);
1150     new = scalePtr->resolution * tick;
1151     rem = value - new;
1152     if (rem < 0) {
1153 	if (rem <= -scalePtr->resolution/2) {
1154 	    new = (tick - 1.0) * scalePtr->resolution;
1155 	}
1156     } else {
1157 	if (rem >= scalePtr->resolution/2) {
1158 	    new = (tick + 1.0) * scalePtr->resolution;
1159 	}
1160     }
1161     return new;
1162 }
1163 
1164 /*
1165  *----------------------------------------------------------------------
1166  *
1167  * ScaleVarProc --
1168  *
1169  *	This procedure is invoked by Tcl whenever someone modifies a
1170  *	variable associated with a scale widget.
1171  *
1172  * Results:
1173  *	NULL is always returned.
1174  *
1175  * Side effects:
1176  *	The value displayed in the scale will change to match the
1177  *	variable's new value.  If the variable has a bogus value then
1178  *	it is reset to the value of the scale.
1179  *
1180  *----------------------------------------------------------------------
1181  */
1182 
1183     /* ARGSUSED */
1184 static char *
ScaleVarProc(clientData,interp,name1,name2,flags)1185 ScaleVarProc(clientData, interp, name1, name2, flags)
1186     ClientData clientData;	/* Information about button. */
1187     Tcl_Interp *interp;		/* Interpreter containing variable. */
1188     Var name1;			/* Name of variable. */
1189     CONST char *name2;		/* Second part of variable name. */
1190     int flags;			/* Information about what happened. */
1191 {
1192     register TkScale *scalePtr = (TkScale *) clientData;
1193     char *resultStr;
1194     double value;
1195     Tcl_Obj *valuePtr;
1196     int result;
1197 
1198     /*
1199      * If the variable is unset, then immediately recreate it unless
1200      * the whole interpreter is going away.
1201      */
1202 
1203     if (flags & TCL_TRACE_UNSETS) {
1204 	if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
1205 	    Lang_TraceVar(interp, scalePtr->varNamePtr,
1206 		    TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
1207 		    ScaleVarProc, clientData);
1208 	    scalePtr->flags |= NEVER_SET;
1209 	    TkScaleSetValue(scalePtr, scalePtr->value, 1, 0);
1210 	}
1211 	return (char *) NULL;
1212     }
1213 
1214     /*
1215      * If we came here because we updated the variable (in TkScaleSetValue),
1216      * then ignore the trace.  Otherwise update the scale with the value
1217      * of the variable.
1218      */
1219 
1220     if (scalePtr->flags & SETTING_VAR) {
1221 	return (char *) NULL;
1222     }
1223     resultStr = NULL;
1224     valuePtr = Tcl_ObjGetVar2(interp, scalePtr->varNamePtr, NULL,
1225             TCL_GLOBAL_ONLY);
1226     result = Tcl_GetDoubleFromObj(interp, valuePtr, &value);
1227     if (result != TCL_OK) {
1228         resultStr = "can't assign non-numeric value to scale variable";
1229 	ScaleSetVariable(scalePtr);
1230     } else {
1231 	scalePtr->value = TkRoundToResolution(scalePtr, value);
1232 
1233 	/*
1234 	 * This code is a bit tricky because it sets the scale's value before
1235 	 * calling TkScaleSetValue.  This way, TkScaleSetValue won't bother
1236 	 * to set the variable again or to invoke the -command.  However, it
1237 	 * also won't redisplay the scale, so we have to ask for that
1238 	 * explicitly.
1239 	 */
1240 
1241 	TkScaleSetValue(scalePtr, scalePtr->value, 1, 0);
1242     }
1243     TkEventuallyRedrawScale(scalePtr, REDRAW_SLIDER);
1244 
1245     return resultStr;
1246 }
1247 
1248 /*
1249  *--------------------------------------------------------------
1250  *
1251  * TkScaleSetValue --
1252  *
1253  *	This procedure changes the value of a scale and invokes
1254  *	a Tcl command to reflect the current position of a scale
1255  *
1256  * Results:
1257  *	None.
1258  *
1259  * Side effects:
1260  *	A Tcl command is invoked, and an additional error-processing
1261  *	command may also be invoked.  The scale's slider is redrawn.
1262  *
1263  *--------------------------------------------------------------
1264  */
1265 
1266 void
TkScaleSetValue(scalePtr,value,setVar,invokeCommand)1267 TkScaleSetValue(scalePtr, value, setVar, invokeCommand)
1268     register TkScale *scalePtr;	/* Info about widget. */
1269     double value;		/* New value for scale.  Gets adjusted
1270 				 * if it's off the scale. */
1271     int setVar;			/* Non-zero means reflect new value through
1272 				 * to associated variable, if any. */
1273     int invokeCommand;		/* Non-zero means invoked -command option
1274 				 * to notify of new value, 0 means don't. */
1275 {
1276     value = TkRoundToResolution(scalePtr, value);
1277     if ((value < scalePtr->fromValue)
1278 	    ^ (scalePtr->toValue < scalePtr->fromValue)) {
1279 	value = scalePtr->fromValue;
1280     }
1281     if ((value > scalePtr->toValue)
1282 	    ^ (scalePtr->toValue < scalePtr->fromValue)) {
1283 	value = scalePtr->toValue;
1284     }
1285     if (scalePtr->flags & NEVER_SET) {
1286 	scalePtr->flags &= ~NEVER_SET;
1287     } else if (scalePtr->value == value) {
1288 	return;
1289     }
1290     scalePtr->value = value;
1291     if (invokeCommand) {
1292 	scalePtr->flags |= INVOKE_COMMAND;
1293     }
1294     TkEventuallyRedrawScale(scalePtr, REDRAW_SLIDER);
1295 
1296     if (setVar && scalePtr->varNamePtr) {
1297 	ScaleSetVariable(scalePtr);
1298     }
1299 }
1300 
1301 /*
1302  *--------------------------------------------------------------
1303  *
1304  * ScaleSetVariable --
1305  *
1306  *	This procedure sets the variable associated with a scale, if any.
1307  *
1308  * Results:
1309  *	None.
1310  *
1311  * Side effects:
1312  *	Other write traces on the variable will trigger.
1313  *
1314  *--------------------------------------------------------------
1315  */
1316 
1317 static void
ScaleSetVariable(scalePtr)1318 ScaleSetVariable(scalePtr)
1319     register TkScale *scalePtr;	/* Info about widget. */
1320 {
1321     if (scalePtr->varNamePtr != NULL) {
1322 	char string[PRINT_CHARS];
1323 	sprintf(string, scalePtr->format, scalePtr->value);
1324 	scalePtr->flags |= SETTING_VAR;
1325 	Tcl_ObjSetVar2(scalePtr->interp, scalePtr->varNamePtr, NULL,
1326 		Tcl_NewStringObj(string, -1), TCL_GLOBAL_ONLY);
1327 	scalePtr->flags &= ~SETTING_VAR;
1328     }
1329 }
1330 
1331 /*
1332  *----------------------------------------------------------------------
1333  *
1334  * TkScalePixelToValue --
1335  *
1336  *	Given a pixel within a scale window, return the scale
1337  *	reading corresponding to that pixel.
1338  *
1339  * Results:
1340  *	A double-precision scale reading.  If the value is outside
1341  *	the legal range for the scale then it's rounded to the nearest
1342  *	end of the scale.
1343  *
1344  * Side effects:
1345  *	None.
1346  *
1347  *----------------------------------------------------------------------
1348  */
1349 
1350 double
TkScalePixelToValue(scalePtr,x,y)1351 TkScalePixelToValue(scalePtr, x, y)
1352     register TkScale *scalePtr;		/* Information about widget. */
1353     int x, y;				/* Coordinates of point within
1354 					 * window. */
1355 {
1356     double value, pixelRange;
1357 
1358     if (scalePtr->orient == ORIENT_VERTICAL) {
1359 	pixelRange = Tk_Height(scalePtr->tkwin) - scalePtr->sliderLength
1360 		- 2*scalePtr->inset - 2*scalePtr->borderWidth;
1361 	value = y;
1362     } else {
1363 	pixelRange = Tk_Width(scalePtr->tkwin) - scalePtr->sliderLength
1364 		- 2*scalePtr->inset - 2*scalePtr->borderWidth;
1365 	value = x;
1366     }
1367 
1368     if (pixelRange <= 0) {
1369 	/*
1370 	 * Not enough room for the slider to actually slide:  just return
1371 	 * the scale's current value.
1372 	 */
1373 
1374 	return scalePtr->value;
1375     }
1376     value -= scalePtr->sliderLength/2 + scalePtr->inset
1377 		+ scalePtr->borderWidth;
1378     value /= pixelRange;
1379     if (value < 0) {
1380 	value = 0;
1381     }
1382     if (value > 1) {
1383 	value = 1;
1384     }
1385     value = scalePtr->fromValue +
1386 		value * (scalePtr->toValue - scalePtr->fromValue);
1387     return TkRoundToResolution(scalePtr, value);
1388 }
1389 
1390 /*
1391  *----------------------------------------------------------------------
1392  *
1393  * TkScaleValueToPixel --
1394  *
1395  *	Given a reading of the scale, return the x-coordinate or
1396  *	y-coordinate corresponding to that reading, depending on
1397  *	whether the scale is vertical or horizontal, respectively.
1398  *
1399  * Results:
1400  *	An integer value giving the pixel location corresponding
1401  *	to reading.  The value is restricted to lie within the
1402  *	defined range for the scale.
1403  *
1404  * Side effects:
1405  *	None.
1406  *
1407  *----------------------------------------------------------------------
1408  */
1409 
1410 int
TkScaleValueToPixel(scalePtr,value)1411 TkScaleValueToPixel(scalePtr, value)
1412     register TkScale *scalePtr;		/* Information about widget. */
1413     double value;			/* Reading of the widget. */
1414 {
1415     int y, pixelRange;
1416     double valueRange;
1417 
1418     valueRange = scalePtr->toValue - scalePtr->fromValue;
1419     pixelRange = ((scalePtr->orient == ORIENT_VERTICAL)
1420 	    ? Tk_Height(scalePtr->tkwin) : Tk_Width(scalePtr->tkwin))
1421 	- scalePtr->sliderLength - 2*scalePtr->inset - 2*scalePtr->borderWidth;
1422     if (valueRange == 0) {
1423 	y = 0;
1424     } else {
1425 	y = (int) ((value - scalePtr->fromValue) * pixelRange
1426 		  / valueRange + 0.5);
1427 	if (y < 0) {
1428 	    y = 0;
1429 	} else if (y > pixelRange) {
1430 	    y = pixelRange;
1431 	}
1432     }
1433     y += scalePtr->sliderLength/2 + scalePtr->inset + scalePtr->borderWidth;
1434     return y;
1435 }
1436 
1437 
1438