I want to be able to select a piece of text in a non-custom way, the way you click on a website link in the middle of a text ( "Hello my name is www.google.com", www.google.com isn't highlighted when you press on it. When you press on it, it takes you to the website the same i want with my text. I want to make it like " hello my name is Jeff, i live in London, i eat potato every day" i want the user to be able to select each piece of sentence alone ("hello my name is Jeff,") alone, ("i live in London,") alone, so when the user moves the mouse cursor on a sentence it goes highlighted (like being ready to be selected) and after that i want to add some functionality to it.
Here is a similar project check the upper text, not the lower text and how it reacts with the mouse.
Here is a start. In the code below, the clickable phrases in the text are translated to a ordered list of text cursors. When the mouse is moved (i.e. when a mouseMove
event occurs) the position in the text under the mouse pointer is compared to this list of text cursors. If this position falls within the bounds of a cursor in the list, the corresponding phrase is highlighted by setting the extra selections of the browser. When a mouse button is clicked and the mouse is over a clickable phrase, a signal is emitted containing a text cursor corresponding to the selected phrase in the text. This signal can then be connected to a slot of another widget for further actions (like opening a message box like in the example below).
from PyQt5.QtCore import pyqtSignal, Qt
from PyQt5.QtWidgets import QTextBrowser, QApplication, QMessageBox
from PyQt5.QtGui import QFont, QSyntaxHighlighter, QTextCharFormat, QTextCursor
import bisect, re
class MyHighlighter(QSyntaxHighlighter):
def __init__(self, keywords, parent):
super().__init__(parent)
self.keywords = keywords
def highlightBlock(self, block):
if not self.keywords:
return
charFormat = QTextCharFormat()
charFormat.setFontWeight(QFont.Bold)
charFormat.setForeground(Qt.darkMagenta)
regex = re.compile('|'.join(self.keywords), re.IGNORECASE)
result = regex.search(block, 0)
while result:
self.setFormat(result.start(),result.end()-result.start(), charFormat)
result = regex.search(block, result.end())
class MyBrowser(QTextBrowser):
text_clicked = pyqtSignal("QTextCursor")
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setMouseTracking(True)
self.setTextInteractionFlags(Qt.NoTextInteraction)
# self.phrases contains all phrases that should be clickable.
self.phrases = set()
self.cursors = []
# ExtraSelection object for highlighting phrases under the mouse cursor
self.selection = QTextBrowser.ExtraSelection()
self.selection.format.setBackground(Qt.blue)
self.selection.format.setForeground(Qt.white)
self.selected_cursor = None
# custom highlighter for highlighting all phrases
self.highlighter = MyHighlighter(self.phrases, self)
self.document().contentsChange.connect(self.text_has_changed)
@property
def selected_cursor(self):
return None if self.selection.cursor == QTextCursor() else self.selection.cursor
@selected_cursor.setter
def selected_cursor(self, cursor):
if cursor is None:
cursor = QTextCursor()
if self.selection.cursor != cursor:
self.selection.cursor = cursor
self.setExtraSelections([self.selection])
def mouseMoveEvent(self, event):
''' Update currently selected cursor '''
cursor = self.cursorForPosition(event.pos())
self.selected_cursor = self.find_selected_cursor(cursor)
def mouseReleaseEvent(self, event):
''' Emit self.selected_cursor signal when currently hovering over selectable phrase'''
if self.selected_cursor:
self.text_clicked.emit(self.selected_cursor)
self.selected_cursor = None
def add_phrase(self, phrase):
''' Add phrase to set of phrases and update list of text cursors'''
if phrase not in self.phrases:
self.phrases.add(phrase)
self.find_cursors(phrase)
self.highlighter.rehighlight()
def find_cursors(self, phrase):
''' Find all occurrences of phrase in the current document and add corresponding text cursor
to self.cursors '''
if not phrase:
return
self.moveCursor(self.textCursor().Start)
while self.find(phrase):
cursor = self.textCursor()
bisect.insort(self.cursors, cursor)
self.moveCursor(self.textCursor().Start)
def find_selected_cursor(self, cursor):
''' return text cursor corresponding to current mouse position or None if mouse not currently
over selectable phrase'''
position = cursor.position()
index = bisect.bisect(self.cursors, cursor)
if index < len(self.cursors) and self.cursors[index].anchor() <= position:
return self.cursors[index]
return None
def text_has_changed(self):
self.cursors.clear()
self.selected_cursor = None
for phrase in self.phrases:
self.find_cursors(phrase)
self.highlighter.rehighlight()
def text_message(widget):
def inner(cursor):
text = cursor.selectedText()
pos = cursor.selectionStart()
QMessageBox.information(widget, 'Information',
f'You have clicked on the phrase <b>{text}</b><br>'
f'which starts at position {pos} in the text')
return inner
if __name__=="__main__":
app = QApplication([])
window = MyBrowser()
window.resize(400,300)
information = text_message(window)
text = '''
<h1>Title</h1>
<p>This is a random text with. The following words are highlighted</p>
<ul>
<li>keyword1</li>
<li>keyword2</li>
</ul>
<p>Click on either keyword1 or keyword2 to get more info.
'''
window.add_phrase('keyword1')
window.add_phrase('keyword2')
window.setText(text)
window.text_clicked.connect(information)
window.show()
app.exec()