0

I want the following behavior:

Sample Image

I've tried this, but it makes the link unusable and the color change only occurs if the mouse is in a very specific spot:

from PyQt6.QtWidgets import QLabel, QApplication
from PyQt6.QtGui import QMouseEvent

import sys


class SpecialLinkLabel(QLabel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.linkHovered.connect(self.change_link_color)

    def mouseMoveEvent(self, event: QMouseEvent):
        if 'color: #999999' in self.text():
            self.setText(self.text().replace('color: #999999', 'color: #1597BB'))
        return super().mouseMoveEvent(event)

    def change_link_color(self):
        if 'color: #1597BB' in self.text():
            self.setText(self.text().replace('color: #1597BB', 'color: #999999'))


app = QApplication(sys.argv)
special_label = SpecialLinkLabel(
    'Click <a href="https://random.dog/" style="color: #1597BB">here</a> for something special!</a>'
)
special_label.setOpenExternalLinks(True)
special_label.show()
sys.exit(app.exec())

Not sure if this possible or not.

Andres
  • 21
  • 4

1 Answers1

0

You already have it. linkHovered passes the URL when it emits you just need to check if it contains any URL or it's just an empty string. If it is not an empty string then change the color else remove it.

from PyQt5.QtWidgets import QLabel, QApplication
from PyQt5.QtGui import QMouseEvent, QCursor
from PyQt5 import QtCore

import sys

class SpecialLinkLabel(QLabel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.link_text = 'Click <a href="https://google.com" style="color: {color}">here</a> for something special!</a>'
        self.setText(self.link_text)
        self.linkHovered.connect(self.change_link_color)
    
    def change_link_color(self, link):
        print("Hovered: ", repr(link))
        
        self.setText(self.link_text.format(color="red") if link else self.link_text.format(color="blue"))
        
        if link:
            self.setCursor(QCursor(QtCore.Qt.PointingHandCursor))

        else:
            self.unsetCursor()

app = QApplication(sys.argv)
special_label = SpecialLinkLabel()
special_label.show()
sys.exit(app.exec())

Art
  • 2,836
  • 4
  • 17
  • 34
  • The big problem with this is that it won't work with multiple links (especially if several have the same url). – ekhumoro Aug 07 '21 at 12:51
  • @ekhumoro it won't work only if all the URLs are the same, besides I am not sure why would there be a need for multiple links to the same URL's. If it is different then a simple check is very much likely to work(eg: if link=='google.com' elif ...). – Art Aug 07 '21 at 13:12
  • It's very common to have several links to the same url. For example, almost any kind of documentation will do this. I also wonder what will happen when two links are right next to each other - is the hit-testing accurate enough? But in any case, your current code example is not a realistic general solution. It should be able to highlight arbitrary links and not require hard-coding the text/urls inside the class. It seems telling that Qt doesn't have this feature built-in. – ekhumoro Aug 07 '21 at 13:39
  • Even `QTextBrowser` doesn't make it any easier to achieve a general solution for this. It's quite hard to reliably determine where each anchor begins and ends so the char-format can be changed dynamically. The underlying `QTextDocument` just doesn't seem well-designed for this kind of thing. – ekhumoro Aug 07 '21 at 13:54
  • @ekhumoro This is the best solution available for now. Yes, it does work perfectly even if the links are right next to each other. Check out [here](https://pastebin.com/30i94i8m). The other solution might require you to manually use mouse pos to check if it's under the correct text which might not be as reliable as this. – Art Aug 07 '21 at 13:57
  • As I suspected, it doesn't work properly if the mouse is moved very quickly (which I also suspect is platform and/or hardware dependent). Also, it fails when the window is deactivated and the mouse is moved away. So this is not currently even close to a general solution. There are many other SO questions on this topic with similar answers to yours, and they all suffer from the same sorts of problems. – ekhumoro Aug 07 '21 at 14:14
  • @ekhumoro Yes, I agree that this would require one to hard code. But, the other answers I have seen [1](https://stackoverflow.com/questions/40729040/how-to-create-qt-label-hover-effect) [2](https://stackoverflow.com/questions/66386835/change-text-color-when-hovered-over-a-link-in-pyqt) on SO makes use of Enter and Leave events which are not as good as this. This answer only overcomes those problems. – Art Aug 07 '21 at 14:22
  • @Art the enter and leave events are very important *exactly* to prevent problems like those ekhumuro pointed out, since linkHovered is not always reliable. – musicamante Aug 08 '21 at 01:08
  • @musicamante Hmmm I could only think of how the leave event would help, but not enter the event. How would you go about using enter event in this case? Wouldn't it require us to check the link position manually, which I think would be a tedious task. Also, doesn't Qt make use of this under the hood? ofc, this isn't a perfect solution for all, but I still think this is a good enough solution for OP and many others who may require both text and link in the same label and still get the hover effect. – Art Aug 08 '21 at 01:30
  • @Art the problem is that the hover signal is fired only when the hovered link is different (including when there's no hovered link at all) and that's because the current link is internally stored to avoid multiple signal on mouse move. Suppose that you implement the leave event in order to reset the stylesheet: you hover a link and quickly leave the label, so the colors get reset due to the leaveEvent, but if you enter again the label and you happen to be exactly on the same link, the hover signal won't be emitted, and since the enter event has not been implemented, the color won't be changed. – musicamante Aug 08 '21 at 19:42