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