/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: Daniel Schierbeck
  • Date: 2008-04-07 20:34:51 UTC
  • mfrom: (450.6.13 bugs)
  • mto: (463.2.1 bug.78765)
  • mto: This revision was merged to the branch mainline in revision 462.
  • Revision ID: daniel.schierbeck@gmail.com-20080407203451-2i6el7jf9t0k9y64
Merged bug page improvements.

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
import gobject
22
22
import gtk
23
23
import pango
 
24
import re
24
25
 
 
26
from bzrlib import patiencediff, tsort
25
27
from bzrlib.errors import NoSuchRevision
26
 
from bzrlib.revision import NULL_REVISION
 
28
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
27
29
 
28
30
from colormap import AnnotateColorMap, AnnotateColorSaturation
29
 
from logview import LogView
30
 
from spanselector import SpanSelector
 
31
from bzrlib.plugins.gtk.revisionview import RevisionView
 
32
from bzrlib.plugins.gtk.window import Window
31
33
 
32
34
 
33
35
(
40
42
) = range(6)
41
43
 
42
44
 
43
 
class GAnnotateWindow(gtk.Window):
 
45
class GAnnotateWindow(Window):
44
46
    """Annotate window."""
45
47
 
46
 
    def __init__(self, all=False, plain=False):
 
48
    def __init__(self, all=False, plain=False, parent=None):
47
49
        self.all = all
48
50
        self.plain = plain
49
51
        
50
 
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
 
52
        Window.__init__(self, parent)
51
53
        
52
54
        self.set_icon(self.render_icon(gtk.STOCK_FIND, gtk.ICON_SIZE_BUTTON))
53
55
        self.annotate_colormap = AnnotateColorSaturation()
54
56
 
55
57
        self._create()
56
 
 
57
 
        if self.plain:
58
 
            self.span_selector.hide()
59
 
 
60
 
    def annotate(self, branch, file_id):
61
58
        self.revisions = {}
 
59
        self.history = []
 
60
        self._no_back = set()
 
61
 
 
62
    def annotate(self, tree, branch, file_id):
62
63
        self.annotations = []
63
64
        self.branch = branch
 
65
        self.tree = tree
64
66
        self.file_id = file_id
 
67
        self.revisionview.set_file_id(file_id)
 
68
        self.revision_id = getattr(tree, 'get_revision_id', 
 
69
                                   lambda: CURRENT_REVISION)()
65
70
        
66
 
        # [revision id, line number, committer, revno, highlight color, line]
 
71
        # [revision id, line number, author, revno, highlight color, line]
67
72
        self.annomodel = gtk.ListStore(gobject.TYPE_STRING,
68
73
                                       gobject.TYPE_STRING,
69
74
                                       gobject.TYPE_STRING,
75
80
        try:
76
81
            branch.lock_read()
77
82
            branch.repository.lock_read()
 
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)
78
87
            for line_no, (revision, revno, line)\
79
 
                    in enumerate(self._annotate(branch, file_id)):
 
88
                    in enumerate(self._annotate(tree, file_id)):
80
89
                if revision.revision_id == last_seen and not self.all:
81
 
                    revno = committer = ""
 
90
                    revno = author = ""
82
91
                else:
83
92
                    last_seen = revision.revision_id
84
 
                    committer = revision.committer
 
93
                    author = revision.get_apparent_author()
85
94
 
86
95
                if revision.revision_id not in self.revisions:
87
96
                    self.revisions[revision.revision_id] = revision
88
97
 
89
98
                self.annomodel.append([revision.revision_id,
90
99
                                       line_no + 1,
91
 
                                       committer,
 
100
                                       author,
92
101
                                       revno,
93
102
                                       None,
94
103
                                       line.rstrip("\r\n")
96
105
                self.annotations.append(revision)
97
106
 
98
107
            if not self.plain:
99
 
                self._set_oldest_newest()
100
 
                # Recall that calling activate_default will emit "span-changed",
101
 
                # so self._span_changed_cb will take care of initial highlighting
102
 
                self.span_selector.activate_default()
 
108
                now = time.time()
 
109
                self.annomodel.foreach(self._highlight_annotation, now)
103
110
        finally:
104
111
            branch.repository.unlock()
105
112
            branch.unlock()
106
113
 
107
114
        self.annoview.set_model(self.annomodel)
108
115
        self.annoview.grab_focus()
 
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)
109
119
 
110
120
    def jump_to_line(self, lineno):
111
121
        if lineno > len(self.annomodel) or lineno < 1:
114
124
            # bar?
115
125
            print("gannotate: Line number %d does't exist. Defaulting to "
116
126
                  "line 1." % lineno)
 
127
            return
117
128
        else:
118
129
            row = lineno - 1
119
130
 
120
131
        self.annoview.set_cursor(row)
121
132
        self.annoview.scroll_to_cell(row, use_align=True)
122
133
 
123
 
    def _annotate(self, branch, file_id):
124
 
        rev_hist = branch.revision_history()
125
 
        repository = branch.repository
126
 
        rev_tree = repository.revision_tree(branch.last_revision())
127
 
        rev_id = rev_tree.inventory[file_id].revision
128
 
        weave = repository.weave_store.get_weave(file_id,
129
 
                                                 branch.get_transaction())
130
 
        
131
 
        revision_cache = RevisionCache(repository)
132
 
        for origin, text in weave.annotate_iter(rev_id):
 
134
 
 
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]'
 
140
        current_revision.parent_ids = tree.get_parent_ids()
 
141
        current_revision.properties['branch-nick'] = self.branch.nick
 
142
        current_revno = '%d?' % (self.branch.revno() + 1)
 
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
 
148
        revision_cache = RevisionCache(repository, self.revisions)
 
149
        for origin, text in tree.annotate_iter(file_id):
133
150
            rev_id = origin
134
 
            try:
135
 
                revision = revision_cache.get_revision(rev_id)
136
 
                if rev_id in rev_hist:
137
 
                    revno = branch.revision_id_to_revno(rev_id)
138
 
                else:
139
 
                    revno = "merge"
140
 
            except NoSuchRevision:
141
 
                revision = NoneRevision(rev_id)
142
 
                revno = "?"
 
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)
 
157
                    revno = self.dotted.get(rev_id, 'merge')
 
158
                    if len(revno) > 15:
 
159
                        revno = 'merge'
 
160
                except NoSuchRevision:
 
161
                    revision = FakeRevision(rev_id)
 
162
                    revno = "?"
143
163
 
144
164
            yield revision, revno, text
145
165
 
146
 
    def _set_oldest_newest(self):
147
 
        rev_dates = map(lambda i: self.revisions[i].timestamp, self.revisions)
148
 
        oldest = min(rev_dates)
149
 
        newest = max(rev_dates)
150
 
 
151
 
        span = self._span_from_seconds(time.time() - oldest)
152
 
        self.span_selector.set_to_oldest_span(span)
153
 
        
154
 
        span = self._span_from_seconds(newest - oldest)
155
 
        self.span_selector.set_newest_to_oldest_span(span)
156
 
 
157
 
    def _span_from_seconds(self, seconds):
158
 
        return (seconds / (24 * 60 * 60))
159
 
    
160
 
    def _span_changed_cb(self, w, span):
161
 
        self.annotate_colormap.set_span(span)
162
 
        now = time.time()
163
 
        self.annomodel.foreach(self._highlight_annotation, now)
164
 
 
165
166
    def _highlight_annotation(self, model, path, iter, now):
166
167
        revision_id, = model.get(iter, REVISION_ID_COL)
167
168
        revision = self.revisions[revision_id]
168
169
        model.set(iter, HIGHLIGHT_COLOR_COL,
169
170
                  self.annotate_colormap.get_color(revision, now))
170
171
 
171
 
    def _show_log(self, w):
 
172
    def _selected_revision(self):
172
173
        (path, col) = self.annoview.get_cursor()
173
 
        rev_id = self.annomodel[path][REVISION_ID_COL]
174
 
        self.logview.set_revision(self.revisions[rev_id])
 
174
        if path is None:
 
175
            return None
 
176
        return self.annomodel[path][REVISION_ID_COL]
 
177
 
 
178
    def _activate_selected_revision(self, w):
 
179
        rev_id = self._selected_revision()
 
180
        if rev_id is None:
 
181
            return
 
182
        selected = self.revisions[rev_id]
 
183
        self.revisionview.set_revision(selected)
 
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)
175
190
 
176
191
    def _create(self):
177
 
        self.logview = self._create_log_view()
 
192
        self.revisionview = self._create_log_view()
178
193
        self.annoview = self._create_annotate_view()
179
 
        self.span_selector = self._create_span_selector()
180
194
 
181
 
        vbox = gtk.VBox(False, 12)
182
 
        vbox.set_border_width(12)
 
195
        vbox = gtk.VBox(False)
183
196
        vbox.show()
184
197
 
185
198
        sw = gtk.ScrolledWindow()
188
201
        sw.add(self.annoview)
189
202
        self.annoview.gwindow = self
190
203
        sw.show()
 
204
 
 
205
        swbox = gtk.VBox()
 
206
        swbox.pack_start(sw)
 
207
        swbox.show()
 
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)
191
216
        
192
217
        self.pane = pane = gtk.VPaned()
193
 
        pane.add1(sw)
194
 
        pane.add2(self.logview)
 
218
        pane.add1(swbox)
 
219
        pane.add2(self.revisionview)
195
220
        pane.show()
196
221
        vbox.pack_start(pane, expand=True, fill=True)
197
 
        
198
 
        hbox = gtk.HBox(True, 6)
199
 
        hbox.pack_start(self.span_selector, expand=False, fill=True)
200
 
        hbox.pack_start(self._create_button_box(), expand=False, fill=True)
201
 
        hbox.show()
202
 
        vbox.pack_start(hbox, expand=False, fill=True)
 
222
 
 
223
        self._search = SearchBox()
 
224
        swbox.pack_start(self._search, expand=False, fill=True)
 
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)
203
233
 
204
234
        self.add(vbox)
205
235
 
206
 
    def row_diff(self, tv, path, tvc):
 
236
    def _search_by_text(self, accel_group, window, key, modifiers):
 
237
        self._search.show_for('text')
 
238
        self._search.set_target(self.annoview, TEXT_LINE_COL)
 
239
 
 
240
    def _search_by_line(self, accel_group, window, key, modifiers):
 
241
        self._search.show_for('line')
 
242
        self._search.set_target(self.annoview, LINE_NUM_COL)
 
243
 
 
244
    def line_diff(self, tv, path, tvc):
207
245
        row = path[0]
208
246
        revision = self.annotations[row]
209
247
        repository = self.branch.repository
210
 
        tree1 = repository.revision_tree(revision.revision_id)
211
 
        if len(revision.parent_ids) > 0:
212
 
            tree2 = repository.revision_tree(revision.parent_ids[0])
 
248
        if revision.revision_id == CURRENT_REVISION:
 
249
            tree1 = self.tree
 
250
            tree2 = self.tree.basis_tree()
213
251
        else:
214
 
            tree2 = repository.revision_tree(NULL_REVISION)
215
 
        from bzrlib.plugins.gtk.viz.diffwin import DiffWindow
 
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)
 
257
        from bzrlib.plugins.gtk.diff import DiffWindow
216
258
        window = DiffWindow()
217
 
        window.set_diff("Diff for row %d" % (row+1), tree1, tree2)
 
259
        window.set_diff("Diff for line %d" % (row+1), tree1, tree2)
218
260
        window.set_file(tree1.id2path(self.file_id))
219
261
        window.show()
220
262
 
222
264
    def _create_annotate_view(self):
223
265
        tv = gtk.TreeView()
224
266
        tv.set_rules_hint(False)
225
 
        tv.connect("cursor-changed", self._show_log)
 
267
        tv.connect("cursor-changed", self._activate_selected_revision)
226
268
        tv.show()
227
 
        tv.connect("row-activated", self.row_diff)
 
269
        tv.connect("row-activated", self.line_diff)
228
270
 
229
271
        cell = gtk.CellRendererText()
230
272
        cell.set_property("xalign", 1.0)
271
313
        col.add_attribute(cell, "text", TEXT_LINE_COL)
272
314
        tv.append_column(col)
273
315
 
 
316
        # FIXME: Now that C-f is now used for search by text we
 
317
        # may as well disable the auto search.
274
318
        tv.set_search_column(LINE_NUM_COL)
275
 
        
 
319
 
276
320
        return tv
277
321
 
278
 
    def _create_span_selector(self):
279
 
        ss = SpanSelector()
280
 
        ss.connect("span-changed", self._span_changed_cb)
281
 
        ss.show()
282
 
 
283
 
        return ss
284
 
 
285
322
    def _create_log_view(self):
286
 
        lv = LogView()
 
323
        lv = RevisionView()
287
324
        lv.show()
288
 
 
289
325
        return lv
290
326
 
291
 
    def _create_button_box(self):
292
 
        box = gtk.HButtonBox()
293
 
        box.set_layout(gtk.BUTTONBOX_END)
294
 
        box.show()
295
 
        
296
 
        button = gtk.Button()
297
 
        button.set_use_stock(True)
298
 
        button.set_label("gtk-close")
299
 
        button.connect("clicked", lambda w: self.destroy())
300
 
        button.show()
301
 
        
302
 
        box.pack_start(button, expand=False, fill=False)
303
 
 
304
 
        return box
305
 
 
306
 
 
307
 
class NoneRevision:
 
327
    def _create_back_button(self):
 
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())
 
332
        button.set_relief(gtk.RELIEF_NONE)
 
333
        button.show()
 
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())
 
341
        button.set_relief(gtk.RELIEF_NONE)
 
342
        button.show()
 
343
        button.set_sensitive(False)
 
344
        return button
 
345
 
 
346
    def go_back(self):
 
347
        last_tree = self.tree
 
348
        rev_id = self._selected_revision()
 
349
        parent_id = self.revisions[rev_id].parent_ids[0]
 
350
        target_tree = self.branch.repository.revision_tree(parent_id)
 
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)
 
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)
 
370
            (row,), col = self.annoview.get_cursor()
 
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)
 
376
            return True
 
377
        else:
 
378
            return False
 
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
 
 
390
 
 
391
class FakeRevision:
308
392
    """ A fake revision.
309
393
 
310
394
    For when a revision is referenced but not present.
311
395
    """
312
396
 
313
 
    def __init__(self, revision_id):
 
397
    def __init__(self, revision_id, committer='?', nick=None):
314
398
        self.revision_id = revision_id
315
399
        self.parent_ids = []
316
 
        self.committer = "?"
 
400
        self.committer = committer
317
401
        self.message = "?"
318
402
        self.timestamp = 0.0
319
403
        self.timezone = 0
 
404
        self.properties = {}
 
405
 
 
406
    def get_apparent_author(self):
 
407
        return self.committer
320
408
 
321
409
 
322
410
class RevisionCache(object):
323
411
    """A caching revision source"""
324
 
    def __init__(self, real_source):
 
412
    def __init__(self, real_source, seed_cache=None):
325
413
        self.__real_source = real_source
326
 
        self.__cache = {}
 
414
        if seed_cache is None:
 
415
            self.__cache = {}
 
416
        else:
 
417
            self.__cache = dict(seed_cache)
327
418
 
328
419
    def get_revision(self, revision_id):
329
420
        if revision_id not in self.__cache:
330
421
            revision = self.__real_source.get_revision(revision_id)
331
422
            self.__cache[revision_id] = revision
332
423
        return self.__cache[revision_id]
 
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