PageRenderTime 51ms CodeModel.GetById 14ms app.highlight 32ms RepoModel.GetById 1ms app.codeStats 0ms

/tortoisehg/hgtk/logview/treemodel.py

https://bitbucket.org/tortoisehg/hgtk/
Python | 357 lines | 320 code | 22 blank | 15 comment | 38 complexity | 09a7a7156fd80dd8061790ef5ccac31d MD5 | raw file
  1# treemodel.py - changelog viewer data model
  2#
  3# Copyright 2008 Steve Borho <steve@borho.org>
  4#
  5# This software may be used and distributed according to the terms of the
  6# GNU General Public License version 2, incorporated herein by reference.
  7
  8''' Mercurial revision DAG visualization library
  9
 10  Implements a gtk.TreeModel which visualizes a Mercurial repository
 11  revision history.
 12
 13Portions of this code stolen mercilessly from bzr-gtk visualization
 14dialog.  Other portions stolen from graphlog extension.
 15'''
 16
 17import gtk
 18import gobject
 19import re
 20
 21from mercurial import util, error
 22from tortoisehg.util import hglib
 23from tortoisehg.hgtk import gtklib
 24
 25# treemodel row enumerated attributes
 26LINES = 0           # These elements come from the changelog walker
 27GRAPHNODE = 1
 28REVID = 2
 29LAST_LINES = 3
 30WFILE = 4
 31
 32BRANCH = 5          # calculated on demand, not cached
 33HEXID = 6
 34REVHEX = 7
 35LOCALTIME = 8
 36UTC = 9
 37
 38MESSAGE = 10        # calculated on demand, cached
 39COMMITER = 11
 40TAGS = 12
 41FGCOLOR = 13
 42AGE = 14
 43CHANGES = 15
 44SVNREV = 16
 45
 46class TreeModel(gtk.GenericTreeModel):
 47
 48    def __init__ (self, repo, graphdata, opts):
 49        gtk.GenericTreeModel.__init__(self)
 50        self.repo = repo
 51        self.outgoing = opts['outgoing']
 52        self.origtip = opts['orig-tip']
 53        self.npreviews = opts['npreviews']
 54        self.showgraph = opts['show-graph']
 55        self.graphdata = graphdata
 56        self.revisions, self.parents = {}, {}
 57        self.wcparents, self.tagrevs, self.branchtags = [], [], {}
 58        self.refresh()
 59
 60    def refresh(self):
 61        repo = self.repo
 62        oldtags, oldparents = self.tagrevs, self.wcparents
 63        try:
 64            oldbranches = [repo[n].rev() for n in self.branchtags.values()]
 65        except error.RepoLookupError:
 66            oldbranches = []
 67
 68        hglib.invalidaterepo(repo)
 69
 70        self.longsummary = repo.ui.configbool('tortoisehg', 'longsummary', False)
 71        self.set_author_color()
 72        self.hidetags = hglib.gethidetags(repo.ui)
 73
 74        self.curbookmark = hglib.get_repo_bookmarkcurrent(repo)
 75        try:
 76            self.wcparents = [x.rev() for x in repo.parents()]
 77            self.tagrevs = [repo[r].rev() for t, r in repo.tagslist()]
 78            self.branchtags = repo.branchtags()
 79        except util.Abort:
 80            pass
 81        brevs = [repo[n].rev() for n in self.branchtags.values()]
 82        allrevs = set(oldtags + oldparents + oldbranches +
 83                      brevs + self.wcparents + self.tagrevs)
 84        for rev in allrevs:
 85            if rev in self.revisions:
 86                del self.revisions[rev]
 87
 88        self.mqpatches = []
 89        if hasattr(self.repo, 'mq'):
 90            self.repo.mq.parse_series()
 91            self.mqpatches = [p.name for p in self.repo.mq.applied]
 92
 93    def on_get_flags(self):
 94        return gtk.TREE_MODEL_LIST_ONLY
 95
 96    def on_get_n_columns(self):
 97        return 13
 98
 99    def on_get_column_type(self, index):
100        if index == GRAPHNODE: return gobject.TYPE_PYOBJECT
101        if index == LINES: return gobject.TYPE_PYOBJECT
102        if index == REVID: return int
103        if index == LAST_LINES: return gobject.TYPE_PYOBJECT
104        if index == WFILE: return str
105
106        if index == BRANCH: return str
107        if index == HEXID: return str
108        if index == REVHEX: return str
109        if index == LOCALTIME: return str
110        if index == UTC: return str
111
112        if index == MESSAGE: return str
113        if index == COMMITER: return str
114        if index == TAGS: return str
115        if index == FGCOLOR: return str
116        if index == AGE: return str
117        if index == CHANGES: return str
118        if index == SVNREV: return str
119
120    def on_get_iter(self, path):
121        return path[0]
122
123    def on_get_path(self, rowref):
124        return rowref
125
126    def on_get_value(self, rowref, column):
127        (revid, graphnode, lines, path) = self.graphdata[rowref]
128
129        if column == REVID: return revid
130        if column == LINES: return lines
131        if column == LAST_LINES:
132            if rowref>0:
133                return self.graphdata[rowref-1][2]
134            return []
135        if column == WFILE: return path or ''
136
137        try:
138            ctx = self.repo[revid]
139        except IndexError:
140            return None
141
142        # non-cached columns
143
144        if column in (HEXID, REVHEX, BRANCH, LOCALTIME, UTC):
145            if column == HEXID:
146                return str(ctx)
147            if column == REVHEX:
148                hexid = gtklib.markup(str(ctx), face='monospace')
149                return '%s: %s' % (revid, hexid)
150            if column == BRANCH:
151                return ctx.branch()
152            if column == LOCALTIME:
153                return hglib.displaytime(ctx.date())
154            if column == UTC:
155                return hglib.utctime(ctx.date())
156
157        # cached columns
158
159        if revid not in self.revisions:
160            self.revisions[revid] = {}
161        cache = self.revisions[revid]
162
163        if cache.has_key(column):
164            return cache[column]
165
166        if column in (COMMITER, FGCOLOR):
167            cache[COMMITER] = hglib.toutf(hglib.username(ctx.user()))
168
169        if column == TAGS:
170            tags = self.repo.nodetags(ctx.node())
171            cache[TAGS] = hglib.toutf(', '.join(tags))
172
173        elif column == SVNREV:
174            extra = ctx.extra()
175            cvt = extra.get('convert_revision', '')
176            if cvt.startswith('svn:'):
177                rev = cvt.split('/', 1)[-1]
178                rev = rev.split('@', 1)[-1]
179            else:
180                rev = None
181            cache[SVNREV] = rev and hglib.toutf('r%s' % rev) or ''
182
183        elif column == AGE:
184            cache[AGE] = hglib.age(ctx.date())
185
186        elif column == FGCOLOR:
187            cache[FGCOLOR] = self.color_func(revid, cache[COMMITER])
188
189        elif column == CHANGES:
190            parent = self.parents.get(revid, None)
191            if parent is None:
192                parent = ctx.parents()[0].node()
193            M, A, R = self.repo.status(parent, ctx.node())[:3]
194            common = dict(color=gtklib.BLACK)
195            M = M and gtklib.markup(' %s ' % len(M),
196                             background=gtklib.PORANGE, **common) or ''
197            A = A and gtklib.markup(' %s ' % len(A),
198                             background=gtklib.PGREEN, **common) or ''
199            R = R and gtklib.markup(' %s ' % len(R),
200                             background=gtklib.PRED, **common) or ''
201            cache[CHANGES] = ''.join((M, A, R))
202
203        elif column in (MESSAGE, GRAPHNODE):
204            # convert to Unicode for valid string operations
205            summary = hglib.tounicode(ctx.description()).replace(u'\0', '')
206            if self.longsummary:
207                limit = 80
208                lines = summary.splitlines()
209                if lines:
210                    summary = lines.pop(0)
211                    while len(summary) < limit and lines:
212                        summary += u'  ' + lines.pop(0)
213                    summary = summary[0:limit]
214                else:
215                    summary = ''
216            else:
217                lines = summary.splitlines()
218                summary = lines and lines[0] or ''
219            escape = gtklib.markup_escape_text
220            summary = escape(hglib.toutf(summary))
221
222            node = ctx.node()
223            tags = self.repo.nodetags(node)
224            tstr = ''
225            for tag in tags:
226                if tag not in self.hidetags:
227                    bg = gtklib.PYELLOW
228                    if tag == self.curbookmark:
229                        bg = gtklib.PORANGE
230                    elif tag in self.mqpatches:
231                        bg = gtklib.PBLUE
232                    style = {'color': gtklib.BLACK, 'background': bg}
233                    tstr += gtklib.markup(' %s ' % tag, **style) + ' '
234
235            branch = ctx.branch()
236            bstr = ''
237            status = 0
238            if self.branchtags.get(branch) == node:
239                bstr += gtklib.markup(' %s ' % branch, color=gtklib.BLACK,
240                                      background=gtklib.PGREEN) + ' '
241                status = 8
242
243            if revid in self.wcparents:
244                sumstr = bstr + tstr + '<b><u>' + summary + '</u></b>'
245                status += 4
246            else:
247                sumstr = bstr + tstr + summary
248                status += 0
249
250            if node in self.outgoing:
251                # outgoing
252                if not self.showgraph:
253                    marker = hglib.toutf(u'\u2191 ') # up arrow
254                    sumstr = marker + sumstr
255                status += 1
256            elif revid >= self.origtip:
257                if revid >= len(self.repo) - self.npreviews:
258                    # incoming
259                    if not self.showgraph:
260                        marker = hglib.toutf(u'\u2193 ') # down arrow
261                        sumstr = marker + sumstr
262                    status += 3
263                else:
264                    # new
265                    status += 2
266
267            cache[MESSAGE] = sumstr
268            gcolumn, gcolor = graphnode
269            cache[GRAPHNODE] = (gcolumn, gcolor, status)
270
271        return cache[column]
272
273    def on_iter_next(self, rowref):
274        if rowref < len(self.graphdata) - 1:
275            return rowref+1
276        return None
277
278    def on_iter_children(self, parent):
279        if parent is None: return 0
280        return None
281
282    def on_iter_has_child(self, rowref):
283        return False
284
285    def on_iter_n_children(self, rowref):
286        if rowref is None: return len(self.graphdata)
287        return 0
288
289    def on_iter_nth_child(self, parent, n):
290        if parent is None: return n
291        return None
292
293    def on_iter_parent(self, child):
294        return None
295
296    def set_author_color(self):
297        # If user has configured authorcolor in [tortoisehg], color
298        # rows by author matches
299        self.author_pats = []
300        self.color_func =  self.text_color_default
301
302        if self.repo is not None:
303            for k, v in self.repo.ui.configitems('tortoisehg'):
304                if not k.startswith('authorcolor.'): continue
305                pat = k[12:]
306                self.author_pats.append((re.compile(pat, re.I), v))
307            enabled = self.repo.ui.configbool('tortoisehg', 'authorcolor', False)
308            if self.author_pats or enabled:
309                self.color_func = self.text_color_author
310
311    def text_color_default(self, rev, author):
312        return int(rev) >= self.origtip and gtklib.NEW_REV_COLOR or gtklib.NORMAL  
313    color_cache = {}
314
315    def hash_author(self, author):
316        h = hash(author) % 0x1000000; #hash, not HSV hue
317
318        r = h % 0x100
319        h /= 0x100
320
321        g = h % 0x100
322        h /= 0x100
323
324        b = h % 0x100
325
326        c= [r,g,b]
327
328        #For a dark theme, use upper two thirds of the RGB scale.
329        if gtklib.is_dark_theme():
330            c = [2*x/3 + 85 for x in c]
331        #Else, use bottom two thirds. 
332        else:
333            c = [2*x/3 for x in c]
334
335        return "#%02x%02x%02x" % (c[0],c[1],c[2])
336
337    def text_color_author(self, rev, author):
338        if int(rev) >= self.origtip:
339            return gtklib.NEW_REV_COLOR
340        for re, v in self.author_pats:
341            if (re.search(author)):
342                return v
343        if author not in self.color_cache:
344            color = self.hash_author(author)
345            self.color_cache[author] = color
346        return self.color_cache[author]
347
348    def set_parent(self, rev, parent):
349        self.parents[rev] = parent
350        if rev in self.revisions:
351            del self.revisions[rev]
352
353    def clear_parents(self):
354        for rev in self.parents.keys():
355            if rev in self.revisions:
356                del self.revisions[rev]
357        self.parents = {}