/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: Szilveszter Farkas (Phanatic)
  • Date: 2006-12-18 10:34:36 UTC
  • Revision ID: szilveszter.farkas@gmail.com-20061218103436-4j6mi3x9m9o5273q
Implemented annotate functionality (Fixed: #73786).

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005 Dan Loda <danloda@gmail.com>
 
2
 
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
 
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
 
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
import time
 
18
 
 
19
import pygtk
 
20
pygtk.require("2.0")
 
21
import gobject
 
22
import gtk
 
23
import pango
 
24
 
 
25
from bzrlib import tsort
 
26
from bzrlib.errors import NoSuchRevision
 
27
from bzrlib.revision import NULL_REVISION
 
28
 
 
29
from colormap import AnnotateColorMap, AnnotateColorSaturation
 
30
from logview import LogView
 
31
from spanselector import SpanSelector
 
32
 
 
33
 
 
34
(
 
35
    REVISION_ID_COL,
 
36
    LINE_NUM_COL,
 
37
    COMMITTER_COL,
 
38
    REVNO_COL,
 
39
    HIGHLIGHT_COLOR_COL,
 
40
    TEXT_LINE_COL
 
41
) = range(6)
 
42
 
 
43
 
 
44
class GAnnotateWindow(gtk.Window):
 
45
    """Annotate window."""
 
46
 
 
47
    def __init__(self, all=False, plain=False):
 
48
        self.all = all
 
49
        self.plain = plain
 
50
        
 
51
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
 
52
        
 
53
        self.set_icon(self.render_icon(gtk.STOCK_FIND, gtk.ICON_SIZE_BUTTON))
 
54
        self.annotate_colormap = AnnotateColorSaturation()
 
55
 
 
56
        self._create()
 
57
 
 
58
        if self.plain:
 
59
            self.span_selector.hide()
 
60
 
 
61
    def annotate(self, branch, file_id, revision_id=None):
 
62
        self.revisions = {}
 
63
        self.annotations = []
 
64
        self.branch = branch
 
65
        self.file_id = file_id
 
66
        self.revision_id = revision_id
 
67
        
 
68
        # [revision id, line number, committer, revno, highlight color, line]
 
69
        self.annomodel = gtk.ListStore(gobject.TYPE_STRING,
 
70
                                       gobject.TYPE_STRING,
 
71
                                       gobject.TYPE_STRING,
 
72
                                       gobject.TYPE_STRING,
 
73
                                       gobject.TYPE_STRING,
 
74
                                       gobject.TYPE_STRING)
 
75
        
 
76
        last_seen = None
 
77
        try:
 
78
            branch.lock_read()
 
79
            branch.repository.lock_read()
 
80
            for line_no, (revision, revno, line)\
 
81
                    in enumerate(self._annotate(branch, file_id, revision_id)):
 
82
                if revision.revision_id == last_seen and not self.all:
 
83
                    revno = committer = ""
 
84
                else:
 
85
                    last_seen = revision.revision_id
 
86
                    committer = revision.committer
 
87
 
 
88
                if revision.revision_id not in self.revisions:
 
89
                    self.revisions[revision.revision_id] = revision
 
90
 
 
91
                self.annomodel.append([revision.revision_id,
 
92
                                       line_no + 1,
 
93
                                       committer,
 
94
                                       revno,
 
95
                                       None,
 
96
                                       line.rstrip("\r\n")
 
97
                                      ])
 
98
                self.annotations.append(revision)
 
99
 
 
100
            if not self.plain:
 
101
                self._set_oldest_newest()
 
102
                # Recall that calling activate_default will emit "span-changed",
 
103
                # so self._span_changed_cb will take care of initial highlighting
 
104
                self.span_selector.activate_default()
 
105
        finally:
 
106
            branch.repository.unlock()
 
107
            branch.unlock()
 
108
 
 
109
        self.annoview.set_model(self.annomodel)
 
110
        self.annoview.grab_focus()
 
111
 
 
112
    def jump_to_line(self, lineno):
 
113
        if lineno > len(self.annomodel) or lineno < 1:
 
114
            row = 0
 
115
            # FIXME:should really deal with this in the gui. Perhaps a status
 
116
            # bar?
 
117
            print("gannotate: Line number %d does't exist. Defaulting to "
 
118
                  "line 1." % lineno)
 
119
            return
 
120
        else:
 
121
            row = lineno - 1
 
122
 
 
123
        self.annoview.set_cursor(row)
 
124
        self.annoview.scroll_to_cell(row, use_align=True)
 
125
 
 
126
    def _dotted_revnos(self, repository, revision_id):
 
127
        """Return a dict of revision_id -> dotted revno
 
128
        
 
129
        :param repository: The repository to get the graph from
 
130
        :param revision_id: The last revision for which this info is needed
 
131
        """
 
132
        graph = repository.get_revision_graph(revision_id)
 
133
        dotted = {}
 
134
        for n, revision_id, d, revno, e in tsort.merge_sort(graph, 
 
135
            revision_id, generate_revno=True):
 
136
            dotted[revision_id] = '.'.join(str(num) for num in revno)
 
137
        return dotted
 
138
 
 
139
    def _annotate(self, branch, file_id, revision_id):
 
140
        repository = branch.repository
 
141
        if revision_id is None:
 
142
            revision_id = branch.last_revision()
 
143
        dotted = self._dotted_revnos(repository, revision_id)
 
144
        rev_tree = repository.revision_tree(revision_id)
 
145
        revision_id = rev_tree.inventory[file_id].revision
 
146
        weave = repository.weave_store.get_weave(file_id,
 
147
                                                 branch.get_transaction())
 
148
        
 
149
        revision_cache = RevisionCache(repository)
 
150
        for origin, text in weave.annotate_iter(revision_id):
 
151
            rev_id = origin
 
152
            try:
 
153
                revision = revision_cache.get_revision(rev_id)
 
154
                revno = dotted.get(rev_id, 'merge')
 
155
                if len(revno) > 15:
 
156
                    revno = 'merge'
 
157
            except NoSuchRevision:
 
158
                revision = NoneRevision(rev_id)
 
159
                revno = "?"
 
160
 
 
161
            yield revision, revno, text
 
162
 
 
163
    def _set_oldest_newest(self):
 
164
        rev_dates = map(lambda i: self.revisions[i].timestamp, self.revisions)
 
165
        if len(rev_dates) == 0:
 
166
            return
 
167
        oldest = min(rev_dates)
 
168
        newest = max(rev_dates)
 
169
 
 
170
        span = self._span_from_seconds(time.time() - oldest)
 
171
        self.span_selector.set_to_oldest_span(span)
 
172
        
 
173
        span = self._span_from_seconds(newest - oldest)
 
174
        self.span_selector.set_newest_to_oldest_span(span)
 
175
 
 
176
    def _span_from_seconds(self, seconds):
 
177
        return (seconds / (24 * 60 * 60))
 
178
    
 
179
    def _span_changed_cb(self, w, span):
 
180
        self.annotate_colormap.set_span(span)
 
181
        now = time.time()
 
182
        self.annomodel.foreach(self._highlight_annotation, now)
 
183
 
 
184
    def _highlight_annotation(self, model, path, iter, now):
 
185
        revision_id, = model.get(iter, REVISION_ID_COL)
 
186
        revision = self.revisions[revision_id]
 
187
        model.set(iter, HIGHLIGHT_COLOR_COL,
 
188
                  self.annotate_colormap.get_color(revision, now))
 
189
 
 
190
    def _show_log(self, w):
 
191
        (path, col) = self.annoview.get_cursor()
 
192
        if path is None:
 
193
            return
 
194
        rev_id = self.annomodel[path][REVISION_ID_COL]
 
195
        self.logview.set_revision(self.revisions[rev_id])
 
196
 
 
197
    def _create(self):
 
198
        self.logview = self._create_log_view()
 
199
        self.annoview = self._create_annotate_view()
 
200
        self.span_selector = self._create_span_selector()
 
201
 
 
202
        vbox = gtk.VBox(False, 12)
 
203
        vbox.set_border_width(12)
 
204
        vbox.show()
 
205
 
 
206
        sw = gtk.ScrolledWindow()
 
207
        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
208
        sw.set_shadow_type(gtk.SHADOW_IN)
 
209
        sw.add(self.annoview)
 
210
        self.annoview.gwindow = self
 
211
        sw.show()
 
212
        
 
213
        self.pane = pane = gtk.VPaned()
 
214
        pane.add1(sw)
 
215
        pane.add2(self.logview)
 
216
        pane.show()
 
217
        vbox.pack_start(pane, expand=True, fill=True)
 
218
        
 
219
        hbox = gtk.HBox(True, 6)
 
220
        hbox.pack_start(self.span_selector, expand=False, fill=True)
 
221
        hbox.pack_start(self._create_button_box(), expand=False, fill=True)
 
222
        hbox.show()
 
223
        vbox.pack_start(hbox, expand=False, fill=True)
 
224
 
 
225
        self.add(vbox)
 
226
 
 
227
    def row_diff(self, tv, path, tvc):
 
228
        row = path[0]
 
229
        revision = self.annotations[row]
 
230
        repository = self.branch.repository
 
231
        tree1 = repository.revision_tree(revision.revision_id)
 
232
        if len(revision.parent_ids) > 0:
 
233
            tree2 = repository.revision_tree(revision.parent_ids[0])
 
234
        else:
 
235
            tree2 = repository.revision_tree(NULL_REVISION)
 
236
        from bzrlib.plugins.gtk.viz.diffwin import DiffWindow
 
237
        window = DiffWindow()
 
238
        window.set_diff("Diff for row %d" % (row+1), tree1, tree2)
 
239
        window.set_file(tree1.id2path(self.file_id))
 
240
        window.show()
 
241
 
 
242
 
 
243
    def _create_annotate_view(self):
 
244
        tv = gtk.TreeView()
 
245
        tv.set_rules_hint(False)
 
246
        tv.connect("cursor-changed", self._show_log)
 
247
        tv.show()
 
248
        tv.connect("row-activated", self.row_diff)
 
249
 
 
250
        cell = gtk.CellRendererText()
 
251
        cell.set_property("xalign", 1.0)
 
252
        cell.set_property("ypad", 0)
 
253
        cell.set_property("family", "Monospace")
 
254
        cell.set_property("cell-background-gdk",
 
255
                          tv.get_style().bg[gtk.STATE_NORMAL])
 
256
        col = gtk.TreeViewColumn()
 
257
        col.set_resizable(False)
 
258
        col.pack_start(cell, expand=True)
 
259
        col.add_attribute(cell, "text", LINE_NUM_COL)
 
260
        tv.append_column(col)
 
261
 
 
262
        cell = gtk.CellRendererText()
 
263
        cell.set_property("ypad", 0)
 
264
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
 
265
        cell.set_property("cell-background-gdk",
 
266
                          self.get_style().bg[gtk.STATE_NORMAL])
 
267
        col = gtk.TreeViewColumn("Committer")
 
268
        col.set_resizable(True)
 
269
        col.pack_start(cell, expand=True)
 
270
        col.add_attribute(cell, "text", COMMITTER_COL)
 
271
        tv.append_column(col)
 
272
 
 
273
        cell = gtk.CellRendererText()
 
274
        cell.set_property("xalign", 1.0)
 
275
        cell.set_property("ypad", 0)
 
276
        cell.set_property("cell-background-gdk",
 
277
                          self.get_style().bg[gtk.STATE_NORMAL])
 
278
        col = gtk.TreeViewColumn("Revno")
 
279
        col.set_resizable(False)
 
280
        col.pack_start(cell, expand=True)
 
281
        col.add_attribute(cell, "markup", REVNO_COL)
 
282
        tv.append_column(col)
 
283
 
 
284
        cell = gtk.CellRendererText()
 
285
        cell.set_property("ypad", 0)
 
286
        cell.set_property("family", "Monospace")
 
287
        col = gtk.TreeViewColumn()
 
288
        col.set_resizable(False)
 
289
        col.pack_start(cell, expand=True)
 
290
#        col.add_attribute(cell, "foreground", HIGHLIGHT_COLOR_COL)
 
291
        col.add_attribute(cell, "background", HIGHLIGHT_COLOR_COL)
 
292
        col.add_attribute(cell, "text", TEXT_LINE_COL)
 
293
        tv.append_column(col)
 
294
 
 
295
        tv.set_search_column(LINE_NUM_COL)
 
296
        
 
297
        return tv
 
298
 
 
299
    def _create_span_selector(self):
 
300
        ss = SpanSelector()
 
301
        ss.connect("span-changed", self._span_changed_cb)
 
302
        ss.show()
 
303
 
 
304
        return ss
 
305
 
 
306
    def _create_log_view(self):
 
307
        lv = LogView()
 
308
        lv.show()
 
309
 
 
310
        return lv
 
311
 
 
312
    def _create_button_box(self):
 
313
        box = gtk.HButtonBox()
 
314
        box.set_layout(gtk.BUTTONBOX_END)
 
315
        box.show()
 
316
        
 
317
        button = gtk.Button()
 
318
        button.set_use_stock(True)
 
319
        button.set_label("gtk-close")
 
320
        button.connect("clicked", lambda w: self.destroy())
 
321
        button.show()
 
322
        
 
323
        box.pack_start(button, expand=False, fill=False)
 
324
 
 
325
        return box
 
326
 
 
327
 
 
328
class NoneRevision:
 
329
    """ A fake revision.
 
330
 
 
331
    For when a revision is referenced but not present.
 
332
    """
 
333
 
 
334
    def __init__(self, revision_id):
 
335
        self.revision_id = revision_id
 
336
        self.parent_ids = []
 
337
        self.committer = "?"
 
338
        self.message = "?"
 
339
        self.timestamp = 0.0
 
340
        self.timezone = 0
 
341
 
 
342
 
 
343
class RevisionCache(object):
 
344
    """A caching revision source"""
 
345
    def __init__(self, real_source):
 
346
        self.__real_source = real_source
 
347
        self.__cache = {}
 
348
 
 
349
    def get_revision(self, revision_id):
 
350
        if revision_id not in self.__cache:
 
351
            revision = self.__real_source.get_revision(revision_id)
 
352
            self.__cache[revision_id] = revision
 
353
        return self.__cache[revision_id]