예제 #1
0
 def setBranch(self, branch=None, allparents=True):
     self.filterbranch = branch  # unicode
     self.invalidateCache()
     if self.revset and self.filterbyrevset:
         grapher = revision_grapher(self.repo, branch=hglib.fromunicode(branch), revset=self.revset)
         self.graph = Graph(self.repo, grapher, include_mq=False)
     else:
         grapher = revision_grapher(self.repo, branch=hglib.fromunicode(branch), allparents=allparents)
         self.graph = Graph(self.repo, grapher, include_mq=True)
     self.rowcount = 0
     self.layoutChanged.emit()
     self.ensureBuilt(row=0)
     self.showMessage.emit("")
     QTimer.singleShot(0, lambda: self.filled.emit())
예제 #2
0
 def setBranch(self, branch=None, allparents=False):
     self.filterbranch = branch  # unicode
     self.invalidateCache()
     if self.revset and self.filterbyrevset:
         grapher = revision_grapher(self.repo,
                                    branch=hglib.fromunicode(branch),
                                    revset=self.revset)
         self.graph = Graph(self.repo, grapher, include_mq=False)
     else:
         grapher = revision_grapher(self.repo,
                                    branch=hglib.fromunicode(branch),
                                    allparents=allparents)
         self.graph = Graph(self.repo, grapher, include_mq=True)
     self.rowcount = 0
     self.layoutChanged.emit()
     self.ensureBuilt(row=0)
     self.showMessage.emit('')
     QTimer.singleShot(0, lambda: self.filled.emit())
예제 #3
0
 def _initGraph(self):
     branch = self.filterbranch
     allparents = self.allparents
     showhidden = self.showhidden
     self.invalidateCache()
     if self.revset and self.filterbyrevset:
         grapher = revision_grapher(self.repo,
                                    branch=hglib.fromunicode(branch),
                                    revset=self.revset,
                                    showhidden=showhidden)
         self.graph = Graph(self.repo, grapher, include_mq=False)
     else:
         grapher = revision_grapher(self.repo,
                                    branch=hglib.fromunicode(branch),
                                    allparents=allparents,
                                    showhidden=showhidden)
         self.graph = Graph(self.repo, grapher, include_mq=True)
     self.rowcount = 0
     self.layoutChanged.emit()
     self.ensureBuilt(row=0)
     self.showMessage.emit('')
     QTimer.singleShot(0, self, SIGNAL('filled()'))
예제 #4
0
 def _initGraph(self):
     branch = self.filterbranch
     allparents = self.allparents
     showhidden = self.showhidden
     self.invalidateCache()
     if self.revset and self.filterbyrevset:
         grapher = revision_grapher(self.repo,
                                    branch=hglib.fromunicode(branch),
                                    revset=self.revset,
                                    showhidden=showhidden)
         self.graph = Graph(self.repo, grapher, include_mq=False)
     else:
         grapher = revision_grapher(self.repo,
                                    branch=hglib.fromunicode(branch),
                                    allparents=allparents,
                                    showhidden=showhidden)
         self.graph = Graph(self.repo, grapher, include_mq=True)
     self.rowcount = 0
     self.layoutChanged.emit()
     self.ensureBuilt(row=0)
     self.showMessage.emit('')
     QTimer.singleShot(0, self, SIGNAL('filled()'))
예제 #5
0
    def setFilename(self, filename):
        self.filename = filename

        self._user_colors = {}
        self._branch_colors = {}

        self.rowcount = 0
        self._datacache = {}

        if self.filename:
            grapher = filelog_grapher(self.repo, self.filename)
            self.graph = Graph(self.repo, grapher)
            fl = self.repo.file(self.filename)
            # we use fl.index here (instead of linkrev) cause
            # linkrev API changed between 1.0 and 1.?. So this
            # works with both versions.
            self.heads = [fl.index[fl.rev(x)][4] for x in fl.heads()]
            self.ensureBuilt(row=self.fill_step/2)
            QTimer.singleShot(0, self, SIGNAL('filled()'))
        else:
            self.graph = None
            self.heads = []
예제 #6
0
class HgRepoListModel(QAbstractTableModel):
    """
    Model used for displaying the revisions of a Hg *local* repository
    """

    showMessage = pyqtSignal(unicode)
    filled = pyqtSignal()
    loaded = pyqtSignal()

    _allcolumns = tuple(h[0] for h in COLUMNHEADERS)
    _allcolnames = dict(COLUMNHEADERS)

    _columns = ("Graph", "Rev", "Branch", "Description", "Author", "Age", "Tags")
    _stretchs = {"Description": 1}
    _mqtags = ("qbase", "qtip", "qparent")

    def __init__(self, repo, cfgname, branch, revset, rfilter, parent):
        """
        repo is a hg repo instance
        """
        QAbstractTableModel.__init__(self, parent)
        self._cache = []
        self.graph = None
        self.timerHandle = None
        self.dotradius = 8
        self.rowheight = 20
        self.rowcount = 0
        self.repo = repo
        self.revset = revset
        self.filterbyrevset = rfilter
        self.unicodestar = True
        self.unicodexinabox = True
        self.cfgname = cfgname

        # To be deleted
        self._user_colors = {}
        self._branch_colors = {}

        self._columnmap = {
            "Rev": self.getrev,
            "Node": lambda ctx, gnode: str(ctx),
            "Graph": lambda ctx, gnode: "",
            "Description": self.getlog,
            "Author": self.getauthor,
            "Tags": self.gettags,
            "Branch": self.getbranch,
            "Filename": lambda ctx, gnode: gnode.extra[0],
            "Age": lambda ctx, gnode: hglib.age(ctx.date()).decode("utf-8"),
            "LocalTime": lambda ctx, gnode: hglib.displaytime(ctx.date()),
            "UTCTime": lambda ctx, gnode: hglib.utctime(ctx.date()),
            "Changes": self.getchanges,
        }

        if repo:
            self.reloadConfig()
            self.updateColumns()
            self.setBranch(branch)

    def setBranch(self, branch=None, allparents=True):
        self.filterbranch = branch  # unicode
        self.invalidateCache()
        if self.revset and self.filterbyrevset:
            grapher = revision_grapher(self.repo, branch=hglib.fromunicode(branch), revset=self.revset)
            self.graph = Graph(self.repo, grapher, include_mq=False)
        else:
            grapher = revision_grapher(self.repo, branch=hglib.fromunicode(branch), allparents=allparents)
            self.graph = Graph(self.repo, grapher, include_mq=True)
        self.rowcount = 0
        self.layoutChanged.emit()
        self.ensureBuilt(row=0)
        self.showMessage.emit("")
        QTimer.singleShot(0, lambda: self.filled.emit())

    def reloadConfig(self):
        _ui = self.repo.ui
        self.fill_step = int(_ui.config("tortoisehg", "graphlimit", 500))
        self.authorcolor = _ui.configbool("tortoisehg", "authorcolor")

    def updateColumns(self):
        s = QSettings()
        cols = s.value(self.cfgname + "/columns").toStringList()
        cols = [str(col) for col in cols]
        # Fixup older names for columns
        if "Log" in cols:
            cols[cols.index("Log")] = "Description"
            s.setValue(self.cfgname + "/columns", cols)
        if "ID" in cols:
            cols[cols.index("ID")] = "Rev"
            s.setValue(self.cfgname + "/columns", cols)
        validcols = [col for col in cols if col in self._allcolumns]
        if validcols:
            self._columns = tuple(validcols)
            self.invalidateCache()
            self.layoutChanged.emit()

    def invalidate(self):
        self.reloadConfig()
        self.invalidateCache()
        self.layoutChanged.emit()

    def branch(self):
        return self.filterbranch

    def ensureBuilt(self, rev=None, row=None):
        """
        Make sure rev data is available (graph element created).

        """
        if self.graph.isfilled():
            return
        required = 0
        buildrev = rev
        n = len(self.graph)
        if rev is not None:
            if n and self.graph[-1].rev <= rev:
                buildrev = None
            else:
                required = self.fill_step / 2
        elif row is not None and row > (n - self.fill_step / 2):
            required = row - n + self.fill_step
        if required or buildrev:
            self.graph.build_nodes(nnodes=required, rev=buildrev)
            self.updateRowCount()

        if self.rowcount >= len(self.graph):
            return  # no need to update row count
        if row and row > self.rowcount:
            # asked row was already built, but views where not aware of this
            self.updateRowCount()
        elif rev is not None and rev <= self.graph[self.rowcount].rev:
            # asked rev was already built, but views where not aware of this
            self.updateRowCount()

    def loadall(self):
        self.timerHandle = self.startTimer(1)

    def timerEvent(self, event):
        if event.timerId() == self.timerHandle:
            self.showMessage.emit(_("filling (%d)") % (len(self.graph)))
            if self.graph.isfilled():
                self.killTimer(self.timerHandle)
                self.timerHandle = None
                self.showMessage.emit("")
                self.loaded.emit()
            # we only fill the graph data structures without telling
            # views until the model is loaded, to keep maximal GUI
            # reactivity
            elif not self.graph.build_nodes():
                self.killTimer(self.timerHandle)
                self.timerHandle = None
                self.updateRowCount()
                self.showMessage.emit("")
                self.loaded.emit()

    def updateRowCount(self):
        currentlen = self.rowcount
        newlen = len(self.graph)

        if newlen > self.rowcount:
            self.beginInsertRows(QModelIndex(), currentlen, newlen - 1)
            self.rowcount = newlen
            self.endInsertRows()

    def rowCount(self, parent):
        if parent.isValid():
            return 0
        return self.rowcount

    def columnCount(self, parent):
        if parent.isValid():
            return 0
        return len(self._columns)

    def maxWidthValueForColumn(self, col):
        if self.graph is None:
            return "XXXX"
        column = self._columns[col]
        if column == "Rev":
            return "8" * len(str(len(self.repo))) + "+"
        if column == "Node":
            return "8" * 12 + "+"
        if column in ("LocalTime", "UTCTime"):
            return hglib.displaytime(util.makedate())
        if column == "Tags":
            try:
                return sorted(self.repo.tags().keys(), key=lambda x: len(x))[-1][:10]
            except IndexError:
                pass
        if column == "Branch":
            try:
                return sorted(self.repo.branchtags().keys(), key=lambda x: len(x))[-1]
            except IndexError:
                pass
        if column == "Filename":
            return self.filename
        if column == "Graph":
            res = self.col2x(self.graph.max_cols)
            return min(res, 150)
        if column == "Changes":
            return "Changes"
        # Fall through for Description
        return None

    def user_color(self, user):
        "deprecated, please replace with hgtk color scheme"
        if user not in self._user_colors:
            self._user_colors[user] = get_color(len(self._user_colors), self._user_colors.values())
        return self._user_colors[user]

    def namedbranch_color(self, branch):
        "deprecated, please replace with hgtk color scheme"
        if branch not in self._branch_colors:
            self._branch_colors[branch] = get_color(len(self._branch_colors))
        return self._branch_colors[branch]

    def col2x(self, col):
        return 2 * self.dotradius * col + self.dotradius / 2 + 8

    def graphctx(self, ctx, gnode):
        w = self.col2x(gnode.cols) + 10
        h = self.rowheight

        pix = QPixmap(w, h)
        pix.fill(QColor(0, 0, 0, 0))
        painter = QPainter(pix)
        try:
            self._drawgraphctx(painter, pix, ctx, gnode)
        finally:
            painter.end()
        return QVariant(pix)

    def _drawgraphctx(self, painter, pix, ctx, gnode):
        h = pix.height()
        dot_y = h / 2

        painter.setRenderHint(QPainter.Antialiasing)

        pen = QPen(Qt.blue)
        pen.setWidth(2)
        painter.setPen(pen)

        lpen = QPen(pen)
        lpen.setColor(Qt.black)
        painter.setPen(lpen)
        for y1, y4, lines in ((dot_y, dot_y + h, gnode.bottomlines), (dot_y - h, dot_y, gnode.toplines)):
            y2 = y1 + 1 * (y4 - y1) / 4
            ymid = (y1 + y4) / 2
            y3 = y1 + 3 * (y4 - y1) / 4

            for start, end, color in lines:
                lpen = QPen(pen)
                lpen.setColor(QColor(get_color(color)))
                lpen.setWidth(2)
                painter.setPen(lpen)
                x1 = self.col2x(start)
                x2 = self.col2x(end)
                path = QPainterPath()
                path.moveTo(x1, y1)
                path.cubicTo(x1, y2, x1, y2, (x1 + x2) / 2, ymid)
                path.cubicTo(x2, y3, x2, y3, x2, y4)
                painter.drawPath(path)

        # Draw node
        dot_color = QColor(self.namedbranch_color(ctx.branch()))
        dotcolor = dot_color.lighter()
        pencolor = dot_color.darker()
        white = QColor("white")
        fillcolor = gnode.rev is None and white or dotcolor

        pen = QPen(pencolor)
        pen.setWidthF(1.5)
        painter.setPen(pen)

        radius = self.dotradius
        centre_x = self.col2x(gnode.x)
        centre_y = h / 2

        def circle(r):
            rect = QRectF(centre_x - r, centre_y - r, 2 * r, 2 * r)
            painter.drawEllipse(rect)

        def closesymbol(s):
            rect_ = QRectF(centre_x - 1.5 * s, centre_y - 0.5 * s, 3 * s, s)
            painter.drawRect(rect_)

        def diamond(r):
            poly = QPolygonF(
                [
                    QPointF(centre_x - r, centre_y),
                    QPointF(centre_x, centre_y - r),
                    QPointF(centre_x + r, centre_y),
                    QPointF(centre_x, centre_y + r),
                    QPointF(centre_x - r, centre_y),
                ]
            )
            painter.drawPolygon(poly)

        if ctx.thgmqappliedpatch():  # diamonds for patches
            if ctx.thgwdparent():
                painter.setBrush(white)
                diamond(2 * 0.9 * radius / 1.5)
            painter.setBrush(fillcolor)
            diamond(radius / 1.5)
        elif ctx.thgmqunappliedpatch():
            patchcolor = QColor("#dddddd")
            painter.setBrush(patchcolor)
            painter.setPen(patchcolor)
            diamond(radius / 1.5)
        elif ctx.extra().get("close"):
            painter.setBrush(fillcolor)
            closesymbol(0.5 * radius)
        else:  # circles for normal revisions
            if ctx.thgwdparent():
                painter.setBrush(white)
                circle(0.9 * radius)
            painter.setBrush(fillcolor)
            circle(0.5 * radius)

    def invalidateCache(self):
        self._cache = []
        for a in ("_roleoffsets",):
            if hasattr(self, a):
                delattr(self, a)

    @propertycache
    def _roleoffsets(self):
        return {Qt.DisplayRole: 0, Qt.ForegroundRole: len(self._columns), Qt.DecorationRole: len(self._columns) * 2}

    def data(self, index, role):
        if not index.isValid():
            return nullvariant
        if role not in self._roleoffsets:
            return nullvariant
        try:
            return self.safedata(index, role)
        except Exception, e:
            if role == Qt.DisplayRole:
                return QVariant(hglib.tounicode(str(e)))
            else:
                return nullvariant
예제 #7
0
class HgRepoListModel(QAbstractTableModel):
    """
    Model used for displaying the revisions of a Hg *local* repository
    """
    showMessage = pyqtSignal(unicode)
    filled = pyqtSignal()
    loaded = pyqtSignal()

    _allcolumns = tuple(h[0] for h in COLUMNHEADERS)
    _allcolnames = dict(COLUMNHEADERS)

    _columns = ('Graph', 'Rev', 'Branch', 'Description', 'Author', 'Age', 'Tags', 'Phase',)
    _columnfonts = {'Node': QFont("Monospace"),
                    'Converted': QFont("Monospace")}
    _stretchs = {'Description': 1, }
    _mqtags = ('qbase', 'qtip', 'qparent')

    def __init__(self, repo, cfgname, branch, revset, rfilter, parent,
            showhidden=False, allparents=False):
        """
        repo is a hg repo instance
        """
        QAbstractTableModel.__init__(self, parent)
        self._cache = []
        self.graph = None
        self.timerHandle = None
        self.dotradius = 8
        self.rowheight = 20
        self.rowcount = 0
        self.repo = repo
        self.revset = revset
        self.filterbyrevset = rfilter
        self.unicodestar = True
        self.unicodexinabox = True
        self.cfgname = cfgname
        self.latesttags = {-1: 'null'}
        self.fullauthorname = False
        self.filterbranch = branch  # unicode
        self.showhidden = showhidden
        self.allparents = allparents

        # To be deleted
        self._user_colors = {}
        self._branch_colors = {}

        if repo:
            self.initBranchColors()
            self.reloadConfig()
            self.updateColumns()
            self._initGraph()

    def initBranchColors(self):
        # Set all the branch colors once on a fixed order,
        # which should make the branch colors more stable

        # Always assign the first color to the default branch
        self.namedbranch_color('default')

        # Set the colors specified in the tortoisehg.brachcolors config key
        self._branch_colors.update(_parsebranchcolors(
            self.repo.ui.config('tortoisehg', 'branchcolors')))

        # Then assign colors to all branches in alphabetical order
        # Note that re-assigning the color to the default branch
        # is not expensive
        for branch in sorted(self.repo.branchtags().keys()):
            self.namedbranch_color(branch)

    def setBranch(self, branch, allparents=False):
        self.filterbranch = branch
        self.allparents = allparents
        self._initGraph()

    def setShowHidden(self, visible):
        self.showhidden = visible
        self._initGraph()

    def _initGraph(self):
        branch = self.filterbranch
        allparents = self.allparents
        showhidden = self.showhidden
        self.invalidateCache()
        if self.revset and self.filterbyrevset:
            grapher = revision_grapher(self.repo,
                                       branch=hglib.fromunicode(branch),
                                       revset=self.revset,
                                       showhidden=showhidden)
            self.graph = Graph(self.repo, grapher, include_mq=False)
        else:
            grapher = revision_grapher(self.repo,
                                       branch=hglib.fromunicode(branch),
                                       allparents=allparents,
                                       showhidden=showhidden)
            self.graph = Graph(self.repo, grapher, include_mq=True)
        self.rowcount = 0
        self.layoutChanged.emit()
        self.ensureBuilt(row=0)
        self.showMessage.emit('')
        QTimer.singleShot(0, self, SIGNAL('filled()'))

    def setRevset(self, revset):
        self.revset = revset
        self.invalidateCache()

    def reloadConfig(self):
        _ui = self.repo.ui
        self.fill_step = int(_ui.config('tortoisehg', 'graphlimit', 500))
        self.authorcolor = _ui.configbool('tortoisehg', 'authorcolor')
        self.fullauthorname = _ui.configbool('tortoisehg', 'fullauthorname')

    def updateColumns(self):
        s = QSettings()
        cols = s.value(self.cfgname + '/columns').toStringList()
        cols = [str(col) for col in cols]
        # Fixup older names for columns
        if 'Log' in cols:
            cols[cols.index('Log')] = 'Description'
            s.setValue(self.cfgname + '/columns', cols)
        if 'ID' in cols:
            cols[cols.index('ID')] = 'Rev'
            s.setValue(self.cfgname + '/columns', cols)
        validcols = [col for col in cols if col in self._allcolumns]
        if validcols:
            self._columns = tuple(validcols)
            self.invalidateCache()
            self.layoutChanged.emit()

    def invalidate(self):
        self.reloadConfig()
        self.invalidateCache()
        self.layoutChanged.emit()

    def branch(self):
        return self.filterbranch

    def ensureBuilt(self, rev=None, row=None):
        """
        Make sure rev data is available (graph element created).

        """
        if self.graph.isfilled():
            return
        required = 0
        buildrev = rev
        n = len(self.graph)
        if rev is not None:
            if n and self.graph[-1].rev <= rev:
                buildrev = None
            else:
                required = self.fill_step/2
        elif row is not None and row > (n - self.fill_step / 2):
            required = row - n + self.fill_step
        if required or buildrev:
            self.graph.build_nodes(nnodes=required, rev=buildrev)
            self.updateRowCount()

        if self.rowcount >= len(self.graph):
            return  # no need to update row count
        if row and row > self.rowcount:
            # asked row was already built, but views where not aware of this
            self.updateRowCount()
        elif rev is not None and rev <= self.graph[self.rowcount].rev:
            # asked rev was already built, but views where not aware of this
            self.updateRowCount()

    def loadall(self):
        self.timerHandle = self.startTimer(1)

    def timerEvent(self, event):
        if event.timerId() == self.timerHandle:
            self.showMessage.emit(_('filling (%d)')%(len(self.graph)))
            if self.graph.isfilled():
                self.killTimer(self.timerHandle)
                self.timerHandle = None
                self.showMessage.emit('')
                self.loaded.emit()
            # we only fill the graph data structures without telling
            # views until the model is loaded, to keep maximal GUI
            # reactivity
            elif not self.graph.build_nodes():
                self.killTimer(self.timerHandle)
                self.timerHandle = None
                self.updateRowCount()
                self.showMessage.emit('')
                self.loaded.emit()

    def updateRowCount(self):
        currentlen = self.rowcount
        newlen = len(self.graph)

        if newlen > self.rowcount:
            self.beginInsertRows(QModelIndex(), currentlen, newlen-1)
            self.rowcount = newlen
            self.endInsertRows()

    def rowCount(self, parent):
        if parent.isValid():
            return 0
        return self.rowcount

    def columnCount(self, parent):
        if parent.isValid():
            return 0
        return len(self._columns)

    def maxWidthValueForColumn(self, col):
        if self.graph is None:
            return 'XXXX'
        column = self._columns[col]
        if column == 'Rev':
            return '8' * len(str(len(self.repo))) + '+'
        if column == 'Node':
            return '8' * 12 + '+'
        if column in ('LocalTime', 'UTCTime'):
            return hglib.displaytime(util.makedate())
        if column in ('Tags', 'Latest tags'):
            try:
                return sorted(self.repo.tags().keys(), key=lambda x: len(x))[-1][:10]
            except IndexError:
                pass
        if column == 'Branch':
            try:
                return sorted(self.repo.branchtags().keys(), key=lambda x: len(x))[-1]
            except IndexError:
                pass
        if column == 'Filename':
            return self.filename
        if column == 'Graph':
            res = self.col2x(self.graph.max_cols)
            return min(res, 150)
        if column == 'Changes':
            return 'Changes'
        # Fall through for Description
        return None

    def user_color(self, user):
        'deprecated, please replace with hgtk color scheme'
        if user not in self._user_colors:
            self._user_colors[user] = get_color(len(self._user_colors),
                                                self._user_colors.values())
        return self._user_colors[user]

    def namedbranch_color(self, branch):
        'deprecated, please replace with hgtk color scheme'
        if branch not in self._branch_colors:
            self._branch_colors[branch] = get_color(len(self._branch_colors))
        return self._branch_colors[branch]

    def col2x(self, col):
        return 2 * self.dotradius * col + self.dotradius/2 + 8

    def graphctx(self, ctx, gnode):
        w = self.col2x(gnode.cols) + 10
        h = self.rowheight

        pix = QPixmap(w, h)
        pix.fill(QColor(0,0,0,0))
        painter = QPainter(pix)
        try:
            self._drawgraphctx(painter, pix, ctx, gnode)
        finally:
            painter.end()
        return QVariant(pix)

    def _drawgraphctx(self, painter, pix, ctx, gnode):
        revset = self.revset
        h = pix.height()
        dot_y = h / 2

        painter.setRenderHint(QPainter.Antialiasing)

        pen = QPen(Qt.blue)
        pen.setWidth(2)
        painter.setPen(pen)

        lpen = QPen(pen)
        lpen.setColor(Qt.black)
        painter.setPen(lpen)
        if revset:
            def isactive(start, end, color, line_type, children, rev):
                return rev in revset and util.any(r in revset for r in children)
        else:
            def isactive(start, end, color, line_type, children, rev):
                return True

        for y1, y4, lines in ((dot_y, dot_y + h, gnode.bottomlines),
                              (dot_y - h, dot_y, gnode.toplines)):
            y2 = y1 + 1 * (y4 - y1)/4
            ymid = (y1 + y4)/2
            y3 = y1 + 3 * (y4 - y1)/4

            lines = sorted((isactive(*l), l) for l in lines)

            for active, (start, end, color, line_type, children, rev) in lines:
                lpen = QPen(pen)
                lpen.setColor(QColor(active and get_color(color) or "gray"))
                lpen.setStyle(get_style(line_type, active))
                lpen.setWidth(get_width(line_type, active))
                painter.setPen(lpen)
                x1 = self.col2x(start)
                x2 = self.col2x(end)
                path = QPainterPath()
                path.moveTo(x1, y1)
                path.cubicTo(x1, y2,
                             x1, y2,
                             (x1 + x2)/2, ymid)
                path.cubicTo(x2, y3,
                             x2, y3,
                             x2, y4)
                painter.drawPath(path)

        # Draw node
        if revset and gnode.rev not in revset:
            dot_color = QColor("gray")
            radius = self.dotradius * 0.8
        else:
            dot_color = QColor(self.namedbranch_color(ctx.branch()))
            radius = self.dotradius
        dotcolor = dot_color.lighter()
        pencolor = dot_color.darker()
        truewhite = QColor("white")
        white = QColor("white")
        fillcolor = gnode.rev is None and white or dotcolor

        pen = QPen(pencolor)
        pen.setWidthF(1.5)
        painter.setPen(pen)

        centre_x = self.col2x(gnode.x)
        centre_y = h/2

        def circle(r):
            rect = QRectF(centre_x - r,
                          centre_y - r,
                          2 * r, 2 * r)
            painter.drawEllipse(rect)

        def closesymbol(s):
            rect_ = QRectF(centre_x - 1.5 * s, centre_y - 0.5 * s, 3 * s, s)
            painter.drawRect(rect_)

        def diamond(r):
            poly = QPolygonF([QPointF(centre_x - r, centre_y),
                              QPointF(centre_x, centre_y - r),
                              QPointF(centre_x + r, centre_y),
                              QPointF(centre_x, centre_y + r),
                              QPointF(centre_x - r, centre_y),])
            painter.drawPolygon(poly)

        hiddenrev = ctx.hidden()
        if hiddenrev:
            painter.setBrush(truewhite)
            white.setAlpha(64)
            fillcolor.setAlpha(64)
        if ctx.thgmqappliedpatch():  # diamonds for patches
            symbolsize = radius / 1.5
            if hiddenrev:
                diamond(symbolsize)
            if ctx.thgwdparent():
                painter.setBrush(white)
                diamond(2 * 0.9 * symbolsize)
            painter.setBrush(fillcolor)
            diamond(symbolsize)
        elif ctx.thgmqunappliedpatch():
            symbolsize = radius / 1.5
            if hiddenrev:
                diamond(symbolsize)
            patchcolor = QColor('#dddddd')
            painter.setBrush(patchcolor)
            painter.setPen(patchcolor)
            diamond(symbolsize)
        elif ctx.extra().get('close'):
            symbolsize = 0.5 * radius
            if hiddenrev:
                closesymbol(symbolsize)
            painter.setBrush(fillcolor)
            closesymbol(symbolsize)
        else:  # circles for normal revisions
            symbolsize = 0.5 * radius
            if hiddenrev:
                circle(symbolsize)
            if ctx.thgwdparent():
                painter.setBrush(white)
                circle(0.9 * radius)
            painter.setBrush(fillcolor)
            circle(symbolsize)

    def invalidateCache(self):
        self._cache = []
        for a in ('_roleoffsets',):
            if hasattr(self, a):
                delattr(self, a)

    @propertycache
    def _roleoffsets(self):
        return {Qt.DisplayRole : 0,
                Qt.ForegroundRole : len(self._columns),
                GraphRole : len(self._columns) * 2}

    def data(self, index, role):
        if not index.isValid():
            return nullvariant
        # font is not cached in self._cache since it is equal for all rows
        if role == Qt.FontRole:
            column = self._columns[index.column()]
            return self._columnfonts.get(column, nullvariant)
        if role not in self._roleoffsets:
            return nullvariant
        # repo may be changed while reading in case of postpull=rebase for
        # example, and result in RevlogError. (issue #429)
        try:
            return self.safedata(index, role)
        except error.RevlogError, e:
            if 'THGDEBUG' in os.environ:
                raise
            if role == Qt.DisplayRole:
                return QVariant(hglib.tounicode(str(e)))
            else:
                return nullvariant
예제 #8
0
class HgRepoListModel(QAbstractTableModel):
    """
    Model used for displaying the revisions of a Hg *local* repository
    """
    showMessage = pyqtSignal(unicode)
    filled = pyqtSignal()
    loaded = pyqtSignal()

    _allcolumns = tuple(h[0] for h in COLUMNHEADERS)
    _allcolnames = dict(COLUMNHEADERS)

    _columns = (
        'Graph',
        'Rev',
        'Branch',
        'Description',
        'Author',
        'Age',
        'Tags',
        'Phase',
    )
    _columnfonts = {
        'Node': QFont("Monospace"),
        'Converted': QFont("Monospace")
    }
    _stretchs = {
        'Description': 1,
    }
    _mqtags = ('qbase', 'qtip', 'qparent')

    def __init__(self,
                 repo,
                 cfgname,
                 branch,
                 revset,
                 rfilter,
                 parent,
                 showhidden=False,
                 allparents=False):
        """
        repo is a hg repo instance
        """
        QAbstractTableModel.__init__(self, parent)
        self._cache = []
        self.graph = None
        self.timerHandle = None
        self.dotradius = 8
        self.rowheight = 20
        self.rowcount = 0
        self.repo = repo
        self.revset = revset
        self.filterbyrevset = rfilter
        self.unicodestar = True
        self.unicodexinabox = True
        self.cfgname = cfgname
        self.latesttags = {-1: 'null'}
        self.fullauthorname = False
        self.filterbranch = branch  # unicode
        self.showhidden = showhidden
        self.allparents = allparents

        # To be deleted
        self._user_colors = {}
        self._branch_colors = {}

        if repo:
            self.initBranchColors()
            self.reloadConfig()
            self.updateColumns()
            self._initGraph()

    def initBranchColors(self):
        # Set all the branch colors once on a fixed order,
        # which should make the branch colors more stable

        # Always assign the first color to the default branch
        self.namedbranch_color('default')

        # Set the colors specified in the tortoisehg.brachcolors config key
        self._branch_colors.update(
            _parsebranchcolors(
                self.repo.ui.config('tortoisehg', 'branchcolors')))

        # Then assign colors to all branches in alphabetical order
        # Note that re-assigning the color to the default branch
        # is not expensive
        for branch in sorted(self.repo.branchtags().keys()):
            self.namedbranch_color(branch)

    def setBranch(self, branch, allparents=False):
        self.filterbranch = branch
        self.allparents = allparents
        self._initGraph()

    def setShowHidden(self, visible):
        self.showhidden = visible
        self._initGraph()

    def _initGraph(self):
        branch = self.filterbranch
        allparents = self.allparents
        showhidden = self.showhidden
        self.invalidateCache()
        if self.revset and self.filterbyrevset:
            grapher = revision_grapher(self.repo,
                                       branch=hglib.fromunicode(branch),
                                       revset=self.revset,
                                       showhidden=showhidden)
            self.graph = Graph(self.repo, grapher, include_mq=False)
        else:
            grapher = revision_grapher(self.repo,
                                       branch=hglib.fromunicode(branch),
                                       allparents=allparents,
                                       showhidden=showhidden)
            self.graph = Graph(self.repo, grapher, include_mq=True)
        self.rowcount = 0
        self.layoutChanged.emit()
        self.ensureBuilt(row=0)
        self.showMessage.emit('')
        QTimer.singleShot(0, self, SIGNAL('filled()'))

    def setRevset(self, revset):
        self.revset = revset
        self.invalidateCache()

    def reloadConfig(self):
        _ui = self.repo.ui
        self.fill_step = int(_ui.config('tortoisehg', 'graphlimit', 500))
        self.authorcolor = _ui.configbool('tortoisehg', 'authorcolor')
        self.fullauthorname = _ui.configbool('tortoisehg', 'fullauthorname')

    def updateColumns(self):
        s = QSettings()
        cols = s.value(self.cfgname + '/columns').toStringList()
        cols = [str(col) for col in cols]
        # Fixup older names for columns
        if 'Log' in cols:
            cols[cols.index('Log')] = 'Description'
            s.setValue(self.cfgname + '/columns', cols)
        if 'ID' in cols:
            cols[cols.index('ID')] = 'Rev'
            s.setValue(self.cfgname + '/columns', cols)
        validcols = [col for col in cols if col in self._allcolumns]
        if validcols:
            self._columns = tuple(validcols)
            self.invalidateCache()
            self.layoutChanged.emit()

    def invalidate(self):
        self.reloadConfig()
        self.invalidateCache()
        self.layoutChanged.emit()

    def branch(self):
        return self.filterbranch

    def ensureBuilt(self, rev=None, row=None):
        """
        Make sure rev data is available (graph element created).

        """
        if self.graph.isfilled():
            return
        required = 0
        buildrev = rev
        n = len(self.graph)
        if rev is not None:
            if n and self.graph[-1].rev <= rev:
                buildrev = None
            else:
                required = self.fill_step / 2
        elif row is not None and row > (n - self.fill_step / 2):
            required = row - n + self.fill_step
        if required or buildrev:
            self.graph.build_nodes(nnodes=required, rev=buildrev)
            self.updateRowCount()

        if self.rowcount >= len(self.graph):
            return  # no need to update row count
        if row and row > self.rowcount:
            # asked row was already built, but views where not aware of this
            self.updateRowCount()
        elif rev is not None and rev <= self.graph[self.rowcount].rev:
            # asked rev was already built, but views where not aware of this
            self.updateRowCount()

    def loadall(self):
        self.timerHandle = self.startTimer(1)

    def timerEvent(self, event):
        if event.timerId() == self.timerHandle:
            self.showMessage.emit(_('filling (%d)') % (len(self.graph)))
            if self.graph.isfilled():
                self.killTimer(self.timerHandle)
                self.timerHandle = None
                self.showMessage.emit('')
                self.loaded.emit()
            # we only fill the graph data structures without telling
            # views until the model is loaded, to keep maximal GUI
            # reactivity
            elif not self.graph.build_nodes():
                self.killTimer(self.timerHandle)
                self.timerHandle = None
                self.updateRowCount()
                self.showMessage.emit('')
                self.loaded.emit()

    def updateRowCount(self):
        currentlen = self.rowcount
        newlen = len(self.graph)

        if newlen > self.rowcount:
            self.beginInsertRows(QModelIndex(), currentlen, newlen - 1)
            self.rowcount = newlen
            self.endInsertRows()

    def rowCount(self, parent):
        if parent.isValid():
            return 0
        return self.rowcount

    def columnCount(self, parent):
        if parent.isValid():
            return 0
        return len(self._columns)

    def maxWidthValueForColumn(self, col):
        if self.graph is None:
            return 'XXXX'
        column = self._columns[col]
        if column == 'Rev':
            return '8' * len(str(len(self.repo))) + '+'
        if column == 'Node':
            return '8' * 12 + '+'
        if column in ('LocalTime', 'UTCTime'):
            return hglib.displaytime(util.makedate())
        if column in ('Tags', 'Latest tags'):
            try:
                return sorted(self.repo.tags().keys(),
                              key=lambda x: len(x))[-1][:10]
            except IndexError:
                pass
        if column == 'Branch':
            try:
                return sorted(self.repo.branchtags().keys(),
                              key=lambda x: len(x))[-1]
            except IndexError:
                pass
        if column == 'Filename':
            return self.filename
        if column == 'Graph':
            res = self.col2x(self.graph.max_cols)
            return min(res, 150)
        if column == 'Changes':
            return 'Changes'
        # Fall through for Description
        return None

    def user_color(self, user):
        'deprecated, please replace with hgtk color scheme'
        if user not in self._user_colors:
            self._user_colors[user] = get_color(len(self._user_colors),
                                                self._user_colors.values())
        return self._user_colors[user]

    def namedbranch_color(self, branch):
        'deprecated, please replace with hgtk color scheme'
        if branch not in self._branch_colors:
            self._branch_colors[branch] = get_color(len(self._branch_colors))
        return self._branch_colors[branch]

    def col2x(self, col):
        return 2 * self.dotradius * col + self.dotradius / 2 + 8

    def graphctx(self, ctx, gnode):
        w = self.col2x(gnode.cols) + 10
        h = self.rowheight

        pix = QPixmap(w, h)
        pix.fill(QColor(0, 0, 0, 0))
        painter = QPainter(pix)
        try:
            self._drawgraphctx(painter, pix, ctx, gnode)
        finally:
            painter.end()
        return QVariant(pix)

    def _drawgraphctx(self, painter, pix, ctx, gnode):
        revset = self.revset
        h = pix.height()
        dot_y = h / 2

        painter.setRenderHint(QPainter.Antialiasing)

        pen = QPen(Qt.blue)
        pen.setWidth(2)
        painter.setPen(pen)

        lpen = QPen(pen)
        lpen.setColor(Qt.black)
        painter.setPen(lpen)
        if revset:

            def isactive(start, end, color, line_type, children, rev):
                return rev in revset and util.any(r in revset
                                                  for r in children)
        else:

            def isactive(start, end, color, line_type, children, rev):
                return True

        for y1, y4, lines in ((dot_y, dot_y + h, gnode.bottomlines),
                              (dot_y - h, dot_y, gnode.toplines)):
            y2 = y1 + 1 * (y4 - y1) / 4
            ymid = (y1 + y4) / 2
            y3 = y1 + 3 * (y4 - y1) / 4

            lines = sorted((isactive(*l), l) for l in lines)

            for active, (start, end, color, line_type, children, rev) in lines:
                lpen = QPen(pen)
                lpen.setColor(QColor(active and get_color(color) or "gray"))
                lpen.setStyle(get_style(line_type, active))
                lpen.setWidth(get_width(line_type, active))
                painter.setPen(lpen)
                x1 = self.col2x(start)
                x2 = self.col2x(end)
                path = QPainterPath()
                path.moveTo(x1, y1)
                path.cubicTo(x1, y2, x1, y2, (x1 + x2) / 2, ymid)
                path.cubicTo(x2, y3, x2, y3, x2, y4)
                painter.drawPath(path)

        # Draw node
        if revset and gnode.rev not in revset:
            dot_color = QColor("gray")
            radius = self.dotradius * 0.8
        else:
            dot_color = QColor(self.namedbranch_color(ctx.branch()))
            radius = self.dotradius
        dotcolor = dot_color.lighter()
        pencolor = dot_color.darker()
        truewhite = QColor("white")
        white = QColor("white")
        fillcolor = gnode.rev is None and white or dotcolor

        pen = QPen(pencolor)
        pen.setWidthF(1.5)
        painter.setPen(pen)

        centre_x = self.col2x(gnode.x)
        centre_y = h / 2

        def circle(r):
            rect = QRectF(centre_x - r, centre_y - r, 2 * r, 2 * r)
            painter.drawEllipse(rect)

        def closesymbol(s):
            rect_ = QRectF(centre_x - 1.5 * s, centre_y - 0.5 * s, 3 * s, s)
            painter.drawRect(rect_)

        def diamond(r):
            poly = QPolygonF([
                QPointF(centre_x - r, centre_y),
                QPointF(centre_x, centre_y - r),
                QPointF(centre_x + r, centre_y),
                QPointF(centre_x, centre_y + r),
                QPointF(centre_x - r, centre_y),
            ])
            painter.drawPolygon(poly)

        hiddenrev = ctx.hidden()
        if hiddenrev:
            painter.setBrush(truewhite)
            white.setAlpha(64)
            fillcolor.setAlpha(64)
        if ctx.thgmqappliedpatch():  # diamonds for patches
            symbolsize = radius / 1.5
            if hiddenrev:
                diamond(symbolsize)
            if ctx.thgwdparent():
                painter.setBrush(white)
                diamond(2 * 0.9 * symbolsize)
            painter.setBrush(fillcolor)
            diamond(symbolsize)
        elif ctx.thgmqunappliedpatch():
            symbolsize = radius / 1.5
            if hiddenrev:
                diamond(symbolsize)
            patchcolor = QColor('#dddddd')
            painter.setBrush(patchcolor)
            painter.setPen(patchcolor)
            diamond(symbolsize)
        elif ctx.extra().get('close'):
            symbolsize = 0.5 * radius
            if hiddenrev:
                closesymbol(symbolsize)
            painter.setBrush(fillcolor)
            closesymbol(symbolsize)
        else:  # circles for normal revisions
            symbolsize = 0.5 * radius
            if hiddenrev:
                circle(symbolsize)
            if ctx.thgwdparent():
                painter.setBrush(white)
                circle(0.9 * radius)
            painter.setBrush(fillcolor)
            circle(symbolsize)

    def invalidateCache(self):
        self._cache = []
        for a in ('_roleoffsets', ):
            if hasattr(self, a):
                delattr(self, a)

    @propertycache
    def _roleoffsets(self):
        return {
            Qt.DisplayRole: 0,
            Qt.ForegroundRole: len(self._columns),
            GraphRole: len(self._columns) * 2
        }

    def data(self, index, role):
        if not index.isValid():
            return nullvariant
        # font is not cached in self._cache since it is equal for all rows
        if role == Qt.FontRole:
            column = self._columns[index.column()]
            return self._columnfonts.get(column, nullvariant)
        if role not in self._roleoffsets:
            return nullvariant
        # repo may be changed while reading in case of postpull=rebase for
        # example, and result in RevlogError. (issue #429)
        try:
            return self.safedata(index, role)
        except error.RevlogError, e:
            if 'THGDEBUG' in os.environ:
                raise
            if role == Qt.DisplayRole:
                return QVariant(hglib.tounicode(str(e)))
            else:
                return nullvariant