/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: 2006-09-30 10:21:43 UTC
  • Revision ID: jelmer@samba.org-20060930102143-c0ef64d6ca860c21
Merge some files from Olive and bzr-gtk.

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