/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: 2006-05-19 16:56:46 UTC
  • mfrom: (0.1.25 gannotate)
  • Revision ID: jelmer@samba.org-20060519165646-0d867938fdbc9097
Merge in Dan Loda's gannotate plugin and put it in annotate/

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
25
24
 
26
 
from bzrlib import patiencediff, tsort
27
25
from bzrlib.errors import NoSuchRevision
28
 
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
29
26
 
30
27
from colormap import AnnotateColorMap, AnnotateColorSaturation
31
 
from bzrlib.plugins.gtk.logview import LogView
 
28
from logview import LogView
 
29
from spanselector import SpanSelector
32
30
 
33
31
 
34
32
(
44
42
class GAnnotateWindow(gtk.Window):
45
43
    """Annotate window."""
46
44
 
47
 
    def __init__(self, all=False, plain=False, parent=None):
 
45
    def __init__(self, all=False, plain=False):
48
46
        self.all = all
49
47
        self.plain = plain
50
 
        self._parent = parent
51
48
        
52
49
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
53
 
 
54
 
        self.connect("key-press-event", self._on_key_pressed)
55
50
        
56
51
        self.set_icon(self.render_icon(gtk.STOCK_FIND, gtk.ICON_SIZE_BUTTON))
57
52
        self.annotate_colormap = AnnotateColorSaturation()
58
53
 
59
54
        self._create()
 
55
 
 
56
        if self.plain:
 
57
            self.span_selector.hide()
 
58
 
 
59
    def annotate(self, branch, file_id):
60
60
        self.revisions = {}
61
 
        self.history = []
62
 
        self._no_back = set()
63
 
 
64
 
    def annotate(self, tree, branch, file_id):
65
 
        self.annotations = []
66
 
        self.branch = branch
67
 
        self.tree = tree
68
 
        self.file_id = file_id
69
 
        self.revision_id = getattr(tree, 'get_revision_id', 
70
 
                                   lambda: CURRENT_REVISION)()
71
61
        
72
 
        # [revision id, line number, author, revno, highlight color, line]
 
62
        # [revision id, line number, committer, revno, highlight color, line]
73
63
        self.annomodel = gtk.ListStore(gobject.TYPE_STRING,
74
64
                                       gobject.TYPE_STRING,
75
65
                                       gobject.TYPE_STRING,
82
72
            branch.lock_read()
83
73
            branch.repository.lock_read()
84
74
            for line_no, (revision, revno, line)\
85
 
                    in enumerate(self._annotate(tree, file_id)):
 
75
                    in enumerate(self._annotate(branch, file_id)):
86
76
                if revision.revision_id == last_seen and not self.all:
87
 
                    revno = author = ""
 
77
                    revno = committer = ""
88
78
                else:
89
79
                    last_seen = revision.revision_id
90
 
                    author = revision.get_apparent_author()
 
80
                    committer = revision.committer
91
81
 
92
82
                if revision.revision_id not in self.revisions:
93
83
                    self.revisions[revision.revision_id] = revision
94
84
 
95
85
                self.annomodel.append([revision.revision_id,
96
86
                                       line_no + 1,
97
 
                                       author,
 
87
                                       committer,
98
88
                                       revno,
99
89
                                       None,
100
90
                                       line.rstrip("\r\n")
101
91
                                      ])
102
 
                self.annotations.append(revision)
103
92
 
104
93
            if not self.plain:
105
 
                now = time.time()
106
 
                self.annomodel.foreach(self._highlight_annotation, now)
 
94
                self._set_oldest_newest()
 
95
                # Recall that calling activate_default will emit "span-changed",
 
96
                # so self._span_changed_cb will take care of initial highlighting
 
97
                self.span_selector.activate_default()
107
98
        finally:
108
99
            branch.repository.unlock()
109
100
            branch.unlock()
118
109
            # bar?
119
110
            print("gannotate: Line number %d does't exist. Defaulting to "
120
111
                  "line 1." % lineno)
121
 
            return
122
112
        else:
123
113
            row = lineno - 1
124
114
 
125
115
        self.annoview.set_cursor(row)
126
 
        self.annoview.scroll_to_cell(row, use_align=True)
127
116
 
128
 
    def _dotted_revnos(self, repository, revision_id):
129
 
        """Return a dict of revision_id -> dotted revno
 
117
    def _annotate(self, branch, file_id):
 
118
        rev_hist = branch.revision_history()
 
119
        repository = branch.repository
 
120
        rev_tree = repository.revision_tree(branch.last_revision())
 
121
        rev_id = rev_tree.inventory[file_id].revision
 
122
        weave = repository.weave_store.get_weave(file_id,
 
123
                                                 branch.get_transaction())
130
124
        
131
 
        :param repository: The repository to get the graph from
132
 
        :param revision_id: The last revision for which this info is needed
133
 
        """
134
 
        graph = repository.get_revision_graph(revision_id)
135
 
        dotted = {}
136
 
        for n, revision_id, d, revno, e in tsort.merge_sort(graph, 
137
 
            revision_id, generate_revno=True):
138
 
            dotted[revision_id] = '.'.join(str(num) for num in revno)
139
 
        return dotted
140
 
 
141
 
    def _annotate(self, tree, file_id):
142
 
        current_revision = FakeRevision(CURRENT_REVISION)
143
 
        current_revision.committer = self.branch.get_config().username()
144
 
        current_revision.timestamp = time.time()
145
 
        current_revision.message = '[Not yet committed]'
146
 
        current_revision.parent_ids = tree.get_parent_ids()
147
 
        current_revision.properties['branch-nick'] = self.branch.nick
148
 
        current_revno = '%d?' % (self.branch.revno() + 1)
149
 
        repository = self.branch.repository
150
 
        if self.revision_id == CURRENT_REVISION:
151
 
            revision_id = self.branch.last_revision()
152
 
        else:
153
 
            revision_id = self.revision_id
154
 
        dotted = self._dotted_revnos(repository, revision_id)
155
 
        revision_cache = RevisionCache(repository, self.revisions)
156
 
        for origin, text in tree.annotate_iter(file_id):
 
125
        revision_cache = RevisionCache(repository)
 
126
        for origin, text in weave.annotate_iter(rev_id):
157
127
            rev_id = origin
158
 
            if rev_id == CURRENT_REVISION:
159
 
                revision = current_revision
160
 
                revno = current_revno
161
 
            else:
162
 
                try:
163
 
                    revision = revision_cache.get_revision(rev_id)
164
 
                    revno = dotted.get(rev_id, 'merge')
165
 
                    if len(revno) > 15:
166
 
                        revno = 'merge'
167
 
                except NoSuchRevision:
168
 
                    revision = FakeRevision(rev_id)
169
 
                    revno = "?"
 
128
            try:
 
129
                revision = revision_cache.get_revision(rev_id)
 
130
                if rev_id in rev_hist:
 
131
                    revno = branch.revision_id_to_revno(rev_id)
 
132
                else:
 
133
                    revno = "merge"
 
134
            except NoSuchRevision:
 
135
                revision = NoneRevision(rev_id)
 
136
                revno = "?"
170
137
 
171
138
            yield revision, revno, text
172
139
 
 
140
    def _set_oldest_newest(self):
 
141
        rev_dates = map(lambda i: self.revisions[i].timestamp, self.revisions)
 
142
        oldest = min(rev_dates)
 
143
        newest = max(rev_dates)
 
144
 
 
145
        span = self._span_from_seconds(time.time() - oldest)
 
146
        self.span_selector.set_to_oldest_span(span)
 
147
        
 
148
        span = self._span_from_seconds(newest - oldest)
 
149
        self.span_selector.set_newest_to_oldest_span(span)
 
150
 
 
151
    def _span_from_seconds(self, seconds):
 
152
        return (seconds / (24 * 60 * 60))
 
153
    
 
154
    def _span_changed_cb(self, w, span):
 
155
        self.annotate_colormap.set_span(span)
 
156
        now = time.time()
 
157
        self.annomodel.foreach(self._highlight_annotation, now)
 
158
 
173
159
    def _highlight_annotation(self, model, path, iter, now):
174
160
        revision_id, = model.get(iter, REVISION_ID_COL)
175
161
        revision = self.revisions[revision_id]
176
162
        model.set(iter, HIGHLIGHT_COLOR_COL,
177
163
                  self.annotate_colormap.get_color(revision, now))
178
164
 
179
 
    def _selected_revision(self):
 
165
    def _show_log(self, w):
180
166
        (path, col) = self.annoview.get_cursor()
181
 
        if path is None:
182
 
            return None
183
 
        return self.annomodel[path][REVISION_ID_COL]
184
 
 
185
 
    def _activate_selected_revision(self, w):
186
 
        rev_id = self._selected_revision()
187
 
        if rev_id is None:
188
 
            return
189
 
        selected = self.revisions[rev_id]
190
 
        self.logview.set_revision(selected)
191
 
        if (len(selected.parent_ids) != 0 and selected.parent_ids[0] not in
192
 
            self._no_back):
193
 
            enable_back = True
194
 
        else:
195
 
            enable_back = False
196
 
        self.back_button.set_sensitive(enable_back)
 
167
        rev_id = self.annomodel[path][REVISION_ID_COL]
 
168
        self.logview.set_revision(self.revisions[rev_id])
197
169
 
198
170
    def _create(self):
199
171
        self.logview = self._create_log_view()
200
172
        self.annoview = self._create_annotate_view()
 
173
        self.span_selector = self._create_span_selector()
201
174
 
202
 
        vbox = gtk.VBox(False)
 
175
        vbox = gtk.VBox(False, 12)
 
176
        vbox.set_border_width(12)
203
177
        vbox.show()
204
178
 
205
179
        sw = gtk.ScrolledWindow()
206
180
        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
207
181
        sw.set_shadow_type(gtk.SHADOW_IN)
208
182
        sw.add(self.annoview)
209
 
        self.annoview.gwindow = self
210
183
        sw.show()
211
 
 
212
 
        swbox = gtk.VBox()
213
 
        swbox.pack_start(sw)
214
 
        swbox.show()
215
 
 
216
 
        hbox = gtk.HBox(False, 6)
217
 
        self.back_button = self._create_back_button()
218
 
        hbox.pack_start(self.back_button, expand=False, fill=True)
219
 
        self.forward_button = self._create_forward_button()
220
 
        hbox.pack_start(self.forward_button, expand=False, fill=True)
221
 
        hbox.show()
222
 
        vbox.pack_start(hbox, expand=False, fill=True)
223
184
        
224
185
        self.pane = pane = gtk.VPaned()
225
 
        pane.add1(swbox)
 
186
        pane.add1(sw)
226
187
        pane.add2(self.logview)
227
188
        pane.show()
228
189
        vbox.pack_start(pane, expand=True, fill=True)
229
 
 
230
 
        self._search = SearchBox()
231
 
        swbox.pack_start(self._search, expand=False, fill=True)
232
 
        accels = gtk.AccelGroup()
233
 
        accels.connect_group(gtk.keysyms.f, gtk.gdk.CONTROL_MASK,
234
 
                             gtk.ACCEL_LOCKED,
235
 
                             self._search_by_text)
236
 
        accels.connect_group(gtk.keysyms.g, gtk.gdk.CONTROL_MASK,
237
 
                             gtk.ACCEL_LOCKED,
238
 
                             self._search_by_line)
239
 
        self.add_accel_group(accels)
 
190
        
 
191
        hbox = gtk.HBox(True, 6)
 
192
        hbox.pack_start(self.span_selector, expand=False, fill=True)
 
193
        hbox.pack_start(self._create_button_box(), expand=False, fill=True)
 
194
        hbox.show()
 
195
        vbox.pack_start(hbox, expand=False, fill=True)
240
196
 
241
197
        self.add(vbox)
242
198
 
243
 
    def _search_by_text(self, accel_group, window, key, modifiers):
244
 
        self._search.show_for('text')
245
 
        self._search.set_target(self.annoview, TEXT_LINE_COL)
246
 
 
247
 
    def _search_by_line(self, accel_group, window, key, modifiers):
248
 
        self._search.show_for('line')
249
 
        self._search.set_target(self.annoview, LINE_NUM_COL)
250
 
 
251
 
    def row_diff(self, tv, path, tvc):
252
 
        row = path[0]
253
 
        revision = self.annotations[row]
254
 
        repository = self.branch.repository
255
 
        if revision.revision_id == CURRENT_REVISION:
256
 
            tree1 = self.tree
257
 
            tree2 = self.tree.basis_tree()
258
 
        else:
259
 
            tree1 = repository.revision_tree(revision.revision_id)
260
 
            if len(revision.parent_ids) > 0:
261
 
                tree2 = repository.revision_tree(revision.parent_ids[0])
262
 
            else:
263
 
                tree2 = repository.revision_tree(NULL_REVISION)
264
 
        from bzrlib.plugins.gtk.diff import DiffWindow
265
 
        window = DiffWindow()
266
 
        window.set_diff("Diff for row %d" % (row+1), tree1, tree2)
267
 
        window.set_file(tree1.id2path(self.file_id))
268
 
        window.show()
269
 
 
270
 
 
271
199
    def _create_annotate_view(self):
272
200
        tv = gtk.TreeView()
273
201
        tv.set_rules_hint(False)
274
 
        tv.connect("cursor-changed", self._activate_selected_revision)
 
202
        tv.connect("cursor-changed", self._show_log)
275
203
        tv.show()
276
 
        tv.connect("row-activated", self.row_diff)
277
204
 
278
205
        cell = gtk.CellRendererText()
279
206
        cell.set_property("xalign", 1.0)
320
247
        col.add_attribute(cell, "text", TEXT_LINE_COL)
321
248
        tv.append_column(col)
322
249
 
323
 
        # FIXME: Now that C-f is now used for search by text we
324
 
        # may as well disable the auto search.
325
250
        tv.set_search_column(LINE_NUM_COL)
326
 
 
 
251
        
327
252
        return tv
328
253
 
 
254
    def _create_span_selector(self):
 
255
        ss = SpanSelector()
 
256
        ss.connect("span-changed", self._span_changed_cb)
 
257
        ss.show()
 
258
 
 
259
        return ss
 
260
 
329
261
    def _create_log_view(self):
330
262
        lv = LogView()
331
263
        lv.show()
 
264
 
332
265
        return lv
333
266
 
334
 
    def _create_back_button(self):
335
 
        button = gtk.Button()
336
 
        button.set_use_stock(True)
337
 
        button.set_label("gtk-go-back")
338
 
        button.connect("clicked", lambda w: self.go_back())
339
 
        button.set_relief(gtk.RELIEF_NONE)
340
 
        button.show()
341
 
        return button
342
 
 
343
 
    def _create_forward_button(self):
344
 
        button = gtk.Button()
345
 
        button.set_use_stock(True)
346
 
        button.set_label("gtk-go-forward")
347
 
        button.connect("clicked", lambda w: self.go_forward())
348
 
        button.set_relief(gtk.RELIEF_NONE)
349
 
        button.show()
350
 
        button.set_sensitive(False)
351
 
        return button
352
 
 
353
 
    def go_back(self):
354
 
        last_tree = self.tree
355
 
        rev_id = self._selected_revision()
356
 
        parent_id = self.revisions[rev_id].parent_ids[0]
357
 
        target_tree = self.branch.repository.revision_tree(parent_id)
358
 
        if self._go(target_tree):
359
 
            self.history.append(last_tree)
360
 
            self.forward_button.set_sensitive(True)
361
 
        else:
362
 
            self._no_back.add(parent_id)
363
 
            self.back_button.set_sensitive(False)
364
 
 
365
 
    def go_forward(self):
366
 
        if len(self.history) == 0:
367
 
            return
368
 
        target_tree = self.history.pop()
369
 
        if len(self.history) == 0:
370
 
            self.forward_button.set_sensitive(False)
371
 
        self._go(target_tree)
372
 
 
373
 
    def _go(self, target_tree):
374
 
        rev_id = self._selected_revision()
375
 
        if self.file_id in target_tree:
376
 
            offset = self.get_scroll_offset(target_tree)
377
 
            (row,), col = self.annoview.get_cursor()
378
 
            self.annotate(target_tree, self.branch, self.file_id)
379
 
            new_row = row+offset
380
 
            if new_row < 0:
381
 
                new_row = 0
382
 
            self.annoview.set_cursor(new_row)
383
 
            return True
384
 
        else:
385
 
            return False
386
 
 
387
 
    def get_scroll_offset(self, tree):
388
 
        old = self.tree.get_file(self.file_id)
389
 
        new = tree.get_file(self.file_id)
390
 
        (row,), col = self.annoview.get_cursor()
391
 
        matcher = patiencediff.PatienceSequenceMatcher(None, old.readlines(),
392
 
                                                       new.readlines())
393
 
        for i, j, n in matcher.get_matching_blocks():
394
 
            if i + n >= row:
395
 
                return j - i
396
 
 
397
 
    def _on_key_pressed(self, widget, event):
398
 
        """ Key press event handler. """
399
 
        keyname = gtk.gdk.keyval_name(event.keyval)
400
 
        func = getattr(self, '_on_key_press_' + keyname, None)
401
 
        if func:
402
 
            return func(event)
403
 
 
404
 
    def _on_key_press_w(self, event):
405
 
        if event.state & gtk.gdk.CONTROL_MASK:
406
 
            self.destroy()
407
 
            if self._parent is None:
408
 
                gtk.main_quit()
409
 
 
410
 
    def _on_key_press_q(self, event):
411
 
        if event.state & gtk.gdk.CONTROL_MASK:
412
 
            gtk.main_quit()
413
 
    
414
 
 
415
 
 
416
 
 
417
 
class FakeRevision:
 
267
    def _create_button_box(self):
 
268
        box = gtk.HButtonBox()
 
269
        box.set_layout(gtk.BUTTONBOX_END)
 
270
        box.show()
 
271
        
 
272
        button = gtk.Button()
 
273
        button.set_use_stock(True)
 
274
        button.set_label("gtk-close")
 
275
        button.connect("clicked", lambda w: self.destroy())
 
276
        button.show()
 
277
        
 
278
        box.pack_start(button, expand=False, fill=False)
 
279
 
 
280
        return box
 
281
 
 
282
 
 
283
class NoneRevision:
418
284
    """ A fake revision.
419
285
 
420
286
    For when a revision is referenced but not present.
421
287
    """
422
288
 
423
 
    def __init__(self, revision_id, committer='?', nick=None):
 
289
    def __init__(self, revision_id):
424
290
        self.revision_id = revision_id
425
291
        self.parent_ids = []
426
 
        self.committer = committer
 
292
        self.committer = "?"
427
293
        self.message = "?"
428
294
        self.timestamp = 0.0
429
295
        self.timezone = 0
430
 
        self.properties = {}
431
 
 
432
 
    def get_apparent_author(self):
433
 
        return self.committer
434
296
 
435
297
 
436
298
class RevisionCache(object):
437
299
    """A caching revision source"""
438
 
    def __init__(self, real_source, seed_cache=None):
 
300
    def __init__(self, real_source):
439
301
        self.__real_source = real_source
440
 
        if seed_cache is None:
441
 
            self.__cache = {}
442
 
        else:
443
 
            self.__cache = dict(seed_cache)
 
302
        self.__cache = {}
444
303
 
445
304
    def get_revision(self, revision_id):
446
305
        if revision_id not in self.__cache:
447
306
            revision = self.__real_source.get_revision(revision_id)
448
307
            self.__cache[revision_id] = revision
449
308
        return self.__cache[revision_id]
450
 
 
451
 
class SearchBox(gtk.HBox):
452
 
    """A button box for searching in text or lines of annotations"""
453
 
    def __init__(self):
454
 
        gtk.HBox.__init__(self, False, 6)
455
 
 
456
 
        # Close button
457
 
        button = gtk.Button()
458
 
        image = gtk.Image()
459
 
        image.set_from_stock('gtk-stop', gtk.ICON_SIZE_BUTTON)
460
 
        button.set_image(image)
461
 
        button.set_relief(gtk.RELIEF_NONE)
462
 
        button.connect("clicked", lambda w: self.hide_all())
463
 
        self.pack_start(button, expand=False, fill=False)
464
 
 
465
 
        # Search entry
466
 
        label = gtk.Label()
467
 
        self._label = label
468
 
        self.pack_start(label, expand=False, fill=False)
469
 
 
470
 
        entry = gtk.Entry()
471
 
        self._entry = entry
472
 
        entry.connect("activate", lambda w, d: self._do_search(d),
473
 
                      'forward')
474
 
        self.pack_start(entry, expand=False, fill=False)
475
 
 
476
 
        # Next/previous buttons
477
 
        button = gtk.Button('_Next')
478
 
        image = gtk.Image()
479
 
        image.set_from_stock('gtk-go-forward', gtk.ICON_SIZE_BUTTON)
480
 
        button.set_image(image)
481
 
        button.connect("clicked", lambda w, d: self._do_search(d),
482
 
                       'forward')
483
 
        self.pack_start(button, expand=False, fill=False)
484
 
 
485
 
        button = gtk.Button('_Previous')
486
 
        image = gtk.Image()
487
 
        image.set_from_stock('gtk-go-back', gtk.ICON_SIZE_BUTTON)
488
 
        button.set_image(image)
489
 
        button.connect("clicked", lambda w, d: self._do_search(d),
490
 
                       'backward')
491
 
        self.pack_start(button, expand=False, fill=False)
492
 
 
493
 
        # Search options
494
 
        check = gtk.CheckButton('Match case')
495
 
        self._match_case = check
496
 
        self.pack_start(check, expand=False, fill=False)
497
 
 
498
 
        check = gtk.CheckButton('Regexp')
499
 
        check.connect("toggled", lambda w: self._set_label())
500
 
        self._regexp = check
501
 
        self.pack_start(check, expand=False, fill=False)
502
 
 
503
 
        self._view = None
504
 
        self._column = None
505
 
        # Note that we stay hidden (we do not call self.show_all())
506
 
 
507
 
 
508
 
    def show_for(self, kind):
509
 
        self._kind = kind
510
 
        self.show_all()
511
 
        self._set_label()
512
 
        # Hide unrelated buttons
513
 
        if kind == 'line':
514
 
            self._match_case.hide()
515
 
            self._regexp.hide()
516
 
        # Be ready
517
 
        self._entry.grab_focus()
518
 
 
519
 
    def _set_label(self):
520
 
        if self._kind == 'line':
521
 
            self._label.set_text('Find Line: ')
522
 
        else:
523
 
            if self._regexp.get_active():
524
 
                self._label.set_text('Find Regexp: ')
525
 
            else:
526
 
                self._label.set_text('Find Text: ')
527
 
 
528
 
    def set_target(self, view,column):
529
 
        self._view = view
530
 
        self._column = column
531
 
 
532
 
    def _match(self, model, iterator, column):
533
 
        matching_case = self._match_case.get_active()
534
 
        string, = model.get(iterator, column)
535
 
        key = self._entry.get_text()
536
 
        if self._regexp.get_active():
537
 
            if matching_case:
538
 
                match = re.compile(key).search(string, 1)
539
 
            else:
540
 
                match = re.compile(key, re.I).search(string, 1)
541
 
        else:
542
 
            if not matching_case:
543
 
                string = string.lower()
544
 
                key = key.lower()
545
 
            match = string.find(key) != -1
546
 
 
547
 
        return match
548
 
 
549
 
    def _iterate_rows_forward(self, model, start):
550
 
        model_size = len(model)
551
 
        current = start + 1
552
 
        while model_size != 0:
553
 
            if current >= model_size: current =  0
554
 
            yield model.get_iter_from_string('%d' % current)
555
 
            if current == start: raise StopIteration
556
 
            current += 1
557
 
 
558
 
    def _iterate_rows_backward(self, model, start):
559
 
        model_size = len(model)
560
 
        current = start - 1
561
 
        while model_size != 0:
562
 
            if current < 0: current = model_size - 1
563
 
            yield model.get_iter_from_string('%d' % current)
564
 
            if current == start: raise StopIteration
565
 
            current -= 1
566
 
 
567
 
    def _do_search(self, direction):
568
 
        if direction == 'forward':
569
 
            iterate = self._iterate_rows_forward
570
 
        else:
571
 
            iterate = self._iterate_rows_backward
572
 
 
573
 
        model, sel = self._view.get_selection().get_selected()
574
 
        if sel is None:
575
 
            start = 0
576
 
        else:
577
 
            path = model.get_string_from_iter(sel)
578
 
            start = int(path)
579
 
 
580
 
        for row in iterate(model, start):
581
 
            if self._match(model, row, self._column):
582
 
                path = model.get_path(row)
583
 
                self._view.set_cursor(path)
584
 
                self._view.scroll_to_cell(path, use_align=True)
585
 
                break