1

I think that this issue has to do with a by-design decision (that I believe is somehow wrong), but I prefer to ask here before submitting a possible bug report.

I have a QTextEdit that inherits the parent window's palette for its default font colors, and that palette might change on the run if the user wants to change the colors of that text edit window.

While this works fine for standard text, there is a problem with the QPalette.Link colors: as soon as I change the palette Text property, the unformatted text is correctly updated, but if I change the Link property the result doesn't work as expected.

from PyQt5 import QtWidgets

class Test(QtWidgets.QWidget):
    def __init__(self):
        QtWidgets.QWidget.__init__(self)
        layout = QtWidgets.QGridLayout(self)
        paletteBtn = QtWidgets.QPushButton('Set palette')
        paletteBtn.clicked.connect(self.selectPalette)

        palette = self.palette()
        palette.setColor(palette.Link, palette.color(palette.Text))
        QtWidgets.QApplication.setPalette(palette)

        self.textEdits = []

        self.baseEdit = QtWidgets.QTextEdit()
        layout.addWidget(self.baseEdit, 1, 0)

        self.preTextEdit = QtWidgets.QTextEdit()
        layout.addWidget(self.preTextEdit, 1, 1)

        self.paletteTextEdit = QtWidgets.QTextEdit()
        layout.addWidget(self.paletteTextEdit, 1, 2)

        self.staticTextEdit = QtWidgets.QTextEdit()
        layout.addWidget(self.staticTextEdit, 1, 3)

        for textEditName in ('baseEdit', 'preTextEdit', 'paletteTextEdit', 'staticTextEdit'):
            textEdit = getattr(self, textEditName)
            self.textEdits.append(textEdit)
            textEdit.setHtml('''
                <html>
                    <body>
                        <b>{}</b><br/>
                        simple text<br/>
                        <a href="link">unformatted link!</a>
                    </body>
                </html>
            '''.format(textEditName))

        self.preTextEdit.document().setDefaultStyleSheet('a {color: green;}')

        layout.addWidget(paletteBtn, 0, 0, 1, layout.columnCount())

    def selectPalette(self):
        palette = self.palette()
        new = QtWidgets.QColorDialog.getColor(palette.color(palette.Text), self)
        palette.setColor(palette.Text, new)
        palette.setColor(palette.Link, new)
        for textEdit in self.textEdits:
            if textEdit == self.paletteTextEdit:
                self.paletteTextEdit.document().setDefaultStyleSheet('a {color: palette(text);')
            elif textEdit == self.staticTextEdit:
                self.staticTextEdit.document().setDefaultStyleSheet('a {{color: rgb{};}}'.format(
                    palette.text().color().getRgb()))
            textEdit.setPalette(palette)
            cursor = textEdit.textCursor()
            cursor.movePosition(cursor.End)
            textEdit.setTextCursor(cursor)
            textEdit.insertHtml('<br/><a href="link">another link!</a>')


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    test = Test()
    test.show()
    sys.exit(app.exec_())

In the following image you can see the starting point applied to the four different approaches. The palette (applied to the application) is consistent.

start situation

If I choose a different color for the palette (both Text and Link) I would expect that both the default font color and link color would be applied, at least for those widgets that use that palette setting. Unfortunately, that only happens in the last widget that uses the current palette.text().color().getRgb() for the default stylesheet of the document, and it only works for the new anchor.

red color applied

At this point we can expect the result whenever I change the color again to blue:

blue color applied

Again, the default text color is correctly applied for all QTextEdit widgets, but it only works for the latest link added to the last text edit.

After some easy toHtml() checks against all widgets and browsing through the Qt sources, I realized that whenever a new anchor is added, the actual html code embeds the text style of the anchor text (instead of leaving the text format to the default settings and overriding it if it's specified).

While I will probably submit a report, as written at the beginning, I know that some time will be required before that will be taken in consideration and finally (if ever) fixed.
So, the question is: am I missing something/doing something wrong, or is there a way to avoid that behavior and ensure that all unformatted anchors actually use the palette set for the widget (or the application)?
Obviously I'm not talking about parsing/hardfixing the existing code by hand.

musicamante
  • 41,230
  • 6
  • 33
  • 58
  • From what I understand you, do you expect that when changing the color of the selector "a" it should be applied to all HTML and not only to those added after applying the change? I am right? – eyllanesc Nov 09 '19 at 03:00
  • @eyllanesc exactly. If I understand it correctly, whenever some html content is added Qt automatically sets a span with an hardcoded html style attribute based on the QTextDocument char format and color (I suppose that the last one is inherited from the application palette, *not* that of the widget) only for anchors. AFAIU there's no direct workaround right now, and the problem is that as soon as the style is applied, it is obviously considered as a custom one (since it becomes part of the html content, just like some pasted metaData.html() content), even if it shouldn't. – musicamante Nov 09 '19 at 03:23
  • 1
    mmm, it seems to me that it is clearly a bug and I think that a workaround is to reallocate HTML but it is clearly not optimal. A search for another possible solution is to identify where the tags are created in the source code, if it is generated in the public API then you could try to patch, but if it is in the private API it would be more complicated. – eyllanesc Nov 09 '19 at 03:30
  • As I was afraid. I've tried to look into the sources, but that's not easy and I can understand why. I'll give it another try in the next days, but from the look of it I can agree with you that it really *looks* like a bug, unless, for some reason we don't understand yet, that behavior has been chosen and/or kept by design. As a temporary workaround I think I will use the default `palette.Link` as a reference to check against the `textCursor.charFormat()`. It's not perfect, but it could work in the meantime and I'll post an answer about it if I can. Thank you! – musicamante Nov 09 '19 at 03:48
  • This has nothing to do with anchors. The real issue is that colours specified with `palette(role)` in css are never updated dynamically. This affects all tags and palette colour roles, not just anchors. If the default stylesheet specifies colours in this way, they will be read *once only*, and then applied to all subsequent html inserts. But note that [changing the default stylesheet has no effect on any pre-existing html](https://doc.qt.io/qt-5/qtextdocument.html#defaultStyleSheet-prop). – ekhumoro Nov 09 '19 at 14:35
  • @ekhumoro I know, my point is that there is no way to "unset" a default anchor color, since no matter how it's set (using css or palette), it's always hardcoded in the anchor *tag* style property, while I don't think it should, or at least we're missing some functionality to unset it: it doesn't make much sense to me that the defaul un-stylized text has no color hardcoded, while anchors do. – musicamante Nov 09 '19 at 15:07
  • @musicamante Okay - I think I slightly misunderstood your question, because your tests are affected by two different issues. The first issue is that `palette(role)` in css is not updated dynamically (which affects all tags). The second issue is that some external palette changes aren't reflected in pre-existing html (which only seems to affect anchors). – ekhumoro Nov 09 '19 at 15:40
  • @musicamante A little more research reveals that the behaviour is definitely not a bug, as per the [Qt docs](https://doc.qt.io/qt-5/qpalette.html#ColorRole-enum): "Note that we do not use the Link and LinkVisited roles when rendering rich text in Qt". I had thought that setting a default stylesheet of `a {color: palette(link)}` *before setting the html* might provide a work-around. This does pick up the correct colours from the palette - but of course it doesn't update them dynamically when the palette is changed. – ekhumoro Nov 09 '19 at 17:23
  • Actually, the docs are a bit wrong about that: Qt uses the *application* palette link roles for rich text. If no default css is set and the new palette is set to the application instead of the widget, the new brush is correctly used for new links. That said, I think I can understand the reasons for this behavior: anchors are considered "sub elements" that are styled "on the way", and since their appearance (partially) depends on the palette/css the style is hardcoded. I don't agree with this approach, but there's nothing I can do about it ;-) I'll add an answer/explanation in the next minutes. – musicamante Nov 09 '19 at 21:19
  • @musicamante The application palette just provides the fallback defaults, so it's effectively the same as setting the default stylesheet. The bottom line is that only *newly added html* will see the default link colours. The Qt docs are correct, but could be clarified to make the distinction between static html creation and dynamic rendering. I think the reason why links are treated differently may be because Qt has to keep track of visited/unvisited changes. The rich-text engine isn't very sophisticated, so I'm not particularly surprised by the approach they've taken. – ekhumoro Nov 09 '19 at 23:03
  • Just setting a the text color of an anchor with "a" matching seems wrong. Due to specificity rules, any more specific rule would override it. Not sure if Qt does that here, but with normal HTML you should add "!important", or simply add a rule more to match (like a class or parent tag), to make your new rule win. – Allan Jensen Nov 10 '19 at 15:19
  • Why not use palette colors on the `staticTextEdit` also? `palette(link)` instead of hard-coded color? I know it should be that by default, but worth a shot. I'd also probably add `a:visited { color: palette(linkVisited); )`. Also, what does the resulting HTML source code of the `QTextDocument` actually look like for the different versions? There's typically a bunch of formatting in the default `QTextDocument` HTML header, could be worth checking for CSS overrides. – Maxim Paperno Nov 10 '19 at 16:10

0 Answers0