44
class GAnnotateWindow(gtk.Window):
45
class GAnnotateWindow(Window):
45
46
"""Annotate window."""
47
def __init__(self, all=False, plain=False):
48
def __init__(self, all=False, plain=False, parent=None):
51
gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
52
Window.__init__(self, parent)
53
54
self.set_icon(self.render_icon(gtk.STOCK_FIND, gtk.ICON_SIZE_BUTTON))
54
55
self.annotate_colormap = AnnotateColorSaturation()
57
58
self.revisions = {}
59
62
def annotate(self, tree, branch, file_id):
60
63
self.annotations = []
61
64
self.branch = branch
63
66
self.file_id = file_id
67
self.revisionview.set_file_id(file_id)
64
68
self.revision_id = getattr(tree, 'get_revision_id',
65
69
lambda: CURRENT_REVISION)()
67
# [revision id, line number, committer, revno, highlight color, line]
71
# [revision id, line number, author, revno, highlight color, line]
68
72
self.annomodel = gtk.ListStore(gobject.TYPE_STRING,
69
73
gobject.TYPE_STRING,
70
74
gobject.TYPE_STRING,
78
82
branch.repository.lock_read()
84
revno_map = self.branch.get_revision_id_to_revno_map()
85
for revision_id, revno in revno_map.iteritems():
86
self.dotted[revision_id] = '.'.join(str(num) for num in revno)
79
87
for line_no, (revision, revno, line)\
80
88
in enumerate(self._annotate(tree, file_id)):
81
89
if revision.revision_id == last_seen and not self.all:
82
revno = committer = ""
84
92
last_seen = revision.revision_id
85
committer = revision.committer
93
author = revision.get_apparent_author()
87
95
if revision.revision_id not in self.revisions:
88
96
self.revisions[revision.revision_id] = revision
90
98
self.annomodel.append([revision.revision_id,
95
103
line.rstrip("\r\n")
106
114
self.annoview.set_model(self.annomodel)
107
115
self.annoview.grab_focus()
116
my_revno = self.dotted.get(self.revision_id, 'current')
117
title = '%s (%s) - gannotate' % (self.tree.id2path(file_id), my_revno)
118
self.set_title(title)
109
120
def jump_to_line(self, lineno):
110
121
if lineno > len(self.annomodel) or lineno < 1:
120
131
self.annoview.set_cursor(row)
121
132
self.annoview.scroll_to_cell(row, use_align=True)
123
def _dotted_revnos(self, repository, revision_id):
124
"""Return a dict of revision_id -> dotted revno
126
:param repository: The repository to get the graph from
127
:param revision_id: The last revision for which this info is needed
129
graph = repository.get_revision_graph(revision_id)
131
for n, revision_id, d, revno, e in tsort.merge_sort(graph,
132
revision_id, generate_revno=True):
133
dotted[revision_id] = '.'.join(str(num) for num in revno)
136
135
def _annotate(self, tree, file_id):
137
136
current_revision = FakeRevision(CURRENT_REVISION)
139
138
current_revision.timestamp = time.time()
140
139
current_revision.message = '[Not yet committed]'
141
140
current_revision.parent_ids = tree.get_parent_ids()
141
current_revision.properties['branch-nick'] = self.branch.nick
142
142
current_revno = '%d?' % (self.branch.revno() + 1)
143
143
repository = self.branch.repository
144
144
if self.revision_id == CURRENT_REVISION:
145
145
revision_id = self.branch.last_revision()
147
147
revision_id = self.revision_id
148
dotted = self._dotted_revnos(repository, revision_id)
149
148
revision_cache = RevisionCache(repository, self.revisions)
150
149
for origin, text in tree.annotate_iter(file_id):
177
176
return self.annomodel[path][REVISION_ID_COL]
179
def _show_log(self, w):
178
def _activate_selected_revision(self, w):
180
179
rev_id = self._selected_revision()
181
180
if rev_id is None:
183
self.logview.set_revision(self.revisions[rev_id])
182
selected = self.revisions[rev_id]
183
self.revisionview.set_revision(selected)
184
if (len(selected.parent_ids) != 0 and selected.parent_ids[0] not in
189
self.back_button.set_sensitive(enable_back)
185
191
def _create(self):
186
self.logview = self._create_log_view()
192
self.revisionview = self._create_log_view()
187
193
self.annoview = self._create_annotate_view()
189
vbox = gtk.VBox(False, 12)
190
vbox.set_border_width(12)
195
vbox = gtk.VBox(False)
193
198
sw = gtk.ScrolledWindow()
196
201
sw.add(self.annoview)
197
202
self.annoview.gwindow = self
209
hbox = gtk.HBox(False, 6)
210
self.back_button = self._create_back_button()
211
hbox.pack_start(self.back_button, expand=False, fill=True)
212
self.forward_button = self._create_forward_button()
213
hbox.pack_start(self.forward_button, expand=False, fill=True)
215
vbox.pack_start(hbox, expand=False, fill=True)
200
217
self.pane = pane = gtk.VPaned()
202
pane.add2(self.logview)
219
pane.add2(self.revisionview)
204
221
vbox.pack_start(pane, expand=True, fill=True)
206
223
self._search = SearchBox()
207
vbox.pack_start(self._search, expand=False, fill=True)
224
swbox.pack_start(self._search, expand=False, fill=True)
208
225
accels = gtk.AccelGroup()
209
226
accels.connect_group(gtk.keysyms.f, gtk.gdk.CONTROL_MASK,
210
227
gtk.ACCEL_LOCKED,
214
231
self._search_by_line)
215
232
self.add_accel_group(accels)
217
hbox = gtk.HBox(True, 6)
218
hbox.pack_start(self._create_prev_button(), expand=False, fill=True)
219
hbox.pack_end(self._create_button_box(), expand=False, fill=True)
221
vbox.pack_start(hbox, expand=False, fill=True)
225
236
def _search_by_text(self, accel_group, window, key, modifiers):
230
241
self._search.show_for('line')
231
242
self._search.set_target(self.annoview, LINE_NUM_COL)
233
def row_diff(self, tv, path, tvc):
244
def line_diff(self, tv, path, tvc):
235
246
revision = self.annotations[row]
236
247
repository = self.branch.repository
253
264
def _create_annotate_view(self):
254
265
tv = gtk.TreeView()
255
266
tv.set_rules_hint(False)
256
tv.connect("cursor-changed", self._show_log)
267
tv.connect("cursor-changed", self._activate_selected_revision)
258
tv.connect("row-activated", self.row_diff)
269
tv.connect("row-activated", self.line_diff)
260
271
cell = gtk.CellRendererText()
261
272
cell.set_property("xalign", 1.0)
311
322
def _create_log_view(self):
316
def _create_button_box(self):
317
box = gtk.HButtonBox()
318
box.set_layout(gtk.BUTTONBOX_END)
321
button = gtk.Button()
322
button.set_use_stock(True)
323
button.set_label("gtk-close")
324
button.connect("clicked", lambda w: self.destroy())
327
box.pack_start(button, expand=False, fill=False)
331
def _create_prev_button(self):
332
box = gtk.HButtonBox()
333
box.set_layout(gtk.BUTTONBOX_START)
327
def _create_back_button(self):
336
328
button = gtk.Button()
337
329
button.set_use_stock(True)
338
330
button.set_label("gtk-go-back")
339
331
button.connect("clicked", lambda w: self.go_back())
341
box.pack_start(button, expand=False, fill=False)
332
button.set_relief(gtk.RELIEF_NONE)
336
def _create_forward_button(self):
337
button = gtk.Button()
338
button.set_use_stock(True)
339
button.set_label("gtk-go-forward")
340
button.connect("clicked", lambda w: self.go_forward())
341
button.set_relief(gtk.RELIEF_NONE)
343
button.set_sensitive(False)
344
346
def go_back(self):
347
last_tree = self.tree
345
348
rev_id = self._selected_revision()
346
349
parent_id = self.revisions[rev_id].parent_ids[0]
347
tree = self.branch.repository.revision_tree(parent_id)
348
if self.file_id in tree:
349
offset = self.get_scroll_offset(tree)
350
target_tree = self.branch.repository.revision_tree(parent_id)
351
if self._go(target_tree):
352
self.history.append(last_tree)
353
self.forward_button.set_sensitive(True)
355
self._no_back.add(parent_id)
356
self.back_button.set_sensitive(False)
358
def go_forward(self):
359
if len(self.history) == 0:
361
target_tree = self.history.pop()
362
if len(self.history) == 0:
363
self.forward_button.set_sensitive(False)
364
self._go(target_tree)
366
def _go(self, target_tree):
367
rev_id = self._selected_revision()
368
if self.file_id in target_tree:
369
offset = self.get_scroll_offset(target_tree)
350
370
(row,), col = self.annoview.get_cursor()
351
self.annotate(tree, self.branch, self.file_id)
352
self.annoview.set_cursor(row+offset)
371
self.annotate(target_tree, self.branch, self.file_id)
375
self.annoview.set_cursor(new_row)
354
380
def get_scroll_offset(self, tree):
355
381
old = self.tree.get_file(self.file_id)
366
391
class FakeRevision:
367
392
""" A fake revision.
369
394
For when a revision is referenced but not present.
372
def __init__(self, revision_id, committer='?'):
397
def __init__(self, revision_id, committer='?', nick=None):
373
398
self.revision_id = revision_id
374
399
self.parent_ids = []
375
400
self.committer = committer
376
401
self.message = "?"
377
402
self.timestamp = 0.0
378
403
self.timezone = 0
406
def get_apparent_author(self):
407
return self.committer
382
410
class RevisionCache(object):