/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz

« back to all changes in this revision

Viewing changes to annotate/gannotate.py

  • Committer: Jelmer Vernooij
  • Date: 2012-07-09 15:23:26 UTC
  • mto: This revision was merged to the branch mainline in revision 794.
  • Revision ID: jelmer@samba.org-20120709152326-dzxb8zoz0btull7n
Remove bzr-notify.

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
import time
18
18
 
19
 
import pygtk
20
 
pygtk.require("2.0")
21
 
import gobject
22
 
import gtk
23
 
import pango
 
19
from gi.repository import GObject
 
20
from gi.repository import Gdk
 
21
from gi.repository import Gtk
 
22
from gi.repository import Pango
24
23
import re
25
24
 
26
 
from bzrlib import patiencediff, tsort
 
25
from bzrlib import patiencediff
27
26
from bzrlib.errors import NoSuchRevision
28
27
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
29
28
 
30
 
from colormap import AnnotateColorMap, AnnotateColorSaturation
31
 
from bzrlib.plugins.gtk.logview import LogView
 
29
from bzrlib.plugins.gtk.annotate.colormap import AnnotateColorSaturation
 
30
from bzrlib.plugins.gtk.i18n import _i18n
 
31
from bzrlib.plugins.gtk.revisionview import RevisionView
 
32
from bzrlib.plugins.gtk.window import Window
32
33
 
33
34
 
34
35
(
41
42
) = range(6)
42
43
 
43
44
 
44
 
class GAnnotateWindow(gtk.Window):
 
45
class GAnnotateWindow(Window):
45
46
    """Annotate window."""
46
47
 
47
 
    def __init__(self, all=False, plain=False):
 
48
    def __init__(self, all=False, plain=False, parent=None, branch=None):
48
49
        self.all = all
49
50
        self.plain = plain
50
 
        
51
 
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
52
 
        
53
 
        self.set_icon(self.render_icon(gtk.STOCK_FIND, gtk.ICON_SIZE_BUTTON))
 
51
        self._branch = branch
 
52
 
 
53
        super(GAnnotateWindow, self).__init__(parent=parent)
 
54
 
 
55
        self.set_icon(
 
56
            self.render_icon_pixbuf(Gtk.STOCK_FIND, Gtk.IconSize.BUTTON))
54
57
        self.annotate_colormap = AnnotateColorSaturation()
55
58
 
56
59
        self._create()
57
60
        self.revisions = {}
 
61
        self.history = []
 
62
        self._no_back = set()
58
63
 
59
64
    def annotate(self, tree, branch, file_id):
60
65
        self.annotations = []
61
66
        self.branch = branch
62
67
        self.tree = tree
63
68
        self.file_id = file_id
 
69
        self.revisionview.set_file_id(file_id)
64
70
        self.revision_id = getattr(tree, 'get_revision_id', 
65
71
                                   lambda: CURRENT_REVISION)()
66
 
        
67
 
        # [revision id, line number, committer, revno, highlight color, line]
68
 
        self.annomodel = gtk.ListStore(gobject.TYPE_STRING,
69
 
                                       gobject.TYPE_STRING,
70
 
                                       gobject.TYPE_STRING,
71
 
                                       gobject.TYPE_STRING,
72
 
                                       gobject.TYPE_STRING,
73
 
                                       gobject.TYPE_STRING)
74
 
        
 
72
 
 
73
        # [revision id, line number, author, revno, highlight color, line]
 
74
        self.annomodel = Gtk.ListStore(GObject.TYPE_STRING,
 
75
                                       GObject.TYPE_INT,
 
76
                                       GObject.TYPE_STRING,
 
77
                                       GObject.TYPE_STRING,
 
78
                                       GObject.TYPE_STRING,
 
79
                                       GObject.TYPE_STRING)
 
80
 
75
81
        last_seen = None
76
82
        try:
77
83
            branch.lock_read()
78
84
            branch.repository.lock_read()
 
85
            self.dotted = {}
 
86
            revno_map = self.branch.get_revision_id_to_revno_map()
 
87
            for revision_id, revno in revno_map.iteritems():
 
88
                self.dotted[revision_id] = '.'.join(str(num) for num in revno)
79
89
            for line_no, (revision, revno, line)\
80
 
                    in enumerate(self._annotate(tree, file_id)):
 
90
                in enumerate(self._annotate(tree, file_id)):
81
91
                if revision.revision_id == last_seen and not self.all:
82
 
                    revno = committer = ""
 
92
                    revno = author = ""
83
93
                else:
84
94
                    last_seen = revision.revision_id
85
 
                    committer = revision.committer
 
95
                    author = ", ".join(revision.get_apparent_authors())
86
96
 
87
97
                if revision.revision_id not in self.revisions:
88
98
                    self.revisions[revision.revision_id] = revision
89
99
 
90
100
                self.annomodel.append([revision.revision_id,
91
101
                                       line_no + 1,
92
 
                                       committer,
 
102
                                       author,
93
103
                                       revno,
94
104
                                       None,
95
105
                                       line.rstrip("\r\n")
96
 
                                      ])
 
106
                                       ])
97
107
                self.annotations.append(revision)
98
108
 
99
109
            if not self.plain:
105
115
 
106
116
        self.annoview.set_model(self.annomodel)
107
117
        self.annoview.grab_focus()
 
118
        my_revno = self.dotted.get(self.revision_id, 'current')
 
119
        title = '%s (%s) - gannotate' % (self.tree.id2path(file_id), my_revno)
 
120
        self.set_title(title)
108
121
 
109
122
    def jump_to_line(self, lineno):
110
123
        if lineno > len(self.annomodel) or lineno < 1:
113
126
            # bar?
114
127
            print("gannotate: Line number %d does't exist. Defaulting to "
115
128
                  "line 1." % lineno)
116
 
            return
 
129
            return
117
130
        else:
118
131
            row = lineno - 1
119
132
 
120
 
        self.annoview.set_cursor(row)
121
 
        self.annoview.scroll_to_cell(row, use_align=True)
122
 
 
123
 
    def _dotted_revnos(self, repository, revision_id):
124
 
        """Return a dict of revision_id -> dotted revno
125
 
        
126
 
        :param repository: The repository to get the graph from
127
 
        :param revision_id: The last revision for which this info is needed
128
 
        """
129
 
        graph = repository.get_revision_graph(revision_id)
130
 
        dotted = {}
131
 
        for n, revision_id, d, revno, e in tsort.merge_sort(graph, 
132
 
            revision_id, generate_revno=True):
133
 
            dotted[revision_id] = '.'.join(str(num) for num in revno)
134
 
        return dotted
 
133
        tree_path = Gtk.TreePath(path=row)
 
134
        self.annoview.set_cursor(tree_path, None, False)
 
135
        self.annoview.scroll_to_cell(tree_path, use_align=True)
135
136
 
136
137
    def _annotate(self, tree, file_id):
137
138
        current_revision = FakeRevision(CURRENT_REVISION)
139
140
        current_revision.timestamp = time.time()
140
141
        current_revision.message = '[Not yet committed]'
141
142
        current_revision.parent_ids = tree.get_parent_ids()
142
 
        current_revision.properties['branch-nick'] = self.branch.nick
 
143
        current_revision.properties['branch-nick'] = self.branch._get_nick(local=True)
143
144
        current_revno = '%d?' % (self.branch.revno() + 1)
144
145
        repository = self.branch.repository
145
146
        if self.revision_id == CURRENT_REVISION:
146
147
            revision_id = self.branch.last_revision()
147
148
        else:
148
149
            revision_id = self.revision_id
149
 
        dotted = self._dotted_revnos(repository, revision_id)
150
150
        revision_cache = RevisionCache(repository, self.revisions)
151
151
        for origin, text in tree.annotate_iter(file_id):
152
152
            rev_id = origin
156
156
            else:
157
157
                try:
158
158
                    revision = revision_cache.get_revision(rev_id)
159
 
                    revno = dotted.get(rev_id, 'merge')
 
159
                    revno = self.dotted.get(rev_id, 'merge')
160
160
                    if len(revno) > 15:
161
161
                        revno = 'merge'
162
162
                except NoSuchRevision:
168
168
    def _highlight_annotation(self, model, path, iter, now):
169
169
        revision_id, = model.get(iter, REVISION_ID_COL)
170
170
        revision = self.revisions[revision_id]
171
 
        model.set(iter, HIGHLIGHT_COLOR_COL,
172
 
                  self.annotate_colormap.get_color(revision, now))
 
171
        # XXX sinzui 2011-08-12: What does get_color return?
 
172
        color = self.annotate_colormap.get_color(revision, now)
 
173
        model.set_value(iter, HIGHLIGHT_COLOR_COL, color)
173
174
 
174
175
    def _selected_revision(self):
175
176
        (path, col) = self.annoview.get_cursor()
177
178
            return None
178
179
        return self.annomodel[path][REVISION_ID_COL]
179
180
 
180
 
    def _show_log(self, w):
 
181
    def _activate_selected_revision(self, w):
181
182
        rev_id = self._selected_revision()
182
 
        if rev_id is None:
 
183
        if not rev_id or rev_id == NULL_REVISION:
183
184
            return
184
 
        self.logview.set_revision(self.revisions[rev_id])
 
185
        selected = self.revisions[rev_id]
 
186
        self.revisionview.set_revision(selected)
 
187
        if (len(selected.parent_ids) != 0 and selected.parent_ids[0] not in
 
188
            self._no_back):
 
189
            enable_back = True
 
190
        else:
 
191
            enable_back = False
 
192
        self.back_button.set_sensitive(enable_back)
185
193
 
186
194
    def _create(self):
187
 
        self.logview = self._create_log_view()
 
195
        self.revisionview = self._create_log_view()
188
196
        self.annoview = self._create_annotate_view()
189
197
 
190
 
        vbox = gtk.VBox(False, 12)
191
 
        vbox.set_border_width(12)
 
198
        vbox = Gtk.VBox(homogeneous=False, spacing=0)
192
199
        vbox.show()
193
200
 
194
 
        sw = gtk.ScrolledWindow()
195
 
        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
196
 
        sw.set_shadow_type(gtk.SHADOW_IN)
 
201
        sw = Gtk.ScrolledWindow()
 
202
        sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
 
203
        sw.set_shadow_type(Gtk.ShadowType.IN)
197
204
        sw.add(self.annoview)
198
205
        self.annoview.gwindow = self
199
206
        sw.show()
200
 
        
201
 
        self.pane = pane = gtk.VPaned()
202
 
        pane.add1(sw)
203
 
        pane.add2(self.logview)
 
207
 
 
208
        swbox = Gtk.VBox()
 
209
        swbox.pack_start(sw, True, True, 0)
 
210
        swbox.show()
 
211
 
 
212
        hbox = Gtk.HBox(homogeneous=False, spacing=6)
 
213
        self.back_button = self._create_back_button()
 
214
        hbox.pack_start(self.back_button, False, True, 0)
 
215
        self.forward_button = self._create_forward_button()
 
216
        hbox.pack_start(self.forward_button, False, True, 0)
 
217
        self.find_button = self._create_find_button()
 
218
        hbox.pack_start(self.find_button, False, True, 0)
 
219
        self.goto_button = self._create_goto_button()
 
220
        hbox.pack_start(self.goto_button, False, True, 0)
 
221
        hbox.show()
 
222
        vbox.pack_start(hbox, False, True, 0)
 
223
 
 
224
        self.pane = pane = Gtk.Paned.new(Gtk.Orientation.VERTICAL)
 
225
        pane.add1(swbox)
 
226
        pane.add2(self.revisionview)
204
227
        pane.show()
205
 
        vbox.pack_start(pane, expand=True, fill=True)
 
228
        vbox.pack_start(pane, True, True, 0)
206
229
 
207
230
        self._search = SearchBox()
208
 
        vbox.pack_start(self._search, expand=False, fill=True)
209
 
        accels = gtk.AccelGroup()
210
 
        accels.connect_group(gtk.keysyms.f, gtk.gdk.CONTROL_MASK,
211
 
                             gtk.ACCEL_LOCKED,
 
231
        swbox.pack_start(self._search, False, True, 0)
 
232
        accels = Gtk.AccelGroup()
 
233
        accels.connect(Gdk.KEY_f, Gdk.ModifierType.CONTROL_MASK,
 
234
                             Gtk.AccelFlags.LOCKED,
212
235
                             self._search_by_text)
213
 
        accels.connect_group(gtk.keysyms.g, gtk.gdk.CONTROL_MASK,
214
 
                             gtk.ACCEL_LOCKED,
 
236
        accels.connect(Gdk.KEY_g, Gdk.ModifierType.CONTROL_MASK,
 
237
                             Gtk.AccelFlags.LOCKED,
215
238
                             self._search_by_line)
216
239
        self.add_accel_group(accels)
217
240
 
218
 
        hbox = gtk.HBox(True, 6)
219
 
        hbox.pack_start(self._create_prev_button(), expand=False, fill=True)
220
 
        hbox.pack_end(self._create_button_box(), expand=False, fill=True)
221
 
        hbox.show()
222
 
        vbox.pack_start(hbox, expand=False, fill=True)
223
 
 
224
241
        self.add(vbox)
225
242
 
226
 
    def _search_by_text(self, accel_group, window, key, modifiers):
 
243
    def _search_by_text(self, *ignored): # (accel_group, window, key, modifiers):
227
244
        self._search.show_for('text')
228
245
        self._search.set_target(self.annoview, TEXT_LINE_COL)
229
246
 
230
 
    def _search_by_line(self, accel_group, window, key, modifiers):
 
247
    def _search_by_line(self, *ignored): # accel_group, window, key, modifiers):
231
248
        self._search.show_for('line')
232
249
        self._search.set_target(self.annoview, LINE_NUM_COL)
233
250
 
234
 
    def row_diff(self, tv, path, tvc):
235
 
        row = path[0]
 
251
    def line_diff(self, tv, path, tvc):
 
252
        row = path.get_indices()[0]
236
253
        revision = self.annotations[row]
237
254
        repository = self.branch.repository
238
255
        if revision.revision_id == CURRENT_REVISION:
245
262
            else:
246
263
                tree2 = repository.revision_tree(NULL_REVISION)
247
264
        from bzrlib.plugins.gtk.diff import DiffWindow
248
 
        window = DiffWindow()
249
 
        window.set_diff("Diff for row %d" % (row+1), tree1, tree2)
 
265
        window = DiffWindow(self)
 
266
        window.set_diff("Diff for line %d" % (row+1), tree1, tree2)
250
267
        window.set_file(tree1.id2path(self.file_id))
251
268
        window.show()
252
269
 
253
270
 
254
271
    def _create_annotate_view(self):
255
 
        tv = gtk.TreeView()
 
272
        tv = Gtk.TreeView()
256
273
        tv.set_rules_hint(False)
257
 
        tv.connect("cursor-changed", self._show_log)
 
274
        tv.connect("cursor-changed", self._activate_selected_revision)
258
275
        tv.show()
259
 
        tv.connect("row-activated", self.row_diff)
 
276
        tv.connect("row-activated", self.line_diff)
260
277
 
261
 
        cell = gtk.CellRendererText()
 
278
        cell = Gtk.CellRendererText()
262
279
        cell.set_property("xalign", 1.0)
263
280
        cell.set_property("ypad", 0)
264
281
        cell.set_property("family", "Monospace")
265
282
        cell.set_property("cell-background-gdk",
266
 
                          tv.get_style().bg[gtk.STATE_NORMAL])
267
 
        col = gtk.TreeViewColumn()
 
283
                          tv.get_style().bg[Gtk.StateType.NORMAL])
 
284
        col = Gtk.TreeViewColumn()
268
285
        col.set_resizable(False)
269
 
        col.pack_start(cell, expand=True)
 
286
        col.pack_start(cell, True)
270
287
        col.add_attribute(cell, "text", LINE_NUM_COL)
271
288
        tv.append_column(col)
272
289
 
273
 
        cell = gtk.CellRendererText()
 
290
        cell = Gtk.CellRendererText()
274
291
        cell.set_property("ypad", 0)
275
 
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
 
292
        cell.set_property("ellipsize", Pango.EllipsizeMode.END)
276
293
        cell.set_property("cell-background-gdk",
277
 
                          self.get_style().bg[gtk.STATE_NORMAL])
278
 
        col = gtk.TreeViewColumn("Committer")
 
294
                          self.get_style().bg[Gtk.StateType.NORMAL])
 
295
        col = Gtk.TreeViewColumn("Committer")
279
296
        col.set_resizable(True)
280
 
        col.pack_start(cell, expand=True)
 
297
        col.pack_start(cell, True)
281
298
        col.add_attribute(cell, "text", COMMITTER_COL)
282
299
        tv.append_column(col)
283
300
 
284
 
        cell = gtk.CellRendererText()
 
301
        cell = Gtk.CellRendererText()
285
302
        cell.set_property("xalign", 1.0)
286
303
        cell.set_property("ypad", 0)
287
304
        cell.set_property("cell-background-gdk",
288
 
                          self.get_style().bg[gtk.STATE_NORMAL])
289
 
        col = gtk.TreeViewColumn("Revno")
 
305
                          self.get_style().bg[Gtk.StateType.NORMAL])
 
306
        col = Gtk.TreeViewColumn("Revno")
290
307
        col.set_resizable(False)
291
 
        col.pack_start(cell, expand=True)
 
308
        col.pack_start(cell, True)
292
309
        col.add_attribute(cell, "markup", REVNO_COL)
293
310
        tv.append_column(col)
294
311
 
295
 
        cell = gtk.CellRendererText()
 
312
        cell = Gtk.CellRendererText()
296
313
        cell.set_property("ypad", 0)
297
314
        cell.set_property("family", "Monospace")
298
 
        col = gtk.TreeViewColumn()
 
315
        col = Gtk.TreeViewColumn()
299
316
        col.set_resizable(False)
300
 
        col.pack_start(cell, expand=True)
 
317
        col.pack_start(cell, True)
301
318
#        col.add_attribute(cell, "foreground", HIGHLIGHT_COLOR_COL)
302
319
        col.add_attribute(cell, "background", HIGHLIGHT_COLOR_COL)
303
320
        col.add_attribute(cell, "text", TEXT_LINE_COL)
304
321
        tv.append_column(col)
305
322
 
306
 
        # FIXME: Now that C-f is now used for search by text we
307
 
        # may as well disable the auto search.
308
 
        tv.set_search_column(LINE_NUM_COL)
 
323
        # interactive substring search
 
324
        def search_equal_func(model, column, key, iter):
 
325
            return model.get_value(iter, TEXT_LINE_COL).lower().find(key.lower()) == -1
 
326
 
 
327
        tv.set_enable_search(True)
 
328
        tv.set_search_equal_func(search_equal_func, None)
309
329
 
310
330
        return tv
311
331
 
312
332
    def _create_log_view(self):
313
 
        lv = LogView()
 
333
        lv = RevisionView(self._branch)
314
334
        lv.show()
315
335
        return lv
316
336
 
317
 
    def _create_button_box(self):
318
 
        box = gtk.HButtonBox()
319
 
        box.set_layout(gtk.BUTTONBOX_END)
320
 
        box.show()
321
 
 
322
 
        button = gtk.Button()
323
 
        button.set_use_stock(True)
324
 
        button.set_label("gtk-close")
325
 
        button.connect("clicked", lambda w: self.destroy())
326
 
        button.show()
327
 
 
328
 
        box.pack_start(button, expand=False, fill=False)
329
 
 
330
 
        return box
331
 
 
332
 
    def _create_prev_button(self):
333
 
        box = gtk.HButtonBox()
334
 
        box.set_layout(gtk.BUTTONBOX_START)
335
 
        box.show()
336
 
        
337
 
        button = gtk.Button()
 
337
    def _create_back_button(self):
 
338
        button = Gtk.Button()
338
339
        button.set_use_stock(True)
339
340
        button.set_label("gtk-go-back")
340
341
        button.connect("clicked", lambda w: self.go_back())
341
 
        button.show()
342
 
        box.pack_start(button, expand=False, fill=False)
343
 
        return box
 
342
        button.set_relief(Gtk.ReliefStyle.NONE)
 
343
        button.show()
 
344
        return button
 
345
 
 
346
    def _create_forward_button(self):
 
347
        button = Gtk.Button()
 
348
        button.set_use_stock(True)
 
349
        button.set_label("gtk-go-forward")
 
350
        button.connect("clicked", lambda w: self.go_forward())
 
351
        button.set_relief(Gtk.ReliefStyle.NONE)
 
352
        button.show()
 
353
        button.set_sensitive(False)
 
354
        return button
 
355
 
 
356
    def _create_find_button(self):
 
357
        button = Gtk.Button()
 
358
        button.set_use_stock(True)
 
359
        button.set_label("gtk-find")
 
360
        button.set_tooltip_text("Search for text (Ctrl+F)")
 
361
        button.connect("clicked", self._search_by_text)
 
362
        button.set_relief(Gtk.ReliefStyle.NONE)
 
363
        button.show()
 
364
        button.set_sensitive(True)
 
365
        return button
 
366
 
 
367
    def _create_goto_button(self):
 
368
        button = Gtk.Button()
 
369
        button.set_label("Goto Line")
 
370
        button.set_tooltip_text("Scroll to a line by entering its number (Ctrl+G)")
 
371
        button.connect("clicked", self._search_by_line)
 
372
        button.set_relief(Gtk.ReliefStyle.NONE)
 
373
        button.show()
 
374
        button.set_sensitive(True)
 
375
        return button
344
376
 
345
377
    def go_back(self):
 
378
        last_tree = self.tree
346
379
        rev_id = self._selected_revision()
347
380
        parent_id = self.revisions[rev_id].parent_ids[0]
348
 
        tree = self.branch.repository.revision_tree(parent_id)
349
 
        if self.file_id in tree:
350
 
            offset = self.get_scroll_offset(tree)
351
 
            (row,), col = self.annoview.get_cursor()
352
 
            self.annotate(tree, self.branch, self.file_id)
353
 
            self.annoview.set_cursor(row+offset)
 
381
        target_tree = self.branch.repository.revision_tree(parent_id)
 
382
        if self._go(target_tree):
 
383
            self.history.append(last_tree)
 
384
            self.forward_button.set_sensitive(True)
 
385
        else:
 
386
            self._no_back.add(parent_id)
 
387
            self.back_button.set_sensitive(False)
 
388
 
 
389
    def go_forward(self):
 
390
        if len(self.history) == 0:
 
391
            return
 
392
        target_tree = self.history.pop()
 
393
        if len(self.history) == 0:
 
394
            self.forward_button.set_sensitive(False)
 
395
        self._go(target_tree)
 
396
 
 
397
    def _go(self, target_tree):
 
398
        rev_id = self._selected_revision()
 
399
        if self.file_id in target_tree:
 
400
            offset = self.get_scroll_offset(target_tree)
 
401
            path, col = self.annoview.get_cursor()
 
402
            (row,) = path.get_indices()
 
403
            self.annotate(target_tree, self.branch, self.file_id)
 
404
            new_row = row+offset
 
405
            if new_row < 0:
 
406
                new_row = 0
 
407
            new_path = Gtk.TreePath(path=new_row)
 
408
            self.annoview.set_cursor(new_path, None, False)
 
409
            return True
 
410
        else:
 
411
            return False
354
412
 
355
413
    def get_scroll_offset(self, tree):
356
414
        old = self.tree.get_file(self.file_id)
357
415
        new = tree.get_file(self.file_id)
358
 
        (row,), col = self.annoview.get_cursor()
 
416
        path, col = self.annoview.get_cursor()
 
417
        (row,) = path.get_indices()
359
418
        matcher = patiencediff.PatienceSequenceMatcher(None, old.readlines(),
360
419
                                                       new.readlines())
361
420
        for i, j, n in matcher.get_matching_blocks():
363
422
                return j - i
364
423
 
365
424
 
366
 
 
367
 
class FakeRevision:
 
425
class FakeRevision(object):
368
426
    """ A fake revision.
369
427
 
370
428
    For when a revision is referenced but not present.
379
437
        self.timezone = 0
380
438
        self.properties = {}
381
439
 
 
440
    def get_apparent_authors(self):
 
441
        return [self.committer]
 
442
 
382
443
 
383
444
class RevisionCache(object):
384
445
    """A caching revision source"""
 
446
 
385
447
    def __init__(self, real_source, seed_cache=None):
386
448
        self.__real_source = real_source
387
449
        if seed_cache is None:
395
457
            self.__cache[revision_id] = revision
396
458
        return self.__cache[revision_id]
397
459
 
398
 
class SearchBox(gtk.HBox):
 
460
 
 
461
class SearchBox(Gtk.HBox):
399
462
    """A button box for searching in text or lines of annotations"""
400
463
    def __init__(self):
401
 
        gtk.HBox.__init__(self, False, 6)
 
464
        super(SearchBox, self).__init__(homogeneous=False, spacing=6)
402
465
 
403
466
        # Close button
404
 
        button = gtk.Button()
405
 
        image = gtk.Image()
406
 
        image.set_from_stock('gtk-stop', gtk.ICON_SIZE_BUTTON)
 
467
        button = Gtk.Button()
 
468
        image = Gtk.Image()
 
469
        image.set_from_stock('gtk-stop', Gtk.IconSize.BUTTON)
407
470
        button.set_image(image)
408
 
        button.set_relief(gtk.RELIEF_NONE)
409
 
        button.connect("clicked", lambda w: self.hide_all())
410
 
        self.pack_start(button, expand=False, fill=False)
 
471
        button.set_relief(Gtk.ReliefStyle.NONE)
 
472
        button.connect("clicked", lambda w: self.hide())
 
473
        self.pack_start(button, False, False, 0)
411
474
 
412
475
        # Search entry
413
 
        label = gtk.Label()
 
476
        label = Gtk.Label()
414
477
        self._label = label
415
 
        self.pack_start(label, expand=False, fill=False)
 
478
        self.pack_start(label, False, False, 0)
416
479
 
417
 
        entry = gtk.Entry()
 
480
        entry = Gtk.Entry()
418
481
        self._entry = entry
419
482
        entry.connect("activate", lambda w, d: self._do_search(d),
420
483
                      'forward')
421
 
        self.pack_start(entry, expand=False, fill=False)
 
484
        self.pack_start(entry, False, False, 0)
422
485
 
423
486
        # Next/previous buttons
424
 
        button = gtk.Button('_Next')
425
 
        image = gtk.Image()
426
 
        image.set_from_stock('gtk-go-forward', gtk.ICON_SIZE_BUTTON)
 
487
        button = Gtk.Button(_i18n('_Next'), use_underline=True)
 
488
        image = Gtk.Image()
 
489
        image.set_from_stock('gtk-go-forward', Gtk.IconSize.BUTTON)
427
490
        button.set_image(image)
428
491
        button.connect("clicked", lambda w, d: self._do_search(d),
429
492
                       'forward')
430
 
        self.pack_start(button, expand=False, fill=False)
 
493
        self.pack_start(button, False, False, 0)
431
494
 
432
 
        button = gtk.Button('_Previous')
433
 
        image = gtk.Image()
434
 
        image.set_from_stock('gtk-go-back', gtk.ICON_SIZE_BUTTON)
 
495
        button = Gtk.Button(_i18n('_Previous'), use_underline=True)
 
496
        image = Gtk.Image()
 
497
        image.set_from_stock('gtk-go-back', Gtk.IconSize.BUTTON)
435
498
        button.set_image(image)
436
499
        button.connect("clicked", lambda w, d: self._do_search(d),
437
500
                       'backward')
438
 
        self.pack_start(button, expand=False, fill=False)
 
501
        self.pack_start(button, False, False, 0)
439
502
 
440
503
        # Search options
441
 
        check = gtk.CheckButton('Match case')
 
504
        check = Gtk.CheckButton('Match case')
442
505
        self._match_case = check
443
 
        self.pack_start(check, expand=False, fill=False)
 
506
        self.pack_start(check, False, False, 0)
444
507
 
445
 
        check = gtk.CheckButton('Regexp')
 
508
        check = Gtk.CheckButton('Regexp')
446
509
        check.connect("toggled", lambda w: self._set_label())
447
510
        self._regexp = check
448
 
        self.pack_start(check, expand=False, fill=False)
 
511
        self.pack_start(check, False, False, 0)
449
512
 
450
513
        self._view = None
451
514
        self._column = None
478
541
 
479
542
    def _match(self, model, iterator, column):
480
543
        matching_case = self._match_case.get_active()
481
 
        string, = model.get(iterator, column)
 
544
        cell_value, = model.get(iterator, column)
482
545
        key = self._entry.get_text()
483
 
        if self._regexp.get_active():
 
546
        if column == LINE_NUM_COL:
 
547
            # FIXME: For goto-line there are faster algorithms than searching 
 
548
            # every line til we find the right one! -- mbp 2011-01-27
 
549
            return key.strip() == str(cell_value)
 
550
        elif self._regexp.get_active():
484
551
            if matching_case:
485
 
                match = re.compile(key).search(string, 1)
 
552
                match = re.compile(key).search(cell_value, 1)
486
553
            else:
487
 
                match = re.compile(key, re.I).search(string, 1)
 
554
                match = re.compile(key, re.I).search(cell_value, 1)
488
555
        else:
489
556
            if not matching_case:
490
 
                string = string.lower()
 
557
                cell_value = cell_value.lower()
491
558
                key = key.lower()
492
 
            match = string.find(key) != -1
 
559
            match = cell_value.find(key) != -1
493
560
 
494
561
        return match
495
562
 
527
594
        for row in iterate(model, start):
528
595
            if self._match(model, row, self._column):
529
596
                path = model.get_path(row)
530
 
                self._view.set_cursor(path)
 
597
                self._view.set_cursor(path, None, False)
531
598
                self._view.scroll_to_cell(path, use_align=True)
532
599
                break