def paintEvent(self, event): QWidget.paintEvent(self, event) pmap = self._pixmap if pmap.isNull(): return w, h = pmap.width(), pmap.height() ow, oh = w, h cw, ch = self.rect().width(), self.rect().height() scaled, nw, nh = fit_image(w, h, cw, ch) if scaled: pmap = pmap.scaled(int(nw * pmap.devicePixelRatio()), int(nh * pmap.devicePixelRatio()), Qt.AspectRatioMode.IgnoreAspectRatio, Qt.TransformationMode.SmoothTransformation) w, h = int(pmap.width() / pmap.devicePixelRatio()), int( pmap.height() / pmap.devicePixelRatio()) x = int(abs(cw - w) / 2) y = int(abs(ch - h) / 2) target = QRect(x, y, w, h) p = QPainter(self) p.setRenderHints(QPainter.RenderHint.Antialiasing | QPainter.RenderHint.SmoothPixmapTransform) p.drawPixmap(target, pmap) if self.draw_border: pen = QPen() pen.setWidth(self.BORDER_WIDTH) p.setPen(pen) p.drawRect(target) if self.show_size: draw_size(p, target, ow, oh) p.end()
def drawContents(self, painter): self.drawn_once = True painter.save() painter.setRenderHint(QPainter.RenderHint.TextAntialiasing, True) painter.setRenderHint(QPainter.RenderHint.Antialiasing, True) pw = self.LOGO_SIZE height = max(pw, self.total_height) width = self.width() # Draw frame y = (self.height() - height) // 2 bottom = y + height painter.fillRect(0, y, width, height, self.light_brush) painter.fillRect(0, y, width, self.title_height, self.dark_brush) painter.fillRect(0, y, pw, height, self.dark_brush) dy = (height - self.LOGO_SIZE) // 2 painter.drawPixmap(0, y + dy, self.pmap) # Draw number painter.setFont(self.num_font) num_width = painter.boundingRect( 0, 0, 0, 0, Qt.AlignmentFlag.AlignCenter | Qt.TextFlag.TextSingleLine, self.num_ch).width() + 12 num_x = width - num_width painter.setPen(QPen(QColor('#d6b865'))) painter.drawText( num_x, y, num_width, height, Qt.AlignmentFlag.AlignCenter | Qt.TextFlag.TextSingleLine, self.num_ch) # Draw title x = pw + 10 width -= num_width + 5 + x painter.setFont(self.title_font) painter.drawText( x, y, width, self.title_height, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter | Qt.TextFlag.TextSingleLine, "CALIBRE") # Draw starting up message y += self.title_height + 5 painter.setPen(QPen(self.dark_brush.color())) painter.setFont(self.body_font) br = painter.drawText( x, y, width, self.line_height, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter | Qt.TextFlag.TextSingleLine, _('Starting up, please wait...')) starting_up_bottom = br.bottom() # Draw footer m = self.message() if m and m.strip(): painter.setFont(self.footer_font) b = max(starting_up_bottom + 5, bottom - self.line_height) painter.drawText( x, b, width, self.line_height, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop | Qt.TextFlag.TextSingleLine, m) painter.restore()
def paintEvent(self, event): w = self.viewport().rect().width() painter = QPainter(self.viewport()) painter.setClipRect(event.rect()) painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform, True) floor = event.rect().bottom() ceiling = event.rect().top() fv = self.firstVisibleBlock().blockNumber() origin = self.contentOffset() doc = self.document() lines = [] for num, text in self.headers: top, bot = num, num + 3 if bot < fv: continue y_top = self.blockBoundingGeometry(doc.findBlockByNumber(top)).translated(origin).y() y_bot = self.blockBoundingGeometry(doc.findBlockByNumber(bot)).translated(origin).y() if max(y_top, y_bot) < ceiling: continue if min(y_top, y_bot) > floor: break painter.setFont(self.heading_font) br = painter.drawText(3, y_top, w, y_bot - y_top - 5, Qt.TextFlag.TextSingleLine, text) painter.setPen(QPen(self.palette().text(), 2)) painter.drawLine(0, br.bottom()+3, w, br.bottom()+3) for top, bot, kind in self.changes: if bot < fv: continue y_top = self.blockBoundingGeometry(doc.findBlockByNumber(top)).translated(origin).y() y_bot = self.blockBoundingGeometry(doc.findBlockByNumber(bot)).translated(origin).y() if max(y_top, y_bot) < ceiling: continue if min(y_top, y_bot) > floor: break if y_top != y_bot: painter.fillRect(0, y_top, w, y_bot - y_top, self.diff_backgrounds[kind]) lines.append((y_top, y_bot, kind)) if top in self.images: img, maxw = self.images[top][:2] if bot > top + 1 and not img.isNull(): y_top = self.blockBoundingGeometry(doc.findBlockByNumber(top+1)).translated(origin).y() + 3 y_bot -= 3 scaled, imgw, imgh = fit_image(int(img.width()/img.devicePixelRatio()), int(img.height()/img.devicePixelRatio()), w - 3, y_bot - y_top) painter.drawPixmap(QRect(3, y_top, imgw, imgh), img) painter.end() PlainTextEdit.paintEvent(self, event) painter = QPainter(self.viewport()) painter.setClipRect(event.rect()) for top, bottom, kind in sorted(lines, key=lambda t_b_k:{'replace':0}.get(t_b_k[2], 1)): painter.setPen(QPen(self.diff_foregrounds[kind], 1)) painter.drawLine(0, top, w, top) painter.drawLine(0, bottom - 1, w, bottom - 1)
def paint(self, painter, option, index): name = index.data(Qt.ItemDataRole.DisplayRole) sz = human_readable(index.data(Qt.ItemDataRole.UserRole)) pmap = index.data(Qt.ItemDataRole.UserRole + 1) irect = option.rect.adjusted(0, 5, 0, -5) irect.setRight(irect.left() + 70) if pmap is None: pmap = QPixmap( current_container().get_file_path_for_processing(name)) scaled, nwidth, nheight = fit_image(pmap.width(), pmap.height(), irect.width(), irect.height()) if scaled: pmap = pmap.scaled( nwidth, nheight, transformMode=Qt.TransformationMode.SmoothTransformation) index.model().setData(index, pmap, Qt.ItemDataRole.UserRole + 1) x, y = (irect.width() - pmap.width()) // 2, (irect.height() - pmap.height()) // 2 r = irect.adjusted(x, y, -x, -y) QStyledItemDelegate.paint(self, painter, option, empty_index) painter.drawPixmap(r, pmap) trect = irect.adjusted(irect.width() + 10, 0, 0, 0) trect.setRight(option.rect.right()) painter.save() if option.state & QStyle.StateFlag.State_Selected: painter.setPen( QPen(option.palette.color(QPalette.ColorRole.HighlightedText))) painter.drawText( trect, Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignLeft, name + '\n' + sz) painter.restore()
def paintEvent(self, event): canvas_size = self.rect() width = self.current_pixmap_size.width() extrax = canvas_size.width() - width if extrax < 0: extrax = 0 x = int(extrax//2) height = self.current_pixmap_size.height() extray = canvas_size.height() - height if extray < 0: extray = 0 y = int(extray//2) target = QRect(x, y, width, height) p = QPainter(self) p.setRenderHints(QPainter.RenderHint.Antialiasing | QPainter.RenderHint.SmoothPixmapTransform) try: dpr = self.devicePixelRatioF() except AttributeError: dpr = self.devicePixelRatio() spmap = self.pixmap.scaled(target.size() * dpr, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation) spmap.setDevicePixelRatio(dpr) p.drawPixmap(target, spmap) if gprefs['bd_overlay_cover_size']: sztgt = target.adjusted(0, 0, 0, -4) f = p.font() f.setBold(True) p.setFont(f) sz = '\u00a0%d x %d\u00a0'%(self.pixmap.width(), self.pixmap.height()) flags = Qt.AlignmentFlag.AlignBottom|Qt.AlignmentFlag.AlignRight|Qt.TextFlag.TextSingleLine szrect = p.boundingRect(sztgt, flags, sz) p.fillRect(szrect.adjusted(0, 0, 0, 4), QColor(0, 0, 0, 200)) p.setPen(QPen(QColor(255,255,255))) p.drawText(sztgt, flags, sz) p.end()
def __init__(self, font_loader, footer, page_style, logger, opts, ruby_tags, link_activated): Canvas.__init__(self, font_loader, footer, logger, opts, ruby_tags, link_activated, page_style.textwidth, page_style.footheight) if opts.visual_debug: self.setPen(QPen(Qt.GlobalColor.blue, 1, Qt.PenStyle.DashLine))
def __init__(self, rl): QGraphicsLineItem.__init__(self, 0, 0, rl.linelength, 0) ContentObject.__init__(self) self.setPen(QPen( COLOR(rl.linecolor, None), rl.linewidth, ))
def paint_non_printing(self, painter, option, charcode): text = self.np_pat.sub(r'\n\1', non_printing[charcode]) painter.drawText( option.rect, Qt.AlignmentFlag.AlignCenter | Qt.TextFlag.TextWordWrap | Qt.TextFlag.TextWrapAnywhere, text) painter.setPen(QPen(Qt.PenStyle.DashLine)) painter.drawRect(option.rect.adjusted(1, 1, -1, -1))
def do_paint(self, painter, option, index): text = str(index.data(Qt.ItemDataRole.DisplayRole) or '') font = QFont(option.font) font.setPointSize(QFontInfo(font).pointSize() * 1.5) font2 = QFont(font) font2.setFamily(text) system, has_latin = writing_system_for_font(font2) if has_latin: font = font2 r = option.rect if option.state & QStyle.StateFlag.State_Selected: painter.setPen(QPen(option.palette.highlightedText(), 0)) if (option.direction == Qt.LayoutDirection.RightToLeft): r.setRight(r.right() - 4) else: r.setLeft(r.left() + 4) painter.setFont(font) painter.drawText(r, Qt.AlignmentFlag.AlignVCenter|Qt.AlignmentFlag.AlignLeading|Qt.TextFlag.TextSingleLine, text) if (system != QFontDatabase.WritingSystem.Any): w = painter.fontMetrics().width(text + " ") painter.setFont(font2) sample = QFontDatabase().writingSystemSample(system) if (option.direction == Qt.LayoutDirection.RightToLeft): r.setRight(r.right() - w) else: r.setLeft(r.left() + w) painter.drawText(r, Qt.AlignmentFlag.AlignVCenter|Qt.AlignmentFlag.AlignLeading|Qt.TextFlag.TextSingleLine, sample)
def paintEvent(self, event): pmap = self.blank if self.pixmap is None or self.pixmap.isNull( ) else self.pixmap target = self.rect() scaled, width, height = fit_image(pmap.width(), pmap.height(), target.width(), target.height()) target.setRect(target.x(), target.y(), width, height) p = QPainter(self) p.setRenderHints(QPainter.RenderHint.Antialiasing | QPainter.RenderHint.SmoothPixmapTransform) p.drawPixmap(target, pmap) if self.pixmap is not None and not self.pixmap.isNull(): sztgt = target.adjusted(0, 0, 0, -4) f = p.font() f.setBold(True) p.setFont(f) sz = u'\u00a0%d x %d\u00a0' % (self.pixmap.width(), self.pixmap.height()) flags = int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignRight | Qt.TextFlag.TextSingleLine) szrect = p.boundingRect(sztgt, flags, sz) p.fillRect(szrect.adjusted(0, 0, 0, 4), QColor(0, 0, 0, 200)) p.setPen(QPen(QColor(255, 255, 255))) p.drawText(sztgt, flags, sz) p.end()
def __init__(self, parent, start, stop, refobj, slot): QGraphicsRectItem.__init__(self, start, 0, stop-start, parent.height, parent) self.refobj = refobj self.slot = slot self.brush = self.__class__.inactive_brush self.setPen(QPen(Qt.PenStyle.NoPen)) self.setCursor(Qt.CursorShape.PointingHandCursor) self.setAcceptHoverEvents(True)
def __init__(self, font_loader, logger, opts, width=0, height=0, parent=None, x=0, y=0): QGraphicsRectItem.__init__(self, x, y, width, height, parent) self.font_loader, self.logger, self.opts = font_loader, logger, opts self.current_y, self.max_y, self.max_x = 0, height, width self.is_full = False pen = QPen() pen.setStyle(Qt.PenStyle.NoPen) self.setPen(pen) if not hasattr(self, 'children'): self.children = self.childItems
def copy(self): ans = GraphicsState() ans.fill = QBrush(self.fill) ans.stroke = QPen(self.stroke) ans.opacity = self.opacity ans.transform = self.transform * QTransform() ans.brush_origin = QPointF(self.brush_origin) ans.clip_updated = self.clip_updated ans.do_fill, ans.do_stroke = self.do_fill, self.do_stroke return ans
def __init__(self): self.fill = QBrush(Qt.GlobalColor.white) self.stroke = QPen() self.opacity = 1.0 self.transform = QTransform() self.brush_origin = QPointF() self.clip_updated = False self.do_fill = False self.do_stroke = True self.qt_pattern_cache = {}
def draw_size(p, rect, w, h): rect = rect.adjusted(0, 0, 0, -4) f = p.font() f.setBold(True) p.setFont(f) sz = '\u00a0%d x %d\u00a0' % (w, h) flags = Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignRight | Qt.TextFlag.TextSingleLine szrect = p.boundingRect(rect, flags, sz) p.fillRect(szrect.adjusted(0, 0, 0, 4), QColor(0, 0, 0, 200)) p.setPen(QPen(QColor(255, 255, 255))) p.drawText(rect, flags, sz)
def paint(self, painter, option, widget): x, y = 0, 0 + self.height - self.descent if self.vdebug: painter.save() painter.setPen(QPen(Qt.GlobalColor.yellow, 1, Qt.PenStyle.DotLine)) painter.drawRect(self.boundingRect()) painter.restore() painter.save() painter.setPen(QPen(Qt.PenStyle.NoPen)) for c in self.children(): painter.setBrush(c.brush) painter.drawRect(c.boundingRect()) painter.restore() painter.save() for tok in self.tokens: if isinstance(tok, numbers.Number): x += tok elif isinstance(tok, Word): painter.setFont(tok.font) if tok.highlight: painter.save() painter.setPen(QPen(Qt.PenStyle.NoPen)) painter.setBrush(QBrush(Qt.GlobalColor.yellow)) painter.drawRect(int(x), 0, tok.width, tok.height) painter.restore() painter.setPen(QPen(tok.text_color)) if tok.valign is None: painter.drawText(int(x), int(y), tok.string) elif tok.valign == 'Sub': painter.drawText(int(x + 1), int(y + self.descent / 1.5), tok.string) elif tok.valign == 'Sup': painter.drawText(int(x + 1), int(y - 2. * self.descent), tok.string) x += tok.width else: painter.drawPixmap(int(x), 0, tok.pixmap()) x += tok.width painter.restore()
def failed_img(self): if self._failed_img is None: try: dpr = self.devicePixelRatioF() except AttributeError: dpr = self.devicePixelRatio() i = QImage(200, 150, QImage.Format.Format_ARGB32) i.setDevicePixelRatio(dpr) i.fill(Qt.GlobalColor.white) p = QPainter(i) r = i.rect().adjusted(10, 10, -10, -10) n = QPen(Qt.PenStyle.DashLine) n.setColor(Qt.GlobalColor.black) p.setPen(n) p.drawRect(r) p.setPen(Qt.GlobalColor.black) f = self.font() f.setPixelSize(20) p.setFont(f) p.drawText(r.adjusted(10, 0, -10, 0), Qt.AlignmentFlag.AlignCenter | Qt.TextFlag.TextWordWrap, _('Image could not be rendered')) p.end() self._failed_img = QPixmap.fromImage(i) return self._failed_img
def __call__(self, painter, rect, color_theme, title_block, subtitle_block, footer_block): painter.fillRect(rect, self.color1) top = title_block.position.y + 2 extra_spacing = subtitle_block.line_spacing // 2 if subtitle_block.line_spacing else title_block.line_spacing // 3 height = title_block.height + subtitle_block.height + extra_spacing + title_block.leading right = rect.right() - self.hmargin width = right - self.hmargin # Draw main banner p = main = QPainterPath(QPointF(self.hmargin, top)) draw_curved_line(p, rect.width() - 2 * self.hmargin, 0, 0.1, -0.1, 0.9, -0.1) deltax = self.GRADE * height p.lineTo(right + deltax, top + height) right_corner = p.currentPosition() draw_curved_line(p, - width - 2 * deltax, 0, 0.1, 0.05, 0.9, 0.05) left_corner = p.currentPosition() p.closeSubpath() # Draw fold rectangles rwidth = self.fold_width yfrac = 0.1 width23 = int(0.67 * rwidth) rtop = top + height * yfrac def draw_fold(x, m=1, corner=left_corner): ans = p = QPainterPath(QPointF(x, rtop)) draw_curved_line(p, rwidth*m, 0, 0.1, 0.1*m, 0.5, -0.2*m) fold_upper = p.currentPosition() p.lineTo(p.currentPosition() + QPointF(-deltax*m, height)) fold_corner = p.currentPosition() draw_curved_line(p, -rwidth*m, 0, 0.2, -0.1*m, 0.8, -0.1*m) draw_curved_line(p, deltax*m, -height, 0.2, 0.1*m, 0.8, 0.1*m) p = inner_fold = QPainterPath(corner) dp = fold_corner - p.currentPosition() draw_curved_line(p, dp.x(), dp.y(), 0.5, 0.3*m, 1, 0*m) p.lineTo(fold_upper), p.closeSubpath() return ans, inner_fold left_fold, left_inner = draw_fold(self.hmargin - width23) right_fold, right_inner = draw_fold(right + width23, m=-1, corner=right_corner) painter.save() painter.setRenderHint(QPainter.RenderHint.Antialiasing) pen = QPen(self.ccolor2) pen.setWidth(3) pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin) painter.setPen(pen) for r in (left_fold, right_fold): painter.fillPath(r, QBrush(self.color2)) painter.drawPath(r) for r in (left_inner, right_inner): painter.fillPath(r, QBrush(self.color2.darker())) painter.drawPath(r) painter.fillPath(main, QBrush(self.color2)) painter.drawPath(main) painter.restore() return self.ccolor2, self.ccolor2, self.ccolor1
def __init__(self, font_loader, chapter, odd, logger, opts, ruby_tags, link_activated): self.logger, self.opts = logger, opts page_style = chapter.style sidemargin = page_style.oddsidemargin if odd else page_style.evensidemargin width = 2 * sidemargin + page_style.textwidth self.content_x = 0 + sidemargin self.text_width = page_style.textwidth self.header_y = page_style.topmargin self.text_y = self.header_y + page_style.headheight + page_style.headsep self.text_height = page_style.textheight self.footer_y = self.text_y + self.text_height + ( page_style.footspace - page_style.footheight) _Canvas.__init__(self, font_loader, logger, opts, width=width, height=self.footer_y + page_style.footheight) if opts.visual_debug: self.setPen(QPen(Qt.GlobalColor.red, 1, Qt.PenStyle.SolidLine)) header = footer = None if page_style.headheight > 0: try: header = chapter.oddheader if odd else chapter.evenheader except AttributeError: pass if page_style.footheight > 0: try: footer = chapter.oddfooter if odd else chapter.evenfooter except AttributeError: pass if header: header = Header(font_loader, header, page_style, logger, opts, ruby_tags, link_activated) self.layout_canvas(header, self.content_x, self.header_y) if footer: footer = Footer(font_loader, footer, page_style, logger, opts, ruby_tags, link_activated) self.layout_canvas(footer, self.content_x, self.header_y) self.page = None
def paint(self, painter, option, index): QStyledItemDelegate.paint(self, painter, option, empty_index) theme = index.data(Qt.ItemDataRole.UserRole) if not theme: return painter.save() pixmap = index.data(Qt.ItemDataRole.DecorationRole) if pixmap and not pixmap.isNull(): rect = option.rect.adjusted(0, self.SPACING, COVER_SIZE[0] - option.rect.width(), -self.SPACING) painter.drawPixmap(rect, pixmap) if option.state & QStyle.StateFlag.State_Selected: painter.setPen( QPen(QApplication.instance().palette().highlightedText().color( ))) bottom = option.rect.bottom() - 2 painter.drawLine(0, bottom, option.rect.right(), bottom) if 'static-text' not in theme: theme['static-text'] = QStaticText( _(''' <h1>{title}</h1> <p>by <i>{author}</i> with <b>{number}</b> icons [{size}]</p> <p>{description}</p> <p>Version: {version} Number of users: {usage}</p> <p><i>Right click to visit theme homepage</i></p> '''.format( title=theme.get('title', _('Unknown')), author=theme.get('author', _('Unknown')), number=theme.get('number', 0), description=theme.get('description', ''), size=human_readable(theme.get('compressed-size', 0)), version=theme.get('version', 1), usage=theme.get('usage', 0), ))) painter.drawStaticText(COVER_SIZE[0] + self.SPACING, option.rect.top() + self.SPACING, theme['static-text']) painter.restore()
def __init__(self, font_loader, logger, opts, width, height): _Canvas.__init__(self, font_loader, logger, opts, width, height) if opts.visual_debug: self.setPen(QPen(Qt.GlobalColor.cyan, 1, Qt.PenStyle.DashLine))
def __init__(self, color, width): QPen.__init__( self, QBrush(Color(color)), width, (Qt.PenStyle.SolidLine if width > 0 else Qt.PenStyle.NoPen))
def paintEvent(self, event): QSplitterHandle.paintEvent(self, event) left, right = self.parent().left, self.parent().right painter = QPainter(self) painter.setClipRect(event.rect()) w = self.width() h = self.height() painter.setRenderHints(QPainter.RenderHint.Antialiasing, True) C = 16 # Curve factor. def create_line(ly, ry, right_to_left=False): ' Create path that represents upper or lower line of change marker ' line = QPainterPath() if not right_to_left: line.moveTo(0, ly) line.cubicTo(C, ly, w - C, ry, w, ry) else: line.moveTo(w, ry) line.cubicTo(w - C, ry, C, ly, 0, ly) return line ldoc, rdoc = left.document(), right.document() lorigin, rorigin = left.contentOffset(), right.contentOffset() lfv, rfv = left.firstVisibleBlock().blockNumber(), right.firstVisibleBlock().blockNumber() lines = [] for (ltop, lbot, kind), (rtop, rbot, kind) in zip(left.changes, right.changes): if lbot < lfv and rbot < rfv: continue ly_top = left.blockBoundingGeometry(ldoc.findBlockByNumber(ltop)).translated(lorigin).y() ly_bot = left.blockBoundingGeometry(ldoc.findBlockByNumber(lbot)).translated(lorigin).y() ry_top = right.blockBoundingGeometry(rdoc.findBlockByNumber(rtop)).translated(rorigin).y() ry_bot = right.blockBoundingGeometry(rdoc.findBlockByNumber(rbot)).translated(rorigin).y() if max(ly_top, ly_bot, ry_top, ry_bot) < 0: continue if min(ly_top, ly_bot, ry_top, ry_bot) > h: break upper_line = create_line(ly_top, ry_top) lower_line = create_line(ly_bot, ry_bot, True) region = QPainterPath() region.moveTo(0, ly_top) region.connectPath(upper_line) region.lineTo(w, ry_bot) region.connectPath(lower_line) region.closeSubpath() painter.fillPath(region, left.diff_backgrounds[kind]) for path, aa in zip((upper_line, lower_line), (ly_top != ry_top, ly_bot != ry_bot)): lines.append((kind, path, aa)) for kind, path, aa in sorted(lines, key=lambda x:{'replace':0}.get(x[0], 1)): painter.setPen(left.diff_foregrounds[kind]) painter.setRenderHints(QPainter.RenderHint.Antialiasing, aa) painter.drawPath(path) painter.setFont(left.heading_font) for (lnum, text), (rnum, text) in zip(left.headers, right.headers): ltop, lbot, rtop, rbot = lnum, lnum + 3, rnum, rnum + 3 if lbot < lfv and rbot < rfv: continue ly_top = left.blockBoundingGeometry(ldoc.findBlockByNumber(ltop)).translated(lorigin).y() ly_bot = left.blockBoundingGeometry(ldoc.findBlockByNumber(lbot)).translated(lorigin).y() ry_top = right.blockBoundingGeometry(rdoc.findBlockByNumber(rtop)).translated(rorigin).y() ry_bot = right.blockBoundingGeometry(rdoc.findBlockByNumber(rbot)).translated(rorigin).y() if max(ly_top, ly_bot, ry_top, ry_bot) < 0: continue if min(ly_top, ly_bot, ry_top, ry_bot) > h: break ly = painter.boundingRect(3, ly_top, left.width(), ly_bot - ly_top - 5, Qt.TextFlag.TextSingleLine, text).bottom() + 3 ry = painter.boundingRect(3, ry_top, right.width(), ry_bot - ry_top - 5, Qt.TextFlag.TextSingleLine, text).bottom() + 3 line = create_line(ly, ry) painter.setPen(QPen(left.palette().text(), 2)) painter.setRenderHints(QPainter.RenderHint.Antialiasing, ly != ry) painter.drawPath(line) painter.end() # Paint the splitter without the change lines if the mouse is over the # splitter if getattr(self, 'hover', False): QSplitterHandle.paintEvent(self, event)
class Canvas(QWidget): BACKGROUND = QColor(60, 60, 60) SHADE_COLOR = QColor(0, 0, 0, 180) SELECT_PEN = QPen(QColor(Qt.GlobalColor.white)) selection_state_changed = pyqtSignal(object) selection_area_changed = pyqtSignal(object) undo_redo_state_changed = pyqtSignal(object, object) image_changed = pyqtSignal(object) @property def has_selection(self): return self.selection_state.current_mode == 'selected' @property def is_modified(self): return self.current_image is not self.original_image # Drag 'n drop {{{ def dragEnterEvent(self, event): md = event.mimeData() if dnd_has_extension(md, image_extensions()) or dnd_has_image(md): event.acceptProposedAction() def dropEvent(self, event): event.setDropAction(Qt.DropAction.CopyAction) md = event.mimeData() x, y = dnd_get_image(md) if x is not None: # We have an image, set cover event.accept() if y is None: # Local image self.undo_stack.push( Replace(x.toImage(), _('Drop image'), self)) else: d = DownloadDialog(x, y, self.gui) d.start_download() if d.err is None: with open(d.fpath, 'rb') as f: img = QImage() img.loadFromData(f.read()) if not img.isNull(): self.undo_stack.push( Replace(img, _('Drop image'), self)) event.accept() def dragMoveEvent(self, event): event.acceptProposedAction() # }}} def __init__(self, parent=None): QWidget.__init__(self, parent) self.setAcceptDrops(True) self.setMouseTracking(True) self.setFocusPolicy(Qt.FocusPolicy.ClickFocus) self.selection_state = SelectionState() self.undo_stack = u = QUndoStack() u.setUndoLimit(10) u.canUndoChanged.connect(self.emit_undo_redo_state) u.canRedoChanged.connect(self.emit_undo_redo_state) self.original_image_data = None self.is_valid = False self.original_image_format = None self.current_image = None self.current_scaled_pixmap = None self.last_canvas_size = None self.target = QRectF(0, 0, 0, 0) self.undo_action = a = self.undo_stack.createUndoAction( self, _('Undo') + ' ') a.setIcon(QIcon(I('edit-undo.png'))) self.redo_action = a = self.undo_stack.createRedoAction( self, _('Redo') + ' ') a.setIcon(QIcon(I('edit-redo.png'))) def load_image(self, data): self.is_valid = False try: fmt = identify(data)[0].encode('ascii') except Exception: fmt = b'' self.original_image_format = fmt.decode('ascii').lower() self.selection_state.reset() self.original_image_data = data self.current_image = i = self.original_image = (QImage.fromData( data, format=fmt) if fmt else QImage.fromData(data)) self.is_valid = not i.isNull() self.current_scaled_pixmap = None self.update() self.image_changed.emit(self.current_image) def set_image(self, qimage): self.selection_state.reset() self.current_scaled_pixmap = None self.current_image = qimage self.is_valid = not qimage.isNull() self.update() self.image_changed.emit(self.current_image) def get_image_data(self, quality=90): if not self.is_modified: return self.original_image_data fmt = self.original_image_format or 'JPEG' if fmt.lower() not in { x.data().decode('utf-8') for x in QImageWriter.supportedImageFormats() }: if fmt.lower() == 'gif': data = image_to_data(self.current_image, fmt='PNG', png_compression_level=0) from PIL import Image i = Image.open(BytesIO(data)) buf = BytesIO() i.save(buf, 'gif') return buf.getvalue() else: raise ValueError('Cannot save %s format images' % fmt) return pixmap_to_data(self.current_image, format=fmt, quality=90) def copy(self): if not self.is_valid: return clipboard = QApplication.clipboard() if not self.has_selection or self.selection_state.rect is None: clipboard.setImage(self.current_image) else: trim = Trim(self) clipboard.setImage(trim.after_image) trim.before_image = trim.after_image = None def paste(self): clipboard = QApplication.clipboard() md = clipboard.mimeData() if md.hasImage(): img = QImage(md.imageData()) if not img.isNull(): self.undo_stack.push(Replace(img, _('Paste image'), self)) else: error_dialog(self, _('No image'), _('No image available in the clipboard'), show=True) def break_cycles(self): self.undo_stack.clear() self.original_image_data = self.current_image = self.current_scaled_pixmap = None def emit_undo_redo_state(self): self.undo_redo_state_changed.emit(self.undo_action.isEnabled(), self.redo_action.isEnabled()) @imageop def trim_image(self): if self.selection_state.rect is None: error_dialog( self, _('No selection'), _('No active selection, first select a region in the image, by dragging with your mouse' ), show=True) return False self.undo_stack.push(Trim(self)) return True @imageop def autotrim_image(self): self.undo_stack.push(AutoTrim(self)) return True @imageop def rotate_image(self): self.undo_stack.push(Rotate(self)) return True @imageop def resize_image(self, width, height): self.undo_stack.push(Scale(width, height, self)) return True @imageop def sharpen_image(self, sigma=3.0): self.undo_stack.push(Sharpen(sigma, self)) return True @imageop def blur_image(self, sigma=3.0): self.undo_stack.push(Blur(sigma, self)) return True @imageop def despeckle_image(self): self.undo_stack.push(Despeckle(self)) return True @imageop def normalize_image(self): self.undo_stack.push(Normalize(self)) return True @imageop def oilify_image(self, radius=4.0): self.undo_stack.push(Oilify(radius, self)) return True # The selection rectangle {{{ @property def dc_size(self): sr = self.selection_state.rect dx = min(75, sr.width() / 4) dy = min(75, sr.height() / 4) return dx, dy def get_drag_corner(self, pos): dx, dy = self.dc_size sr = self.selection_state.rect x, y = pos.x(), pos.y() hedge = 'left' if x < sr.x() + dx else 'right' if x > sr.right( ) - dx else None vedge = 'top' if y < sr.y() + dy else 'bottom' if y > sr.bottom( ) - dy else None return (hedge, vedge) if hedge or vedge else None def get_drag_rect(self): sr = self.selection_state.rect dc = self.selection_state.drag_corner if None in (sr, dc): return dx, dy = self.dc_size if None in dc: # An edge if dc[0] is None: top = sr.top() if dc[1] == 'top' else sr.bottom() - dy ans = QRectF(sr.left() + dx, top, sr.width() - 2 * dx, dy) else: left = sr.left() if dc[0] == 'left' else sr.right() - dx ans = QRectF(left, sr.top() + dy, dx, sr.height() - 2 * dy) else: # A corner left = sr.left() if dc[0] == 'left' else sr.right() - dx top = sr.top() if dc[1] == 'top' else sr.bottom() - dy ans = QRectF(left, top, dx, dy) return ans def get_cursor(self): dc = self.selection_state.drag_corner if dc is None: ans = Qt.CursorShape.OpenHandCursor if self.selection_state.last_drag_pos is None else Qt.CursorShape.ClosedHandCursor elif None in dc: ans = Qt.CursorShape.SizeVerCursor if dc[ 0] is None else Qt.CursorShape.SizeHorCursor else: ans = Qt.CursorShape.SizeBDiagCursor if dc in { ('left', 'bottom'), ('right', 'top') } else Qt.CursorShape.SizeFDiagCursor return ans def update(self): super().update() self.selection_area_changed.emit(self.selection_state.rect) def move_edge(self, edge, dp): sr = self.selection_state.rect horiz = edge in {'left', 'right'} func = getattr(sr, 'set' + capitalize(edge)) delta = getattr(dp, 'x' if horiz else 'y')() buf = 50 if horiz: minv = self.target.left() if edge == 'left' else sr.left() + buf maxv = sr.right() - buf if edge == 'left' else self.target.right() else: minv = self.target.top() if edge == 'top' else sr.top() + buf maxv = sr.bottom() - buf if edge == 'top' else self.target.bottom() func(max(minv, min(maxv, delta + getattr(sr, edge)()))) def move_selection_rect(self, x, y): sr = self.selection_state.rect half_width = sr.width() / 2.0 half_height = sr.height() / 2.0 c = sr.center() nx = c.x() + x ny = c.y() + y minx = self.target.left() + half_width maxx = self.target.right() - half_width miny, maxy = self.target.top() + half_height, self.target.bottom( ) - half_height nx = max(minx, min(maxx, nx)) ny = max(miny, min(maxy, ny)) sr.moveCenter(QPointF(nx, ny)) def move_selection(self, dp): dm = self.selection_state.dragging if dm is None: self.move_selection_rect(dp.x(), dp.y()) else: for edge in dm: if edge is not None: self.move_edge(edge, dp) def mousePressEvent(self, ev): if ev.button() == Qt.MouseButton.LeftButton and self.target.contains( ev.pos()): pos = ev.pos() self.selection_state.last_press_point = pos if self.selection_state.current_mode is None: self.selection_state.current_mode = 'select' elif self.selection_state.current_mode == 'selected': if self.selection_state.rect is not None and self.selection_state.rect.contains( pos): self.selection_state.drag_corner = self.selection_state.dragging = self.get_drag_corner( pos) self.selection_state.last_drag_pos = pos self.setCursor(self.get_cursor()) else: self.selection_state.current_mode = 'select' self.selection_state.rect = None self.selection_state_changed.emit(False) def mouseMoveEvent(self, ev): changed = False if self.selection_state.in_selection: changed = True self.selection_state.in_selection = False self.selection_state.drag_corner = None pos = ev.pos() cursor = Qt.CursorShape.ArrowCursor try: if not self.target.contains(pos): return if ev.buttons() & Qt.MouseButton.LeftButton: if self.selection_state.last_press_point is not None and self.selection_state.current_mode is not None: if self.selection_state.current_mode == 'select': self.selection_state.rect = QRectF( self.selection_state.last_press_point, pos).normalized() changed = True elif self.selection_state.last_drag_pos is not None: self.selection_state.in_selection = True self.selection_state.drag_corner = self.selection_state.dragging dp = pos - self.selection_state.last_drag_pos self.selection_state.last_drag_pos = pos self.move_selection(dp) cursor = self.get_cursor() changed = True else: if self.selection_state.rect is None or not self.selection_state.rect.contains( pos): return if self.selection_state.current_mode == 'selected': if self.selection_state.rect is not None and self.selection_state.rect.contains( pos): self.selection_state.drag_corner = self.get_drag_corner( pos) self.selection_state.in_selection = True cursor = self.get_cursor() changed = True finally: if changed: self.update() self.setCursor(cursor) def mouseReleaseEvent(self, ev): if ev.button() == Qt.MouseButton.LeftButton: self.selection_state.dragging = self.selection_state.last_drag_pos = None if self.selection_state.current_mode == 'select': r = self.selection_state.rect if r is None or max(r.width(), r.height()) < 3: self.selection_state.reset() else: self.selection_state.current_mode = 'selected' self.selection_state_changed.emit(self.has_selection) elif self.selection_state.current_mode == 'selected' and self.selection_state.rect is not None and self.selection_state.rect.contains( ev.pos()): self.setCursor(self.get_cursor()) self.update() def keyPressEvent(self, ev): k = ev.key() if k in ( Qt.Key.Key_Left, Qt.Key.Key_Right, Qt.Key.Key_Up, Qt.Key.Key_Down ) and self.selection_state.rect is not None and self.has_selection: ev.accept() delta = 10 if ev.modifiers( ) & Qt.KeyboardModifier.ShiftModifier else 1 x = y = 0 if k in (Qt.Key.Key_Left, Qt.Key.Key_Right): x = delta * (-1 if k == Qt.Key.Key_Left else 1) else: y = delta * (-1 if k == Qt.Key.Key_Up else 1) self.move_selection_rect(x, y) self.update() else: return QWidget.keyPressEvent(self, ev) # }}} # Painting {{{ @painter def draw_background(self, painter): painter.fillRect(self.rect(), self.BACKGROUND) @painter def draw_image_error(self, painter): font = painter.font() font.setPointSize(3 * font.pointSize()) font.setBold(True) painter.setFont(font) painter.setPen(QColor(Qt.GlobalColor.black)) painter.drawText(self.rect(), Qt.AlignmentFlag.AlignCenter, _('Not a valid image')) def load_pixmap(self): canvas_size = self.rect().width(), self.rect().height() if self.last_canvas_size != canvas_size: if self.last_canvas_size is not None and self.selection_state.rect is not None: self.selection_state.reset() # TODO: Migrate the selection rect self.last_canvas_size = canvas_size self.current_scaled_pixmap = None if self.current_scaled_pixmap is None: pwidth, pheight = self.last_canvas_size i = self.current_image width, height = i.width(), i.height() scaled, width, height = fit_image(width, height, pwidth, pheight) try: dpr = self.devicePixelRatioF() except AttributeError: dpr = self.devicePixelRatio() if scaled: i = self.current_image.scaled( int(dpr * width), int(dpr * height), transformMode=Qt.TransformationMode.SmoothTransformation) self.current_scaled_pixmap = QPixmap.fromImage(i) self.current_scaled_pixmap.setDevicePixelRatio(dpr) @painter def draw_pixmap(self, painter): p = self.current_scaled_pixmap try: dpr = self.devicePixelRatioF() except AttributeError: dpr = self.devicePixelRatio() width, height = int(p.width() / dpr), int(p.height() / dpr) pwidth, pheight = self.last_canvas_size x = int(abs(pwidth - width) / 2.) y = int(abs(pheight - height) / 2.) self.target = QRectF(x, y, width, height) painter.drawPixmap(self.target, p, QRectF(p.rect())) @painter def draw_selection_rect(self, painter): cr, sr = self.target, self.selection_state.rect painter.setPen(self.SELECT_PEN) painter.setRenderHint(QPainter.RenderHint.Antialiasing, False) if self.selection_state.current_mode == 'selected': # Shade out areas outside the selection rect for r in ( QRectF(cr.topLeft(), QPointF(sr.left(), cr.bottom())), # left QRectF(QPointF(sr.left(), cr.top()), sr.topRight()), # top QRectF(QPointF(sr.right(), cr.top()), cr.bottomRight()), # right QRectF(sr.bottomLeft(), QPointF(sr.right(), cr.bottom())), # bottom ): painter.fillRect(r, self.SHADE_COLOR) dr = self.get_drag_rect() if self.selection_state.in_selection and dr is not None: # Draw the resize rectangle painter.save() painter.setCompositionMode( QPainter.CompositionMode.RasterOp_SourceAndNotDestination) painter.setClipRect(sr.adjusted(1, 1, -1, -1)) painter.drawRect(dr) painter.restore() # Draw the selection rectangle painter.setCompositionMode( QPainter.CompositionMode.RasterOp_SourceAndNotDestination) painter.drawRect(sr) def paintEvent(self, event): QWidget.paintEvent(self, event) p = QPainter(self) p.setRenderHints(QPainter.RenderHint.Antialiasing | QPainter.RenderHint.SmoothPixmapTransform) try: self.draw_background(p) if self.original_image_data is None: return if not self.is_valid: return self.draw_image_error(p) self.load_pixmap() self.draw_pixmap(p) if self.selection_state.rect is not None: self.draw_selection_rect(p) finally: p.end()