# Copyright (c) 2009-2013 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation; either version 2 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . """ Contains the StyledItemDelegate used as item deleate by qt to render log table cell. """ from functools import wraps from PyQt4.QtCore import QSize, Qt, QPointF from PyQt4.QtGui import QStyledItemDelegate, QStyleOptionViewItemV4, \ QStyle, QPixmap, QColor, QPen, QPainter, QTextDocument, \ QAbstractTextDocumentLayout, QPalette from hgviewlib.hgpatches import phases from hgviewlib.qt4 import icon as geticon def secured(func): """A Decorator that call ``func(self, painter, *ags, **kws)`` in a secured way, e.g. the painter is returned to the state it was supplied in when ``func`` was called. """ @wraps(func) def _func(self, painter, *ags, **kws): painter.save() try: out = func(self, painter, *ags, **kws) finally: painter.restore() return out return _func class StyledItemDelegate(QStyledItemDelegate): """Render revisions tree graph and styled column content.""" def __init__(self, parent=0): super(StyledItemDelegate, self).__init__(parent) self._model = None def paint(self, painter, option, index): """Render the Delegate using the given ``painter`` and style ``option`` for the item specified by ``index``. """ self._model = index.model() # draw selection option = QStyleOptionViewItemV4(option) self.parent().style().drawControl(QStyle.CE_ItemViewItem, option, painter) self._draw_background(painter, option, index) self._draw_graph(painter, option, index) self._draw_text(painter, option, index) def _graph_width(self, nb_branches): """Return graph width in pix for ``nb_branches``. """ return (self._model.dot_radius + 2) * nb_branches + 2 # max pen width @secured def _draw_background(self, painter, option, index): """Draw the background if specified by the model excepts when the row is selected in which case the selection color has precedence. """ background = self._model.data(index, Qt.BackgroundRole) # we draw specified background except when row is selected as selection color # always have precedence if background is not None and not option.state & QStyle.State_Selected: painter.fillRect(option.rect, background) @secured def _draw_text(self, painter, option, index): """Draw the content text in the cell. We currently use the default styled item delegate to render the cell content. """ doc = QTextDocument() text = self._model.data(index, Qt.DisplayRole) doc.setHtml(text) painter.setClipRect(option.rect) painter.translate(QPointF( option.rect.left(), option.rect.top() + (option.rect.height() - doc.size().height()) / 2)) layout = QAbstractTextDocumentLayout.PaintContext() layout.palette = option.palette if option.state & QStyle.State_Selected: if option.state & QStyle.State_Active: layout.palette.setCurrentColorGroup(QPalette.Active) else: layout.palette.setCurrentColorGroup(QPalette.Inactive) layout.palette.setBrush(QPalette.Text, layout.palette.highlightedText()) elif not option.state & QStyle.State_Enabled: layout.palette.setCurrentColorGroup(QPalette.Disabled) doc.documentLayout().draw(painter, layout) @secured def _draw_graph(self, painter, option, index): """ Draw the tree edges for the mercurial context ``ctx`` at the graph node ``gnode``. ..note:: ``ctx`` and ``gnode`` is quite redundant as ``ctx=self.repo.changectx(gnode.rev)``. But this avoids computing ``ctx`` twice. """ painter.setClipRect(option.rect) painter.translate(QPointF(option.rect.left(), option.rect.top())) row = index.row() gnode = index.model().graph[row] ctx = index.model().repo.changectx(gnode.rev) w = self._graph_width(gnode.cols) h = option.rect.height() pix = QPixmap(w, h) pix.fill(QColor(0,0,0,0)) self._draw_graph_ctx(painter, pix, ctx, gnode) option.rect.setLeft(option.rect.left() + w + 5) def _draw_graph_ctx(self, painter, pix, ctx, gnode): h = pix.height() radius = self._model.dot_radius dot_x = self._graph_width(gnode.x) dot_y = h / 2 painter.setRenderHint(QPainter.Antialiasing) for y1, y2, lines in ((dot_y, dot_y + h, gnode.bottomlines), (dot_y - h, dot_y, gnode.toplines)): for start, end, color, fill in lines: x1 = self._graph_width(start) + radius / 2 x2 = self._graph_width(end) + radius / 2 color = QColor(self._model.get_color(color)) _draw_graph_line(painter, x1, x2, y1, y2, color, not fill) dot_color = QColor(self._model.namedbranch_color(ctx.branch())) self._draw_graph_node(painter, dot_x, dot_y, radius, dot_color, ctx) def _draw_graph_node(self, painter, x, y, r, color, ctx): y -= r / 2 # middle -> border tags = set(ctx.tags()) phase = ctx.phase() if ctx.rev() is None: # WD is displayed only if there are local # modifications, so let's use the modified icon _draw_graph_node_modified(painter, x, y) elif tags.intersection(self._model.mqueues): _draw_graph_node_mqpatch(painter, x, y) elif phase == phases.draft: self._draw_graph_node_draft(painter, x, y, r, color, ctx) elif phase == phases.secret: self._draw_graph_node_secret(painter, x, y, r, color, ctx) else: self._draw_graph_node_public(painter, x, y, r, color, ctx) def _set_graph_node_style(self, painter, dot_color, ctx): rev = ctx.rev() dotcolor = QColor(dot_color) if ctx.obsolete(): penradius = 1 pencolor = dotcolor.setAlpha(150) elif rev in self._model.heads: penradius = 2 pencolor = dotcolor.darker() else: penradius = 1 pencolor = Qt.black if rev in self._model.wd_revs: pen = QPen(Qt.red) pen.setWidth(2) else: pen = QPen(pencolor) pen.setWidth(1) painter.setPen(pen) painter.setBrush(dotcolor) def _draw_graph_node_public(self, painter, x, y, r, color, ctx): if ctx.rev() in self._model.wd_revs: geticon('clean').paint(painter, x - 5, y - 5, 17, 17) else: self._set_graph_node_style(painter, color, ctx) painter.drawEllipse(x, y, r, r) def _draw_graph_node_draft(self, painter, x, y, r, color, ctx): self._set_graph_node_style(painter, color, ctx) painter.drawRect(x, y, r, r) def _draw_graph_node_secret(self, painter, x, y, r, color, ctx): self._set_graph_node_style(painter, color, ctx) painter.drawPolygon( QPointF(x + (r // 2), y), QPointF(x, y + r), QPointF(x + r, y + r) ) def _draw_graph_node_mqpatch(painter, x, y): geticon('mqpatch').paint(painter, x - 5, y - 5, 17, 17) def _draw_graph_node_modified(painter, x, y): geticon('modified').paint(painter, x - 5, y - 5, 17, 17) def _draw_graph_line(painter, x1, x2, y1, y2, color, isobsolete): lpen = QPen(color) if isobsolete: lpen.setStyle(Qt.DotLine) color.setAlpha(150) lpen.setWidth(2) painter.setPen(lpen) painter.drawLine(x1, y1, x2, y2)