45
 
class GAnnotateWindow(Window):
 
 
45
class GAnnotateWindow(gtk.Window):
 
46
46
    """Annotate window."""
 
48
 
    def __init__(self, all=False, plain=False, parent=None, branch=None):
 
 
48
    def __init__(self, all=False, plain=False):
 
53
 
        Window.__init__(self, parent)
 
 
52
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
 
55
54
        self.set_icon(self.render_icon(gtk.STOCK_FIND, gtk.ICON_SIZE_BUTTON))
 
56
55
        self.annotate_colormap = AnnotateColorSaturation()
 
59
58
        self.revisions = {}
 
63
60
    def annotate(self, tree, branch, file_id):
 
64
61
        self.annotations = []
 
65
62
        self.branch = branch
 
67
64
        self.file_id = file_id
 
68
 
        self.revisionview.set_file_id(file_id)
 
69
65
        self.revision_id = getattr(tree, 'get_revision_id', 
 
70
66
                                   lambda: CURRENT_REVISION)()
 
72
 
        # [revision id, line number, author, revno, highlight color, line]
 
 
68
        # [revision id, line number, committer, revno, highlight color, line]
 
73
69
        self.annomodel = gtk.ListStore(gobject.TYPE_STRING,
 
74
70
                                       gobject.TYPE_STRING,
 
75
71
                                       gobject.TYPE_STRING,
 
 
83
79
            branch.repository.lock_read()
 
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)
 
88
80
            for line_no, (revision, revno, line)\
 
89
81
                    in enumerate(self._annotate(tree, file_id)):
 
90
82
                if revision.revision_id == last_seen and not self.all:
 
 
83
                    revno = committer = ""
 
93
85
                    last_seen = revision.revision_id
 
94
 
                    author = revision.get_apparent_author()
 
 
86
                    committer = revision.committer
 
96
88
                if revision.revision_id not in self.revisions:
 
97
89
                    self.revisions[revision.revision_id] = revision
 
99
91
                self.annomodel.append([revision.revision_id,
 
104
96
                                       line.rstrip("\r\n")
 
 
115
107
        self.annoview.set_model(self.annomodel)
 
116
108
        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)
 
121
110
    def jump_to_line(self, lineno):
 
122
111
        if lineno > len(self.annomodel) or lineno < 1:
 
 
132
121
        self.annoview.set_cursor(row)
 
133
122
        self.annoview.scroll_to_cell(row, use_align=True)
 
 
124
    def _dotted_revnos(self, repository, revision_id):
 
 
125
        """Return a dict of revision_id -> dotted revno
 
 
127
        :param repository: The repository to get the graph from
 
 
128
        :param revision_id: The last revision for which this info is needed
 
 
130
        graph = repository.get_revision_graph(revision_id)
 
 
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)
 
136
137
    def _annotate(self, tree, file_id):
 
137
138
        current_revision = FakeRevision(CURRENT_REVISION)
 
 
139
140
        current_revision.timestamp = time.time()
 
140
141
        current_revision.message = '[Not yet committed]'
 
141
142
        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()
 
148
148
            revision_id = self.revision_id
 
 
149
        dotted = self._dotted_revnos(repository, revision_id)
 
149
150
        revision_cache = RevisionCache(repository, self.revisions)
 
150
151
        for origin, text in tree.annotate_iter(file_id):
 
 
177
178
        return self.annomodel[path][REVISION_ID_COL]
 
179
 
    def _activate_selected_revision(self, w):
 
 
180
    def _show_log(self, w):
 
180
181
        rev_id = self._selected_revision()
 
181
 
        if not rev_id or rev_id == NULL_REVISION:
 
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
 
190
 
        self.back_button.set_sensitive(enable_back)
 
 
184
        self.logview.set_revision(self.revisions[rev_id])
 
192
186
    def _create(self):
 
193
 
        self.revisionview = self._create_log_view()
 
 
187
        self.logview = self._create_log_view()
 
194
188
        self.annoview = self._create_annotate_view()
 
196
 
        vbox = gtk.VBox(False)
 
 
190
        vbox = gtk.VBox(False, 12)
 
 
191
        vbox.set_border_width(12)
 
199
194
        sw = gtk.ScrolledWindow()
 
 
202
197
        sw.add(self.annoview)
 
203
198
        self.annoview.gwindow = self
 
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)
 
216
 
        vbox.pack_start(hbox, expand=False, fill=True)
 
218
201
        self.pane = pane = gtk.VPaned()
 
220
 
        pane.add2(self.revisionview)
 
 
203
        pane.add2(self.logview)
 
222
205
        vbox.pack_start(pane, expand=True, fill=True)
 
224
207
        self._search = SearchBox()
 
225
 
        swbox.pack_start(self._search, expand=False, fill=True)
 
 
208
        vbox.pack_start(self._search, expand=False, fill=True)
 
226
209
        accels = gtk.AccelGroup()
 
227
210
        accels.connect_group(gtk.keysyms.f, gtk.gdk.CONTROL_MASK,
 
228
211
                             gtk.ACCEL_LOCKED,
 
 
232
215
                             self._search_by_line)
 
233
216
        self.add_accel_group(accels)
 
 
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)
 
 
222
        vbox.pack_start(hbox, expand=False, fill=True)
 
237
226
    def _search_by_text(self, accel_group, window, key, modifiers):
 
 
242
231
        self._search.show_for('line')
 
243
232
        self._search.set_target(self.annoview, LINE_NUM_COL)
 
245
 
    def line_diff(self, tv, path, tvc):
 
 
234
    def row_diff(self, tv, path, tvc):
 
247
236
        revision = self.annotations[row]
 
248
237
        repository = self.branch.repository
 
 
255
244
                tree2 = repository.revision_tree(revision.parent_ids[0])
 
257
246
                tree2 = repository.revision_tree(NULL_REVISION)
 
258
 
        from bzrlib.plugins.gtk.diff import DiffWindow
 
 
247
        from bzrlib.plugins.gtk.viz.diffwin import DiffWindow
 
259
248
        window = DiffWindow()
 
260
 
        window.set_diff("Diff for line %d" % (row+1), tree1, tree2)
 
 
249
        window.set_diff("Diff for row %d" % (row+1), tree1, tree2)
 
261
250
        window.set_file(tree1.id2path(self.file_id))
 
 
265
254
    def _create_annotate_view(self):
 
266
255
        tv = gtk.TreeView()
 
267
256
        tv.set_rules_hint(False)
 
268
 
        tv.connect("cursor-changed", self._activate_selected_revision)
 
 
257
        tv.connect("cursor-changed", self._show_log)
 
270
 
        tv.connect("row-activated", self.line_diff)
 
 
259
        tv.connect("row-activated", self.row_diff)
 
272
261
        cell = gtk.CellRendererText()
 
273
262
        cell.set_property("xalign", 1.0)
 
 
323
312
    def _create_log_view(self):
 
324
 
        lv = RevisionView(self._branch)
 
328
 
    def _create_back_button(self):
 
 
318
    def _create_button_box(self):
 
 
319
        box = gtk.HButtonBox()
 
 
320
        box.set_layout(gtk.BUTTONBOX_END)
 
 
323
        button = gtk.Button()
 
 
324
        button.set_use_stock(True)
 
 
325
        button.set_label("gtk-close")
 
 
326
        button.connect("clicked", lambda w: self.destroy())
 
 
329
        box.pack_start(button, expand=False, fill=False)
 
 
333
    def _create_prev_button(self):
 
 
334
        box = gtk.HButtonBox()
 
 
335
        box.set_layout(gtk.BUTTONBOX_START)
 
329
338
        button = gtk.Button()
 
330
339
        button.set_use_stock(True)
 
331
340
        button.set_label("gtk-go-back")
 
332
341
        button.connect("clicked", lambda w: self.go_back())
 
333
 
        button.set_relief(gtk.RELIEF_NONE)
 
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)
 
344
 
        button.set_sensitive(False)
 
 
343
        box.pack_start(button, expand=False, fill=False)
 
347
346
    def go_back(self):
 
348
 
        last_tree = self.tree
 
349
347
        rev_id = self._selected_revision()
 
350
348
        parent_id = self.revisions[rev_id].parent_ids[0]
 
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)
 
356
 
            self._no_back.add(parent_id)
 
357
 
            self.back_button.set_sensitive(False)
 
359
 
    def go_forward(self):
 
360
 
        if len(self.history) == 0:
 
362
 
        target_tree = self.history.pop()
 
363
 
        if len(self.history) == 0:
 
364
 
            self.forward_button.set_sensitive(False)
 
365
 
        self._go(target_tree)
 
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)
 
 
349
        tree = self.branch.repository.revision_tree(parent_id)
 
 
350
        if self.file_id in tree:
 
 
351
            offset = self.get_scroll_offset(tree)
 
371
352
            (row,), col = self.annoview.get_cursor()
 
372
 
            self.annotate(target_tree, self.branch, self.file_id)
 
376
 
            self.annoview.set_cursor(new_row)
 
 
353
            self.annotate(tree, self.branch, self.file_id)
 
 
354
            self.annoview.set_cursor(row+offset)
 
381
356
    def get_scroll_offset(self, tree):
 
382
357
        old = self.tree.get_file(self.file_id)
 
 
392
368
class FakeRevision:
 
393
369
    """ A fake revision.
 
395
371
    For when a revision is referenced but not present.
 
398
 
    def __init__(self, revision_id, committer='?', nick=None):
 
 
374
    def __init__(self, revision_id, committer='?'):
 
399
375
        self.revision_id = revision_id
 
400
376
        self.parent_ids = []
 
401
377
        self.committer = committer
 
402
378
        self.message = "?"
 
403
379
        self.timestamp = 0.0
 
404
380
        self.timezone = 0
 
407
 
    def get_apparent_author(self):
 
408
 
        return self.committer
 
411
383
class RevisionCache(object):