/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-05-19 16:56:46 UTC
  • mfrom: (0.1.25 gannotate)
  • Revision ID: jelmer@samba.org-20060519165646-0d867938fdbc9097
Merge in Dan Loda's gannotate plugin and put it in annotate/

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