Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

How can I do something when the user clicks on a particular span/frame/block/fragment/whatever of text in a QTextEdit?



  • I'm writing an IRC client, and I have message/channel windows update with lines like

    <from_nick> message

    and I want to be able to bring up a context menu when the user right clicks on a nick (like the "from_nick" part above) and to know what the nick they clicked on is. I've tried numerous things, can't figure it out.

    Thanks.



  • Well, since it is a text-edit field the widget sees what is in it is text, so if you are not satisifed with cut-n-paste text operations then you'd need to subclass the widget and get the mouse coordinates of the click in the widget, and then you would need to use the font metrics information to calculate the boundaries of each character and react accordingly. ps - this only seems possible if the font is monospaced.

    Or ditch text-edit and parse the incoming messages into separate widgets, the nickname containing one being a pushbutton.



  • If using rich text is acceptable, there's QTextEdit::anchorAt(). Handle the context menu event, and use anchorAt to retrieve the anchor embedded in the nick text.



  • Thanks for the lead, @jeremy_k, though I'm having some trouble making it work...

    I tried two different ways of making an anchor, one with insertHtml, inserting something like '<a href="nick">nick</a>', and one with QTextFormat, setAnchorHref, and insertText... in both cases, the link extends to the end of the line, e.g. in my original example of

    <from_nick> message

    the whole 'from_nick> message' would be the link, instead of the link starting and ending with 'some_nick' like I want.

    I haven't posted my code because it's in PyQt and I don't know if that would annoy some people because this isn't the Qt for Python forum. I posted the question here, assuming that I'd be able to adapt any advice I get to PyQt, because I didn't get any response in the Qt for Python forum which I think is a less popular forum..



  • import PyQt5.QtCore as QtCore
    import PyQt5.QtWidgets as QtWidgets
    
    class MyTextEdit(QtWidgets.QTextEdit):
        def contextMenuEvent(self, event):
            anchor = self.anchorAt(event.pos())
            if len(anchor) > 0:
                QtWidgets.QMessageBox.information(self, "Anchor Found", "The anchor {} was found".format(anchor))
    
    app = QtWidgets.QApplication([])
    textEdit = MyTextEdit()
    textEdit.setHtml('<a href="first">&lt;user1&gt;</a> first user<br><a href="second">&lt;second&gt;</a> second user')
    textEdit.show()
    app.exec()
    


  • here's my code:

    def addmsg(textedit, nick, message):
      if config.show_timestamp:
        obj_now = datetime.now()
        textedit.insertPlainText(f"[{obj_now.hour: >2}:{obj_now.minute: >2}] ")
      textedit.insertPlainText("<")
      #textedit.insertHtml(f'<a href="{nick}">{nick}</a>') #doesn't work right, bug in qt
      charFormat = QTextCharFormat()
      charFormat.setAnchorHref(nick)
      textedit.textCursor().insertText(nick, charFormat)
      textedit.insertPlainText("> ")
      colorify(textedit, message)
      textedit.insertHtml("<br>")
    

    both the QTextCharFormat approach and the insertHtml approach (commented out in the above) result in the link extending all the way to the end of the line, which it seems it shouldn't. Just posting it in case you happen to have any insight; I expect I'll have to figure this out myself ;/



  • I verified that the problem isn't in my colorify function, I've temporarily replaced that with a simple insertPlainText:

    def addmsg(textedit, nick, message):
      if config.show_timestamp:
        obj_now = datetime.now()
        textedit.insertPlainText(f"[{obj_now.hour: >2}:{obj_now.minute: >2}] ")
      textedit.insertPlainText("<")
      textedit.insertHtml(f'<a href="{nick}">{nick}</a>') #doesn't work right, bug in qt
      textedit.insertPlainText("> ")
      textedit.insertPlainText(message.decode('utf-8'))
      textedit.insertHtml("<br>")
    


  • @jeremy_k said in How can I do something when the user clicks on a particular span/frame/block/fragment/whatever of text in a QTextEdit?:

    import PyQt5.QtCore as QtCore
    import PyQt5.QtWidgets as QtWidgets

    class MyTextEdit(QtWidgets.QTextEdit):
    def contextMenuEvent(self, event):
    anchor = self.anchorAt(event.pos())
    if len(anchor) > 0:
    QtWidgets.QMessageBox.information(self, "Anchor Found", "The anchor {} was found".format(anchor))

    app = QtWidgets.QApplication([])
    textEdit = MyTextEdit()
    textEdit.setHtml('<a href="first"><user1></a> first user<br><a href="second"><second></a> second user')
    textEdit.show()
    app.exec()

    Here's a minimal example that does the wrong thing:

    import PyQt5.QtCore as QtCore
    import PyQt5.QtWidgets as QtWidgets
    
    class MyTextEdit(QtWidgets.QTextEdit):
        def contextMenuEvent(self, event):
            anchor = self.anchorAt(event.pos())
            if len(anchor) > 0:
                QtWidgets.QMessageBox.information(self, "Anchor Found", "The anchor {} was found".format(anchor))
    
    app = QtWidgets.QApplication([])
    textEdit = MyTextEdit()
    textEdit.insertHtml('<a href="test1">test1</a>')
    textEdit.insertPlainText("test2")
    textEdit.show()
    app.exec()
    

    both test1 and test2 in the textEdit window are underlined and in blue.



  • I figured it out. Thanks for your help.

    def addmsg(textedit, nick, message):
      if config.show_timestamp:
        obj_now = datetime.now()
        textedit.insertPlainText(f"[{obj_now.hour: >2}:{obj_now.minute: >2}] ")
      textedit.insertHtml(f'&lt;<a style="text-decoration: none; color: inherit" href="{nick}">{nick}</a>&gt; ')
      colorify(textedit, message)
      textedit.insertHtml("<br>")
    


  • @Inhahe said in How can I do something when the user clicks on a particular span/frame/block/fragment/whatever of text in a QTextEdit?:

    here's my code:

    def addmsg(textedit, nick, message):
      if config.show_timestamp:
        obj_now = datetime.now()
        textedit.insertPlainText(f"[{obj_now.hour: >2}:{obj_now.minute: >2}] ")
      textedit.insertPlainText("<")
      #textedit.insertHtml(f'<a href="{nick}">{nick}</a>') #doesn't work right, bug in qt
      charFormat = QTextCharFormat()
      charFormat.setAnchorHref(nick)
      textedit.textCursor().insertText(nick, charFormat)
      textedit.insertPlainText("> ")
      colorify(textedit, message)
      textedit.insertHtml("<br>")
    

    both the QTextCharFormat approach and the insertHtml approach (commented out in the above) result in the link extending all the way to the end of the line, which it seems it shouldn't. Just posting it in case you happen to have any insight; I expect I'll have to figure this out myself ;/

    Using textedit.insertHtml(f'<a href="{nick}">{nick}</a>&gt;') rather than adding > as plain text fixes the issue for me with Qt 5.15.2 and PyQt 5.15.3. I haven't tried using the QTextCharFormat method.


Log in to reply