/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: Aaron Bentley
  • Date: 2006-11-21 14:19:41 UTC
  • mfrom: (91.1.12 trunk)
  • mto: (66.6.5 gtk)
  • mto: This revision was merged to the branch mainline in revision 112.
  • Revision ID: abentley@panoramicfeedback.com-20061121141941-m82u7iqexarwirkv
MergeĀ fromĀ upstream

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