/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: 2011-04-10 18:44:39 UTC
  • mto: This revision was merged to the branch mainline in revision 730.
  • Revision ID: jelmer@samba.org-20110410184439-g7hqaacexqtviq13
Move i18n support to a separate file, so gettext files aren't loaded unless bzr-gtk is used.

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
import pygtk
 
20
pygtk.require("2.0")
 
21
import gobject
 
22
import gtk
 
23
import pango
 
24
import re
 
25
 
 
26
from bzrlib import patiencediff
 
27
from bzrlib.errors import NoSuchRevision
 
28
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
 
29
 
 
30
from bzrlib.plugins.gtk.annotate.colormap import AnnotateColorSaturation
 
31
from bzrlib.plugins.gtk.revisionview import RevisionView
 
32
from bzrlib.plugins.gtk.window import Window
 
33
 
 
34
 
 
35
(
 
36
    REVISION_ID_COL,
 
37
    LINE_NUM_COL,
 
38
    COMMITTER_COL,
 
39
    REVNO_COL,
 
40
    HIGHLIGHT_COLOR_COL,
 
41
    TEXT_LINE_COL
 
42
) = range(6)
 
43
 
 
44
 
 
45
class GAnnotateWindow(Window):
 
46
    """Annotate window."""
 
47
 
 
48
    def __init__(self, all=False, plain=False, parent=None, branch=None):
 
49
        self.all = all
 
50
        self.plain = plain
 
51
        self._branch = branch
 
52
 
 
53
        Window.__init__(self, parent)
 
54
 
 
55
        self.set_icon(self.render_icon(gtk.STOCK_FIND, gtk.ICON_SIZE_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
        self.annoview.set_cursor(row)
 
133
        self.annoview.scroll_to_cell(row, use_align=True)
 
134
 
 
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
        model.set(iter, HIGHLIGHT_COLOR_COL,
 
171
                  self.annotate_colormap.get_color(revision, now))
 
172
 
 
173
    def _selected_revision(self):
 
174
        (path, col) = self.annoview.get_cursor()
 
175
        if path is None:
 
176
            return None
 
177
        return self.annomodel[path][REVISION_ID_COL]
 
178
 
 
179
    def _activate_selected_revision(self, w):
 
180
        rev_id = self._selected_revision()
 
181
        if not rev_id or rev_id == NULL_REVISION:
 
182
            return
 
183
        selected = self.revisions[rev_id]
 
184
        self.revisionview.set_revision(selected)
 
185
        if (len(selected.parent_ids) != 0 and selected.parent_ids[0] not in
 
186
            self._no_back):
 
187
            enable_back = True
 
188
        else:
 
189
            enable_back = False
 
190
        self.back_button.set_sensitive(enable_back)
 
191
 
 
192
    def _create(self):
 
193
        self.revisionview = self._create_log_view()
 
194
        self.annoview = self._create_annotate_view()
 
195
 
 
196
        vbox = gtk.VBox(False)
 
197
        vbox.show()
 
198
 
 
199
        sw = gtk.ScrolledWindow()
 
200
        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
201
        sw.set_shadow_type(gtk.SHADOW_IN)
 
202
        sw.add(self.annoview)
 
203
        self.annoview.gwindow = self
 
204
        sw.show()
 
205
 
 
206
        swbox = gtk.VBox()
 
207
        swbox.pack_start(sw)
 
208
        swbox.show()
 
209
 
 
210
        hbox = gtk.HBox(False, 6)
 
211
        self.back_button = self._create_back_button()
 
212
        hbox.pack_start(self.back_button, expand=False, fill=True)
 
213
        self.forward_button = self._create_forward_button()
 
214
        hbox.pack_start(self.forward_button, expand=False, fill=True)
 
215
        self.find_button = self._create_find_button()
 
216
        hbox.pack_start(self.find_button, expand=False, fill=True)
 
217
        self.goto_button = self._create_goto_button()
 
218
        hbox.pack_start(self.goto_button, expand=False, fill=True)
 
219
        hbox.show()
 
220
        vbox.pack_start(hbox, expand=False, fill=True)
 
221
 
 
222
        self.pane = pane = gtk.VPaned()
 
223
        pane.add1(swbox)
 
224
        pane.add2(self.revisionview)
 
225
        pane.show()
 
226
        vbox.pack_start(pane, expand=True, fill=True)
 
227
 
 
228
        self._search = SearchBox()
 
229
        swbox.pack_start(self._search, expand=False, fill=True)
 
230
        accels = gtk.AccelGroup()
 
231
        accels.connect_group(gtk.keysyms.f, gtk.gdk.CONTROL_MASK,
 
232
                             gtk.ACCEL_LOCKED,
 
233
                             self._search_by_text)
 
234
        accels.connect_group(gtk.keysyms.g, gtk.gdk.CONTROL_MASK,
 
235
                             gtk.ACCEL_LOCKED,
 
236
                             self._search_by_line)
 
237
        self.add_accel_group(accels)
 
238
 
 
239
        self.add(vbox)
 
240
 
 
241
    def _search_by_text(self, *ignored): # (accel_group, window, key, modifiers):
 
242
        self._search.show_for('text')
 
243
        self._search.set_target(self.annoview, TEXT_LINE_COL)
 
244
 
 
245
    def _search_by_line(self, *ignored): # accel_group, window, key, modifiers):
 
246
        self._search.show_for('line')
 
247
        self._search.set_target(self.annoview, LINE_NUM_COL)
 
248
 
 
249
    def line_diff(self, tv, path, tvc):
 
250
        row = path[0]
 
251
        revision = self.annotations[row]
 
252
        repository = self.branch.repository
 
253
        if revision.revision_id == CURRENT_REVISION:
 
254
            tree1 = self.tree
 
255
            tree2 = self.tree.basis_tree()
 
256
        else:
 
257
            tree1 = repository.revision_tree(revision.revision_id)
 
258
            if len(revision.parent_ids) > 0:
 
259
                tree2 = repository.revision_tree(revision.parent_ids[0])
 
260
            else:
 
261
                tree2 = repository.revision_tree(NULL_REVISION)
 
262
        from bzrlib.plugins.gtk.diff import DiffWindow
 
263
        window = DiffWindow(self)
 
264
        window.set_diff("Diff for line %d" % (row+1), tree1, tree2)
 
265
        window.set_file(tree1.id2path(self.file_id))
 
266
        window.show()
 
267
 
 
268
 
 
269
    def _create_annotate_view(self):
 
270
        tv = gtk.TreeView()
 
271
        tv.set_rules_hint(False)
 
272
        tv.connect("cursor-changed", self._activate_selected_revision)
 
273
        tv.show()
 
274
        tv.connect("row-activated", self.line_diff)
 
275
 
 
276
        cell = gtk.CellRendererText()
 
277
        cell.set_property("xalign", 1.0)
 
278
        cell.set_property("ypad", 0)
 
279
        cell.set_property("family", "Monospace")
 
280
        cell.set_property("cell-background-gdk",
 
281
                          tv.get_style().bg[gtk.STATE_NORMAL])
 
282
        col = gtk.TreeViewColumn()
 
283
        col.set_resizable(False)
 
284
        col.pack_start(cell, expand=True)
 
285
        col.add_attribute(cell, "text", LINE_NUM_COL)
 
286
        tv.append_column(col)
 
287
 
 
288
        cell = gtk.CellRendererText()
 
289
        cell.set_property("ypad", 0)
 
290
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
 
291
        cell.set_property("cell-background-gdk",
 
292
                          self.get_style().bg[gtk.STATE_NORMAL])
 
293
        col = gtk.TreeViewColumn("Committer")
 
294
        col.set_resizable(True)
 
295
        col.pack_start(cell, expand=True)
 
296
        col.add_attribute(cell, "text", COMMITTER_COL)
 
297
        tv.append_column(col)
 
298
 
 
299
        cell = gtk.CellRendererText()
 
300
        cell.set_property("xalign", 1.0)
 
301
        cell.set_property("ypad", 0)
 
302
        cell.set_property("cell-background-gdk",
 
303
                          self.get_style().bg[gtk.STATE_NORMAL])
 
304
        col = gtk.TreeViewColumn("Revno")
 
305
        col.set_resizable(False)
 
306
        col.pack_start(cell, expand=True)
 
307
        col.add_attribute(cell, "markup", REVNO_COL)
 
308
        tv.append_column(col)
 
309
 
 
310
        cell = gtk.CellRendererText()
 
311
        cell.set_property("ypad", 0)
 
312
        cell.set_property("family", "Monospace")
 
313
        col = gtk.TreeViewColumn()
 
314
        col.set_resizable(False)
 
315
        col.pack_start(cell, expand=True)
 
316
#        col.add_attribute(cell, "foreground", HIGHLIGHT_COLOR_COL)
 
317
        col.add_attribute(cell, "background", HIGHLIGHT_COLOR_COL)
 
318
        col.add_attribute(cell, "text", TEXT_LINE_COL)
 
319
        tv.append_column(col)
 
320
 
 
321
        # interactive substring search
 
322
        def search_equal_func(model, column, key, iter):
 
323
            return model.get_value(iter, TEXT_LINE_COL).lower().find(key.lower()) == -1
 
324
 
 
325
        tv.set_enable_search(True)
 
326
        tv.set_search_equal_func(search_equal_func)
 
327
 
 
328
        return tv
 
329
 
 
330
    def _create_log_view(self):
 
331
        lv = RevisionView(self._branch)
 
332
        lv.show()
 
333
        return lv
 
334
 
 
335
    def _create_back_button(self):
 
336
        button = gtk.Button()
 
337
        button.set_use_stock(True)
 
338
        button.set_label("gtk-go-back")
 
339
        button.connect("clicked", lambda w: self.go_back())
 
340
        button.set_relief(gtk.RELIEF_NONE)
 
341
        button.show()
 
342
        return button
 
343
 
 
344
    def _create_forward_button(self):
 
345
        button = gtk.Button()
 
346
        button.set_use_stock(True)
 
347
        button.set_label("gtk-go-forward")
 
348
        button.connect("clicked", lambda w: self.go_forward())
 
349
        button.set_relief(gtk.RELIEF_NONE)
 
350
        button.show()
 
351
        button.set_sensitive(False)
 
352
        return button
 
353
 
 
354
    def _create_find_button(self):
 
355
        button = gtk.Button()
 
356
        button.set_use_stock(True)
 
357
        button.set_label("gtk-find")
 
358
        button.set_tooltip_text("Search for text (Ctrl+F)")
 
359
        button.connect("clicked", self._search_by_text)
 
360
        button.set_relief(gtk.RELIEF_NONE)
 
361
        button.show()
 
362
        button.set_sensitive(True)
 
363
        return button
 
364
 
 
365
    def _create_goto_button(self):
 
366
        button = gtk.Button()
 
367
        button.set_label("Goto Line")
 
368
        button.set_tooltip_text("Scroll to a line by entering its number (Ctrl+G)")
 
369
        button.connect("clicked", self._search_by_line)
 
370
        button.set_relief(gtk.RELIEF_NONE)
 
371
        button.show()
 
372
        button.set_sensitive(True)
 
373
        return button
 
374
 
 
375
    def go_back(self):
 
376
        last_tree = self.tree
 
377
        rev_id = self._selected_revision()
 
378
        parent_id = self.revisions[rev_id].parent_ids[0]
 
379
        target_tree = self.branch.repository.revision_tree(parent_id)
 
380
        if self._go(target_tree):
 
381
            self.history.append(last_tree)
 
382
            self.forward_button.set_sensitive(True)
 
383
        else:
 
384
            self._no_back.add(parent_id)
 
385
            self.back_button.set_sensitive(False)
 
386
 
 
387
    def go_forward(self):
 
388
        if len(self.history) == 0:
 
389
            return
 
390
        target_tree = self.history.pop()
 
391
        if len(self.history) == 0:
 
392
            self.forward_button.set_sensitive(False)
 
393
        self._go(target_tree)
 
394
 
 
395
    def _go(self, target_tree):
 
396
        rev_id = self._selected_revision()
 
397
        if self.file_id in target_tree:
 
398
            offset = self.get_scroll_offset(target_tree)
 
399
            (row,), col = self.annoview.get_cursor()
 
400
            self.annotate(target_tree, self.branch, self.file_id)
 
401
            new_row = row+offset
 
402
            if new_row < 0:
 
403
                new_row = 0
 
404
            self.annoview.set_cursor(new_row)
 
405
            return True
 
406
        else:
 
407
            return False
 
408
 
 
409
    def get_scroll_offset(self, tree):
 
410
        old = self.tree.get_file(self.file_id)
 
411
        new = tree.get_file(self.file_id)
 
412
        (row,), col = self.annoview.get_cursor()
 
413
        matcher = patiencediff.PatienceSequenceMatcher(None, old.readlines(),
 
414
                                                       new.readlines())
 
415
        for i, j, n in matcher.get_matching_blocks():
 
416
            if i + n >= row:
 
417
                return j - i
 
418
 
 
419
 
 
420
class FakeRevision(object):
 
421
    """ A fake revision.
 
422
 
 
423
    For when a revision is referenced but not present.
 
424
    """
 
425
 
 
426
    def __init__(self, revision_id, committer='?', nick=None):
 
427
        self.revision_id = revision_id
 
428
        self.parent_ids = []
 
429
        self.committer = committer
 
430
        self.message = "?"
 
431
        self.timestamp = 0.0
 
432
        self.timezone = 0
 
433
        self.properties = {}
 
434
 
 
435
    def get_apparent_authors(self):
 
436
        return [self.committer]
 
437
 
 
438
 
 
439
class RevisionCache(object):
 
440
    """A caching revision source"""
 
441
 
 
442
    def __init__(self, real_source, seed_cache=None):
 
443
        self.__real_source = real_source
 
444
        if seed_cache is None:
 
445
            self.__cache = {}
 
446
        else:
 
447
            self.__cache = dict(seed_cache)
 
448
 
 
449
    def get_revision(self, revision_id):
 
450
        if revision_id not in self.__cache:
 
451
            revision = self.__real_source.get_revision(revision_id)
 
452
            self.__cache[revision_id] = revision
 
453
        return self.__cache[revision_id]
 
454
 
 
455
class SearchBox(gtk.HBox):
 
456
    """A button box for searching in text or lines of annotations"""
 
457
    def __init__(self):
 
458
        gtk.HBox.__init__(self, False, 6)
 
459
 
 
460
        # Close button
 
461
        button = gtk.Button()
 
462
        image = gtk.Image()
 
463
        image.set_from_stock('gtk-stop', gtk.ICON_SIZE_BUTTON)
 
464
        button.set_image(image)
 
465
        button.set_relief(gtk.RELIEF_NONE)
 
466
        button.connect("clicked", lambda w: self.hide_all())
 
467
        self.pack_start(button, expand=False, fill=False)
 
468
 
 
469
        # Search entry
 
470
        label = gtk.Label()
 
471
        self._label = label
 
472
        self.pack_start(label, expand=False, fill=False)
 
473
 
 
474
        entry = gtk.Entry()
 
475
        self._entry = entry
 
476
        entry.connect("activate", lambda w, d: self._do_search(d),
 
477
                      'forward')
 
478
        self.pack_start(entry, expand=False, fill=False)
 
479
 
 
480
        # Next/previous buttons
 
481
        button = gtk.Button('_Next')
 
482
        image = gtk.Image()
 
483
        image.set_from_stock('gtk-go-forward', gtk.ICON_SIZE_BUTTON)
 
484
        button.set_image(image)
 
485
        button.connect("clicked", lambda w, d: self._do_search(d),
 
486
                       'forward')
 
487
        self.pack_start(button, expand=False, fill=False)
 
488
 
 
489
        button = gtk.Button('_Previous')
 
490
        image = gtk.Image()
 
491
        image.set_from_stock('gtk-go-back', gtk.ICON_SIZE_BUTTON)
 
492
        button.set_image(image)
 
493
        button.connect("clicked", lambda w, d: self._do_search(d),
 
494
                       'backward')
 
495
        self.pack_start(button, expand=False, fill=False)
 
496
 
 
497
        # Search options
 
498
        check = gtk.CheckButton('Match case')
 
499
        self._match_case = check
 
500
        self.pack_start(check, expand=False, fill=False)
 
501
 
 
502
        check = gtk.CheckButton('Regexp')
 
503
        check.connect("toggled", lambda w: self._set_label())
 
504
        self._regexp = check
 
505
        self.pack_start(check, expand=False, fill=False)
 
506
 
 
507
        self._view = None
 
508
        self._column = None
 
509
        # Note that we stay hidden (we do not call self.show_all())
 
510
 
 
511
 
 
512
    def show_for(self, kind):
 
513
        self._kind = kind
 
514
        self.show_all()
 
515
        self._set_label()
 
516
        # Hide unrelated buttons
 
517
        if kind == 'line':
 
518
            self._match_case.hide()
 
519
            self._regexp.hide()
 
520
        # Be ready
 
521
        self._entry.grab_focus()
 
522
 
 
523
    def _set_label(self):
 
524
        if self._kind == 'line':
 
525
            self._label.set_text('Find Line: ')
 
526
        else:
 
527
            if self._regexp.get_active():
 
528
                self._label.set_text('Find Regexp: ')
 
529
            else:
 
530
                self._label.set_text('Find Text: ')
 
531
 
 
532
    def set_target(self, view,column):
 
533
        self._view = view
 
534
        self._column = column
 
535
 
 
536
    def _match(self, model, iterator, column):
 
537
        matching_case = self._match_case.get_active()
 
538
        cell_value, = model.get(iterator, column)
 
539
        key = self._entry.get_text()
 
540
        if column == LINE_NUM_COL:
 
541
            # FIXME: For goto-line there are faster algorithms than searching 
 
542
            # every line til we find the right one! -- mbp 2011-01-27
 
543
            return key.strip() == str(cell_value)
 
544
        elif self._regexp.get_active():
 
545
            if matching_case:
 
546
                match = re.compile(key).search(cell_value, 1)
 
547
            else:
 
548
                match = re.compile(key, re.I).search(cell_value, 1)
 
549
        else:
 
550
            if not matching_case:
 
551
                cell_value = cell_value.lower()
 
552
                key = key.lower()
 
553
            match = cell_value.find(key) != -1
 
554
 
 
555
        return match
 
556
 
 
557
    def _iterate_rows_forward(self, model, start):
 
558
        model_size = len(model)
 
559
        current = start + 1
 
560
        while model_size != 0:
 
561
            if current >= model_size: current =  0
 
562
            yield model.get_iter_from_string('%d' % current)
 
563
            if current == start: raise StopIteration
 
564
            current += 1
 
565
 
 
566
    def _iterate_rows_backward(self, model, start):
 
567
        model_size = len(model)
 
568
        current = start - 1
 
569
        while model_size != 0:
 
570
            if current < 0: current = model_size - 1
 
571
            yield model.get_iter_from_string('%d' % current)
 
572
            if current == start: raise StopIteration
 
573
            current -= 1
 
574
 
 
575
    def _do_search(self, direction):
 
576
        if direction == 'forward':
 
577
            iterate = self._iterate_rows_forward
 
578
        else:
 
579
            iterate = self._iterate_rows_backward
 
580
 
 
581
        model, sel = self._view.get_selection().get_selected()
 
582
        if sel is None:
 
583
            start = 0
 
584
        else:
 
585
            path = model.get_string_from_iter(sel)
 
586
            start = int(path)
 
587
 
 
588
        for row in iterate(model, start):
 
589
            if self._match(model, row, self._column):
 
590
                path = model.get_path(row)
 
591
                self._view.set_cursor(path)
 
592
                self._view.scroll_to_cell(path, use_align=True)
 
593
                break