def draw_match(self, painter, flags, before, text, after, rect, before_width, match_width, after_width, ellipsis_width, emphasis_font, normal_font): extra_width = int(rect.width() - match_width) if before_width < after_width: left_width = min(extra_width // 2, before_width) right_width = extra_width - left_width else: right_width = min(extra_width // 2, after_width) left_width = min(before_width, extra_width - right_width) x = rect.left() nfm = QFontMetrics(normal_font) if before_width and left_width: r = rect.adjusted(0, 0, 0, 0) r.setRight(x + left_width) painter.setFont(normal_font) ebefore = nfm.elidedText(before, Qt.ElideLeft, left_width) if self.add_ellipsis and ebefore == before: ebefore = '…' + before[1:] r.setLeft(x) x += painter.drawText(r, flags, ebefore).width() painter.setFont(emphasis_font) r = rect.adjusted(0, 0, 0, 0) r.setLeft(x) painter.drawText(r, flags, text).width() x += match_width if after_width and right_width: painter.setFont(normal_font) r = rect.adjusted(0, 0, 0, 0) r.setLeft(x) eafter = nfm.elidedText(after, Qt.ElideRight, right_width) if self.add_ellipsis and eafter == after: eafter = after[:-1] + '…' painter.setFont(normal_font) painter.drawText(r, flags, eafter)
def paint(self, painter, option, index): QStyledItemDelegate.paint(self, painter, option, index) result = index.data(Qt.ItemDataRole.UserRole) is_hidden, result_before, result_text, result_after, show_leading_dot = self.result_data( result) if result_text is None: return painter.save() try: p = option.palette c = p.HighlightedText if option.state & QStyle.StateFlag.State_Selected else p.Text group = (p.Active if option.state & QStyle.StateFlag.State_Active else p.Inactive) c = p.color(group, c) painter.setPen(c) font = option.font if self.emphasize_text: emphasis_font = QFont(font) emphasis_font.setBold(True) else: emphasis_font = font flags = Qt.AlignmentFlag.AlignTop | Qt.TextFlag.TextSingleLine | Qt.TextFlag.TextIncludeTrailingSpaces rect = option.rect.adjusted( option.decorationSize.width() + 4 if is_hidden else 0, 0, 0, 0) painter.setClipRect(rect) before = re.sub(r'\s+', ' ', result_before) if show_leading_dot: before = '•' + before before_width = 0 if before: before_width = painter.boundingRect(rect, flags, before).width() after = re.sub(r'\s+', ' ', result_after.rstrip()) after_width = 0 if after: after_width = painter.boundingRect(rect, flags, after).width() ellipsis_width = painter.boundingRect(rect, flags, '...').width() painter.setFont(emphasis_font) text = re.sub(r'\s+', ' ', result_text) match_width = painter.boundingRect(rect, flags, text).width() if match_width >= rect.width() - 3 * ellipsis_width: efm = QFontMetrics(emphasis_font) if show_leading_dot: text = '•' + text text = efm.elidedText(text, Qt.TextElideMode.ElideRight, rect.width()) painter.drawText(rect, flags, text) else: self.draw_match(painter, flags, before, text, after, rect, before_width, match_width, after_width, ellipsis_width, emphasis_font, font) except Exception: import traceback traceback.print_exc() painter.restore()
def viewItemDrawText(self, painter, option, rect, text, color, searchText=''): # noqa C901 ''' @note: most of codes taken from QCommonStylePrivate::viewItemDrawText added highlighting and simplified for single-line textlayouts @param painter QPainter @param option QStyleOptionViewItem @param rect QRect @param text QString @param color QColor @param searchText QString @return: int ''' if not text: return 0 fontMetrics = QFontMetrics(painter.font()) elidedText = fontMetrics.elidedText(text, option.textElideMode, rect.width()) textOption = QTextOption() textOption.setWrapMode(QTextOption.NoWrap) textOption.setAlignment(QStyle.visualAlignment(textOption.textDirection(), option.displayAlignment)) textLayout = QTextLayout() textLayout.setFont(painter.font()) textLayout.setText(elidedText) textLayout.setTextOption(textOption) if searchText: # QList<int> delimiters = [] searchStrings = [ item.strip() for item in searchText.split(' ') ] searchStrings = [ item for item in searchStrings if item ] # Look for longer parts first searchStrings.sort(key=lambda x: len(x), reverse=True) text0 = text.lower() for string in searchStrings: string0 = string.lower() delimiter = text0.find(string0) while delimiter != -1: start = delimiter end = delimiter + len(string) alreadyContains = False for idx in range(0, len(delimiters), 2): dStart = delimiters[idx] dEnd = delimiters[idx+1] if dStart <= start and dEnd >= end: alreadyContains = True break if not alreadyContains: delimiters.append(start) delimiters.append(end) delimiter = text0.find(string0, end) # We need to sort delimiters to properly paint all parts that user typed delimiters.sort() # If we don't find any match, just paint it withoutany highlight if delimiters and len(delimiters) % 2 == 0: highlightParts = [] # QList<QTextLayout::FormatRange> while delimiters: highlightedPart = QTextLayout.FormatRange() start = delimiters.pop(0) end = delimiters.pop(0) highlightedPart.start = start highlightedPart.length = end - start highlightedPart.format.setFontWeight(QFont.Bold) highlightedPart.format.setUnderlineStyle(QTextCharFormat.SingleUnderline) highlightParts.append(highlightedPart) textLayout.setAdditionalFormats(highlightParts) # do layout self._s_viewItemDrawText(textLayout, rect.width()) if textLayout.lineCount() <= 0: return 0 textLine = textLayout.lineAt(0) # if elidedText after highlighting is longer than available width then # re-elide it and redo layout diff = textLine.naturalTextWidth() - rect.width() if diff > 0: # TODO: ? elidedText = fontMetrics.elidedText(elidedText, option.textElideMode, rect.width() - diff) textLayout.setText(elidedText) # redo layout self._s_viewItemDrawText(textLayout, rect.width()) if textLayout.lineCount() <= 0: return 0 textLine = textLayout.lineAt(0) # draw line painter.setPen(color) # qreal width = max(rect.width(), textLayout.lineAt(0).width()) # QRect layoutRect = QStyle.alignedRect(option.direction, option.displayAlignment, QSize(int(width), int(textLine.height())), rect) # QPointF position = layoutRect.topLeft() textLine.draw(painter, position) return int(min(rect.width(), textLayout.lineAt(0).naturalTextWidth()))