/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz

« back to all changes in this revision

Viewing changes to annotate/gannotate.py

  • Committer: Jelmer Vernooij
  • Date: 2008-06-28 17:13:46 UTC
  • mto: This revision was merged to the branch mainline in revision 517.
  • Revision ID: jelmer@samba.org-20080628171346-ob42iggcc663s4xz
Show full license details in about dialog.

Show diffs side-by-side

added added

removed removed

Lines of Context:
28
28
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
29
29
 
30
30
from colormap import AnnotateColorMap, AnnotateColorSaturation
31
 
from logview import LogView
32
 
from spanselector import SpanSelector
 
31
from bzrlib.plugins.gtk.revisionview import RevisionView
 
32
from bzrlib.plugins.gtk.window import Window
33
33
 
34
34
 
35
35
(
42
42
) = range(6)
43
43
 
44
44
 
45
 
class GAnnotateWindow(gtk.Window):
 
45
class GAnnotateWindow(Window):
46
46
    """Annotate window."""
47
47
 
48
 
    def __init__(self, all=False, plain=False):
 
48
    def __init__(self, all=False, plain=False, parent=None, branch=None):
49
49
        self.all = all
50
50
        self.plain = plain
 
51
        self._branch = branch
51
52
        
52
 
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
 
53
        Window.__init__(self, parent)
53
54
        
54
55
        self.set_icon(self.render_icon(gtk.STOCK_FIND, gtk.ICON_SIZE_BUTTON))
55
56
        self.annotate_colormap = AnnotateColorSaturation()
56
57
 
57
58
        self._create()
58
59
        self.revisions = {}
 
60
        self.history = []
 
61
        self._no_back = set()
59
62
 
60
63
    def annotate(self, tree, branch, file_id):
61
64
        self.annotations = []
62
65
        self.branch = branch
63
66
        self.tree = tree
64
67
        self.file_id = file_id
 
68
        self.revisionview.set_file_id(file_id)
65
69
        self.revision_id = getattr(tree, 'get_revision_id', 
66
70
                                   lambda: CURRENT_REVISION)()
67
71
        
68
 
        # [revision id, line number, committer, revno, highlight color, line]
 
72
        # [revision id, line number, author, revno, highlight color, line]
69
73
        self.annomodel = gtk.ListStore(gobject.TYPE_STRING,
70
74
                                       gobject.TYPE_STRING,
71
75
                                       gobject.TYPE_STRING,
77
81
        try:
78
82
            branch.lock_read()
79
83
            branch.repository.lock_read()
 
84
            self.dotted = {}
 
85
            revno_map = self.branch.get_revision_id_to_revno_map()
 
86
            for revision_id, revno in revno_map.iteritems():
 
87
                self.dotted[revision_id] = '.'.join(str(num) for num in revno)
80
88
            for line_no, (revision, revno, line)\
81
89
                    in enumerate(self._annotate(tree, file_id)):
82
90
                if revision.revision_id == last_seen and not self.all:
83
 
                    revno = committer = ""
 
91
                    revno = author = ""
84
92
                else:
85
93
                    last_seen = revision.revision_id
86
 
                    committer = revision.committer
 
94
                    author = revision.get_apparent_author()
87
95
 
88
96
                if revision.revision_id not in self.revisions:
89
97
                    self.revisions[revision.revision_id] = revision
90
98
 
91
99
                self.annomodel.append([revision.revision_id,
92
100
                                       line_no + 1,
93
 
                                       committer,
 
101
                                       author,
94
102
                                       revno,
95
103
                                       None,
96
104
                                       line.rstrip("\r\n")
106
114
 
107
115
        self.annoview.set_model(self.annomodel)
108
116
        self.annoview.grab_focus()
 
117
        my_revno = self.dotted.get(self.revision_id, 'current')
 
118
        title = '%s (%s) - gannotate' % (self.tree.id2path(file_id), my_revno)
 
119
        self.set_title(title)
109
120
 
110
121
    def jump_to_line(self, lineno):
111
122
        if lineno > len(self.annomodel) or lineno < 1:
121
132
        self.annoview.set_cursor(row)
122
133
        self.annoview.scroll_to_cell(row, use_align=True)
123
134
 
124
 
    def _dotted_revnos(self, repository, revision_id):
125
 
        """Return a dict of revision_id -> dotted revno
126
 
        
127
 
        :param repository: The repository to get the graph from
128
 
        :param revision_id: The last revision for which this info is needed
129
 
        """
130
 
        graph = repository.get_revision_graph(revision_id)
131
 
        dotted = {}
132
 
        for n, revision_id, d, revno, e in tsort.merge_sort(graph, 
133
 
            revision_id, generate_revno=True):
134
 
            dotted[revision_id] = '.'.join(str(num) for num in revno)
135
 
        return dotted
136
135
 
137
136
    def _annotate(self, tree, file_id):
138
137
        current_revision = FakeRevision(CURRENT_REVISION)
140
139
        current_revision.timestamp = time.time()
141
140
        current_revision.message = '[Not yet committed]'
142
141
        current_revision.parent_ids = tree.get_parent_ids()
 
142
        current_revision.properties['branch-nick'] = self.branch.nick
143
143
        current_revno = '%d?' % (self.branch.revno() + 1)
144
144
        repository = self.branch.repository
145
145
        if self.revision_id == CURRENT_REVISION:
146
146
            revision_id = self.branch.last_revision()
147
147
        else:
148
148
            revision_id = self.revision_id
149
 
        dotted = self._dotted_revnos(repository, revision_id)
150
149
        revision_cache = RevisionCache(repository, self.revisions)
151
150
        for origin, text in tree.annotate_iter(file_id):
152
151
            rev_id = origin
156
155
            else:
157
156
                try:
158
157
                    revision = revision_cache.get_revision(rev_id)
159
 
                    revno = dotted.get(rev_id, 'merge')
 
158
                    revno = self.dotted.get(rev_id, 'merge')
160
159
                    if len(revno) > 15:
161
160
                        revno = 'merge'
162
161
                except NoSuchRevision:
177
176
            return None
178
177
        return self.annomodel[path][REVISION_ID_COL]
179
178
 
180
 
    def _show_log(self, w):
 
179
    def _activate_selected_revision(self, w):
181
180
        rev_id = self._selected_revision()
182
 
        if rev_id is None:
 
181
        if not rev_id or rev_id == NULL_REVISION:
183
182
            return
184
 
        self.logview.set_revision(self.revisions[rev_id])
 
183
        selected = self.revisions[rev_id]
 
184
        self.revisionview.set_revision(selected)
 
185
        if (len(selected.parent_ids) != 0 and selected.parent_ids[0] not in
 
186
            self._no_back):
 
187
            enable_back = True
 
188
        else:
 
189
            enable_back = False
 
190
        self.back_button.set_sensitive(enable_back)
185
191
 
186
192
    def _create(self):
187
 
        self.logview = self._create_log_view()
 
193
        self.revisionview = self._create_log_view()
188
194
        self.annoview = self._create_annotate_view()
189
195
 
190
 
        vbox = gtk.VBox(False, 12)
191
 
        vbox.set_border_width(12)
 
196
        vbox = gtk.VBox(False)
192
197
        vbox.show()
193
198
 
194
199
        sw = gtk.ScrolledWindow()
197
202
        sw.add(self.annoview)
198
203
        self.annoview.gwindow = self
199
204
        sw.show()
 
205
 
 
206
        swbox = gtk.VBox()
 
207
        swbox.pack_start(sw)
 
208
        swbox.show()
 
209
 
 
210
        hbox = gtk.HBox(False, 6)
 
211
        self.back_button = self._create_back_button()
 
212
        hbox.pack_start(self.back_button, expand=False, fill=True)
 
213
        self.forward_button = self._create_forward_button()
 
214
        hbox.pack_start(self.forward_button, expand=False, fill=True)
 
215
        hbox.show()
 
216
        vbox.pack_start(hbox, expand=False, fill=True)
200
217
        
201
218
        self.pane = pane = gtk.VPaned()
202
 
        pane.add1(sw)
203
 
        pane.add2(self.logview)
 
219
        pane.add1(swbox)
 
220
        pane.add2(self.revisionview)
204
221
        pane.show()
205
222
        vbox.pack_start(pane, expand=True, fill=True)
206
223
 
207
224
        self._search = SearchBox()
208
 
        vbox.pack_start(self._search, expand=False, fill=True)
 
225
        swbox.pack_start(self._search, expand=False, fill=True)
209
226
        accels = gtk.AccelGroup()
210
227
        accels.connect_group(gtk.keysyms.f, gtk.gdk.CONTROL_MASK,
211
228
                             gtk.ACCEL_LOCKED,
215
232
                             self._search_by_line)
216
233
        self.add_accel_group(accels)
217
234
 
218
 
        hbox = gtk.HBox(True, 6)
219
 
        hbox.pack_start(self._create_prev_button(), expand=False, fill=True)
220
 
        hbox.pack_end(self._create_button_box(), expand=False, fill=True)
221
 
        hbox.show()
222
 
        vbox.pack_start(hbox, expand=False, fill=True)
223
 
 
224
235
        self.add(vbox)
225
236
 
226
237
    def _search_by_text(self, accel_group, window, key, modifiers):
231
242
        self._search.show_for('line')
232
243
        self._search.set_target(self.annoview, LINE_NUM_COL)
233
244
 
234
 
    def row_diff(self, tv, path, tvc):
 
245
    def line_diff(self, tv, path, tvc):
235
246
        row = path[0]
236
247
        revision = self.annotations[row]
237
248
        repository = self.branch.repository
244
255
                tree2 = repository.revision_tree(revision.parent_ids[0])
245
256
            else:
246
257
                tree2 = repository.revision_tree(NULL_REVISION)
247
 
        from bzrlib.plugins.gtk.viz.diffwin import DiffWindow
 
258
        from bzrlib.plugins.gtk.diff import DiffWindow
248
259
        window = DiffWindow()
249
 
        window.set_diff("Diff for row %d" % (row+1), tree1, tree2)
 
260
        window.set_diff("Diff for line %d" % (row+1), tree1, tree2)
250
261
        window.set_file(tree1.id2path(self.file_id))
251
262
        window.show()
252
263
 
254
265
    def _create_annotate_view(self):
255
266
        tv = gtk.TreeView()
256
267
        tv.set_rules_hint(False)
257
 
        tv.connect("cursor-changed", self._show_log)
 
268
        tv.connect("cursor-changed", self._activate_selected_revision)
258
269
        tv.show()
259
 
        tv.connect("row-activated", self.row_diff)
 
270
        tv.connect("row-activated", self.line_diff)
260
271
 
261
272
        cell = gtk.CellRendererText()
262
273
        cell.set_property("xalign", 1.0)
310
321
        return tv
311
322
 
312
323
    def _create_log_view(self):
313
 
        lv = LogView()
 
324
        lv = RevisionView(self._branch)
314
325
        lv.show()
315
 
 
316
326
        return lv
317
327
 
318
 
    def _create_button_box(self):
319
 
        box = gtk.HButtonBox()
320
 
        box.set_layout(gtk.BUTTONBOX_END)
321
 
        box.show()
322
 
 
323
 
        button = gtk.Button()
324
 
        button.set_use_stock(True)
325
 
        button.set_label("gtk-close")
326
 
        button.connect("clicked", lambda w: self.destroy())
327
 
        button.show()
328
 
 
329
 
        box.pack_start(button, expand=False, fill=False)
330
 
 
331
 
        return box
332
 
 
333
 
    def _create_prev_button(self):
334
 
        box = gtk.HButtonBox()
335
 
        box.set_layout(gtk.BUTTONBOX_START)
336
 
        box.show()
337
 
        
 
328
    def _create_back_button(self):
338
329
        button = gtk.Button()
339
330
        button.set_use_stock(True)
340
331
        button.set_label("gtk-go-back")
341
332
        button.connect("clicked", lambda w: self.go_back())
342
 
        button.show()
343
 
        box.pack_start(button, expand=False, fill=False)
344
 
        return box
 
333
        button.set_relief(gtk.RELIEF_NONE)
 
334
        button.show()
 
335
        return button
 
336
 
 
337
    def _create_forward_button(self):
 
338
        button = gtk.Button()
 
339
        button.set_use_stock(True)
 
340
        button.set_label("gtk-go-forward")
 
341
        button.connect("clicked", lambda w: self.go_forward())
 
342
        button.set_relief(gtk.RELIEF_NONE)
 
343
        button.show()
 
344
        button.set_sensitive(False)
 
345
        return button
345
346
 
346
347
    def go_back(self):
 
348
        last_tree = self.tree
347
349
        rev_id = self._selected_revision()
348
350
        parent_id = self.revisions[rev_id].parent_ids[0]
349
 
        tree = self.branch.repository.revision_tree(parent_id)
350
 
        if self.file_id in tree:
351
 
            offset = self.get_scroll_offset(tree)
 
351
        target_tree = self.branch.repository.revision_tree(parent_id)
 
352
        if self._go(target_tree):
 
353
            self.history.append(last_tree)
 
354
            self.forward_button.set_sensitive(True)
 
355
        else:
 
356
            self._no_back.add(parent_id)
 
357
            self.back_button.set_sensitive(False)
 
358
 
 
359
    def go_forward(self):
 
360
        if len(self.history) == 0:
 
361
            return
 
362
        target_tree = self.history.pop()
 
363
        if len(self.history) == 0:
 
364
            self.forward_button.set_sensitive(False)
 
365
        self._go(target_tree)
 
366
 
 
367
    def _go(self, target_tree):
 
368
        rev_id = self._selected_revision()
 
369
        if self.file_id in target_tree:
 
370
            offset = self.get_scroll_offset(target_tree)
352
371
            (row,), col = self.annoview.get_cursor()
353
 
            self.annotate(tree, self.branch, self.file_id)
354
 
            self.annoview.set_cursor(row+offset)
 
372
            self.annotate(target_tree, self.branch, self.file_id)
 
373
            new_row = row+offset
 
374
            if new_row < 0:
 
375
                new_row = 0
 
376
            self.annoview.set_cursor(new_row)
 
377
            return True
 
378
        else:
 
379
            return False
355
380
 
356
381
    def get_scroll_offset(self, tree):
357
382
        old = self.tree.get_file(self.file_id)
364
389
                return j - i
365
390
 
366
391
 
367
 
 
368
392
class FakeRevision:
369
393
    """ A fake revision.
370
394
 
371
395
    For when a revision is referenced but not present.
372
396
    """
373
397
 
374
 
    def __init__(self, revision_id, committer='?'):
 
398
    def __init__(self, revision_id, committer='?', nick=None):
375
399
        self.revision_id = revision_id
376
400
        self.parent_ids = []
377
401
        self.committer = committer
378
402
        self.message = "?"
379
403
        self.timestamp = 0.0
380
404
        self.timezone = 0
 
405
        self.properties = {}
 
406
 
 
407
    def get_apparent_author(self):
 
408
        return self.committer
381
409
 
382
410
 
383
411
class RevisionCache(object):