Search code examples
pythonpython-3.xpyqtpyqt5qtablewidget

Highlight search results in qtablewidget(select and highlight that text or character not all of the row or column)


I use method1 to find some text in qtablewidget rows. method1 :

def FindItem(self):
    items = self.SuraBRS.findItems(
        self.SearchTbox.text(), QtCore.Qt.MatchContains)
    if items:
        results = '\n'.join(
            'row %d column %d' % (item.row() + 1, item.column() + 1)
            for item in items)
    else:
        results = 'Found Nothing'
    print(results)

Now I want to know how to highlight results or change their color.I want to select and highlight that text or character not all of the row or column.


Solution

  • To be able to change only a part of the text you must use an HTMLdelegate that I built in this other answer, but it must be modified to avoid changing the html that can be the information and not the text that is desired:

    from PyQt5 import QtCore, QtGui, QtWidgets
    
    import random
    
    try:
        from html import escape
    except ImportError:
        from cgi import escape
    
    words = [
        "Hello",
        "world",
        "Stack",
        "Overflow",
        "Hello world",
        """<font color="red">Hello world</font>""",
    ]
    
    
    class HTMLDelegate(QtWidgets.QStyledItemDelegate):
        def __init__(self, parent=None):
            super(HTMLDelegate, self).__init__(parent)
            self.doc = QtGui.QTextDocument(self)
    
        def paint(self, painter, option, index):
            substring = index.data(QtCore.Qt.UserRole)
            painter.save()
            options = QtWidgets.QStyleOptionViewItem(option)
            self.initStyleOption(options, index)
            res = ""
            color = QtGui.QColor("orange")
            if substring:
                substrings = options.text.split(substring)
                res = """<font color="{}">{}</font>""".format(
                    color.name(QtGui.QColor.HexRgb), substring
                ).join(list(map(escape, substrings)))
            else:
                res = escape(options.text)
            self.doc.setHtml(res)
    
            options.text = ""
            style = (
                QtWidgets.QApplication.style()
                if options.widget is None
                else options.widget.style()
            )
            style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, options, painter)
    
            ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
            if option.state & QtWidgets.QStyle.State_Selected:
                ctx.palette.setColor(
                    QtGui.QPalette.Text,
                    option.palette.color(
                        QtGui.QPalette.Active, QtGui.QPalette.HighlightedText
                    ),
                )
            else:
                ctx.palette.setColor(
                    QtGui.QPalette.Text,
                    option.palette.color(QtGui.QPalette.Active, QtGui.QPalette.Text),
                )
    
            textRect = style.subElementRect(QtWidgets.QStyle.SE_ItemViewItemText, options)
    
            if index.column() != 0:
                textRect.adjust(5, 0, 0, 0)
    
            thefuckyourshitup_constant = 4
            margin = (option.rect.height() - options.fontMetrics.height()) // 2
            margin = margin - thefuckyourshitup_constant
            textRect.setTop(textRect.top() + margin)
    
            painter.translate(textRect.topLeft())
            painter.setClipRect(textRect.translated(-textRect.topLeft()))
            self.doc.documentLayout().draw(painter, ctx)
    
            painter.restore()
    
        def sizeHint(self, option, index):
            return QSize(self.doc.idealWidth(), self.doc.size().height())
    
    
    class Widget(QtWidgets.QWidget):
        def __init__(self, parent=None):
            super(Widget, self).__init__(parent)
            hlay = QtWidgets.QHBoxLayout()
            lay = QtWidgets.QVBoxLayout(self)
    
            self.le = QtWidgets.QLineEdit()
            self.button = QtWidgets.QPushButton("filter")
            self.table = QtWidgets.QTableWidget(5, 5)
            hlay.addWidget(self.le)
            hlay.addWidget(self.button)
            lay.addLayout(hlay)
            lay.addWidget(self.table)
            self.button.clicked.connect(self.find_items)
            self.table.setItemDelegate(HTMLDelegate(self.table))
    
            for i in range(self.table.rowCount()):
                for j in range(self.table.columnCount()):
                    it = QtWidgets.QTableWidgetItem(random.choice(words))
                    self.table.setItem(i, j, it)
    
        def find_items(self):
            text = self.le.text()
            # clear
            allitems = self.table.findItems("", QtCore.Qt.MatchContains)
            selected_items = self.table.findItems(self.le.text(), QtCore.Qt.MatchContains)
            for item in allitems:
                item.setData(QtCore.Qt.UserRole, text if item in selected_items else None)
    
    
    if __name__ == "__main__":
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
        w = Widget()
        w.show()
        sys.exit(app.exec_())
    

    enter image description here

    PyQt4:

    from PyQt4 import QtCore, QtGui
    import random
    
    try:
        from html import escape
    except ImportError:
        from cgi import escape
    
    words = [
        "Hello",
        "world",
        "Stack",
        "Overflow",
        "Hello world",
        """<font color="red">Hello world</font>""",
    ]
    
    
    class HTMLDelegate(QtGui.QStyledItemDelegate):
        def __init__(self, parent=None):
            super(HTMLDelegate, self).__init__(parent)
            self.doc = QtGui.QTextDocument(self)
    
        def paint(self, painter, option, index):
            substring = index.data(QtCore.Qt.UserRole)
            if hasattr(substring, "toPyObject"):
                substring = str(substring.toPyObject())
            painter.save()
            options = QtGui.QStyleOptionViewItem(option)
            self.initStyleOption(options, index)
            text = index.data()
            if hasattr(text, "toPyObject"):
                text = str(text.toPyObject())
            res = ""
            color = QtGui.QColor("orange")
            if substring:
                substrings = text.split(substring)
                res = """<font color="{}">{}</font>""".format(color.name(), substring).join(
                    list(map(escape, substrings))
                )
            else:
                res = escape(text)
            self.doc.setHtml(res)
    
            options.text = ""
            style = (
                QtGui.QApplication.style()
                # if options.widget is None
                # else options.widget.style()
            )
            style.drawControl(QtGui.QStyle.CE_ItemViewItem, options, painter)
    
            ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
            if option.state & QtGui.QStyle.State_Selected:
                ctx.palette.setColor(
                    QtGui.QPalette.Text,
                    option.palette.color(
                        QtGui.QPalette.Active, QtGui.QPalette.HighlightedText
                    ),
                )
            else:
                ctx.palette.setColor(
                    QtGui.QPalette.Text,
                    option.palette.color(QtGui.QPalette.Active, QtGui.QPalette.Text),
                )
    
            textRect = (
                options.rect
            )  # style.subElementRect(QtGui.QStyle.SE_ItemViewItemText, options)
    
            if index.column() != 0:
                textRect.adjust(5, 0, 0, 0)
    
            thefuckyourshitup_constant = 4
            margin = (option.rect.height() - options.fontMetrics.height()) // 2
            margin = margin - thefuckyourshitup_constant
            textRect.setTop(textRect.top() + margin)
    
            painter.translate(textRect.topLeft())
            painter.setClipRect(textRect.translated(-textRect.topLeft()))
            self.doc.documentLayout().draw(painter, ctx)
    
            painter.restore()
    
        def sizeHint(self, option, index):
            return QSize(self.doc.idealWidth(), self.doc.size().height())
    
    
    class Widget(QtGui.QWidget):
        def __init__(self, parent=None):
            super(Widget, self).__init__(parent)
            hlay = QtGui.QHBoxLayout()
            lay = QtGui.QVBoxLayout(self)
    
            self.le = QtGui.QLineEdit()
            self.button = QtGui.QPushButton("filter")
            self.table = QtGui.QTableWidget(5, 5)
            hlay.addWidget(self.le)
            hlay.addWidget(self.button)
            lay.addLayout(hlay)
            lay.addWidget(self.table)
            self.button.clicked.connect(self.find_items)
            self.table.setItemDelegate(HTMLDelegate(self.table))
    
            for i in range(self.table.rowCount()):
                for j in range(self.table.columnCount()):
                    it = QtGui.QTableWidgetItem(random.choice(words))
                    self.table.setItem(i, j, it)
    
        def find_items(self):
            text = self.le.text()
            # clear
            allitems = self.table.findItems("", QtCore.Qt.MatchContains)
            selected_items = self.table.findItems(self.le.text(), QtCore.Qt.MatchContains)
            for item in allitems:
                item.setData(QtCore.Qt.UserRole, text if item in selected_items else None)
    
    
    if __name__ == "__main__":
        import sys
    
        app = QtGui.QApplication(sys.argv)
        w = Widget()
        w.show()
        sys.exit(app.exec_())