Tk Source Code

View Ticket
Login
Ticket UUID: 85cea56c277940cae05d645ad8775fae9bd400f6
Title: The -underline option does not show an underline on ttk::label
Type: Bug Version: ae5acb5a
Submitter: sbron Created on: 2023-02-22 21:11:31
Subsystem: 88. Themed Tk Assigned To: jan.nijtmans
Priority: 5 Medium Severity: Important
Status: Closed Last Modified: 2023-07-11 11:01:56
Resolution: Fixed Closed By: jan.nijtmans
    Closed on: 2023-07-11 11:01:56
Description:
The -underline option of at least the ttk::label and ttk::button widgets does not produce a visible underline. Old-style labels and buttons are fine.

I suspect the problem may be related to the TIP #577 implementation, "Enhanced index values for Tk"

The behavior can be observed using the following command:

        pack [ttk::label .l -text Hello -underline 0]

The 'H' should be underlined, but it isn't.
User Comments: jan.nijtmans added on 2023-07-11 07:20:42:

Final part of fix committed here


fvogel added on 2023-06-26 20:12:39:

Quite some progress has been made by Jan very recently on this, see [a9929f112a].


fvogel added on 2023-04-04 19:52:10:

Hmmm, not quite I would say.

The current state does not resolve what Csaba reports below on 2023-03-12 10:43:50.

Csaba's output is from Tk trunk linked with Tcl trunk aka 9.0, but we have the same problem with Tk trunk linked with Tcl core-8-branch aka 8.7. The output is not the same for the two versions, but it is different between Tk and Ttk widgets in both versions.

Also with Tk trunk linked with Tcl trunk, an underline is actually drawn when requesting, say, index -4 (for both Tk and Ttk widgets). I don't think this is expected since negative indices should be hidden from the script level (according to Jan on 2021-11-04 20:44:20 in [a9929f112a] - and this is happening with Tk trunk linked with Tcl core-8-branch).

So I believe we still have these two issues to fix.


jan.nijtmans added on 2023-04-02 22:31:01:

Then, the final part of the fix is [71f4c24a10e3953e|this] (just going back to underlineObj, as it was before TIP #577).

Many, many, thanks François! You pinpointed it exactly!


fvogel added on 2023-04-02 12:31:14:

And then of course the difference in behavior in Csaba's script when calling "$widget configure -underline" for Tk and Ttk widget is clear.

Tk widgets store the underline index in an int at position internalOffset.

Ttk widgets store the underline index in a (Tcl_Obj *) at position objOffset.

When requesting "configure -underline", Ttk retrieves the exact thing that was stored in the Tcl_Obj, but Tk processes the internal integer in order to hide raw negative values and transform them to the form "end-n".


fvogel added on 2023-04-02 10:45:03:

After heavy head scratching I think I'm starting to finally undersdand what's going on here.

First of all we only have issues with Ttk widgets, there is no problem with legacy Tk widgets.

Let's take ttk::label as an example (it's the same for ttk::button, ttk::checkbutton, and so on). A ttk::label widget is composed (see ttkButton.c) of a WidgetCore, a BasePart and a LabelPart. The BasePart builds upon a Tcl_Obj *textObj which points to a Text element.

In Ttk, the underline of Text elements is drawn in TextDraw() (ttkLabel.c, implementing label elements). The index of the underline is stored in text->underlineObj, which is linked to the -underline ttk label widget configuration option through an element in the LabelElementOptions array. This element is of type Ttk_ElementOptionSpec, which is defined (in ttktheme.h) as:

typedef struct Ttk_ElementOptionSpec
{
    const char *optionName;		/* Command-line name of the widget option */
    Tk_OptionType type; 		/* Accepted option types */
    Tcl_Size offset;			/* Offset of Tcl_Obj* field in element record */
    const char *defaultValue;		/* Default value to used if resource missing */
} Ttk_ElementOptionSpec;

This says that this option value is stored as a (Tcl_Obj *) in the Text element record. And indeed this is the case (see definition of the TextElement struct at the beginning of ttkLabel.c, it says Tcl_Obj *underlineObj).

In the current state of things, there is no way to store the option value as a type-specific internal form at an internalOffset position. Only the objOffset way of storing the option value is available from Ttk (see details about objOffset vs internalOffset in the Tk_SetOptions manual page).

I believe this explains why the original code (TIP #577 implementation [95b2bdbd]) didn't work (underlines were missing from Ttk widgets) and why my fix [41e57b5e] works (underlines are back): TIP #577 implementation tried to make use of an internalOffset machinery that is just not supported by Ttk, only the objOffset machinery can be used in Ttk.


fvogel added on 2023-04-01 15:20:52:
I'm totally lost.

The whole thing has to do with whether the underline option value is stored in the internalPtr or not. I'm afraid I can't say anything more precise. I tend to give up, sorry.

jan.nijtmans added on 2023-03-14 15:57:08:
> What do you think, Jan?

I'm really puzzeled why the fix works (I tested it). Now merged to trunk (making a new macro for TTK)

Thanks, François, for taking this! I have too many things on my plate recently ....

So, I will have a further look at what more is still wrong. Keeping this ticket open (since it's still not 100% OK)

nemethi (claiming to be Csaba Nemethi) added on 2023-03-12 12:34:58:
The problem that the values reported by the configure (or cget) command for a Tk core label and a ttk::label are not identical was not present before the commit implementing the fix proposal, as can easily be seen by using the attached test script.

nemethi (claiming to be Csaba Nemethi) added on 2023-03-12 10:43:50:
The fix proposal seems to have improved things a lot, but there are still several inconsistencies regarding the behavior of the -underline option.  For example, the index -2 will underline the last character of both a Tk core and a Ttk label (this is a bug in both cases), and the values reported by the configure (or cget) command are not identical (they should be).  As stated by Jan in the ticket referenced in my first comment below, negative index values should result in no underlining.

The following script (provided as attachment, too) might prove useful when testing the behavior of the -underline option:


label .l1 -text "label"
ttk::label .l2 -text "ttk::label"

ttk::frame .f
ttk::label .f.l -text "Index:"
ttk::entry .f.e -width 10 -textvariable index
bind .f.e <Return> {
    puts "\nIndex:\t$index"
    foreach l {.l1 .l2} {
        $l configure -underline $index
        puts "[winfo class $l]:\t[$l configure -underline]"
    }
}
pack .f.l .f.e -side left -padx 3p

pack .f -side bottom -padx 3p -pady 3p
pack .l1 .l2 -pady 3p


Here is the output for a few index values:

Index:	-2
Label:	-underline underline Underline {} end
TLabel:	-underline underline Underline {} -2

Index:	-3
Label:	-underline underline Underline {} end-1
TLabel:	-underline underline Underline {} -3

Index:	-4
Label:	-underline underline Underline {} end-2
TLabel:	-underline underline Underline {} -4

fvogel added on 2023-03-11 13:57:38:

Question: is it normal that, with Tcl 9.0 headers, the following script underlines the last character 'o'? Do we have yet another off-by-one bug here?

  package require Tk
  pack [ttk::label .f -text Hello -underline -2]


fvogel added on 2023-03-11 13:46:47:

Fix proposal for this ticket: [41e57b5e]. This works (the underline is back in ttk::label, ttk:: labelframe and ttk::notebook tabs) but I'm not totally sure why.

What do you think, Jan?


jan.nijtmans added on 2023-03-05 21:38:05:

Slightly related to this ticket:

% label .l -underline end-1
.l
% .l configure -underline
-underline underline Underline {} end-2
% 

Fixed [2509c07906cd2f50|here]

I still don't have a clue about what's happening here .... :-(


fvogel added on 2023-03-05 21:14:02:

Careful reading of the SetOptions man page gives some light on this. There is indeed a swapping, but it is correct because at the same time the underline option changed from a Tcl_Obj to an int in TIP#577.


fvogel added on 2023-03-05 18:44:21:

The problem comes from the use of the TK_OPTION_UNDERLINE_DEF macro with Ttk.

In that macro expansion, TCL_INDEX_NONE comes before offsetof(type, field)

But in the definition of the underline option in ttkButton.c:68, the order is the opposite (before TIP#577 implementation). Therefore, TIP #577 implementation effectively resulted in swapping the objOffset and internalOffset fields of the Tk_OptionSpec structure for -underline in Ttk widgets.

ttkButton.c:68 before TIP #577:

    {TK_OPTION_INT, "-underline", "underline", "Underline",
	"-1", offsetof(Base,base.underlineObj), TCL_INDEX_NONE,
	0,0,0 },

ttkButton.c:68 afterTIP #577:

    {TK_OPTION_INDEX, "-underline", "underline", "Underline",
	TK_OPTION_UNDERLINE_DEF(Base, base.underline), 0},
which expands to (with Tcl8.7 headers):
    {TK_OPTION_INDEX, "-underline", "underline", "Underline",
	"-1", TCL_INDEX_NONE, offsetof(Base, base.underline), 0, NULL, 0},

See the parameters order difference?

Switching parameters order in the TK_OPTION_UNDERLINE_DEF macro fixes this bug, but triggers crashes in the legacy widgets, of course, since order of the Tk legacy widgets underline option parameter is now swapped.

How was this working before TIP #577? It looks like objOffset and internalOffset fields of the Tk_OptionSpec structure are swapped in Ttk compared to Tk? How can this be since all are the same Tk_OptionSpec structs...? I must still be missing something.


fvogel added on 2023-03-05 10:21:30:

Bisection confirms that [95b2bdbd2bff87b3|TIP #577 implementation] is the first commit showing this regression.


nemethi (claiming to be Csaba Nemethi) added on 2023-03-01 17:16:29:
See also the ticket https://core.tcl-lang.org/tk/tktview/a9929f112a .

fvogel added on 2023-02-27 07:05:34:
And, as a data point, no underline shows up when building Tk main branch against either Tcl's core-8-branch or Tcl's main branch.

fvogel added on 2023-02-27 07:03:00:

[95b2bdbd2bff87b3|TIP #577] must be the culprit indeed. Not just the ttk::label (your example) is broken, but also the ttk::button. I didn't check the other widgets but thorough testing should happen which apparently didn't before merging TIP #577.

Jan, may I assign to you?


fvogel added on 2023-02-27 06:51:26:
Sorry. Only now I'm realizing that the commit you mention in the "Version" field" points to trunk. I was testing with core-8-6-branch. I can reproduce now on Windows as well.

fvogel added on 2023-02-27 06:46:46:
Thank you. On Windows, all themes available by default with Ttk (that is: winnative clam alt default classic vista xpnative) work as expected.

sbron added on 2023-02-26 21:29:51:
Sorry, I expected this to be a general drawing problem.

I found it on linux x86_64. Tested with default, clam, and plastik theme. All exhibit the problem.

fvogel added on 2023-02-26 20:05:59:
Platform?

On Windows the 'H' is correctly underlined with your example script.

Attachments: