def __init__(self, parent): QWidget.__init__(self, parent) self.layout = QTextLayout() self.layout.setFont(self.font()) self.layout.setCacheEnabled(True) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.last_layout_rect = None
class Message(QWidget): def __init__(self, parent): QWidget.__init__(self, parent) self.layout = QTextLayout() self.layout.setFont(self.font()) self.layout.setCacheEnabled(True) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.last_layout_rect = None def setText(self, text): self.layout.setText(text) self.last_layout_rect = None self.update() def sizeHint(self): return QSize(10, 10) def do_layout(self): ly = self.layout ly.beginLayout() w = self.width() - 5 height = 0 leading = self.fontMetrics().leading() while True: line = ly.createLine() if not line.isValid(): break line.setLineWidth(w) height += leading line.setPosition(QPointF(5, height)) height += line.height() ly.endLayout() def paintEvent(self, ev): if self.last_layout_rect != self.rect(): self.do_layout() p = QPainter(self) br = self.layout.boundingRect() y = 0 if br.height() < self.height(): y = (self.height() - br.height()) / 2 self.layout.draw(p, QPointF(0, y))
def _refreshTextFormat(self): if not self._webView: return textFormat = [] # typedef QList<QTextLayout::FormatRange> if self._webView.url().isEmpty(): hostName = QUrl(self.text()).host() else: hostName = self._webView.url().host() if hostName: hostPos = self.text().find(hostName) if hostPos > 0: format_ = QTextCharFormat() palette = self.palette() color = Colors.mid(palette.color(QPalette.Base), palette.color(QPalette.Text), 1, 1) format_.setForeground(color) schemePart = QTextLayout.FormatRange() schemePart.start = 0 schemePart.length = hostPos schemePart.format = format_ hostPart = QTextLayout.FormatRange() hostPart.start = hostPos hostPart.length = len(hostName) remainingPart = QTextLayout.FormatRange() remainingPart.start = hostPos + len(hostName) remainingPart.length = len(self.text()) - remainingPart.start remainingPart.format = format_ textFormat.append(schemePart) textFormat.append(hostPart) textFormat.append(remainingPart) self.setTextFormat(textFormat)
def parse_text_formatting(text): pos = 0 tokens = [] for m in re.finditer(r'</?([a-zA-Z1-6]+)/?>', text): q = text[pos:m.start()] if q: tokens.append((False, q)) tokens.append((True, (m.group(1).lower(), '/' in m.group()[:2]))) pos = m.end() if tokens: if text[pos:]: tokens.append((False, text[pos:])) else: tokens = [(False, text)] ranges, open_ranges, text = [], [], [] offset = 0 for is_tag, tok in tokens: if is_tag: tag, closing = tok if closing: if open_ranges: r = open_ranges.pop() r[-1] = offset - r[-2] if r[-1] > 0: ranges.append(r) else: if tag in {'b', 'strong', 'i', 'em'}: open_ranges.append([tag, offset, -1]) else: offset += len(tok) text.append(tok) text = ''.join(text) formats = [] for tag, start, length in chain(ranges, open_ranges): fmt = QTextCharFormat() if tag in {'b', 'strong'}: fmt.setFontWeight(QFont.Bold) elif tag in {'i', 'em'}: fmt.setFontItalic(True) else: continue if length == -1: length = len(text) - start if length > 0: r = QTextLayout.FormatRange() r.format = fmt r.start, r.length = start, length formats.append(r) return text, formats
def do_tag(block, words, lo, hi, pos, fmts): for word in words[lo:hi]: if word == '\n': if fmts: block.layout().setAdditionalFormats(fmts) pos, block, fmts = 0, block.next(), [] continue if tag in {'replace', 'insert', 'delete'}: fmt = getattr(self.left, '%s_format' % ('replacereplace' if tag == 'replace' else tag)) f = QTextLayout.FormatRange() f.start, f.length, f.format = pos, len(word), fmt fmts.append(f) pos += len(word) return block, pos, fmts
def parse_single_block(self, block): ud, is_new_ud = self.get_user_data(block) orig_state = ud.state pblock = block.previous() if pblock.isValid(): start_state = pblock.userData() if start_state is None: start_state = self.user_data_factory().state else: start_state = start_state.state.copy() else: start_state = self.user_data_factory().state ud.clear(state=start_state, doc_name=self.doc_name) # Ensure no stale user data lingers formats = [] for i, num, fmt in run_loop(ud, self.state_map, self.formats, unicode_type(block.text())): if fmt is not None: r = QTextLayout.FormatRange() r.start, r.length, r.format = i, num, fmt formats.append(r) force_next_highlight = is_new_ud or ud.state != orig_state return formats, force_next_highlight
def __init__(self, text='', width=0, font=None, img=None, max_height=100, align=Qt.AlignCenter): self.layouts = [] self._position = Point(0, 0) self.leading = self.line_spacing = 0 if font is not None: fm = QFontMetrics(font, img) self.leading = fm.leading() self.line_spacing = fm.lineSpacing() for text in text.split('<br>') if text else (): text, formats = parse_text_formatting(sanitize(text)) l = QTextLayout(unescape_formatting(text), font, img) l.setAdditionalFormats(formats) to = QTextOption(align) to.setWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere) l.setTextOption(to) l.beginLayout() height = 0 while height + 3*self.leading < max_height: line = l.createLine() if not line.isValid(): break line.setLineWidth(width) height += self.leading line.setPosition(QPointF(0, height)) height += line.height() max_height -= height l.endLayout() if self.layouts: self.layouts.append(self.leading) else: self._position = Point(l.position().x(), l.position().y()) self.layouts.append(l) if self.layouts: self.layouts.append(self.leading)
def __init__(self, text='', width=0, font=None, img=None, max_height=100, align=Qt.AlignCenter): self.layouts = [] self._position = Point(0, 0) self.leading = self.line_spacing = 0 if font is not None: fm = QFontMetrics(font, img) self.leading = fm.leading() self.line_spacing = fm.lineSpacing() for text in text.split('<br>') if text else (): text, formats = parse_text_formatting(sanitize(text)) l = QTextLayout(unescape_formatting(text), font, img) l.setAdditionalFormats(formats) to = QTextOption(align) to.setWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere) l.setTextOption(to) l.beginLayout() height = 0 while height + 3 * self.leading < max_height: line = l.createLine() if not line.isValid(): break line.setLineWidth(width) height += self.leading line.setPosition(QPointF(0, height)) height += line.height() max_height -= height l.endLayout() if self.layouts: self.layouts.append(self.leading) else: self._position = Point(l.position().x(), l.position().y()) self.layouts.append(l) if self.layouts: self.layouts.append(self.leading)
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()))