/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz
0.1.1 by Dan Loda
First working version of xannotate.
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
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
17
import time
18
0.1.1 by Dan Loda
First working version of xannotate.
19
import pygtk
20
pygtk.require("2.0")
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
21
import gobject
0.1.1 by Dan Loda
First working version of xannotate.
22
import gtk
23
import pango
24
66.2.4 by Aaron Bentley
Use dotted revnos instead of 'merge' where possible
25
from bzrlib import tsort
0.1.1 by Dan Loda
First working version of xannotate.
26
from bzrlib.errors import NoSuchRevision
65 by Aaron Bentley
Handle first revision properly
27
from bzrlib.revision import NULL_REVISION
0.1.1 by Dan Loda
First working version of xannotate.
28
0.1.18 by Aaron Bentley
Switched to using pink backgrounds
29
from colormap import AnnotateColorMap, AnnotateColorSaturation
0.1.1 by Dan Loda
First working version of xannotate.
30
from logview import LogView
0.2.4 by Dan Loda
Extract class SpanSelector out of GAnnotateWindow.
31
from spanselector import SpanSelector
0.1.1 by Dan Loda
First working version of xannotate.
32
33
34
(
35
    REVISION_ID_COL,
36
    LINE_NUM_COL,
37
    COMMITTER_COL,
38
    REVNO_COL,
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
39
    HIGHLIGHT_COLOR_COL,
0.1.1 by Dan Loda
First working version of xannotate.
40
    TEXT_LINE_COL
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
41
) = range(6)
0.1.1 by Dan Loda
First working version of xannotate.
42
43
0.1.2 by Dan Loda
Rename plugin: xannotate => gannotate
44
class GAnnotateWindow(gtk.Window):
0.1.1 by Dan Loda
First working version of xannotate.
45
    """Annotate window."""
46
0.2.6 by Dan Loda
--plain option to disable highlighting. And update README
47
    def __init__(self, all=False, plain=False):
48
        self.all = all
49
        self.plain = plain
50
        
0.1.1 by Dan Loda
First working version of xannotate.
51
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
0.2.6 by Dan Loda
--plain option to disable highlighting. And update README
52
        
0.1.1 by Dan Loda
First working version of xannotate.
53
        self.set_icon(self.render_icon(gtk.STOCK_FIND, gtk.ICON_SIZE_BUTTON))
0.1.18 by Aaron Bentley
Switched to using pink backgrounds
54
        self.annotate_colormap = AnnotateColorSaturation()
0.1.1 by Dan Loda
First working version of xannotate.
55
56
        self._create()
57
0.1.17 by Dan Loda
A little refactoring.
58
        if self.plain:
59
            self.span_selector.hide()
60
66.2.1 by Aaron Bentley
Gannotate takes a revision argument
61
    def annotate(self, branch, file_id, revision_id=None):
0.1.1 by Dan Loda
First working version of xannotate.
62
        self.revisions = {}
59.2.2 by Aaron Bentley
Annotate launches a diff for the particular revision
63
        self.annotations = []
64
        self.branch = branch
59.2.3 by Aaron Bentley
Gannotate-launched diffs now jump to correct file
65
        self.file_id = file_id
66.2.1 by Aaron Bentley
Gannotate takes a revision argument
66
        self.revision_id = revision_id
0.1.1 by Dan Loda
First working version of xannotate.
67
        
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
68
        # [revision id, line number, committer, revno, highlight color, line]
0.1.17 by Dan Loda
A little refactoring.
69
        self.annomodel = gtk.ListStore(gobject.TYPE_STRING,
70
                                       gobject.TYPE_STRING,
71
                                       gobject.TYPE_STRING,
72
                                       gobject.TYPE_STRING,
73
                                       gobject.TYPE_STRING,
74
                                       gobject.TYPE_STRING)
0.1.1 by Dan Loda
First working version of xannotate.
75
        
76
        last_seen = None
0.4.1 by Aaron Bentley
Updated performance, API use
77
        try:
78
            branch.lock_read()
79
            branch.repository.lock_read()
80
            for line_no, (revision, revno, line)\
66.2.1 by Aaron Bentley
Gannotate takes a revision argument
81
                    in enumerate(self._annotate(branch, file_id, revision_id)):
0.4.1 by Aaron Bentley
Updated performance, API use
82
                if revision.revision_id == last_seen and not self.all:
83
                    revno = committer = ""
84
                else:
85
                    last_seen = revision.revision_id
86
                    committer = revision.committer
87
88
                if revision.revision_id not in self.revisions:
89
                    self.revisions[revision.revision_id] = revision
90
91
                self.annomodel.append([revision.revision_id,
92
                                       line_no + 1,
93
                                       committer,
94
                                       revno,
95
                                       None,
96
                                       line.rstrip("\r\n")
97
                                      ])
59.2.2 by Aaron Bentley
Annotate launches a diff for the particular revision
98
                self.annotations.append(revision)
0.4.1 by Aaron Bentley
Updated performance, API use
99
100
            if not self.plain:
101
                self._set_oldest_newest()
102
                # Recall that calling activate_default will emit "span-changed",
103
                # so self._span_changed_cb will take care of initial highlighting
104
                self.span_selector.activate_default()
105
        finally:
106
            branch.repository.unlock()
107
            branch.unlock()
0.2.6 by Dan Loda
--plain option to disable highlighting. And update README
108
0.1.17 by Dan Loda
A little refactoring.
109
        self.annoview.set_model(self.annomodel)
110
        self.annoview.grab_focus()
0.1.1 by Dan Loda
First working version of xannotate.
111
0.1.12 by Dan Loda
New --line option. Can now jump to a specific line number.
112
    def jump_to_line(self, lineno):
0.1.17 by Dan Loda
A little refactoring.
113
        if lineno > len(self.annomodel) or lineno < 1:
0.1.12 by Dan Loda
New --line option. Can now jump to a specific line number.
114
            row = 0
115
            # FIXME:should really deal with this in the gui. Perhaps a status
116
            # bar?
117
            print("gannotate: Line number %d does't exist. Defaulting to "
118
                  "line 1." % lineno)
79 by Jelmer Vernooij
Handle empty files more gracefully. Fixes #58951.
119
	    return
0.1.12 by Dan Loda
New --line option. Can now jump to a specific line number.
120
        else:
121
            row = lineno - 1
122
0.1.17 by Dan Loda
A little refactoring.
123
        self.annoview.set_cursor(row)
59.2.1 by Aaron Bentley
Gannotate takes a line number
124
        self.annoview.scroll_to_cell(row, use_align=True)
0.1.12 by Dan Loda
New --line option. Can now jump to a specific line number.
125
66.2.4 by Aaron Bentley
Use dotted revnos instead of 'merge' where possible
126
    def _dotted_revnos(self, repository, revision_id):
127
        """Return a dict of revision_id -> dotted revno
128
        
129
        :param repository: The repository to get the graph from
130
        :param revision_id: The last revision for which this info is needed
131
        """
132
        graph = repository.get_revision_graph(revision_id)
133
        dotted = {}
134
        for n, revision_id, d, revno, e in tsort.merge_sort(graph, 
135
            revision_id, generate_revno=True):
136
            dotted[revision_id] = '.'.join(str(num) for num in revno)
137
        return dotted
138
66.2.1 by Aaron Bentley
Gannotate takes a revision argument
139
    def _annotate(self, branch, file_id, revision_id):
0.1.20 by Aaron Bentley
Updated for api changes
140
        repository = branch.repository
66.2.1 by Aaron Bentley
Gannotate takes a revision argument
141
        if revision_id is None:
142
            revision_id = branch.last_revision()
66.2.4 by Aaron Bentley
Use dotted revnos instead of 'merge' where possible
143
        dotted = self._dotted_revnos(repository, revision_id)
66.2.1 by Aaron Bentley
Gannotate takes a revision argument
144
        rev_tree = repository.revision_tree(revision_id)
145
        revision_id = rev_tree.inventory[file_id].revision
0.1.20 by Aaron Bentley
Updated for api changes
146
        weave = repository.weave_store.get_weave(file_id,
147
                                                 branch.get_transaction())
0.1.1 by Dan Loda
First working version of xannotate.
148
        
0.1.23 by Aaron Bentley
Added revision caching to initial annotation
149
        revision_cache = RevisionCache(repository)
66.2.1 by Aaron Bentley
Gannotate takes a revision argument
150
        for origin, text in weave.annotate_iter(revision_id):
0.4.1 by Aaron Bentley
Updated performance, API use
151
            rev_id = origin
0.1.1 by Dan Loda
First working version of xannotate.
152
            try:
0.1.23 by Aaron Bentley
Added revision caching to initial annotation
153
                revision = revision_cache.get_revision(rev_id)
66.2.5 by Aaron Bentley
force long revnos to 'merge'
154
                revno = dotted.get(rev_id, 'merge')
155
                if len(revno) > 15:
156
                    revno = 'merge'
0.1.1 by Dan Loda
First working version of xannotate.
157
            except NoSuchRevision:
158
                revision = NoneRevision(rev_id)
159
                revno = "?"
160
161
            yield revision, revno, text
162
0.2.4 by Dan Loda
Extract class SpanSelector out of GAnnotateWindow.
163
    def _set_oldest_newest(self):
0.2.3 by Dan Loda
Initial support for custom highlight spans.
164
        rev_dates = map(lambda i: self.revisions[i].timestamp, self.revisions)
79 by Jelmer Vernooij
Handle empty files more gracefully. Fixes #58951.
165
        if len(rev_dates) == 0:
166
            return
0.2.2 by Dan Loda
Add file's age as default span
167
        oldest = min(rev_dates)
0.2.3 by Dan Loda
Initial support for custom highlight spans.
168
        newest = max(rev_dates)
169
0.2.4 by Dan Loda
Extract class SpanSelector out of GAnnotateWindow.
170
        span = self._span_from_seconds(time.time() - oldest)
171
        self.span_selector.set_to_oldest_span(span)
172
        
173
        span = self._span_from_seconds(newest - oldest)
174
        self.span_selector.set_newest_to_oldest_span(span)
175
176
    def _span_from_seconds(self, seconds):
177
        return (seconds / (24 * 60 * 60))
0.2.2 by Dan Loda
Add file's age as default span
178
    
0.2.4 by Dan Loda
Extract class SpanSelector out of GAnnotateWindow.
179
    def _span_changed_cb(self, w, span):
0.2.5 by Dan Loda
Use granny-like colors as default and rename ColorMap => AnnotateColorMap.
180
        self.annotate_colormap.set_span(span)
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
181
        now = time.time()
0.1.17 by Dan Loda
A little refactoring.
182
        self.annomodel.foreach(self._highlight_annotation, now)
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
183
0.2.2 by Dan Loda
Add file's age as default span
184
    def _highlight_annotation(self, model, path, iter, now):
185
        revision_id, = model.get(iter, REVISION_ID_COL)
186
        revision = self.revisions[revision_id]
0.2.5 by Dan Loda
Use granny-like colors as default and rename ColorMap => AnnotateColorMap.
187
        model.set(iter, HIGHLIGHT_COLOR_COL,
0.1.19 by Aaron Bentley
Different colours for different committers
188
                  self.annotate_colormap.get_color(revision, now))
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
189
0.1.1 by Dan Loda
First working version of xannotate.
190
    def _show_log(self, w):
0.1.17 by Dan Loda
A little refactoring.
191
        (path, col) = self.annoview.get_cursor()
79 by Jelmer Vernooij
Handle empty files more gracefully. Fixes #58951.
192
        if path is None:
193
            return
0.1.17 by Dan Loda
A little refactoring.
194
        rev_id = self.annomodel[path][REVISION_ID_COL]
0.1.1 by Dan Loda
First working version of xannotate.
195
        self.logview.set_revision(self.revisions[rev_id])
196
197
    def _create(self):
0.1.17 by Dan Loda
A little refactoring.
198
        self.logview = self._create_log_view()
199
        self.annoview = self._create_annotate_view()
200
        self.span_selector = self._create_span_selector()
201
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
202
        vbox = gtk.VBox(False, 12)
203
        vbox.set_border_width(12)
204
        vbox.show()
0.1.17 by Dan Loda
A little refactoring.
205
206
        sw = gtk.ScrolledWindow()
207
        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
208
        sw.set_shadow_type(gtk.SHADOW_IN)
209
        sw.add(self.annoview)
59.2.2 by Aaron Bentley
Annotate launches a diff for the particular revision
210
        self.annoview.gwindow = self
0.1.17 by Dan Loda
A little refactoring.
211
        sw.show()
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
212
        
0.1.8 by Dan Loda
Remember window state. This introduces the gannotate.conf configuration file,
213
        self.pane = pane = gtk.VPaned()
0.1.17 by Dan Loda
A little refactoring.
214
        pane.add1(sw)
215
        pane.add2(self.logview)
0.1.1 by Dan Loda
First working version of xannotate.
216
        pane.show()
217
        vbox.pack_start(pane, expand=True, fill=True)
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
218
        
219
        hbox = gtk.HBox(True, 6)
0.1.17 by Dan Loda
A little refactoring.
220
        hbox.pack_start(self.span_selector, expand=False, fill=True)
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
221
        hbox.pack_start(self._create_button_box(), expand=False, fill=True)
222
        hbox.show()
223
        vbox.pack_start(hbox, expand=False, fill=True)
224
0.1.1 by Dan Loda
First working version of xannotate.
225
        self.add(vbox)
226
59.2.2 by Aaron Bentley
Annotate launches a diff for the particular revision
227
    def row_diff(self, tv, path, tvc):
228
        row = path[0]
229
        revision = self.annotations[row]
65 by Aaron Bentley
Handle first revision properly
230
        repository = self.branch.repository
231
        tree1 = repository.revision_tree(revision.revision_id)
232
        if len(revision.parent_ids) > 0:
233
            tree2 = repository.revision_tree(revision.parent_ids[0])
234
        else:
235
            tree2 = repository.revision_tree(NULL_REVISION)
59.2.2 by Aaron Bentley
Annotate launches a diff for the particular revision
236
        from bzrlib.plugins.gtk.viz.diffwin import DiffWindow
237
        window = DiffWindow()
66 by Aaron Bentley
Fix annotate diff window title
238
        window.set_diff("Diff for row %d" % (row+1), tree1, tree2)
59.2.3 by Aaron Bentley
Gannotate-launched diffs now jump to correct file
239
        window.set_file(tree1.id2path(self.file_id))
59.2.2 by Aaron Bentley
Annotate launches a diff for the particular revision
240
        window.show()
241
242
0.1.1 by Dan Loda
First working version of xannotate.
243
    def _create_annotate_view(self):
0.1.17 by Dan Loda
A little refactoring.
244
        tv = gtk.TreeView()
245
        tv.set_rules_hint(False)
246
        tv.connect("cursor-changed", self._show_log)
247
        tv.show()
59.2.2 by Aaron Bentley
Annotate launches a diff for the particular revision
248
        tv.connect("row-activated", self.row_diff)
0.1.1 by Dan Loda
First working version of xannotate.
249
250
        cell = gtk.CellRendererText()
251
        cell.set_property("xalign", 1.0)
252
        cell.set_property("ypad", 0)
253
        cell.set_property("family", "Monospace")
254
        cell.set_property("cell-background-gdk",
0.1.17 by Dan Loda
A little refactoring.
255
                          tv.get_style().bg[gtk.STATE_NORMAL])
0.1.1 by Dan Loda
First working version of xannotate.
256
        col = gtk.TreeViewColumn()
257
        col.set_resizable(False)
258
        col.pack_start(cell, expand=True)
259
        col.add_attribute(cell, "text", LINE_NUM_COL)
0.1.17 by Dan Loda
A little refactoring.
260
        tv.append_column(col)
0.1.1 by Dan Loda
First working version of xannotate.
261
262
        cell = gtk.CellRendererText()
263
        cell.set_property("ypad", 0)
264
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
265
        cell.set_property("cell-background-gdk",
266
                          self.get_style().bg[gtk.STATE_NORMAL])
267
        col = gtk.TreeViewColumn("Committer")
268
        col.set_resizable(True)
269
        col.pack_start(cell, expand=True)
270
        col.add_attribute(cell, "text", COMMITTER_COL)
0.1.17 by Dan Loda
A little refactoring.
271
        tv.append_column(col)
0.1.1 by Dan Loda
First working version of xannotate.
272
273
        cell = gtk.CellRendererText()
274
        cell.set_property("xalign", 1.0)
275
        cell.set_property("ypad", 0)
276
        cell.set_property("cell-background-gdk",
277
                          self.get_style().bg[gtk.STATE_NORMAL])
278
        col = gtk.TreeViewColumn("Revno")
279
        col.set_resizable(False)
280
        col.pack_start(cell, expand=True)
281
        col.add_attribute(cell, "markup", REVNO_COL)
0.1.17 by Dan Loda
A little refactoring.
282
        tv.append_column(col)
0.1.1 by Dan Loda
First working version of xannotate.
283
284
        cell = gtk.CellRendererText()
285
        cell.set_property("ypad", 0)
286
        cell.set_property("family", "Monospace")
287
        col = gtk.TreeViewColumn()
288
        col.set_resizable(False)
289
        col.pack_start(cell, expand=True)
0.1.18 by Aaron Bentley
Switched to using pink backgrounds
290
#        col.add_attribute(cell, "foreground", HIGHLIGHT_COLOR_COL)
291
        col.add_attribute(cell, "background", HIGHLIGHT_COLOR_COL)
0.1.1 by Dan Loda
First working version of xannotate.
292
        col.add_attribute(cell, "text", TEXT_LINE_COL)
0.1.17 by Dan Loda
A little refactoring.
293
        tv.append_column(col)
0.1.1 by Dan Loda
First working version of xannotate.
294
0.1.17 by Dan Loda
A little refactoring.
295
        tv.set_search_column(LINE_NUM_COL)
0.1.1 by Dan Loda
First working version of xannotate.
296
        
0.1.17 by Dan Loda
A little refactoring.
297
        return tv
0.1.1 by Dan Loda
First working version of xannotate.
298
0.2.4 by Dan Loda
Extract class SpanSelector out of GAnnotateWindow.
299
    def _create_span_selector(self):
0.1.17 by Dan Loda
A little refactoring.
300
        ss = SpanSelector()
301
        ss.connect("span-changed", self._span_changed_cb)
302
        ss.show()
0.2.4 by Dan Loda
Extract class SpanSelector out of GAnnotateWindow.
303
0.1.17 by Dan Loda
A little refactoring.
304
        return ss
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
305
0.1.1 by Dan Loda
First working version of xannotate.
306
    def _create_log_view(self):
0.1.17 by Dan Loda
A little refactoring.
307
        lv = LogView()
308
        lv.show()
309
310
        return lv
0.1.1 by Dan Loda
First working version of xannotate.
311
312
    def _create_button_box(self):
313
        box = gtk.HButtonBox()
314
        box.set_layout(gtk.BUTTONBOX_END)
315
        box.show()
316
        
317
        button = gtk.Button()
318
        button.set_use_stock(True)
319
        button.set_label("gtk-close")
320
        button.connect("clicked", lambda w: self.destroy())
321
        button.show()
322
        
323
        box.pack_start(button, expand=False, fill=False)
0.2.4 by Dan Loda
Extract class SpanSelector out of GAnnotateWindow.
324
0.1.1 by Dan Loda
First working version of xannotate.
325
        return box
326
327
328
class NoneRevision:
329
    """ A fake revision.
330
331
    For when a revision is referenced but not present.
332
    """
333
334
    def __init__(self, revision_id):
335
        self.revision_id = revision_id
336
        self.parent_ids = []
337
        self.committer = "?"
338
        self.message = "?"
339
        self.timestamp = 0.0
340
        self.timezone = 0
341
0.1.23 by Aaron Bentley
Added revision caching to initial annotation
342
343
class RevisionCache(object):
344
    """A caching revision source"""
345
    def __init__(self, real_source):
346
        self.__real_source = real_source
347
        self.__cache = {}
348
349
    def get_revision(self, revision_id):
350
        if revision_id not in self.__cache:
351
            revision = self.__real_source.get_revision(revision_id)
352
            self.__cache[revision_id] = revision
353
        return self.__cache[revision_id]