/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: Szilveszter Farkas (Phanatic)
  • Date: 2006-08-07 16:51:21 UTC
  • mto: (0.14.1 main) (93.1.1 win32.bialix)
  • mto: This revision was merged to the branch mainline in revision 83.
  • Revision ID: Szilveszter.Farkas@gmail.com-20060807165121-10fe27c374bbdffd
Added new artwork.

2006-08-07  Szilveszter Farkas <Szilveszter.Farkas@gmail.com>

    * olive.galde: added custom artwork (icons)
    * icons/*: new icons for the toolbar
    * setup.py: install the icons

Show diffs side-by-side

added added

removed removed

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