summaryrefslogtreecommitdiffstats
path: root/generic
diff options
context:
space:
mode:
authorfvogel <fvogelnew1@free.fr>2019-03-31 13:56:17 (GMT)
committerfvogel <fvogelnew1@free.fr>2019-03-31 13:56:17 (GMT)
commit3b936e0067e5f4ced677fdc4cbfc4a2cdb42ea79 (patch)
treeb1afb7b892ce5dff5c73025fdea9c31f6f710577 /generic
parenta88925e14d0096bf75e3338b939fdf6ec49c72b5 (diff)
parentdc37e2284b4baccb9a8e7220604bc667d7050129 (diff)
downloadtk-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.c257
-rw-r--r--generic/tkScale.h12
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.
*/