Thank you very much for replying!
Please excuse me, it turns out that a modification I made caused the problem. The original version he provided me with seems to work perfectly, at least in isolation: There really was something missing.
from PyQt5.QtWidgets import QApplication, QMainWindow, QTextEdit
from PyQt5.QtGui import QCursor, QColor, QBrush, QTextLayout, QTextCharFormat, QTextCursor
from PyQt5.Qt import QPoint
app = QApplication([])
main_window = QMainWindow()
class HighlightTextEdit(QTextEdit):
highlightPos = -1, -1
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setMouseTracking(True)
self.highlightBlock = self.document().firstBlock()
self.highlightFormat = QTextCharFormat()
self.highlightFormat.setBackground(
QBrush(QColor('#FFFF00')))
self.highlightFormat.setFontUnderline(True)
self.document().contentsChanged.connect(self.highlight)
def highlight(self, pos=None):
if not self.toPlainText() or not self.isVisible():
return
if pos is None:
pos = self.mapFromGlobal(QCursor.pos())
cursor = self.cursorForPosition(pos)
cursor.select(cursor.WordUnderCursor)
start = cursor.selectionStart()
end = cursor.selectionEnd()
doc = self.document()
block = doc.findBlock(start)
# check if the mouse is actually inside the rectangle of the block
blockRect = doc.documentLayout().blockBoundingRect(block)
blockLayout = block.layout()
if not pos in blockRect.translated(0, -self.verticalScrollBar().value()):
# mouse is outside of the block, no highlight
start = end = -1
startFromBlock = start - block.position()
length = end - start
if length:
# ensure that the cursor is actually within the boundaries of the word
textLine = blockLayout.lineForTextPosition(startFromBlock)
endFromBlock = startFromBlock + length
x, _ = textLine.cursorToX(endFromBlock)
if pos.x() > blockRect.x() + x:
start = end = -1
length = 0
# if the range is the same as the previous call, we can ignore it
if self.highlightPos == (start, end):
return
# clear the previous highlighting
self.highlightBlock.layout().clearFormats()
self.highlightPos = start, end
if length:
# create a FormatRange for the highlight using the current format
r = QTextLayout.FormatRange()
r.start = startFromBlock
r.length = length
r.format = self.highlightFormat
blockLayout.setFormats([r])
# notify that the document must be layed out (and repainted) again
dirtyEnd = max(
self.highlightBlock.position() + self.highlightBlock.length(),
block.position() + block.length()
)
dirtyStart = min(self.highlightBlock.position(), block.position())
doc.markContentsDirty(dirtyStart, dirtyEnd - dirtyStart)
self.highlightBlock = block
def viewportEvent(self, event):
if event.type() == event.Leave:
# disable highlight when leaving, using coordinates outside of the
# viewport to ensure that highlighting is cleared
self.highlight(QPoint(-1, -1))
elif event.type() == event.Enter:
self.highlight()
elif event.type() == event.MouseMove:
if not event.buttons():
self.highlight(event.pos())
elif event.type() == event.MouseButtonRelease:
self.highlight(event.pos())
return super().viewportEvent(event)
text_edit = HighlightTextEdit()
text_edit.setText("Óleo de motor Gás Racing Team Marca Vintage Sinais de metal Garage Man Cave Acessórios de decoração de parede Placas de metal retrô adesivos de parede")
main_window.setCentralWidget(text_edit)
main_window.show()
text_edit.setFocus()
app.exec()
I'll just rollback to this one. Thanks a lot and sorry for the trouble!
EDIT.: I came up with a better check for my use case:
def check_if_mouse_position_is_over_text(self, position):
if self.underMouse():
pos = position
# create a QTextCursor at that position and select text
text_cursor = self.cursorForPosition(pos)
text_cursor.select(text_cursor.WordUnderCursor)
selected_text = text_cursor.selectedText()
start = text_cursor.selectionStart()
end = text_cursor.selectionEnd()
length = end - start
text_cursor.clearSelection()
block = text_cursor.block()
block_rect = self.document().documentLayout().blockBoundingRect(block)
# translate by the offset caused by the scroll bar
block_rect.translate(0, -self.verticalScrollBar().value())
if length:
new_cursor = self.textCursor()
new_cursor.setPosition(start)
self.setTextCursor(new_cursor)
start_rect = self.cursorRect()
start_point = self.mapToGlobal(start_rect.topLeft())
new_cursor.setPosition(end)
self.setTextCursor(new_cursor)
end_rect = self.cursorRect()
end_point = self.mapToGlobal(end_rect.topLeft())
new_cursor.clearSelection()
start_point_within_block = start - block.position()
text_line = block.layout().lineForTextPosition(start_point_within_block)
end_point_within_block = start_point_within_block + length
x_end, _ = text_line.cursorToX(end_point_within_block)
# extends the acceptable margin by 15 on both sides because Qt's falls slightly short of the real one
error_margin = 15
if pos.x() + error_margin >= start_point.x() and pos.x() - error_margin <= end_point.x():
return True
return False