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

Maurizio Berti maurizio.berti at gmail.com
Mon Aug 20 19:29:24 BST 2018


In my opinion, if I may, it seems a bit too complex and elaborate than it
could be, and seems to be even more prone to error; for instance, it's not
clear why you init a new QStyleOptionViewItem, while you can just
initStyleOption the one from the paint argument, and the use of
PaintContext is missing important state cases (focus/active, enabled,
hover, etc). But, if it works for you, that's fine :-)

About the size, since you are not using character distinction
(bold/italic), you could use QFontMetrics (that will not take kerning into
account, obviously) with its width or boundingRect methods.
If you are interested in width only, I suppose QTextDocument's textWidth
should be fine also, but I actually never used for this kind of situations.

If alignment is an issue, you should be able to fix those data with
QStyleOption's displayAlignment, along with QWidget's layoutDirection and
QLocale settings, but I suppose you already know that.

Maurizio

Il giorno lun 20 ago 2018 alle ore 18:58 Maziar Parsijani <
maziar.parsijani at gmail.com> ha scritto:

> Here I think Maurizio code will functioning like this :
>
> from PyQt5 import QtCore, QtGui, QtWidgets
> import random
> import html
>
> 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(html.escape, substrings)))
>         else:
>             res = html.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_())
>
> But the main problem of this method as you said is changing in font size and aligning because my data's are Arabic and English.
>
>
> On Mon, Aug 20, 2018 at 7:15 PM, Maurizio Berti <maurizio.berti at gmail.com>
> wrote:
>
>> I'm not an expert on regular expressions, maybe there's a better and
>> faster approach than the one I used in the example.
>> I initially tried using re.sub, but works correctly only if you want to
>> match case, as the substitution string is fixed.
>> Anyway, it seems fast enough to me, as the viewport update() takes care
>> of repainting only visible elements.
>>
>> Another (derivate) approach could be subclassing the model too (the
>> data() method), adding a custom user role for the html filtered text that
>> can be read by the delegate, maybe with some sort of caching in the model
>> to speed up reading. It may be slightly faster and more "elegant" (the
>> model takes care of the string match, not the delegate), and could be
>> useful for further implementation.
>>
>> I forgot to say: my example obviously doesn't consider spacings, grid
>> lines pen width and icon decoration.
>>
>> Maurizio
>>
>> Il giorno lun 20 ago 2018 alle ore 16:01 Maziar Parsijani <
>> maziar.parsijani at gmail.com> ha scritto:
>>
>>> Really thanks to Maurizio
>>> I will try this.I agree with you and was thinking if someone had a
>>> solution with re library and regix could be better for me .
>>>
>>>
>>> On Mon, Aug 20, 2018 at 6:09 PM, Maurizio Berti <
>>> maurizio.berti at gmail.com> wrote:
>>>
>>>> I know you already found a solution, but I decided to give it a try
>>>> anyway, just out of curiosity.
>>>> I didn't like the idea of using another QWidget, which might involve
>>>> QStyle issues, then I found out that it's possible to use QTextDocument.
>>>> This is a simple implementation that just matches the text in all items
>>>> (no actual search in the model).
>>>>
>>>>
>>>> class MatchDelegate(QtWidgets.QStyledItemDelegate):
>>>>     regex = re.compile('')
>>>>     fakeIndex = QtCore.QModelIndex()
>>>>
>>>>     def paint(self, qp, option, index):
>>>>         self.initStyleOption(option, index)
>>>>         td = QtGui.QTextDocument()
>>>>         if self.regex.pattern:
>>>>             splitted = self.regex.split(option.text)
>>>>             matches = iter(self.regex.findall(option.text))
>>>>             text = ''
>>>>             for words in splitted[:-1]:
>>>>                 text += words + '<b>{}</b>'.format(matches.next())
>>>>             text += splitted[-1]
>>>>         else:
>>>>             text = option.text
>>>>         td.setHtml(text)
>>>>         option.text = ''
>>>>         # Using an invalid index avoids painting of the text
>>>>         QtWidgets.QStyledItemDelegate.paint(self, qp, option,
>>>> self.fakeIndex)
>>>>         qp.save()
>>>>         qp.translate(option.rect.topLeft())
>>>>         td.drawContents(qp, QtCore.QRectF(0, 0, option.rect.width(),
>>>> option.rect.height()))
>>>>         qp.restore()
>>>>
>>>>
>>>> class Widget(QtWidgets.QWidget):
>>>>     def __init__(self):
>>>>         [...]
>>>>         self.delegate = MatchDelegate()
>>>>         self.table.setItemDelegate(self.delegate)
>>>>         self.search = QtWidgets.QLineEdit()
>>>>         self.search.textChanged.connect(self.findText)
>>>>         [...]
>>>>
>>>>     def findText(self, text):
>>>>         self.delegate.regex = re.compile(text, re.I)
>>>>         self.table.viewport().update()
>>>>
>>>>
>>>> Of course it doesn't take the text size change into account whenever
>>>> bold characters are in place, so the sizeHint is not perfect. Also, it
>>>> might be possible to implement QFontMetrics' elidedText, again with some
>>>> small issues about font size changes.
>>>>
>>>> Maurizio
>>>>
>>>>
>>>> Il giorno lun 20 ago 2018 alle ore 11:31 Maziar Parsijani <
>>>> maziar.parsijani at gmail.com> ha scritto:
>>>>
>>>>> Hi Denis Rouzaud
>>>>>
>>>>> This question was for 19days ago.But you are correct the best method
>>>>> is html delegate and I did it but I couldn't fix that with my table and the
>>>>> table was changed but it worked nice.
>>>>>
>>>>> On Mon, Aug 20, 2018 at 12:28 PM, Denis Rouzaud <
>>>>> denis.rouzaud at gmail.com> wrote:
>>>>>
>>>>>> This is not easily done.
>>>>>> You'd have to create a custom delegate using a QLabbel and use html
>>>>>> in there.
>>>>>>
>>>>>> I have been creating a search tool for tables and ending up
>>>>>> highlighting the whole cell.
>>>>>> The effort and the risk of bad results is just not worth the effort
>>>>>> IMHO.
>>>>>>
>>>>>> Denis
>>>>>>
>>>>>> Le mar. 31 juil. 2018 à 23:05, Maziar Parsijani <
>>>>>> maziar.parsijani at gmail.com> a écrit :
>>>>>>
>>>>>>> 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*.
>>>>>>> _______________________________________________
>>>>>>> PyQt mailing list    PyQt at riverbankcomputing.com
>>>>>>> https://www.riverbankcomputing.com/mailman/listinfo/pyqt
>>>>>>
>>>>>> --
>>>>>>
>>>>>> Denis Rouzaud
>>>>>> denis at opengis.ch  <denis at opengis.ch>
>>>>>> +41 76 370 21 22
>>>>>>
>>>>>>
>>>>>>
>>>>> _______________________________________________
>>>>> PyQt mailing list    PyQt at riverbankcomputing.com
>>>>> https://www.riverbankcomputing.com/mailman/listinfo/pyqt
>>>>
>>>>
>>>>
>>>> --
>>>> È difficile avere una convinzione precisa quando si parla delle ragioni
>>>> del cuore. - "Sostiene Pereira", Antonio Tabucchi
>>>> http://www.jidesk.net
>>>>
>>>
>>>
>>
>> --
>> È difficile avere una convinzione precisa quando si parla delle ragioni
>> del cuore. - "Sostiene Pereira", Antonio Tabucchi
>> http://www.jidesk.net
>>
>
>

-- 
È difficile avere una convinzione precisa quando si parla delle ragioni del
cuore. - "Sostiene Pereira", Antonio Tabucchi
http://www.jidesk.net
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20180820/78baacd8/attachment-0001.html>


More information about the PyQt mailing list