/tortoisehg/hgtk/logview/treemodel.py

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