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 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())
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 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 = []
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
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
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