diff options
author | fvogel <fvogelnew1@free.fr> | 2019-03-31 13:56:17 (GMT) |
---|---|---|
committer | fvogel <fvogelnew1@free.fr> | 2019-03-31 13:56:17 (GMT) |
commit | 3b936e0067e5f4ced677fdc4cbfc4a2cdb42ea79 (patch) | |
tree | b1afb7b892ce5dff5c73025fdea9c31f6f710577 /generic | |
parent | a88925e14d0096bf75e3338b939fdf6ec49c72b5 (diff) | |
parent | dc37e2284b4baccb9a8e7220604bc667d7050129 (diff) | |
download | tk-3b936e0067e5f4ced677fdc4cbfc4a2cdb42ea79.zip tk-3b936e0067e5f4ced677fdc4cbfc4a2cdb42ea79.tar.gz tk-3b936e0067e5f4ced677fdc4cbfc4a2cdb42ea79.tar.bz2 |
Fix [c9887a1fc9]: Trailing zeros on scale widget ticks. The fix was subject of TIP #535 which was accepted through TCT vote.
Diffstat (limited to 'generic')
-rw-r--r-- | generic/tkScale.c | 257 | ||||
-rw-r--r-- | generic/tkScale.h | 12 |
2 files changed, 213 insertions, 56 deletions
diff --git a/generic/tkScale.c b/generic/tkScale.c index 29f8722..c5cdaff 100644 --- a/generic/tkScale.c +++ b/generic/tkScale.c @@ -157,11 +157,13 @@ enum command { * Forward declarations for procedures defined later in this file: */ -static void ComputeFormat(TkScale *scalePtr); +static void ComputeFormat(TkScale *scalePtr, int forTicks); static void ComputeScaleGeometry(TkScale *scalePtr); static int ConfigureScale(Tcl_Interp *interp, TkScale *scalePtr, int objc, Tcl_Obj *const objv[]); static void DestroyScale(char *memPtr); +static double MaxTickRoundingError(TkScale *scalePtr, + double tickResolution); static void ScaleCmdDeletedProc(ClientData clientData); static void ScaleEventProc(ClientData clientData, XEvent *eventPtr); @@ -182,13 +184,54 @@ static void ScaleSetVariable(TkScale *scalePtr); static const Tk_ClassProcs scaleClass = { sizeof(Tk_ClassProcs), /* size */ ScaleWorldChanged, /* worldChangedProc */ - NULL, /* createProc */ - NULL /* modalProc */ + NULL, /* createProc */ + NULL /* modalProc */ }; /* *-------------------------------------------------------------- * + * ScaleDigit, ScaleMax, ScaleMin, ScaleRound -- + * + * Simple math helper functions, designed to be automatically inlined by + * the compiler most of the time. + * + *-------------------------------------------------------------- + */ + +static inline int +ScaleDigit( + double value) +{ + return (int) floor(log10(fabs(value))); +} + +static inline double +ScaleMax( + double a, + double b) +{ + return (a > b) ? a : b; +} + +static inline double +ScaleMin( + double a, + double b) +{ + return (a < b) ? a : b; +} + +static inline int +ScaleRound( + double value) +{ + return (int) floor(value + 0.5); +} + +/* + *-------------------------------------------------------------- + * * Tk_ScaleObjCmd -- * * This procedure is invoked to process the "scale" Tcl command. See the @@ -430,7 +473,7 @@ ScaleWidgetObjCmd( } value = TkScalePixelToValue(scalePtr, x, y); } - Tcl_SetObjResult(interp, Tcl_ObjPrintf(scalePtr->format, value)); + Tcl_SetObjResult(interp, Tcl_ObjPrintf(scalePtr->valueFormat, value)); break; } case COMMAND_IDENTIFY: { @@ -636,7 +679,8 @@ ConfigureScale( scalePtr->tickInterval = -scalePtr->tickInterval; } - ComputeFormat(scalePtr); + ComputeFormat(scalePtr, 0); + ComputeFormat(scalePtr, 1); scalePtr->labelLength = scalePtr->label ? (int)strlen(scalePtr->label) : 0; @@ -759,27 +803,77 @@ ScaleWorldChanged( TkEventuallyRedrawScale(scalePtr, REDRAW_ALL); } + /* + *---------------------------------------------------------------------- + * + * MaxTickRoundingError -- + * + * Given the separation between values that can be displayed on ticks, + * this calculates the maximum magnitude of error for the displayed + * value. Tries to be clever by working out the increment in error + * between ticks rather than testing all of them, so may overestimate + * error if it is greater than 0.25 x the value separation. + * + * Results: + * Maximum error magnitude of tick numbers. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static double +MaxTickRoundingError( + TkScale *scalePtr, /* Information about scale widget. */ + double tickResolution) /* Separation between displayable values. */ +{ + double tickPosn, firstTickError, lastTickError, intervalError; + int tickCount; + + /* + * Compute the error for each tick-related measure. + */ + + tickPosn = scalePtr->fromValue / tickResolution; + firstTickError = tickPosn - ScaleRound(tickPosn); + + tickPosn = scalePtr->tickInterval / tickResolution; + intervalError = tickPosn - ScaleRound(tickPosn); + + tickCount = (int) ((scalePtr->toValue - scalePtr->fromValue) / + scalePtr->tickInterval); /* not including first */ + lastTickError = ScaleMin(0.5, + fabs(firstTickError + tickCount * intervalError)); + + /* + * Compute the maximum cumulative rounding error. + */ + + return ScaleMax(fabs(firstTickError), lastTickError) * tickResolution; +} + /* *---------------------------------------------------------------------- * * 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. + * This procedure is invoked to recompute the "valueFormat" or + * "tickFormat" field of a scale's widget record, which determines how + * the value of the scale or one of its ticks is converted to a string. * * Results: * None. * - * Side effects: - * The format field of scalePtr is modified. + * Side effects: The valueFormat or tickFormat field of scalePtr is modified. * *---------------------------------------------------------------------- */ static void ComputeFormat( - TkScale *scalePtr) /* Information about scale widget. */ + TkScale *scalePtr, /* Information about scale widget. */ + int forTicks) /* Do for ticks rather than value */ { double maxValue, x; int mostSigDigit, numDigits, leastSigDigit, afterDecimal; @@ -798,48 +892,73 @@ ComputeFormat( if (maxValue == 0) { maxValue = 1; } - mostSigDigit = (int) floor(log10(maxValue)); + mostSigDigit = ScaleDigit(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. - */ + if (forTicks) { + /* + * Display only enough digits to ensure adjacent ticks have different + * values. + */ - numDigits = scalePtr->digits; - if (numDigits > TCL_MAX_PREC) { - numDigits = 0; - } - if (numDigits <= 0) { - if (scalePtr->resolution > 0) { - /* - * A resolution was specified for the scale, so just use it. - */ + if (scalePtr->tickInterval != 0) { + leastSigDigit = ScaleDigit(scalePtr->tickInterval); - 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. + * Now add more digits until max error is less than + * TICK_VALUES_DISPLAY_ACCURACY intervals */ - x = fabs(scalePtr->fromValue - scalePtr->toValue); - if (scalePtr->length > 0) { - x /= scalePtr->length; + while (MaxTickRoundingError(scalePtr, pow(10, leastSigDigit)) + > fabs(TICK_VALUES_DISPLAY_ACCURACY * scalePtr->tickInterval)) { + --leastSigDigit; } - if (x > 0){ - leastSigDigit = (int) floor(log10(x)); + numDigits = 1 + mostSigDigit - leastSigDigit; + } else { + numDigits = 1; + } + } else { + /* + * 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 > TCL_MAX_PREC) { + numDigits = 0; + } + if (numDigits <= 0) { + if (scalePtr->resolution > 0) { + /* + * A resolution was specified for the scale, so just use it. + */ + + leastSigDigit = ScaleDigit(scalePtr->resolution); } else { - leastSigDigit = 0; + /* + * 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 = ScaleDigit(x); + } else { + leastSigDigit = 0; + } + } + numDigits = mostSigDigit - leastSigDigit + 1; + if (numDigits < 1) { + numDigits = 1; } - } - numDigits = mostSigDigit - leastSigDigit + 1; - if (numDigits < 1) { - numDigits = 1; } } @@ -863,10 +982,19 @@ ComputeFormat( if (mostSigDigit < 0) { fDigits++; /* Zero to left of decimal point. */ } - if (fDigits <= eDigits) { - sprintf(scalePtr->format, "%%.%df", afterDecimal); + + if (forTicks) { + if (fDigits <= eDigits) { + sprintf(scalePtr->tickFormat, "%%.%df", afterDecimal); + } else { + sprintf(scalePtr->tickFormat, "%%.%de", numDigits - 1); + } } else { - sprintf(scalePtr->format, "%%.%de", numDigits-1); + if (fDigits <= eDigits) { + sprintf(scalePtr->valueFormat, "%%.%df", afterDecimal); + } else { + sprintf(scalePtr->valueFormat, "%%.%de", numDigits - 1); + } } } @@ -894,7 +1022,7 @@ ComputeScaleGeometry( register TkScale *scalePtr) /* Information about widget. */ { char valueString[TCL_DOUBLE_SPACE]; - int tmp, valuePixels, x, y, extraSpace; + int tmp, valuePixels, tickPixels, x, y, extraSpace; Tk_FontMetrics fm; Tk_GetFontMetrics(scalePtr->tkfont, &fm); @@ -940,13 +1068,13 @@ ComputeScaleGeometry( * whichever length is longer. */ - if (snprintf(valueString, TCL_DOUBLE_SPACE, scalePtr->format, + if (snprintf(valueString, TCL_DOUBLE_SPACE, scalePtr->valueFormat, scalePtr->fromValue) < 0) { valueString[TCL_DOUBLE_SPACE - 1] = '\0'; } valuePixels = Tk_TextWidth(scalePtr->tkfont, valueString, -1); - if (snprintf(valueString, TCL_DOUBLE_SPACE, scalePtr->format, + if (snprintf(valueString, TCL_DOUBLE_SPACE, scalePtr->valueFormat, scalePtr->toValue) < 0) { valueString[TCL_DOUBLE_SPACE - 1] = '\0'; } @@ -956,18 +1084,37 @@ ComputeScaleGeometry( } /* + * Now do the same thing for the tick values + */ + + if (snprintf(valueString, TCL_DOUBLE_SPACE, scalePtr->tickFormat, + scalePtr->fromValue) < 0) { + valueString[TCL_DOUBLE_SPACE - 1] = '\0'; + } + tickPixels = Tk_TextWidth(scalePtr->tkfont, valueString, -1); + + if (snprintf(valueString, TCL_DOUBLE_SPACE, scalePtr->tickFormat, + scalePtr->toValue) < 0) { + valueString[TCL_DOUBLE_SPACE - 1] = '\0'; + } + tmp = Tk_TextWidth(scalePtr->tkfont, valueString, -1); + if (tickPixels < tmp) { + tickPixels = 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->vertTickRightX = x + SPACING + tickPixels; scalePtr->vertValueRightX = scalePtr->vertTickRightX + valuePixels + fm.ascent/2; x = scalePtr->vertValueRightX + SPACING; } else if (scalePtr->tickInterval != 0) { - scalePtr->vertTickRightX = x + SPACING + valuePixels; + scalePtr->vertTickRightX = x + SPACING + tickPixels; scalePtr->vertValueRightX = scalePtr->vertTickRightX; x = scalePtr->vertTickRightX + SPACING; } else if (scalePtr->showValue) { @@ -1350,7 +1497,7 @@ ScaleSetVariable( if (scalePtr->varNamePtr != NULL) { char string[TCL_DOUBLE_SPACE]; - if (snprintf(string, TCL_DOUBLE_SPACE, scalePtr->format, + if (snprintf(string, TCL_DOUBLE_SPACE, scalePtr->valueFormat, scalePtr->value) < 0) { string[TCL_DOUBLE_SPACE - 1] = '\0'; } @@ -1452,8 +1599,8 @@ TkScaleValueToPixel( if (valueRange == 0) { y = 0; } else { - y = (int) ((value - scalePtr->fromValue) * pixelRange - / valueRange + 0.5); + y = ScaleRound((value - scalePtr->fromValue) * pixelRange + / valueRange); if (y < 0) { y = 0; } else if (y > pixelRange) { diff --git a/generic/tkScale.h b/generic/tkScale.h index d44e65b..e68b786 100644 --- a/generic/tkScale.h +++ b/generic/tkScale.h @@ -73,8 +73,10 @@ typedef struct TkScale { * values. 0 means we get to choose the number * based on resolution and/or the range of the * scale. */ - char format[16]; /* Sprintf conversion specifier computed from + char valueFormat[16]; /* Sprintf conversion specifier computed from * digits and other information. */ + char tickFormat[16]; /* Sprintf conversion specifier computed from + * tick interval. */ double bigIncrement; /* Amount to use for large increments to scale * value. (0 means we pick a value). */ char *command; /* Command prefix to use when invoking Tcl @@ -215,6 +217,14 @@ typedef struct TkScale { #define SPACING 2 /* + * The tick values are all displayed with the same number of decimal places. + * This number of decimal places is such that the displayed values are all + * accurate to within the following proportion of a tick interval. + */ + +#define TICK_VALUES_DISPLAY_ACCURACY 0.2 + +/* * Declaration of procedures used in the implementation of the scale widget. */ |