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