tree-declutter 'WEIGHT bold' no longer working after upgrading to PyQt6

70 views
Skip to first unread message

jkn

unread,
Oct 30, 2024, 11:58:51 AM10/30/24
to leo-editor
Hi all
    I've just upgraded to PyQt6 (v6.7.3, on Linux), so that I could run a more recent Leo with the new layouts.

That is going OK, but my tree-declutter settings appear not to be working any longer.

ie: I have a pattern which sets

WEIGHT Bold

and this is not having the desired effect on the node headline. the 'matching' pattern at the front is not being displayed, so that part is working.

I note that another tree-declutter pattern, with both colour and weight changes:

FG red
WEIGHT Bold

is showing the red, but not the bold...

Do I need to update another part of PyQt to re-instate this behaviour?

Thanks, Jon N

jkn

unread,
Oct 30, 2024, 2:58:37 PM10/30/24
to leo-editor
... I see that the values for the Weight enum for QFont.setWeight() seem to have changed for PyQt6. It is now a scale of 1 to 1000, instead of 1 to 99 as previously.


Changing this in qt_tree helps:

--- a/leo/plugins/qt_tree.py
+++ b/leo/plugins/qt_tree.py
@@ -307,7 +307,7 @@ class LeoQtTree(leoFrame.LeoTree):
            elif cmd == 'WEIGHT':
 
                def weight_modifier(item: Item, param: str) -> None:
-                    arg = getattr(QtGui.QFont, param, 75)
+                    arg = getattr(QtGui.QFont, param, 700) # WAS 75
                    font = item.font(0)
                    font.setWeight(arg)
                    item.setFont(0, font)

but I think this cannot be the whole story. As mentioned, I previously had

WEIGHT Bold


should I actually be using a number here?

Thanks, J^n

Thomas Passin

unread,
Oct 31, 2024, 7:45:05 AM10/31/24
to leo-editor
Either of these work in the sense of executing without producing an error.  I haven't tried applying the font to see the results:

from leo.core.leoQt import QtGui, QtWidgets
QFont = QtGui.QFont

newfont = QFont('Georgia')
newfont.setWeight(QFont.Weight.Bold)
# or
newfont.setWeight(700)

Thomas Passin

unread,
Oct 31, 2024, 9:15:28 AM10/31/24
to leo-editor
On Thursday, October 31, 2024 at 7:45:05 AM UTC-4 Thomas Passin wrote:
Either of these work in the sense of executing without producing an error.  I haven't tried applying the font to see the results:

from leo.core.leoQt import QtGui, QtWidgets
QFont = QtGui.QFont

newfont = QFont('Georgia')
newfont.setWeight(QFont.Weight.Bold)
# or
newfont.setWeight(700)

Now I've tried it and yes, I do get bold text.  That detour with arg = getAttr() isn't needed.

jkn

unread,
Oct 31, 2024, 9:23:44 AM10/31/24
to leo-editor
it is the getting of the argument, from eg:

# part of declutter-pattern
WEIGHT 700
or ?
WEIGHT Bold     # as per documentation

=> "arg = 700"

that is not working, I think.

Thomas Passin

unread,
Oct 31, 2024, 10:24:38 AM10/31/24
to leo-editor
I had a quick look at the way it's used, and I find it hard to understand.  I can see the intention but the layers of indirection make it hard. Say the pattern in myLeoSettings is WEIGHT BOLD, as you wrote. What string gets fed into declutter_style()?   declutter_style() uses the string in the method call param = c.styleSheetManager.expand_css_constants(arg).split()[0].   Every string I've given that method returns the same string, or the first word of it. None of those strings exist of attributes of QFont, so the default always comes back, which is 75.

Anyway, 700 is the value to use for bold, not 75.  It's an integer, not a string.

jkn

unread,
Oct 31, 2024, 10:47:34 AM10/31/24
to leo-editor
Yes, this matches my (brief) investigations.

But using

WEIGHT 700

did not work either - I had to use 700 as the default in the getattr() call, as I wrote a few posts above.

i am suspecting that the original code didn't properly work in PyQt5, and any WEIGHT line would cause the default in the getattr() to be returned. That used to be 75, but has to be er. 700 for bold in PyQt6.

I will try whether WEIGHT 100 does anything (eg. feint, the opposite of bold) in PyQt5.  suspect not ... in which case there is a small bug here, I think. I will attempt to fix it.

Thomas Passin

unread,
Oct 31, 2024, 11:26:40 AM10/31/24
to leo-editor
I don't see how it can work because any string that goes along with WEIGHT that feed into the function gets discarded, and then you get the default.  But as I said, it's hard to work through to be sure. "75" would give a light to normal weight, depending on the font.

jkn

unread,
Oct 31, 2024, 12:36:00 PM10/31/24
to leo-editor
indeed. My cheap fix is to patch and set the default to 700, as you have done. But what if I want to see an effect line "WEIGHT DemiBold" for some declutter patterns?

Thomas Passin

unread,
Oct 31, 2024, 12:57:26 PM10/31/24
to leo-editor
You will probably want to put some print statements into the declutter_style() method to see what arguments get passed when you ask for WEIGHT demibold or whatever. I suspect that nothing you pass in will give any result except the default 75, but that's where the print statements may help.  Even the signature of the method contradicts how the code works. The docstring isn't right, either.  The method probably got massively revised somewhere in the past and a few things got garbled. 

In addition, the line getattr(QtGui.QFont, param,75) queries the QFont class, not the instance actually being used by the item whose font is supposed to be changed, and I don't see how that makes sense.

So add some print statements  and see if they can help sort it all out.  I don't feel like spending time figuring out how to set up for decluttering in a way that will demonstrate a visibly decluttered headline with bold type.  If I did, print statements would be my starting point.

jkn

unread,
Oct 31, 2024, 2:00:18 PM10/31/24
to leo-editor
Indeed - I started doing this before my initial posting, I just have limited time windows available. Since we seem to agreed that there is something amiss here, I am over my initial question about needing an additional package to install as well as PyQt6, and I can experiment from there.

Thanks for helping to confirm my suspicions...

    J^n

jkn

unread,
Oct 31, 2024, 4:33:42 PM10/31/24
to leo-editor
This works, I think, and is closer to the documented behaviour, but it is not very beautiful.

                def weight_modifier(item: Item, param: str) -> None:
                    wd = {"Thin": QtGui.QFont.Weight.Thin,
                        "ExtraLight": QtGui.QFont.Weight.ExtraLight,
                        "Light": QtGui.QFont.Weight.Light,
                        "Normal": QtGui.QFont.Weight.Normal,
                        "Medium": QtGui.QFont.Weight.Medium,
                        "DemiBold": QtGui.QFont.Weight.DemiBold,
                        "Bold": QtGui.QFont.Weight.Bold,
                        "ExtraBold": QtGui.QFont.Weight.ExtraBold,
                        "Black": QtGui.QFont.Weight.Black
                        }
                    arg = wd.get(param, QtGui.QFont.Weight.Medium)

                    font = item.font(0)
                    font.setWeight(arg)
                    item.setFont(0, font)

                modifier = weight_modifier

Thomas Passin

unread,
Oct 31, 2024, 4:59:31 PM10/31/24
to leo-editor
What value does  param have when the method is called?  One of the dictionary keys, I hope.   setFont() won't be in effect if there is already a stylesheet specifying the value, according to the docs, so let's hope that's not the case.

You can iterate over the Weight enum:

for w in QFont.Weight:
    w_str = f'{repr(w)}'
    g.es(w_str)

<Weight.Thin: 100>
<Weight.ExtraLight: 200>
<Weight.Light: 300>
<Weight.Normal: 400>
<Weight.Medium: 500>
<Weight.DemiBold: 600>
<Weight.Bold: 700>
<Weight.ExtraBold: 800>
<Weight.Black: 900>

With a little string manipulation you could build the table without hardcoding anything. That would probably be better.

jkn

unread,
Oct 31, 2024, 5:15:00 PM10/31/24
to leo-editor
On Thursday, October 31, 2024 at 8:59:31 PM UTC tbp1...@gmail.com wrote:
What value does  param have when the method is called?  One of the dictionary keys, I hope.

err, well clearly eg 'Bold', for instance. <param> is the parameter for a line like "WEIGHT Bold" for all of the xxx_modifier functions in this part of the code/

and if it is not one of the dictionary keys ... then since I use dict.get() instead of dict[], then get() will return QtGui.QFont.Weight.Medium

   setFont() won't be in effect if there is already a stylesheet specifying the value, according to the docs, so let's hope that's not the case.

Yes, I haven't gone down that particular rabbit hole. I only wanted to change the bit I understood...
 
You can iterate over the Weight enum:

for w in QFont.Weight:
    w_str = f'{repr(w)}'
    g.es(w_str)

True, but I have a mild aversion to (more) run-time work like this. The available 'parameter strings' are (supposedly) defined in the Leo documentation, so I would rather hardcode that mapping.
Otherwise the documentation would have to say "look at the values of QFont.Weight in the PyQt documentation", rather than "use one of: Thin, ...Bold, ..."

This is possibly influenced by the fact that I do a lot of embedded programming, where every cycle counts ;-)

Thomas Passin

unread,
Oct 31, 2024, 5:28:54 PM10/31/24
to leo-editor
There's a slicker way to build the dictionary, I've just learned:

weights = {w.name: w for w in QFont.Weight}

It turns out that PyQt enums all have name and value attributes.  Very handy.

But I take your point about wanting to avoid a divergence between the values in the settings file (and their documentation) and the ones in the method. At least you can run the line above to see that name:value pairs to copy them into both places.

jkn

unread,
Oct 31, 2024, 6:22:20 PM10/31/24
to leo-editor
yes, that's nice to know, thanks for the tip. I got the values from the PyQt documentation ... but I should have realised from the fact that QFont.Weight is an Enum, that it would have names. Fancy new Python features ... bring back v1.52, I say...

Thomas Passin

unread,
Oct 31, 2024, 6:47:38 PM10/31/24
to leo-editor
On Thursday, October 31, 2024 at 6:22:20 PM UTC-4 jkn wrote:
yes, that's nice to know, thanks for the tip. I got the values from the PyQt documentation ... but I should have realised from the fact that QFont.Weight is an Enum, that it would have names. Fancy new Python features ... bring back v1.52, I say...

Hear, Hear!

jkn

unread,
Nov 1, 2024, 5:52:08 AM11/1/24
to leo-editor
Actually, assuming that the getattr() call *can* return a value in some circumstances, mumble mumble stylesheets, then presumably this is the fix?

                def weight_modifier(item: Item, param: str) -> None:
                    # attempt to turn eg 'Bold' into a stylesheet-derived value
                    arg = getattr(QtGui.QFont, param, None)
                    if arg is None:
                        # use our own mapping, eg. if no stylesheet in effect?
                        arg = {"Thin": QtGui.QFont.Weight.Thin,

                            "ExtraLight": QtGui.QFont.Weight.ExtraLight,
                            "Light": QtGui.QFont.Weight.Light,
                            "Normal": QtGui.QFont.Weight.Normal,
                            "Medium": QtGui.QFont.Weight.Medium,
                            "DemiBold": QtGui.QFont.Weight.DemiBold,
                            "Bold": QtGui.QFont.Weight.Bold,
                            "ExtraBold": QtGui.QFont.Weight.ExtraBold,
                            "Black": QtGui.QFont.Weight.Black
                            }.get(param, QtGui.QFont.Weight.Medium)
                    # apply the discovered weight
                    font = item.font(0)
                    font.setWeight(arg)
                    item.setFont(0, font)

                modifier = weight_modifier







Thomas Passin

unread,
Nov 1, 2024, 7:34:27 AM11/1/24
to leo-editor
On Friday, November 1, 2024 at 5:52:08 AM UTC-4 jkn wrote:
Actually, assuming that the getattr() call *can* return a value in some circumstances, mumble mumble stylesheets, then presumably this is the fix?

                def weight_modifier(item: Item, param: str) -> None:
                    # attempt to turn eg 'Bold' into a stylesheet-derived value
                    arg = getattr(QtGui.QFont, param, None)

I've got it.  My brain finally woke up.  Here's what will make it work (tested):

arg = getattr(QtGui.QFont.Weight, param,  QtGui.QFont.Weight.Medium )

There's no need to build the translation mapping because if the param doesn't match in the getattr()call, it won't match in the mapping, either. I suppose you could try spell-checking against the weight names, but that's got to be overkill, doesn't it?  Go ahead (after trying it out) and put in an issue for this fix if you would.

This is a typical Qt5 -> Qt6 problem. They changed the location of many of the enums. I expect that this won't be the last to be found.

jkn

unread,
Nov 1, 2024, 8:28:01 AM11/1/24
to leo-editor
Ah yes, I think I agree with your assessment. i will try and make a PR assuming success.

Thanks, J^n
Reply all
Reply to author
Forward
0 new messages