/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
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
24
import re
0.1.1 by Dan Loda
First working version of xannotate.
25
66.6.6 by Aaron Bentley
Support scrolling based on an offset
26
from bzrlib import patiencediff, tsort
0.1.1 by Dan Loda
First working version of xannotate.
27
from bzrlib.errors import NoSuchRevision
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
28
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
0.1.1 by Dan Loda
First working version of xannotate.
29
0.1.18 by Aaron Bentley
Switched to using pink backgrounds
30
from colormap import AnnotateColorMap, AnnotateColorSaturation
330.3.1 by Daniel Schierbeck
Renamed logview 'revisionview'.
31
from bzrlib.plugins.gtk.revisionview import RevisionView
298.2.1 by Daniel Schierbeck
Refactored the GTK window code, creating a single base window class that handles keyboard events.
32
from bzrlib.plugins.gtk.window import Window
0.1.1 by Dan Loda
First working version of xannotate.
33
34
35
(
36
    REVISION_ID_COL,
37
    LINE_NUM_COL,
38
    COMMITTER_COL,
39
    REVNO_COL,
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
40
    HIGHLIGHT_COLOR_COL,
0.1.1 by Dan Loda
First working version of xannotate.
41
    TEXT_LINE_COL
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
42
) = range(6)
0.1.1 by Dan Loda
First working version of xannotate.
43
44
298.2.1 by Daniel Schierbeck
Refactored the GTK window code, creating a single base window class that handles keyboard events.
45
class GAnnotateWindow(Window):
0.1.1 by Dan Loda
First working version of xannotate.
46
    """Annotate window."""
47
473.1.1 by Andrew Bennetts
Simple hack to fix gannotate.
48
    def __init__(self, all=False, plain=False, parent=None, branch=None):
0.2.6 by Dan Loda
--plain option to disable highlighting. And update README
49
        self.all = all
50
        self.plain = plain
473.1.1 by Andrew Bennetts
Simple hack to fix gannotate.
51
        self._branch = branch
0.2.6 by Dan Loda
--plain option to disable highlighting. And update README
52
        
298.2.1 by Daniel Schierbeck
Refactored the GTK window code, creating a single base window class that handles keyboard events.
53
        Window.__init__(self, parent)
0.2.6 by Dan Loda
--plain option to disable highlighting. And update README
54
        
0.1.1 by Dan Loda
First working version of xannotate.
55
        self.set_icon(self.render_icon(gtk.STOCK_FIND, gtk.ICON_SIZE_BUTTON))
0.1.18 by Aaron Bentley
Switched to using pink backgrounds
56
        self.annotate_colormap = AnnotateColorSaturation()
0.1.1 by Dan Loda
First working version of xannotate.
57
58
        self._create()
59
        self.revisions = {}
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
60
        self.history = []
173.1.1 by Aaron Bentley
Better behavior when unable to go back
61
        self._no_back = set()
0.1.17 by Dan Loda
A little refactoring.
62
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
63
    def annotate(self, tree, branch, file_id):
59.2.2 by Aaron Bentley
Annotate launches a diff for the particular revision
64
        self.annotations = []
65
        self.branch = branch
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
66
        self.tree = tree
59.2.3 by Aaron Bentley
Gannotate-launched diffs now jump to correct file
67
        self.file_id = file_id
330.3.6 by Daniel Schierbeck
Fixed bug in gannotate where logview was used instead of revisionview.
68
        self.revisionview.set_file_id(file_id)
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
69
        self.revision_id = getattr(tree, 'get_revision_id', 
70
                                   lambda: CURRENT_REVISION)()
0.1.1 by Dan Loda
First working version of xannotate.
71
        
259 by Aaron Bentley
Add author support to gannotate and log viewer
72
        # [revision id, line number, author, revno, highlight color, line]
0.1.17 by Dan Loda
A little refactoring.
73
        self.annomodel = gtk.ListStore(gobject.TYPE_STRING,
74
                                       gobject.TYPE_STRING,
75
                                       gobject.TYPE_STRING,
76
                                       gobject.TYPE_STRING,
77
                                       gobject.TYPE_STRING,
78
                                       gobject.TYPE_STRING)
0.1.1 by Dan Loda
First working version of xannotate.
79
        
80
        last_seen = None
0.4.1 by Aaron Bentley
Updated performance, API use
81
        try:
82
            branch.lock_read()
83
            branch.repository.lock_read()
436 by Aaron Bentley
Use Branch.get_revision_id_to_revno_map in gannotate
84
            self.dotted = {}
85
            revno_map = self.branch.get_revision_id_to_revno_map()
86
            for revision_id, revno in revno_map.iteritems():
87
                self.dotted[revision_id] = '.'.join(str(num) for num in revno)
0.4.1 by Aaron Bentley
Updated performance, API use
88
            for line_no, (revision, revno, line)\
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
89
                    in enumerate(self._annotate(tree, file_id)):
0.4.1 by Aaron Bentley
Updated performance, API use
90
                if revision.revision_id == last_seen and not self.all:
273 by Aaron Bentley
Use get_apparent_author, rename variables to 'author'
91
                    revno = author = ""
0.4.1 by Aaron Bentley
Updated performance, API use
92
                else:
93
                    last_seen = revision.revision_id
642 by Jelmer Vernooij
Use get_apparent_authors() rather than deprecated get_apparent_author().
94
                    author = ", ".join(revision.get_apparent_authors())
0.4.1 by Aaron Bentley
Updated performance, API use
95
96
                if revision.revision_id not in self.revisions:
97
                    self.revisions[revision.revision_id] = revision
98
99
                self.annomodel.append([revision.revision_id,
100
                                       line_no + 1,
273 by Aaron Bentley
Use get_apparent_author, rename variables to 'author'
101
                                       author,
0.4.1 by Aaron Bentley
Updated performance, API use
102
                                       revno,
103
                                       None,
104
                                       line.rstrip("\r\n")
105
                                      ])
59.2.2 by Aaron Bentley
Annotate launches a diff for the particular revision
106
                self.annotations.append(revision)
0.4.1 by Aaron Bentley
Updated performance, API use
107
108
            if not self.plain:
66.6.1 by Aaron Bentley
Remove usused span selector
109
                now = time.time()
110
                self.annomodel.foreach(self._highlight_annotation, now)
0.4.1 by Aaron Bentley
Updated performance, API use
111
        finally:
112
            branch.repository.unlock()
113
            branch.unlock()
0.2.6 by Dan Loda
--plain option to disable highlighting. And update README
114
0.1.17 by Dan Loda
A little refactoring.
115
        self.annoview.set_model(self.annomodel)
116
        self.annoview.grab_focus()
438 by Aaron Bentley
Update window titles
117
        my_revno = self.dotted.get(self.revision_id, 'current')
118
        title = '%s (%s) - gannotate' % (self.tree.id2path(file_id), my_revno)
119
        self.set_title(title)
0.1.1 by Dan Loda
First working version of xannotate.
120
0.1.12 by Dan Loda
New --line option. Can now jump to a specific line number.
121
    def jump_to_line(self, lineno):
0.1.17 by Dan Loda
A little refactoring.
122
        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.
123
            row = 0
124
            # FIXME:should really deal with this in the gui. Perhaps a status
125
            # bar?
126
            print("gannotate: Line number %d does't exist. Defaulting to "
127
                  "line 1." % lineno)
79 by Jelmer Vernooij
Handle empty files more gracefully. Fixes #58951.
128
	    return
0.1.12 by Dan Loda
New --line option. Can now jump to a specific line number.
129
        else:
130
            row = lineno - 1
131
0.1.17 by Dan Loda
A little refactoring.
132
        self.annoview.set_cursor(row)
59.2.1 by Aaron Bentley
Gannotate takes a line number
133
        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.
134
66.2.4 by Aaron Bentley
Use dotted revnos instead of 'merge' where possible
135
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
136
    def _annotate(self, tree, file_id):
137
        current_revision = FakeRevision(CURRENT_REVISION)
138
        current_revision.committer = self.branch.get_config().username()
139
        current_revision.timestamp = time.time()
140
        current_revision.message = '[Not yet committed]'
66.6.6 by Aaron Bentley
Support scrolling based on an offset
141
        current_revision.parent_ids = tree.get_parent_ids()
630 by Jelmer Vernooij
Use _get_nick(local=True) rather than .nick to get at a branches' nick, since
142
        current_revision.properties['branch-nick'] = self.branch._get_nick(local=True)
66.2.15 by Aaron Bentley
fix future revno
143
        current_revno = '%d?' % (self.branch.revno() + 1)
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
144
        repository = self.branch.repository
145
        if self.revision_id == CURRENT_REVISION:
146
            revision_id = self.branch.last_revision()
147
        else:
148
            revision_id = self.revision_id
66.6.5 by Aaron Bentley
Speed up the 'back' operation
149
        revision_cache = RevisionCache(repository, self.revisions)
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
150
        for origin, text in tree.annotate_iter(file_id):
0.4.1 by Aaron Bentley
Updated performance, API use
151
            rev_id = origin
66.6.7 by Aaron Bentley
Handle current revision better
152
            if rev_id == CURRENT_REVISION:
153
                revision = current_revision
154
                revno = current_revno
155
            else:
156
                try:
157
                    revision = revision_cache.get_revision(rev_id)
436 by Aaron Bentley
Use Branch.get_revision_id_to_revno_map in gannotate
158
                    revno = self.dotted.get(rev_id, 'merge')
66.6.7 by Aaron Bentley
Handle current revision better
159
                    if len(revno) > 15:
160
                        revno = 'merge'
161
                except NoSuchRevision:
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
162
                    revision = FakeRevision(rev_id)
163
                    revno = "?"
0.1.1 by Dan Loda
First working version of xannotate.
164
165
            yield revision, revno, text
166
0.2.2 by Dan Loda
Add file's age as default span
167
    def _highlight_annotation(self, model, path, iter, now):
168
        revision_id, = model.get(iter, REVISION_ID_COL)
169
        revision = self.revisions[revision_id]
0.2.5 by Dan Loda
Use granny-like colors as default and rename ColorMap => AnnotateColorMap.
170
        model.set(iter, HIGHLIGHT_COLOR_COL,
0.1.19 by Aaron Bentley
Different colours for different committers
171
                  self.annotate_colormap.get_color(revision, now))
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
172
66.6.4 by Aaron Bentley
Add back button to see older versions
173
    def _selected_revision(self):
0.1.17 by Dan Loda
A little refactoring.
174
        (path, col) = self.annoview.get_cursor()
79 by Jelmer Vernooij
Handle empty files more gracefully. Fixes #58951.
175
        if path is None:
66.6.4 by Aaron Bentley
Add back button to see older versions
176
            return None
177
        return self.annomodel[path][REVISION_ID_COL]
178
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
179
    def _activate_selected_revision(self, w):
66.6.4 by Aaron Bentley
Add back button to see older versions
180
        rev_id = self._selected_revision()
464.2.1 by Adrian Wilkins
Detect the reserved null: revision in appropriate places.
181
        if not rev_id or rev_id == NULL_REVISION:
79 by Jelmer Vernooij
Handle empty files more gracefully. Fixes #58951.
182
            return
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
183
        selected = self.revisions[rev_id]
330.3.1 by Daniel Schierbeck
Renamed logview 'revisionview'.
184
        self.revisionview.set_revision(selected)
173.1.1 by Aaron Bentley
Better behavior when unable to go back
185
        if (len(selected.parent_ids) != 0 and selected.parent_ids[0] not in
186
            self._no_back):
187
            enable_back = True
188
        else:
189
            enable_back = False
190
        self.back_button.set_sensitive(enable_back)
0.1.1 by Dan Loda
First working version of xannotate.
191
192
    def _create(self):
330.3.1 by Daniel Schierbeck
Renamed logview 'revisionview'.
193
        self.revisionview = self._create_log_view()
0.1.17 by Dan Loda
A little refactoring.
194
        self.annoview = self._create_annotate_view()
195
170.1.6 by Aaron Bentley
Move buttons to top, tweak layout
196
        vbox = gtk.VBox(False)
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
197
        vbox.show()
0.1.17 by Dan Loda
A little refactoring.
198
199
        sw = gtk.ScrolledWindow()
200
        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
201
        sw.set_shadow_type(gtk.SHADOW_IN)
202
        sw.add(self.annoview)
59.2.2 by Aaron Bentley
Annotate launches a diff for the particular revision
203
        self.annoview.gwindow = self
0.1.17 by Dan Loda
A little refactoring.
204
        sw.show()
170.1.4 by Aaron Bentley
Move search fields directly below source window
205
206
        swbox = gtk.VBox()
207
        swbox.pack_start(sw)
208
        swbox.show()
170.1.6 by Aaron Bentley
Move buttons to top, tweak layout
209
210
        hbox = gtk.HBox(False, 6)
211
        self.back_button = self._create_back_button()
212
        hbox.pack_start(self.back_button, expand=False, fill=True)
213
        self.forward_button = self._create_forward_button()
214
        hbox.pack_start(self.forward_button, expand=False, fill=True)
215
        hbox.show()
216
        vbox.pack_start(hbox, expand=False, fill=True)
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
217
        
0.1.8 by Dan Loda
Remember window state. This introduces the gannotate.conf configuration file,
218
        self.pane = pane = gtk.VPaned()
170.1.4 by Aaron Bentley
Move search fields directly below source window
219
        pane.add1(swbox)
330.3.1 by Daniel Schierbeck
Renamed logview 'revisionview'.
220
        pane.add2(self.revisionview)
0.1.1 by Dan Loda
First working version of xannotate.
221
        pane.show()
222
        vbox.pack_start(pane, expand=True, fill=True)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
223
224
        self._search = SearchBox()
170.1.4 by Aaron Bentley
Move search fields directly below source window
225
        swbox.pack_start(self._search, expand=False, fill=True)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
226
        accels = gtk.AccelGroup()
227
        accels.connect_group(gtk.keysyms.f, gtk.gdk.CONTROL_MASK,
228
                             gtk.ACCEL_LOCKED,
229
                             self._search_by_text)
230
        accels.connect_group(gtk.keysyms.g, gtk.gdk.CONTROL_MASK,
231
                             gtk.ACCEL_LOCKED,
232
                             self._search_by_line)
233
        self.add_accel_group(accels)
234
0.1.1 by Dan Loda
First working version of xannotate.
235
        self.add(vbox)
236
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
237
    def _search_by_text(self, accel_group, window, key, modifiers):
238
        self._search.show_for('text')
66.5.3 by v.ladeuil+lp at free
Realbetter fix for bug #73965.
239
        self._search.set_target(self.annoview, TEXT_LINE_COL)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
240
241
    def _search_by_line(self, accel_group, window, key, modifiers):
242
        self._search.show_for('line')
66.5.3 by v.ladeuil+lp at free
Realbetter fix for bug #73965.
243
        self._search.set_target(self.annoview, LINE_NUM_COL)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
244
438 by Aaron Bentley
Update window titles
245
    def line_diff(self, tv, path, tvc):
59.2.2 by Aaron Bentley
Annotate launches a diff for the particular revision
246
        row = path[0]
247
        revision = self.annotations[row]
65 by Aaron Bentley
Handle first revision properly
248
        repository = self.branch.repository
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
249
        if revision.revision_id == CURRENT_REVISION:
250
            tree1 = self.tree
251
            tree2 = self.tree.basis_tree()
65 by Aaron Bentley
Handle first revision properly
252
        else:
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
253
            tree1 = repository.revision_tree(revision.revision_id)
254
            if len(revision.parent_ids) > 0:
255
                tree2 = repository.revision_tree(revision.parent_ids[0])
256
            else:
257
                tree2 = repository.revision_tree(NULL_REVISION)
150 by Jelmer Vernooij
Fix handling showing diffs of working tree changes.
258
        from bzrlib.plugins.gtk.diff import DiffWindow
59.2.2 by Aaron Bentley
Annotate launches a diff for the particular revision
259
        window = DiffWindow()
438 by Aaron Bentley
Update window titles
260
        window.set_diff("Diff for line %d" % (row+1), tree1, tree2)
59.2.3 by Aaron Bentley
Gannotate-launched diffs now jump to correct file
261
        window.set_file(tree1.id2path(self.file_id))
59.2.2 by Aaron Bentley
Annotate launches a diff for the particular revision
262
        window.show()
263
264
0.1.1 by Dan Loda
First working version of xannotate.
265
    def _create_annotate_view(self):
0.1.17 by Dan Loda
A little refactoring.
266
        tv = gtk.TreeView()
267
        tv.set_rules_hint(False)
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
268
        tv.connect("cursor-changed", self._activate_selected_revision)
0.1.17 by Dan Loda
A little refactoring.
269
        tv.show()
438 by Aaron Bentley
Update window titles
270
        tv.connect("row-activated", self.line_diff)
0.1.1 by Dan Loda
First working version of xannotate.
271
272
        cell = gtk.CellRendererText()
273
        cell.set_property("xalign", 1.0)
274
        cell.set_property("ypad", 0)
275
        cell.set_property("family", "Monospace")
276
        cell.set_property("cell-background-gdk",
0.1.17 by Dan Loda
A little refactoring.
277
                          tv.get_style().bg[gtk.STATE_NORMAL])
0.1.1 by Dan Loda
First working version of xannotate.
278
        col = gtk.TreeViewColumn()
279
        col.set_resizable(False)
280
        col.pack_start(cell, expand=True)
281
        col.add_attribute(cell, "text", LINE_NUM_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("ellipsize", pango.ELLIPSIZE_END)
287
        cell.set_property("cell-background-gdk",
288
                          self.get_style().bg[gtk.STATE_NORMAL])
289
        col = gtk.TreeViewColumn("Committer")
290
        col.set_resizable(True)
291
        col.pack_start(cell, expand=True)
292
        col.add_attribute(cell, "text", COMMITTER_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
295
        cell = gtk.CellRendererText()
296
        cell.set_property("xalign", 1.0)
297
        cell.set_property("ypad", 0)
298
        cell.set_property("cell-background-gdk",
299
                          self.get_style().bg[gtk.STATE_NORMAL])
300
        col = gtk.TreeViewColumn("Revno")
301
        col.set_resizable(False)
302
        col.pack_start(cell, expand=True)
303
        col.add_attribute(cell, "markup", REVNO_COL)
0.1.17 by Dan Loda
A little refactoring.
304
        tv.append_column(col)
0.1.1 by Dan Loda
First working version of xannotate.
305
306
        cell = gtk.CellRendererText()
307
        cell.set_property("ypad", 0)
308
        cell.set_property("family", "Monospace")
309
        col = gtk.TreeViewColumn()
310
        col.set_resizable(False)
311
        col.pack_start(cell, expand=True)
0.1.18 by Aaron Bentley
Switched to using pink backgrounds
312
#        col.add_attribute(cell, "foreground", HIGHLIGHT_COLOR_COL)
313
        col.add_attribute(cell, "background", HIGHLIGHT_COLOR_COL)
0.1.1 by Dan Loda
First working version of xannotate.
314
        col.add_attribute(cell, "text", TEXT_LINE_COL)
0.1.17 by Dan Loda
A little refactoring.
315
        tv.append_column(col)
0.1.1 by Dan Loda
First working version of xannotate.
316
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
317
        # FIXME: Now that C-f is now used for search by text we
318
        # may as well disable the auto search.
319
        tv.set_search_column(LINE_NUM_COL)
66.5.1 by v.ladeuil+lp at free
Minimal fix for bug #73965.
320
0.1.17 by Dan Loda
A little refactoring.
321
        return tv
0.1.1 by Dan Loda
First working version of xannotate.
322
323
    def _create_log_view(self):
473.1.1 by Andrew Bennetts
Simple hack to fix gannotate.
324
        lv = RevisionView(self._branch)
0.1.17 by Dan Loda
A little refactoring.
325
        lv.show()
326
        return lv
0.1.1 by Dan Loda
First working version of xannotate.
327
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
328
    def _create_back_button(self):
66.6.4 by Aaron Bentley
Add back button to see older versions
329
        button = gtk.Button()
330
        button.set_use_stock(True)
331
        button.set_label("gtk-go-back")
332
        button.connect("clicked", lambda w: self.go_back())
170.1.6 by Aaron Bentley
Move buttons to top, tweak layout
333
        button.set_relief(gtk.RELIEF_NONE)
66.6.4 by Aaron Bentley
Add back button to see older versions
334
        button.show()
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
335
        return button
336
337
    def _create_forward_button(self):
338
        button = gtk.Button()
339
        button.set_use_stock(True)
340
        button.set_label("gtk-go-forward")
341
        button.connect("clicked", lambda w: self.go_forward())
170.1.6 by Aaron Bentley
Move buttons to top, tweak layout
342
        button.set_relief(gtk.RELIEF_NONE)
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
343
        button.show()
344
        button.set_sensitive(False)
345
        return button
66.6.4 by Aaron Bentley
Add back button to see older versions
346
347
    def go_back(self):
173.1.1 by Aaron Bentley
Better behavior when unable to go back
348
        last_tree = self.tree
66.6.4 by Aaron Bentley
Add back button to see older versions
349
        rev_id = self._selected_revision()
350
        parent_id = self.revisions[rev_id].parent_ids[0]
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
351
        target_tree = self.branch.repository.revision_tree(parent_id)
173.1.1 by Aaron Bentley
Better behavior when unable to go back
352
        if self._go(target_tree):
353
            self.history.append(last_tree)
354
            self.forward_button.set_sensitive(True)
355
        else:
356
            self._no_back.add(parent_id)
357
            self.back_button.set_sensitive(False)
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
358
359
    def go_forward(self):
360
        if len(self.history) == 0:
361
            return
362
        target_tree = self.history.pop()
363
        if len(self.history) == 0:
364
            self.forward_button.set_sensitive(False)
365
        self._go(target_tree)
366
367
    def _go(self, target_tree):
368
        rev_id = self._selected_revision()
369
        if self.file_id in target_tree:
370
            offset = self.get_scroll_offset(target_tree)
66.6.6 by Aaron Bentley
Support scrolling based on an offset
371
            (row,), col = self.annoview.get_cursor()
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
372
            self.annotate(target_tree, self.branch, self.file_id)
373
            new_row = row+offset
374
            if new_row < 0:
375
                new_row = 0
376
            self.annoview.set_cursor(new_row)
173.1.1 by Aaron Bentley
Better behavior when unable to go back
377
            return True
378
        else:
379
            return False
66.6.6 by Aaron Bentley
Support scrolling based on an offset
380
381
    def get_scroll_offset(self, tree):
382
        old = self.tree.get_file(self.file_id)
383
        new = tree.get_file(self.file_id)
384
        (row,), col = self.annoview.get_cursor()
385
        matcher = patiencediff.PatienceSequenceMatcher(None, old.readlines(),
386
                                                       new.readlines())
387
        for i, j, n in matcher.get_matching_blocks():
388
            if i + n >= row:
389
                return j - i
390
0.1.1 by Dan Loda
First working version of xannotate.
391
642 by Jelmer Vernooij
Use get_apparent_authors() rather than deprecated get_apparent_author().
392
class FakeRevision(object):
0.1.1 by Dan Loda
First working version of xannotate.
393
    """ A fake revision.
394
395
    For when a revision is referenced but not present.
396
    """
397
157.1.7 by Aaron Bentley
Fix branch-nick handling
398
    def __init__(self, revision_id, committer='?', nick=None):
0.1.1 by Dan Loda
First working version of xannotate.
399
        self.revision_id = revision_id
400
        self.parent_ids = []
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
401
        self.committer = committer
0.1.1 by Dan Loda
First working version of xannotate.
402
        self.message = "?"
403
        self.timestamp = 0.0
404
        self.timezone = 0
157.1.7 by Aaron Bentley
Fix branch-nick handling
405
        self.properties = {}
0.1.1 by Dan Loda
First working version of xannotate.
406
642 by Jelmer Vernooij
Use get_apparent_authors() rather than deprecated get_apparent_author().
407
    def get_apparent_authors(self):
408
        return [self.committer]
273 by Aaron Bentley
Use get_apparent_author, rename variables to 'author'
409
0.1.23 by Aaron Bentley
Added revision caching to initial annotation
410
411
class RevisionCache(object):
412
    """A caching revision source"""
642 by Jelmer Vernooij
Use get_apparent_authors() rather than deprecated get_apparent_author().
413
66.6.5 by Aaron Bentley
Speed up the 'back' operation
414
    def __init__(self, real_source, seed_cache=None):
0.1.23 by Aaron Bentley
Added revision caching to initial annotation
415
        self.__real_source = real_source
66.6.5 by Aaron Bentley
Speed up the 'back' operation
416
        if seed_cache is None:
417
            self.__cache = {}
418
        else:
419
            self.__cache = dict(seed_cache)
0.1.23 by Aaron Bentley
Added revision caching to initial annotation
420
421
    def get_revision(self, revision_id):
422
        if revision_id not in self.__cache:
423
            revision = self.__real_source.get_revision(revision_id)
424
            self.__cache[revision_id] = revision
425
        return self.__cache[revision_id]
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
426
427
class SearchBox(gtk.HBox):
428
    """A button box for searching in text or lines of annotations"""
429
    def __init__(self):
430
        gtk.HBox.__init__(self, False, 6)
431
432
        # Close button
433
        button = gtk.Button()
434
        image = gtk.Image()
435
        image.set_from_stock('gtk-stop', gtk.ICON_SIZE_BUTTON)
436
        button.set_image(image)
437
        button.set_relief(gtk.RELIEF_NONE)
438
        button.connect("clicked", lambda w: self.hide_all())
439
        self.pack_start(button, expand=False, fill=False)
440
441
        # Search entry
442
        label = gtk.Label()
443
        self._label = label
444
        self.pack_start(label, expand=False, fill=False)
445
446
        entry = gtk.Entry()
447
        self._entry = entry
448
        entry.connect("activate", lambda w, d: self._do_search(d),
449
                      'forward')
450
        self.pack_start(entry, expand=False, fill=False)
451
452
        # Next/previous buttons
453
        button = gtk.Button('_Next')
454
        image = gtk.Image()
455
        image.set_from_stock('gtk-go-forward', gtk.ICON_SIZE_BUTTON)
456
        button.set_image(image)
457
        button.connect("clicked", lambda w, d: self._do_search(d),
458
                       'forward')
459
        self.pack_start(button, expand=False, fill=False)
460
461
        button = gtk.Button('_Previous')
462
        image = gtk.Image()
463
        image.set_from_stock('gtk-go-back', gtk.ICON_SIZE_BUTTON)
464
        button.set_image(image)
465
        button.connect("clicked", lambda w, d: self._do_search(d),
466
                       'backward')
467
        self.pack_start(button, expand=False, fill=False)
468
469
        # Search options
470
        check = gtk.CheckButton('Match case')
471
        self._match_case = check
472
        self.pack_start(check, expand=False, fill=False)
473
474
        check = gtk.CheckButton('Regexp')
475
        check.connect("toggled", lambda w: self._set_label())
476
        self._regexp = check
477
        self.pack_start(check, expand=False, fill=False)
478
479
        self._view = None
480
        self._column = None
481
        # Note that we stay hidden (we do not call self.show_all())
482
483
484
    def show_for(self, kind):
485
        self._kind = kind
486
        self.show_all()
487
        self._set_label()
488
        # Hide unrelated buttons
489
        if kind == 'line':
490
            self._match_case.hide()
491
            self._regexp.hide()
492
        # Be ready
493
        self._entry.grab_focus()
494
495
    def _set_label(self):
496
        if self._kind == 'line':
497
            self._label.set_text('Find Line: ')
498
        else:
499
            if self._regexp.get_active():
500
                self._label.set_text('Find Regexp: ')
501
            else:
502
                self._label.set_text('Find Text: ')
503
504
    def set_target(self, view,column):
505
        self._view = view
506
        self._column = column
507
508
    def _match(self, model, iterator, column):
509
        matching_case = self._match_case.get_active()
510
        string, = model.get(iterator, column)
511
        key = self._entry.get_text()
512
        if self._regexp.get_active():
513
            if matching_case:
514
                match = re.compile(key).search(string, 1)
515
            else:
516
                match = re.compile(key, re.I).search(string, 1)
517
        else:
518
            if not matching_case:
519
                string = string.lower()
520
                key = key.lower()
521
            match = string.find(key) != -1
522
523
        return match
524
525
    def _iterate_rows_forward(self, model, start):
526
        model_size = len(model)
527
        current = start + 1
528
        while model_size != 0:
529
            if current >= model_size: current =  0
530
            yield model.get_iter_from_string('%d' % current)
531
            if current == start: raise StopIteration
532
            current += 1
533
534
    def _iterate_rows_backward(self, model, start):
535
        model_size = len(model)
536
        current = start - 1
537
        while model_size != 0:
538
            if current < 0: current = model_size - 1
539
            yield model.get_iter_from_string('%d' % current)
540
            if current == start: raise StopIteration
541
            current -= 1
542
543
    def _do_search(self, direction):
544
        if direction == 'forward':
545
            iterate = self._iterate_rows_forward
546
        else:
547
            iterate = self._iterate_rows_backward
548
549
        model, sel = self._view.get_selection().get_selected()
550
        if sel is None:
551
            start = 0
552
        else:
553
            path = model.get_string_from_iter(sel)
554
            start = int(path)
555
556
        for row in iterate(model, start):
557
            if self._match(model, row, self._column):
558
                path = model.get_path(row)
559
                self._view.set_cursor(path)
560
                self._view.scroll_to_cell(path, use_align=True)
561
                break