/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: 2007-01-17 06:42:55 UTC
  • mto: This revision was merged to the branch mainline in revision 129.
  • Revision ID: aaron.bentley@utoronto.ca-20070117064255-x4gznz5e0lyjq3gk
Remove usused span selector

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, CURRENT_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
    def annotate(self, tree, branch, file_id):
 
59
        self.revisions = {}
 
60
        self.annotations = []
 
61
        self.branch = branch
 
62
        self.tree = tree
 
63
        self.file_id = file_id
 
64
        self.revision_id = getattr(tree, 'get_revision_id', 
 
65
                                   lambda: CURRENT_REVISION)()
 
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(tree, file_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
                now = time.time()
 
102
                self.annomodel.foreach(self._highlight_annotation, now)
 
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 _dotted_revnos(self, repository, revision_id):
 
125
        """Return a dict of revision_id -> dotted revno
 
126
        
 
127
        :param repository: The repository to get the graph from
 
128
        :param revision_id: The last revision for which this info is needed
 
129
        """
 
130
        graph = repository.get_revision_graph(revision_id)
 
131
        dotted = {}
 
132
        for n, revision_id, d, revno, e in tsort.merge_sort(graph, 
 
133
            revision_id, generate_revno=True):
 
134
            dotted[revision_id] = '.'.join(str(num) for num in revno)
 
135
        return dotted
 
136
 
 
137
    def _annotate(self, tree, file_id):
 
138
        current_revision = FakeRevision(CURRENT_REVISION)
 
139
        current_revision.committer = self.branch.get_config().username()
 
140
        current_revision.timestamp = time.time()
 
141
        current_revision.message = '[Not yet committed]'
 
142
        current_revno = '%d?' % (self.branch.revno() + 1)
 
143
        repository = self.branch.repository
 
144
        if self.revision_id == CURRENT_REVISION:
 
145
            revision_id = self.branch.last_revision()
 
146
        else:
 
147
            revision_id = self.revision_id
 
148
        dotted = self._dotted_revnos(repository, revision_id)
 
149
        revision_cache = RevisionCache(repository)
 
150
        for origin, text in tree.annotate_iter(file_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
                committer = "?"
 
159
                if rev_id == CURRENT_REVISION:
 
160
                    revision = current_revision
 
161
                    revno = current_revno
 
162
                else:
 
163
                    revision = FakeRevision(rev_id)
 
164
                    revno = "?"
 
165
 
 
166
            yield revision, revno, text
 
167
 
 
168
    def _set_oldest_newest(self):
 
169
        rev_dates = map(lambda i: self.revisions[i].timestamp, self.revisions)
 
170
        if len(rev_dates) == 0:
 
171
            return
 
172
        oldest = min(rev_dates)
 
173
        newest = max(rev_dates)
 
174
 
 
175
    def _highlight_annotation(self, model, path, iter, now):
 
176
        revision_id, = model.get(iter, REVISION_ID_COL)
 
177
        revision = self.revisions[revision_id]
 
178
        model.set(iter, HIGHLIGHT_COLOR_COL,
 
179
                  self.annotate_colormap.get_color(revision, now))
 
180
 
 
181
    def _show_log(self, w):
 
182
        (path, col) = self.annoview.get_cursor()
 
183
        if path is None:
 
184
            return
 
185
        rev_id = self.annomodel[path][REVISION_ID_COL]
 
186
        self.logview.set_revision(self.revisions[rev_id])
 
187
 
 
188
    def _create(self):
 
189
        self.logview = self._create_log_view()
 
190
        self.annoview = self._create_annotate_view()
 
191
 
 
192
        vbox = gtk.VBox(False, 12)
 
193
        vbox.set_border_width(12)
 
194
        vbox.show()
 
195
 
 
196
        sw = gtk.ScrolledWindow()
 
197
        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
198
        sw.set_shadow_type(gtk.SHADOW_IN)
 
199
        sw.add(self.annoview)
 
200
        self.annoview.gwindow = self
 
201
        sw.show()
 
202
        
 
203
        self.pane = pane = gtk.VPaned()
 
204
        pane.add1(sw)
 
205
        pane.add2(self.logview)
 
206
        pane.show()
 
207
        vbox.pack_start(pane, expand=True, fill=True)
 
208
        
 
209
        hbox = gtk.HBox(True, 6)
 
210
        hbox.pack_start(self._create_button_box(), expand=False, fill=True)
 
211
        hbox.show()
 
212
        vbox.pack_start(hbox, expand=False, fill=True)
 
213
 
 
214
        self.add(vbox)
 
215
 
 
216
    def row_diff(self, tv, path, tvc):
 
217
        row = path[0]
 
218
        revision = self.annotations[row]
 
219
        repository = self.branch.repository
 
220
        if revision.revision_id == CURRENT_REVISION:
 
221
            tree1 = self.tree
 
222
            tree2 = self.tree.basis_tree()
 
223
        else:
 
224
            tree1 = repository.revision_tree(revision.revision_id)
 
225
            if len(revision.parent_ids) > 0:
 
226
                tree2 = repository.revision_tree(revision.parent_ids[0])
 
227
            else:
 
228
                tree2 = repository.revision_tree(NULL_REVISION)
 
229
        from bzrlib.plugins.gtk.viz.diffwin import DiffWindow
 
230
        window = DiffWindow()
 
231
        window.set_diff("Diff for row %d" % (row+1), tree1, tree2)
 
232
        window.set_file(tree1.id2path(self.file_id))
 
233
        window.show()
 
234
 
 
235
 
 
236
    def _create_annotate_view(self):
 
237
        tv = gtk.TreeView()
 
238
        tv.set_rules_hint(False)
 
239
        tv.connect("cursor-changed", self._show_log)
 
240
        tv.show()
 
241
        tv.connect("row-activated", self.row_diff)
 
242
 
 
243
        cell = gtk.CellRendererText()
 
244
        cell.set_property("xalign", 1.0)
 
245
        cell.set_property("ypad", 0)
 
246
        cell.set_property("family", "Monospace")
 
247
        cell.set_property("cell-background-gdk",
 
248
                          tv.get_style().bg[gtk.STATE_NORMAL])
 
249
        col = gtk.TreeViewColumn()
 
250
        col.set_resizable(False)
 
251
        col.pack_start(cell, expand=True)
 
252
        col.add_attribute(cell, "text", LINE_NUM_COL)
 
253
        tv.append_column(col)
 
254
 
 
255
        cell = gtk.CellRendererText()
 
256
        cell.set_property("ypad", 0)
 
257
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
 
258
        cell.set_property("cell-background-gdk",
 
259
                          self.get_style().bg[gtk.STATE_NORMAL])
 
260
        col = gtk.TreeViewColumn("Committer")
 
261
        col.set_resizable(True)
 
262
        col.pack_start(cell, expand=True)
 
263
        col.add_attribute(cell, "text", COMMITTER_COL)
 
264
        tv.append_column(col)
 
265
 
 
266
        cell = gtk.CellRendererText()
 
267
        cell.set_property("xalign", 1.0)
 
268
        cell.set_property("ypad", 0)
 
269
        cell.set_property("cell-background-gdk",
 
270
                          self.get_style().bg[gtk.STATE_NORMAL])
 
271
        col = gtk.TreeViewColumn("Revno")
 
272
        col.set_resizable(False)
 
273
        col.pack_start(cell, expand=True)
 
274
        col.add_attribute(cell, "markup", REVNO_COL)
 
275
        tv.append_column(col)
 
276
 
 
277
        cell = gtk.CellRendererText()
 
278
        cell.set_property("ypad", 0)
 
279
        cell.set_property("family", "Monospace")
 
280
        col = gtk.TreeViewColumn()
 
281
        col.set_resizable(False)
 
282
        col.pack_start(cell, expand=True)
 
283
#        col.add_attribute(cell, "foreground", HIGHLIGHT_COLOR_COL)
 
284
        col.add_attribute(cell, "background", HIGHLIGHT_COLOR_COL)
 
285
        col.add_attribute(cell, "text", TEXT_LINE_COL)
 
286
        tv.append_column(col)
 
287
 
 
288
        tv.set_search_column(LINE_NUM_COL)
 
289
        
 
290
        return tv
 
291
 
 
292
    def _create_log_view(self):
 
293
        lv = LogView()
 
294
        lv.show()
 
295
 
 
296
        return lv
 
297
 
 
298
    def _create_button_box(self):
 
299
        box = gtk.HButtonBox()
 
300
        box.set_layout(gtk.BUTTONBOX_END)
 
301
        box.show()
 
302
        
 
303
        button = gtk.Button()
 
304
        button.set_use_stock(True)
 
305
        button.set_label("gtk-close")
 
306
        button.connect("clicked", lambda w: self.destroy())
 
307
        button.show()
 
308
        
 
309
        box.pack_start(button, expand=False, fill=False)
 
310
 
 
311
        return box
 
312
 
 
313
 
 
314
class FakeRevision:
 
315
    """ A fake revision.
 
316
 
 
317
    For when a revision is referenced but not present.
 
318
    """
 
319
 
 
320
    def __init__(self, revision_id, committer='?'):
 
321
        self.revision_id = revision_id
 
322
        self.parent_ids = []
 
323
        self.committer = committer
 
324
        self.message = "?"
 
325
        self.timestamp = 0.0
 
326
        self.timezone = 0
 
327
 
 
328
 
 
329
class RevisionCache(object):
 
330
    """A caching revision source"""
 
331
    def __init__(self, real_source):
 
332
        self.__real_source = real_source
 
333
        self.__cache = {}
 
334
 
 
335
    def get_revision(self, revision_id):
 
336
        if revision_id not in self.__cache:
 
337
            revision = self.__real_source.get_revision(revision_id)
 
338
            self.__cache[revision_id] = revision
 
339
        return self.__cache[revision_id]