Attachment "tk-lcnumeric-fix.patch" to
ticket [348b482f]
added by
serhiy.storchaka
2026-06-24 09:26:56.
Index: generic/tkEntry.c
==================================================================
--- generic/tkEntry.c
+++ generic/tkEntry.c
@@ -1387,11 +1387,12 @@
} else if (dvalue > sbPtr->toValue) {
dvalue = sbPtr->toValue;
} else if (dvalue < sbPtr->fromValue) {
dvalue = sbPtr->fromValue;
}
- snprintf(sbPtr->formatBuf, formatSpace, sbPtr->valueFormat, dvalue);
+ TkFormatDouble(sbPtr->formatBuf, formatSpace, sbPtr->valueFormat,
+ dvalue);
/*
* No check for error return here as well, because any possible
* error will be trapped below when attempting tracing.
*/
@@ -4505,11 +4506,12 @@
*/
dvalue = sbPtr->toValue;
}
}
- snprintf(sbPtr->formatBuf, TCL_DOUBLE_SPACE, sbPtr->valueFormat, dvalue);
+ TkFormatDouble(sbPtr->formatBuf, TCL_DOUBLE_SPACE, sbPtr->valueFormat,
+ dvalue);
code = EntryValueChanged(entryPtr, sbPtr->formatBuf);
}
}
if (code != TCL_OK) {
return TCL_ERROR;
Index: generic/tkInt.h
==================================================================
--- generic/tkInt.h
+++ generic/tkInt.h
@@ -1241,10 +1241,12 @@
void *clientData, const TkEnsemble *map);
MODULE_SCOPE double TkScalingLevel(Tk_Window tkwin);
MODULE_SCOPE double TkScalingLevel2(Tk_Window tkwin);
MODULE_SCOPE int TkGetScaledPixelValue(Tcl_Interp *interp, Tk_Window tkwin,
Tcl_Obj *valuePtr, int *size);
+MODULE_SCOPE int TkFormatDouble(char *buffer, size_t size,
+ const char *format, double value);
MODULE_SCOPE bool TkObjIsEmpty(Tcl_Obj *objPtr);
MODULE_SCOPE int TkInitTkCmd(Tcl_Interp *interp,
void *clientData);
MODULE_SCOPE int TkInitFontchooser(Tcl_Interp *interp,
void *clientData);
Index: generic/tkPlace.c
==================================================================
--- generic/tkPlace.c
+++ generic/tkPlace.c
@@ -777,10 +777,11 @@
Tcl_Interp *interp, /* Interp into which to place result. */
Tk_Window tkwin) /* Token for the window to get info on. */
{
Content *contentPtr;
Tcl_Obj *infoObj;
+ char relStr[TCL_DOUBLE_SPACE];
contentPtr = FindContent(tkwin);
if (contentPtr == NULL) {
return TCL_OK;
}
@@ -789,32 +790,40 @@
Tcl_AppendToObj(infoObj, "-in", TCL_INDEX_NONE);
Tcl_ListObjAppendElement(NULL, infoObj,
Tk_NewWindowObj(contentPtr->containerPtr->tkwin));
Tcl_AppendToObj(infoObj, " ", TCL_INDEX_NONE);
}
- Tcl_AppendPrintfToObj(infoObj,
- "-x %d -relx %.4g -y %d -rely %.4g",
- contentPtr->x, contentPtr->relX, contentPtr->y, contentPtr->relY);
+ /*
+ * Format the relative (floating-point) values with TkFormatDouble() so
+ * they use '.' as the decimal separator regardless of LC_NUMERIC and stay
+ * valid Tcl numbers; see TkFormatDouble in tkUtil.c.
+ */
+ Tcl_AppendPrintfToObj(infoObj, "-x %d", contentPtr->x);
+ TkFormatDouble(relStr, sizeof(relStr), "%.4g", contentPtr->relX);
+ Tcl_AppendPrintfToObj(infoObj, " -relx %s", relStr);
+ Tcl_AppendPrintfToObj(infoObj, " -y %d", contentPtr->y);
+ TkFormatDouble(relStr, sizeof(relStr), "%.4g", contentPtr->relY);
+ Tcl_AppendPrintfToObj(infoObj, " -rely %s", relStr);
if (contentPtr->widthObj) {
Tcl_AppendPrintfToObj(infoObj, " -width %d", contentPtr->width);
} else {
Tcl_AppendToObj(infoObj, " -width {}", TCL_INDEX_NONE);
}
if (contentPtr->relWidthObj) {
- Tcl_AppendPrintfToObj(infoObj,
- " -relwidth %.4g", contentPtr->relWidth);
+ TkFormatDouble(relStr, sizeof(relStr), "%.4g", contentPtr->relWidth);
+ Tcl_AppendPrintfToObj(infoObj, " -relwidth %s", relStr);
} else {
Tcl_AppendToObj(infoObj, " -relwidth {}", TCL_INDEX_NONE);
}
if (contentPtr->heightObj) {
Tcl_AppendPrintfToObj(infoObj, " -height %d", contentPtr->height);
} else {
Tcl_AppendToObj(infoObj, " -height {}", TCL_INDEX_NONE);
}
if (contentPtr->relHeightObj) {
- Tcl_AppendPrintfToObj(infoObj,
- " -relheight %.4g", contentPtr->relHeight);
+ TkFormatDouble(relStr, sizeof(relStr), "%.4g", contentPtr->relHeight);
+ Tcl_AppendPrintfToObj(infoObj, " -relheight %s", relStr);
} else {
Tcl_AppendToObj(infoObj, " -relheight {}", TCL_INDEX_NONE);
}
Tcl_AppendPrintfToObj(infoObj, " -anchor %s -bordermode %s",
Index: generic/tkScale.c
==================================================================
--- generic/tkScale.c
+++ generic/tkScale.c
@@ -449,10 +449,11 @@
break;
}
case COMMAND_GET: {
double value;
int x, y;
+ char string[TCL_DOUBLE_SPACE];
if ((objc != 2) && (objc != 4)) {
Tcl_WrongNumArgs(interp, 1, objv, "get ?x y?");
goto error;
}
@@ -463,11 +464,12 @@
(Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK)) {
goto error;
}
value = TkScalePixelToValue(scalePtr, x, y);
}
- Tcl_SetObjResult(interp, Tcl_ObjPrintf(scalePtr->valueFormat, value));
+ TkFormatDouble(string, TCL_DOUBLE_SPACE, scalePtr->valueFormat, value);
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(string, TCL_INDEX_NONE));
break;
}
case COMMAND_IDENTIFY: {
int x, y;
const char *zone = "";
@@ -1062,17 +1064,17 @@
* 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.
*/
- if (snprintf(valueString, TCL_DOUBLE_SPACE, scalePtr->valueFormat,
+ if (TkFormatDouble(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->valueFormat,
+ if (TkFormatDouble(valueString, TCL_DOUBLE_SPACE, scalePtr->valueFormat,
scalePtr->toValue) < 0) {
valueString[TCL_DOUBLE_SPACE - 1] = '\0';
}
tmp = Tk_TextWidth(scalePtr->tkfont, valueString, -1);
if (valuePixels < tmp) {
@@ -1081,17 +1083,17 @@
/*
* Now do the same thing for the tick values
*/
- if (snprintf(valueString, TCL_DOUBLE_SPACE, scalePtr->tickFormat,
+ if (TkFormatDouble(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,
+ if (TkFormatDouble(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) {
@@ -1504,11 +1506,11 @@
TkScale *scalePtr) /* Info about widget. */
{
if (scalePtr->varNamePtr != NULL) {
char string[TCL_DOUBLE_SPACE];
- if (snprintf(string, TCL_DOUBLE_SPACE, scalePtr->valueFormat,
+ if (TkFormatDouble(string, TCL_DOUBLE_SPACE, scalePtr->valueFormat,
scalePtr->value) < 0) {
string[TCL_DOUBLE_SPACE - 1] = '\0';
}
scalePtr->flags |= SETTING_VAR;
Tcl_ObjSetVar2(scalePtr->interp, scalePtr->varNamePtr, NULL,
Index: generic/tkUtil.c
==================================================================
--- generic/tkUtil.c
+++ generic/tkUtil.c
@@ -10,10 +10,11 @@
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
#include "tkInt.h"
+#include <locale.h>
#ifdef _WIN32
#include "tkWinInt.h"
#endif
@@ -1366,13 +1367,72 @@
event.virt.user_data = detail;
if (detail) Tcl_IncrRefCount(detail); // Event code will DecrRefCount
Tk_QueueWindowEvent(&event.general, TCL_QUEUE_TAIL);
}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkFormatDouble --
+ *
+ * Format a floating-point value with a printf-style format, like
+ * snprintf(), but always using '.' as the decimal separator regardless
+ * of the process LC_NUMERIC locale.
+ *
+ * Tcl treats numbers as locale-independent: the decimal separator is
+ * always '.' and Tcl forces LC_NUMERIC to "C" during initialization (see
+ * TclpSetInitialEncodings). However an embedding application (e.g. a
+ * Python program using tkinter) may set LC_NUMERIC to a locale that uses
+ * another separator after Tcl is initialized. Plain snprintf() would
+ * then format e.g. 0.1 as "0,1", which is not a valid Tcl number and
+ * fails to parse when read back from a linked variable or a widget
+ * subcommand result. This wrapper normalizes the separator back to '.'.
+ *
+ * Results:
+ * The return value of snprintf(): the number of characters that would
+ * have been written had the buffer been large enough (excluding the
+ * terminating NUL), or a negative value on error.
+ *
+ * Side effects:
+ * Writes the formatted string to buffer.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+TkFormatDouble(
+ char *buffer, /* Where to store the formatted string. */
+ size_t size, /* Size of buffer in bytes. */
+ const char *format, /* A printf-style format containing a single
+ * floating-point conversion, e.g. "%.4g". */
+ double value) /* The value to format. */
+{
+ int length = snprintf(buffer, size, format, value);
+ const char *decimal = localeconv()->decimal_point;
+
+ /*
+ * snprintf() above honours LC_NUMERIC. If that locale uses a single-byte
+ * decimal separator other than '.' (e.g. ',' in many European locales),
+ * replace it so the result stays a valid Tcl number. printf's "%f"/"%e"/
+ * "%g" conversions emit at most one such separator and never a thousands
+ * separator, so a single replacement is sufficient.
+ */
+
+ if (length > 0 && decimal[0] != '.' && decimal[1] == '\0') {
+ size_t scan = ((size_t)length < size) ? (size_t)length : size - 1;
+ char *p = (char *)memchr(buffer, decimal[0], scan);
+
+ if (p != NULL) {
+ *p = '.';
+ }
+ }
+ return length;
+}
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
* fill-column: 78
* End:
*/
Index: unix/tkUnixScale.c
==================================================================
--- unix/tkUnixScale.c
+++ unix/tkUnixScale.c
@@ -277,11 +277,11 @@
char valueString[TCL_DOUBLE_SPACE];
Tk_FontMetrics fm;
Tk_GetFontMetrics(scalePtr->tkfont, &fm);
y = TkScaleValueToPixel(scalePtr, value) + fm.ascent/2;
- if (snprintf(valueString, TCL_DOUBLE_SPACE, format, value) < 0) {
+ if (TkFormatDouble(valueString, TCL_DOUBLE_SPACE, format, value) < 0) {
valueString[TCL_DOUBLE_SPACE - 1] = '\0';
}
Tcl_Size length = strlen(valueString);
width = Tk_TextWidth(scalePtr->tkfont, valueString, length);
@@ -368,11 +368,11 @@
* fromValue would take. Not exact, but better than no constraint.
*/
ticks = fabs((scalePtr->toValue - scalePtr->fromValue)
/ tickInterval);
- if (snprintf(valueString, TCL_DOUBLE_SPACE, scalePtr->tickFormat,
+ if (TkFormatDouble(valueString, TCL_DOUBLE_SPACE, scalePtr->tickFormat,
scalePtr->fromValue) < 0) {
valueString[TCL_DOUBLE_SPACE - 1] = '\0';
}
maxTicks = (double) Tk_Width(tkwin)
/ (double) Tk_TextWidth(scalePtr->tkfont, valueString, -1);
@@ -506,11 +506,11 @@
Tk_FontMetrics fm;
x = TkScaleValueToPixel(scalePtr, value);
Tk_GetFontMetrics(scalePtr->tkfont, &fm);
y = top + fm.ascent;
- if (snprintf(valueString, TCL_DOUBLE_SPACE, format, value) < 0) {
+ if (TkFormatDouble(valueString, TCL_DOUBLE_SPACE, format, value) < 0) {
valueString[TCL_DOUBLE_SPACE - 1] = '\0';
}
Tcl_Size length = strlen(valueString);
width = Tk_TextWidth(scalePtr->tkfont, valueString, length);
@@ -576,11 +576,11 @@
*/
Tcl_Preserve(scalePtr);
if ((scalePtr->flags & INVOKE_COMMAND) && (scalePtr->commandObj != NULL)) {
Tcl_Preserve(interp);
- if (snprintf(string, TCL_DOUBLE_SPACE, scalePtr->valueFormat,
+ if (TkFormatDouble(string, TCL_DOUBLE_SPACE, scalePtr->valueFormat,
scalePtr->value) < 0) {
string[TCL_DOUBLE_SPACE - 1] = '\0';
}
Tcl_DStringInit(&buf);
Tcl_DStringAppend(&buf, Tcl_GetString(scalePtr->commandObj), TCL_INDEX_NONE);