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