How can I find a substring and highlight it in QTextEdit? - python

I have a QTextEdit window that shows the content of a file.
I would like to be able to find all matches inside the text using a regex and highlight them either by making the match background different or by changing the match text color or making it bold. How can I do this?

I think the simplest solution to your problem is to use the cursor associated to your editor in order to do the formatting. This way you can set the foreground, the background, the font style... The following example marks the matches with a different background.
from PyQt4 import QtGui
from PyQt4 import QtCore
class MyHighlighter(QtGui.QTextEdit):
def __init__(self, parent=None):
super(MyHighlighter, self).__init__(parent)
# Setup the text editor
text = """In this text I want to highlight this word and only this word.\n""" +\
"""Any other word shouldn't be highlighted"""
self.setText(text)
cursor = self.textCursor()
# Setup the desired format for matches
format = QtGui.QTextCharFormat()
format.setBackground(QtGui.QBrush(QtGui.QColor("red")))
# Setup the regex engine
pattern = "word"
regex = QtCore.QRegExp(pattern)
# Process the displayed document
pos = 0
index = regex.indexIn(self.toPlainText(), pos)
while (index != -1):
# Select the matched text and apply the desired format
cursor.setPosition(index)
cursor.movePosition(QtGui.QTextCursor.EndOfWord, 1)
cursor.mergeCharFormat(format)
# Move to the next match
pos = index + regex.matchedLength()
index = regex.indexIn(self.toPlainText(), pos)
if __name__ == "__main__":
import sys
a = QtGui.QApplication(sys.argv)
t = MyHighlighter()
t.show()
sys.exit(a.exec_())
The code is self-explanatory but if you have any questions just ask them.

Here is a sample of how can you highlight text in a QTextEdit:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class highlightSyntax(QSyntaxHighlighter):
def __init__(self, listKeywords, parent=None):
super(highlightSyntax, self).__init__(parent)
brush = QBrush(Qt.darkBlue, Qt.SolidPattern)
keyword = QTextCharFormat()
keyword.setForeground(brush)
keyword.setFontWeight(QFont.Bold)
self.highlightingRules = [ highlightRule(QRegExp("\\b" + key + "\\b"), keyword)
for key in listKeywords
]
def highlightBlock(self, text):
for rule in self.highlightingRules:
expression = QRegExp(rule.pattern)
index = expression.indexIn(text)
while index >= 0:
length = expression.matchedLength()
self.setFormat(index, length, rule.format)
index = text.indexOf(expression, index + length)
self.setCurrentBlockState(0)
class highlightRule(object):
def __init__(self, pattern, format):
self.pattern = pattern
self.format = format
class highlightTextEdit(QTextEdit):
def __init__(self, fileInput, listKeywords, parent=None):
super(highlightTextEdit, self).__init__(parent)
highlightSyntax(QStringList(listKeywords), self)
with open(fileInput, "r") as fInput:
self.setPlainText(fInput.read())
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
main = highlightTextEdit("/path/to/file", ["foo", "bar", "baz"])
main.show()
sys.exit(app.exec_())

QT5 has updated the RegEx, see QRegularExpression https://dangelog.wordpress.com/2012/04/07/qregularexpression/
I have updated the first example using cursors.
Note the following changes:
This doesn't wrap an edit, but uses the edit box inside, it could easily be changed to allow you to pass in the edit widget.
This does a proper regex find, not just a single word.
def do_find_highlight(self, pattern):
cursor = self.editor.textCursor()
# Setup the desired format for matches
format = QTextCharFormat()
format.setBackground(QBrush(QColor("red")))
# Setup the regex engine
re = QRegularExpression(pattern)
i = re.globalMatch(self.editor.toPlainText()) # QRegularExpressionMatchIterator
# iterate through all the matches and highlight
while i.hasNext():
match = i.next() #QRegularExpressionMatch
# Select the matched text and apply the desired format
cursor.setPosition(match.capturedStart(), QTextCursor.MoveAnchor)
cursor.setPosition(match.capturedEnd(), QTextCursor.KeepAnchor)
cursor.mergeCharFormat(format)

Related

WordUnderCursor not working as it should (word being detected when not on top of any words)

Why is a word being detected as being under the cursor here? The red arrow in the image starts where the cursor actually is. No matter where I place it, as long as it is inside the window, the program thinks a word is being selected. If the cursor is below the text, it defaults to the very last one. If the cursor is above, it defaults to the first one.
IMAGE:
All of my code:
from PyQt5.QtWidgets import QTextEdit, QMainWindow, QApplication
from PyQt5.QtGui import QMouseEvent, QTextCursor
class Editor(QTextEdit):
def __init__(self):
super(Editor, self).__init__()
# make sure this widget is tracking the mouse position at all times
self.setMouseTracking(True)
def mouseMoveEvent(self, mouse_event: QMouseEvent) -> None:
if self.underMouse():
# create a QTextCursor at that position and select text
text_cursor = self.cursorForPosition(mouse_event.pos())
text_cursor.select(QTextCursor.WordUnderCursor)
word_under_cursor = text_cursor.selectedText()
print(word_under_cursor)
# replace substring with placeholder so that repeat occurrences aren't highlighted as well
selected_word_placeholder = self.replace_selected_text_with_placeholder(text_cursor)
word_under_cursor = '<span style="background-color: #FFFF00;font-weight:bold;">' + word_under_cursor + '</span>'
# replace the sentence with the new formatting
self.setHtml(self.toPlainText().replace(selected_word_placeholder, word_under_cursor))
def replace_in_html(self, old_string, new_string):
old_html = self.toHtml()
new_html = old_html.replace(old_string, new_string)
self.setHtml(new_html)
# use placeholder so that repeat occurrences of the word are not highlighted
def replace_selected_text_with_placeholder(self, text_cursor):
# remove the selected word to be replaced by the placeholder
text_cursor.removeSelectedText()
# create a placeholder with as many characters as the original word
word_placeholder = ''
for char in range(10):
word_placeholder += '#'
text_cursor.insertText(word_placeholder)
return word_placeholder
def set_up(main_window):
title_editor = Editor()
title_editor.setText('Venda quente original xiaomi redmi airdots 2 tws fones de ouvido sem fio bluetooth fones controle ai gaming headset come')
main_window.setCentralWidget(title_editor)
main_window.show()
application = QApplication([])
window = QMainWindow()
set_up(window)
application.exec()
The problem is caused by the fact that select() always tries to select something, and even if the mouse is not actually over a text block, it will get the closest word.
The solution is to check if the mouse is actually inside the rectangle of the text block:
if self.underMouse():
pos = mouse_event.pos()
# create a QTextCursor at that position and select text
text_cursor = self.cursorForPosition(pos)
text_cursor.select(QTextCursor.WordUnderCursor)
start = text_cursor.selectionStart()
end = text_cursor.selectionEnd()
length = end - start
block = text_cursor.block()
blockRect = self.document().documentLayout().blockBoundingRect(block)
# translate by the offset caused by the scroll bar
blockRect.translate(0, -self.verticalScrollBar().value())
if not pos in blockRect:
# clear the selection since the mouse is not over the block
text_cursor.setPosition(text_cursor.position())
elif length:
# ensure that the mouse is actually over a word
startFromBlock = start - block.position()
textLine = block.layout().lineForTextPosition(startFromBlock)
endFromBlock = startFromBlock + length
x, _ = textLine.cursorToX(endFromBlock)
if pos.x() > blockRect.x() + x:
# mouse cursor is not over a word, clear the selection
text_cursor.setPosition(text_cursor.position())
Please consider that, as suggested for your previous question, highlighting text using setHtml is not a good choice, as it always resets the contents of the editor; this is not only a problem for performance, but also for usability (even ignoring the scroll bar issue): setHtml always resets the undo stack, so the user cannot use undo operations anymore.

QRegExp and single-quoted text for QSyntaxHighlighter

What would the QRegExp pattern be for capturing single quoted text for QSyntaxHighlighter? The matches should include the quotes, because I am building a sql code editor.
Test Pattern
string1 = 'test' and string2 = 'ajsijd'
So far I have tried:
QRegExp("\'.*\'")
I got it working on this regex tester: https://regex101.com/r/eq7G1v/2
but when I try to use that regex in python its not working probably because I need to escape a character?
self.highlightingRules.append((QRegExp("(['])(?:(?=(\\?))\2.)*?\1"), quotationFormat))
I am using Python 3.6 and PyQt5.
I am not an expert in regex but using a C++ answer to detect texts between double quotes changing it to single quote I see that it works:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class SyntaxHighlighter(QtGui.QSyntaxHighlighter):
def __init__(self, parent=None):
super(SyntaxHighlighter, self).__init__(parent)
keywordFormat = QtGui.QTextCharFormat()
keywordFormat.setForeground(QtCore.Qt.darkBlue)
keywordFormat.setFontWeight(QtGui.QFont.Bold)
keywordPatterns = ["'([^'']*)'"]
self.highlightingRules = [(QtCore.QRegExp(pattern), keywordFormat)
for pattern in keywordPatterns]
def highlightBlock(self, text):
for pattern, _format in self.highlightingRules:
expression = QtCore.QRegExp(pattern)
index = expression.indexIn(text)
while index >= 0:
length = expression.matchedLength()
self.setFormat(index, length, _format)
index = expression.indexIn(text, index + length)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
editor = QtWidgets.QTextEdit()
editor.append("string1 = 'test' and string2 = 'ajsijd'")
highlighter = SyntaxHighlighter(editor.document())
editor.show()
sys.exit(app.exec_())

phrase highlight QEditText PyQt4 Python3

I am creating a GUI application in python 3.4 using PyQt4 GUI-tool & NLTK. Here below i am explaining what task i have to perform in my application and what part i have completed:
Goal:
1) User will create category and provide very large text to save into database
2) based on uploaded text, user will search phrases( group of words). Phrases can occur on multiple line.
3) matched phrases list will be maintain according to lines where they found on document.
4) When user will select matched phrase line, cursor should move to that matched phrase line and highlight that phrase.
Completed:
1) I can upload documents and search phrase based on matched line.
2) and highlight in QEditText text box
Issue:
1) I am not able to highlight phrase. It only highlights single word from phrase
Code provided below what i have perform to highlight phrase:
import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
import string
# list view class
from view_matched_phrases_ui import Ui_ViewList
from PyDB import DatabaseHandle
class ViewList(QtGui.QMainWindow):
def __init__(self, parent=None):
super(ViewList, self).__init__(parent)
self.list = Ui_ViewList()
self.list.setupUi(self)
def show_list(self, phrases):
# self.list.phrase_text_view.setText("<div>Hello</div> Sahadev")
# cursor = self.list.phrase_line_view.setCursor()
# cursor.movePosition(QtGui.QTextCursor.Start)
# self.list.phrase_text_view.setTextCursor(curson)
ids = phrases[1]['pid']
with DatabaseHandle() as db:
for id in ids:
sql = "SELECT document FROM contracts WHERE id="+str(id)
data = db.get_single_data(sql)
phrase_data = str.maketrans({key: None for key in string.punctuation})
new_s = data[0].translate(phrase_data).lower()
for line in new_s.splitlines():
if phrases[0].replace('_', ' ') in line:
self.list.phrase_line_view.addItem(line)
# set selected contract in QTextEdit
self.list.phrase_text_view.setPlainText(new_s)
# self.list.phrase_text_view
cursor = self.list.phrase_text_view.textCursor()
# setup match
format = QtGui.QTextCharFormat()
format.setBackground(QtGui.QBrush(QtGui.QColor("yellow")))
# Setup the regex engine
pattern = phrases[0].replace("_", " ")
regex = QtCore.QRegExp(pattern)
# Process the displayed document
pos = 0
index = regex.indexIn(self.list.phrase_text_view.toPlainText(), pos)
while index != -1:
# Select the matched text and apply the desired format
cursor.setPosition(index)
cursor.movePosition(QtGui.QTextCursor.EndOfWord, 1)
cursor.mergeCharFormat(format)
# Move to the next match
pos = index + regex.matchedLength()
index = regex.indexIn(self.list.phrase_text_view.toPlainText(), pos)
# print(self.list.phrase_text_view)
# print(phrases)
This contribution will be great help for me.
I solved this issue by counting phrase length and nextWord method with Qt Text Object.
Rest of the code will be same only changes part i am providing here:
while index != -1:
# Select the matched text and apply the desired format
cursor.setPosition(index)
# go to next word
for i in range(len(self.matched_phrase.split(" "))):
cursor.movePosition(QtGui.QTextCursor.NextWord, 1)
cursor.mergeCharFormat(format)
# Move to the next match
pos = index + regex.matchedLength()
index = regex.indexIn(self.list.phrase_text_view.toPlainText(), pos)

(PyQt) How can I reset the CharFormat of an entire QTextEdit?

I'm using mergeCharFormat on several words within my QTextEdit, in an effort to highlight them. Something like this:
import sys
from PyQt4 import QtGui, uic
from PyQt4.QtCore import *
def drawGUI():
app = QtGui.QApplication(sys.argv)
w = QtGui.QWidget()
w.setGeometry(200, 200, 200, 50)
editBox = QtGui.QTextEdit(w)
text = 'Hello stack overflow, this is a test and tish is a misspelled word'
editBox.setText(text)
""" Now there'd be a function that finds misspelled words """
# Highlight misspelled words
misspelledWord = 'tish'
cursor = editBox.textCursor()
format_ = QtGui.QTextCharFormat()
format_.setBackground(QtGui.QBrush(QtGui.QColor("pink")))
pattern = "\\b" + misspelledWord + "\\b"
regex = QRegExp(pattern)
index = regex.indexIn(editBox.toPlainText(), 0)
cursor.setPosition(index)
cursor.movePosition(QtGui.QTextCursor.EndOfWord, 1)
cursor.mergeCharFormat(format_)
w.showFullScreen()
sys.exit(app.exec_())
if __name__ == '__main__':
drawGUI()
So, this highlighting feature works exactly as intended. However, I can't find a good way to clear the highlights from the textarea. What is a good method of doing such a thing- essentially just setting the char format of the entire QTextEdit back to its defaults?
What I've tried so far is getting the cursor again, and setting its format to a new format with a clear background, then putting the cursor over the entire selection and using QTextCursor.setCharFormat(), but this appears to do nothing.
Applying a new QTextCharFormat to the whole document works for me:
def drawGUI():
...
cursor.mergeCharFormat(format_)
def clear():
cursor = editBox.textCursor()
cursor.select(QtGui.QTextCursor.Document)
cursor.setCharFormat(QtGui.QTextCharFormat())
cursor.clearSelection()
editBox.setTextCursor(cursor)
button = QtGui.QPushButton('Clear')
button.clicked.connect(clear)
layout = QtGui.QVBoxLayout(w)
layout.addWidget(editBox)
layout.addWidget(button)

Appending namedtuple data on a QTreeView

I'm trying to append nodes of namedtuple object to a treeView but I'm not sure how to subclass QAbstractItem (if that's even the proper way). I'm still very new to Python so this is confusing to me. Here is my problem code:
Exercise = namedtuple('Exercise','name html list')
e_list = []
for i in range(1,6,1):
dummy_list = [1,2,3,'a','b','c']
ntup = Exercise("exercise{0}".format(i),'html here',dummy_list)
e_list.append(ntup)
for e in e_list:
item = QtGui.QStandardItem(e) # gives error
self.tree_model.appendRow(item) # doesnt execute
And here is the the whole program:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from collections import namedtuple
class MyWindow(QtGui.QWidget):
def __init__(self, parent=None):
super(MyWindow, self).__init__(parent)
self.pushButton = QtGui.QPushButton(self)
self.pushButton.setText("Test 1 - doesn't work")
self.pushButton.clicked.connect(self.on_pushbutton)
self.pushButton2 = QtGui.QPushButton(self)
self.pushButton2.setText("Test 2 - works")
self.pushButton2.clicked.connect(self.on_pushbutton2)
self.treeView = QtGui.QTreeView(self)
self.treeView.clicked[QModelIndex].connect(self.on_clickitem)
self.tree_model = QtGui.QStandardItemModel()
self.treeView.setModel(self.tree_model)
self.layoutVertical = QtGui.QVBoxLayout(self)
self.layoutVertical.addWidget(self.pushButton)
self.layoutVertical.addWidget(self.pushButton2)
self.layoutVertical.addWidget(self.treeView)
def on_pushbutton(self):
Exercise = namedtuple('Exercise','name html list')
e_list = []
for i in range(1,3,1):
dummy_list = [1,2,3,'a','b','c']
ntup = Exercise("exercise{}".format(i),'html here',dummy_list)
e_list.append(ntup)
for e in e_list:
item = QtGui.QStandardItem(e) # gives error
self.tree_model.appendRow(item) # never occurs
def on_pushbutton2(self):
txt = 'hello world'
item = QtGui.QStandardItem(txt)
self.tree_model.appendRow(item)
def on_clickitem(self,index):
item = self.tree_model.itemFromIndex(index) # doesn't work
print "item name:",item.getName() # doesn't work
print "item html:",item.getHtml() # doesn't work
print "item list:",item.getList() # doesn't work
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
app.setApplicationName('MyWindow')
main = MyWindow()
main.show()
sys.exit(app.exec_())
I want to append the nodes into a tree and when I click on an item I want to get the values of the namedtuple (i.e. the values of 'name', 'html', and 'alist'). Thanks for your help.
Paul
I just ended up using QTreeWidget and setting the tree item data like this:
item = QtGui.QTreeWidgetItem()
item.mydata = my_namedtuple
I found the answer here: QtGui QTreeWidgetItem setData holding a float number
I didn't know you could just dynamically make an attribute for the tree item.

Categories

Resources