Tk Source Code

Artifact [1889ed23]
Login

Artifact 1889ed231c8d3ac6cfc7b21679c08a851d00218b2e06b4d6406fa19bb0d8fa0c:

Attachment "tk-lcnumeric-fix.patch" to ticket [348b482f] added by serhiy.storchaka 2026-06-24 09:26:56. (unpublished)
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);