/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:
121
131
        self.annoview.set_cursor(row)
122
132
        self.annoview.scroll_to_cell(row, use_align=True)
123
133
 
124
 
    def _annotate(self, branch, file_id):
125
 
        rev_hist = branch.revision_history()
126
 
        repository = branch.repository
127
 
        rev_tree = repository.revision_tree(branch.last_revision())
128
 
        rev_id = rev_tree.inventory[file_id].revision
129
 
        weave = repository.weave_store.get_weave(file_id,
130
 
                                                 branch.get_transaction())
131
 
        
132
 
        revision_cache = RevisionCache(repository)
133
 
        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):
134
150
            rev_id = origin
135
 
            try:
136
 
                revision = revision_cache.get_revision(rev_id)
137
 
                if rev_id in rev_hist:
138
 
                    revno = branch.revision_id_to_revno(rev_id)
139
 
                else:
140
 
                    revno = "merge"
141
 
            except NoSuchRevision:
142
 
                revision = NoneRevision(rev_id)
143
 
                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 = "?"
144
163
 
145
164
            yield revision, revno, text
146
165
 
147
 
    def _set_oldest_newest(self):
148
 
        rev_dates = map(lambda i: self.revisions[i].timestamp, self.revisions)
149
 
        if len(rev_dates) == 0:
150
 
            return
151
 
        oldest = min(rev_dates)
152
 
        newest = max(rev_dates)
153
 
 
154
 
        span = self._span_from_seconds(time.time() - oldest)
155
 
        self.span_selector.set_to_oldest_span(span)
156
 
        
157
 
        span = self._span_from_seconds(newest - oldest)
158
 
        self.span_selector.set_newest_to_oldest_span(span)
159
 
 
160
 
    def _span_from_seconds(self, seconds):
161
 
        return (seconds / (24 * 60 * 60))
162
 
    
163
 
    def _span_changed_cb(self, w, span):
164
 
        self.annotate_colormap.set_span(span)
165
 
        now = time.time()
166
 
        self.annomodel.foreach(self._highlight_annotation, now)
167
 
 
168
166
    def _highlight_annotation(self, model, path, iter, now):
169
167
        revision_id, = model.get(iter, REVISION_ID_COL)
170
168
        revision = self.revisions[revision_id]
171
169
        model.set(iter, HIGHLIGHT_COLOR_COL,
172
170
                  self.annotate_colormap.get_color(revision, now))
173
171
 
174
 
    def _show_log(self, w):
 
172
    def _selected_revision(self):
175
173
        (path, col) = self.annoview.get_cursor()
176
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:
177
181
            return
178
 
        rev_id = self.annomodel[path][REVISION_ID_COL]
179
 
        self.logview.set_revision(self.revisions[rev_id])
 
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)
180
190
 
181
191
    def _create(self):
182
 
        self.logview = self._create_log_view()
 
192
        self.revisionview = self._create_log_view()
183
193
        self.annoview = self._create_annotate_view()
184
 
        self.span_selector = self._create_span_selector()
185
194
 
186
 
        vbox = gtk.VBox(False, 12)
187
 
        vbox.set_border_width(12)
 
195
        vbox = gtk.VBox(False)
188
196
        vbox.show()
189
197
 
190
198
        sw = gtk.ScrolledWindow()
193
201
        sw.add(self.annoview)
194
202
        self.annoview.gwindow = self
195
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)
196
216
        
197
217
        self.pane = pane = gtk.VPaned()
198
 
        pane.add1(sw)
199
 
        pane.add2(self.logview)
 
218
        pane.add1(swbox)
 
219
        pane.add2(self.revisionview)
200
220
        pane.show()
201
221
        vbox.pack_start(pane, expand=True, fill=True)
202
 
        
203
 
        hbox = gtk.HBox(True, 6)
204
 
        hbox.pack_start(self.span_selector, expand=False, fill=True)
205
 
        hbox.pack_start(self._create_button_box(), expand=False, fill=True)
206
 
        hbox.show()
207
 
        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)
208
233
 
209
234
        self.add(vbox)
210
235
 
211
 
    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):
212
245
        row = path[0]
213
246
        revision = self.annotations[row]
214
247
        repository = self.branch.repository
215
 
        tree1 = repository.revision_tree(revision.revision_id)
216
 
        if len(revision.parent_ids) > 0:
217
 
            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()
218
251
        else:
219
 
            tree2 = repository.revision_tree(NULL_REVISION)
220
 
        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
221
258
        window = DiffWindow()
222
 
        window.set_diff("Diff for row %d" % (row+1), tree1, tree2)
 
259
        window.set_diff("Diff for line %d" % (row+1), tree1, tree2)
223
260
        window.set_file(tree1.id2path(self.file_id))
224
261
        window.show()
225
262
 
227
264
    def _create_annotate_view(self):
228
265
        tv = gtk.TreeView()
229
266
        tv.set_rules_hint(False)
230
 
        tv.connect("cursor-changed", self._show_log)
 
267
        tv.connect("cursor-changed", self._activate_selected_revision)
231
268
        tv.show()
232
 
        tv.connect("row-activated", self.row_diff)
 
269
        tv.connect("row-activated", self.line_diff)
233
270
 
234
271
        cell = gtk.CellRendererText()
235
272
        cell.set_property("xalign", 1.0)
276
313
        col.add_attribute(cell, "text", TEXT_LINE_COL)
277
314
        tv.append_column(col)
278
315
 
 
316
        # FIXME: Now that C-f is now used for search by text we
 
317
        # may as well disable the auto search.
279
318
        tv.set_search_column(LINE_NUM_COL)
280
 
        
 
319
 
281
320
        return tv
282
321
 
283
 
    def _create_span_selector(self):
284
 
        ss = SpanSelector()
285
 
        ss.connect("span-changed", self._span_changed_cb)
286
 
        ss.show()
287
 
 
288
 
        return ss
289
 
 
290
322
    def _create_log_view(self):
291
 
        lv = LogView()
 
323
        lv = RevisionView()
292
324
        lv.show()
293
 
 
294
325
        return lv
295
326
 
296
 
    def _create_button_box(self):
297
 
        box = gtk.HButtonBox()
298
 
        box.set_layout(gtk.BUTTONBOX_END)
299
 
        box.show()
300
 
        
301
 
        button = gtk.Button()
302
 
        button.set_use_stock(True)
303
 
        button.set_label("gtk-close")
304
 
        button.connect("clicked", lambda w: self.destroy())
305
 
        button.show()
306
 
        
307
 
        box.pack_start(button, expand=False, fill=False)
308
 
 
309
 
        return box
310
 
 
311
 
 
312
 
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:
313
392
    """ A fake revision.
314
393
 
315
394
    For when a revision is referenced but not present.
316
395
    """
317
396
 
318
 
    def __init__(self, revision_id):
 
397
    def __init__(self, revision_id, committer='?', nick=None):
319
398
        self.revision_id = revision_id
320
399
        self.parent_ids = []
321
 
        self.committer = "?"
 
400
        self.committer = committer
322
401
        self.message = "?"
323
402
        self.timestamp = 0.0
324
403
        self.timezone = 0
 
404
        self.properties = {}
 
405
 
 
406
    def get_apparent_author(self):
 
407
        return self.committer
325
408
 
326
409
 
327
410
class RevisionCache(object):
328
411
    """A caching revision source"""
329
 
    def __init__(self, real_source):
 
412
    def __init__(self, real_source, seed_cache=None):
330
413
        self.__real_source = real_source
331
 
        self.__cache = {}
 
414
        if seed_cache is None:
 
415
            self.__cache = {}
 
416
        else:
 
417
            self.__cache = dict(seed_cache)
332
418
 
333
419
    def get_revision(self, revision_id):
334
420
        if revision_id not in self.__cache:
335
421
            revision = self.__real_source.get_revision(revision_id)
336
422
            self.__cache[revision_id] = revision
337
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