/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-07-06 19:34:35 UTC
  • Revision ID: jelmer@samba.org-20060706193435-eaf2ffcc552b4185
Ignore pyc files

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
        else:
 
118
            row = lineno - 1
 
119
 
 
120
        self.annoview.set_cursor(row)
 
121
        self.annoview.scroll_to_cell(row, use_align=True)
 
122
 
 
123
    def _annotate(self, branch, file_id):
 
124
        rev_hist = branch.revision_history()
 
125
        repository = branch.repository
 
126
        rev_tree = repository.revision_tree(branch.last_revision())
 
127
        rev_id = rev_tree.inventory[file_id].revision
 
128
        weave = repository.weave_store.get_weave(file_id,
 
129
                                                 branch.get_transaction())
 
130
        
 
131
        revision_cache = RevisionCache(repository)
 
132
        for origin, text in weave.annotate_iter(rev_id):
 
133
            rev_id = origin
 
134
            try:
 
135
                revision = revision_cache.get_revision(rev_id)
 
136
                if rev_id in rev_hist:
 
137
                    revno = branch.revision_id_to_revno(rev_id)
 
138
                else:
 
139
                    revno = "merge"
 
140
            except NoSuchRevision:
 
141
                revision = NoneRevision(rev_id)
 
142
                revno = "?"
 
143
 
 
144
            yield revision, revno, text
 
145
 
 
146
    def _set_oldest_newest(self):
 
147
        rev_dates = map(lambda i: self.revisions[i].timestamp, self.revisions)
 
148
        oldest = min(rev_dates)
 
149
        newest = max(rev_dates)
 
150
 
 
151
        span = self._span_from_seconds(time.time() - oldest)
 
152
        self.span_selector.set_to_oldest_span(span)
 
153
        
 
154
        span = self._span_from_seconds(newest - oldest)
 
155
        self.span_selector.set_newest_to_oldest_span(span)
 
156
 
 
157
    def _span_from_seconds(self, seconds):
 
158
        return (seconds / (24 * 60 * 60))
 
159
    
 
160
    def _span_changed_cb(self, w, span):
 
161
        self.annotate_colormap.set_span(span)
 
162
        now = time.time()
 
163
        self.annomodel.foreach(self._highlight_annotation, now)
 
164
 
 
165
    def _highlight_annotation(self, model, path, iter, now):
 
166
        revision_id, = model.get(iter, REVISION_ID_COL)
 
167
        revision = self.revisions[revision_id]
 
168
        model.set(iter, HIGHLIGHT_COLOR_COL,
 
169
                  self.annotate_colormap.get_color(revision, now))
 
170
 
 
171
    def _show_log(self, w):
 
172
        (path, col) = self.annoview.get_cursor()
 
173
        rev_id = self.annomodel[path][REVISION_ID_COL]
 
174
        self.logview.set_revision(self.revisions[rev_id])
 
175
 
 
176
    def _create(self):
 
177
        self.logview = self._create_log_view()
 
178
        self.annoview = self._create_annotate_view()
 
179
        self.span_selector = self._create_span_selector()
 
180
 
 
181
        vbox = gtk.VBox(False, 12)
 
182
        vbox.set_border_width(12)
 
183
        vbox.show()
 
184
 
 
185
        sw = gtk.ScrolledWindow()
 
186
        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
187
        sw.set_shadow_type(gtk.SHADOW_IN)
 
188
        sw.add(self.annoview)
 
189
        self.annoview.gwindow = self
 
190
        sw.show()
 
191
        
 
192
        self.pane = pane = gtk.VPaned()
 
193
        pane.add1(sw)
 
194
        pane.add2(self.logview)
 
195
        pane.show()
 
196
        vbox.pack_start(pane, expand=True, fill=True)
 
197
        
 
198
        hbox = gtk.HBox(True, 6)
 
199
        hbox.pack_start(self.span_selector, expand=False, fill=True)
 
200
        hbox.pack_start(self._create_button_box(), expand=False, fill=True)
 
201
        hbox.show()
 
202
        vbox.pack_start(hbox, expand=False, fill=True)
 
203
 
 
204
        self.add(vbox)
 
205
 
 
206
    def row_diff(self, tv, path, tvc):
 
207
        row = path[0]
 
208
        revision = self.annotations[row]
 
209
        repository = self.branch.repository
 
210
        tree1 = repository.revision_tree(revision.revision_id)
 
211
        if len(revision.parent_ids) > 0:
 
212
            tree2 = repository.revision_tree(revision.parent_ids[0])
 
213
        else:
 
214
            tree2 = repository.revision_tree(NULL_REVISION)
 
215
        from bzrlib.plugins.gtk.viz.diffwin import DiffWindow
 
216
        window = DiffWindow()
 
217
        window.set_diff("Diff for row %d" % (row+1), tree1, tree2)
 
218
        window.set_file(tree1.id2path(self.file_id))
 
219
        window.show()
 
220
 
 
221
 
 
222
    def _create_annotate_view(self):
 
223
        tv = gtk.TreeView()
 
224
        tv.set_rules_hint(False)
 
225
        tv.connect("cursor-changed", self._show_log)
 
226
        tv.show()
 
227
        tv.connect("row-activated", self.row_diff)
 
228
 
 
229
        cell = gtk.CellRendererText()
 
230
        cell.set_property("xalign", 1.0)
 
231
        cell.set_property("ypad", 0)
 
232
        cell.set_property("family", "Monospace")
 
233
        cell.set_property("cell-background-gdk",
 
234
                          tv.get_style().bg[gtk.STATE_NORMAL])
 
235
        col = gtk.TreeViewColumn()
 
236
        col.set_resizable(False)
 
237
        col.pack_start(cell, expand=True)
 
238
        col.add_attribute(cell, "text", LINE_NUM_COL)
 
239
        tv.append_column(col)
 
240
 
 
241
        cell = gtk.CellRendererText()
 
242
        cell.set_property("ypad", 0)
 
243
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
 
244
        cell.set_property("cell-background-gdk",
 
245
                          self.get_style().bg[gtk.STATE_NORMAL])
 
246
        col = gtk.TreeViewColumn("Committer")
 
247
        col.set_resizable(True)
 
248
        col.pack_start(cell, expand=True)
 
249
        col.add_attribute(cell, "text", COMMITTER_COL)
 
250
        tv.append_column(col)
 
251
 
 
252
        cell = gtk.CellRendererText()
 
253
        cell.set_property("xalign", 1.0)
 
254
        cell.set_property("ypad", 0)
 
255
        cell.set_property("cell-background-gdk",
 
256
                          self.get_style().bg[gtk.STATE_NORMAL])
 
257
        col = gtk.TreeViewColumn("Revno")
 
258
        col.set_resizable(False)
 
259
        col.pack_start(cell, expand=True)
 
260
        col.add_attribute(cell, "markup", REVNO_COL)
 
261
        tv.append_column(col)
 
262
 
 
263
        cell = gtk.CellRendererText()
 
264
        cell.set_property("ypad", 0)
 
265
        cell.set_property("family", "Monospace")
 
266
        col = gtk.TreeViewColumn()
 
267
        col.set_resizable(False)
 
268
        col.pack_start(cell, expand=True)
 
269
#        col.add_attribute(cell, "foreground", HIGHLIGHT_COLOR_COL)
 
270
        col.add_attribute(cell, "background", HIGHLIGHT_COLOR_COL)
 
271
        col.add_attribute(cell, "text", TEXT_LINE_COL)
 
272
        tv.append_column(col)
 
273
 
 
274
        tv.set_search_column(LINE_NUM_COL)
 
275
        
 
276
        return tv
 
277
 
 
278
    def _create_span_selector(self):
 
279
        ss = SpanSelector()
 
280
        ss.connect("span-changed", self._span_changed_cb)
 
281
        ss.show()
 
282
 
 
283
        return ss
 
284
 
 
285
    def _create_log_view(self):
 
286
        lv = LogView()
 
287
        lv.show()
 
288
 
 
289
        return lv
 
290
 
 
291
    def _create_button_box(self):
 
292
        box = gtk.HButtonBox()
 
293
        box.set_layout(gtk.BUTTONBOX_END)
 
294
        box.show()
 
295
        
 
296
        button = gtk.Button()
 
297
        button.set_use_stock(True)
 
298
        button.set_label("gtk-close")
 
299
        button.connect("clicked", lambda w: self.destroy())
 
300
        button.show()
 
301
        
 
302
        box.pack_start(button, expand=False, fill=False)
 
303
 
 
304
        return box
 
305
 
 
306
 
 
307
class NoneRevision:
 
308
    """ A fake revision.
 
309
 
 
310
    For when a revision is referenced but not present.
 
311
    """
 
312
 
 
313
    def __init__(self, revision_id):
 
314
        self.revision_id = revision_id
 
315
        self.parent_ids = []
 
316
        self.committer = "?"
 
317
        self.message = "?"
 
318
        self.timestamp = 0.0
 
319
        self.timezone = 0
 
320
 
 
321
 
 
322
class RevisionCache(object):
 
323
    """A caching revision source"""
 
324
    def __init__(self, real_source):
 
325
        self.__real_source = real_source
 
326
        self.__cache = {}
 
327
 
 
328
    def get_revision(self, revision_id):
 
329
        if revision_id not in self.__cache:
 
330
            revision = self.__real_source.get_revision(revision_id)
 
331
            self.__cache[revision_id] = revision
 
332
        return self.__cache[revision_id]