/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: 2007-10-02 17:23:52 UTC
  • mto: This revision was merged to the branch mainline in revision 292.
  • Revision ID: jelmer@samba.org-20071002172352-icn8h0b5iolowfkg
Avoid deprecation warning for all_plugins() in gpreferences.

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, tsort
 
27
from bzrlib.errors import NoSuchRevision
 
28
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
 
29
 
 
30
from colormap import AnnotateColorMap, AnnotateColorSaturation
 
31
from bzrlib.plugins.gtk.logview import LogView
 
32
 
 
33
 
 
34
(
 
35
    REVISION_ID_COL,
 
36
    LINE_NUM_COL,
 
37
    COMMITTER_COL,
 
38
    REVNO_COL,
 
39
    HIGHLIGHT_COLOR_COL,
 
40
    TEXT_LINE_COL
 
41
) = range(6)
 
42
 
 
43
 
 
44
class GAnnotateWindow(gtk.Window):
 
45
    """Annotate window."""
 
46
 
 
47
    def __init__(self, all=False, plain=False, parent=None):
 
48
        self.all = all
 
49
        self.plain = plain
 
50
        self._parent = parent
 
51
        
 
52
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
 
53
 
 
54
        self.connect("key-press-event", self._on_key_pressed)
 
55
        
 
56
        self.set_icon(self.render_icon(gtk.STOCK_FIND, gtk.ICON_SIZE_BUTTON))
 
57
        self.annotate_colormap = AnnotateColorSaturation()
 
58
 
 
59
        self._create()
 
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
        
 
72
        # [revision id, line number, author, revno, highlight color, line]
 
73
        self.annomodel = gtk.ListStore(gobject.TYPE_STRING,
 
74
                                       gobject.TYPE_STRING,
 
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
            for line_no, (revision, revno, line)\
 
85
                    in enumerate(self._annotate(tree, file_id)):
 
86
                if revision.revision_id == last_seen and not self.all:
 
87
                    revno = author = ""
 
88
                else:
 
89
                    last_seen = revision.revision_id
 
90
                    author = revision.get_apparent_author()
 
91
 
 
92
                if revision.revision_id not in self.revisions:
 
93
                    self.revisions[revision.revision_id] = revision
 
94
 
 
95
                self.annomodel.append([revision.revision_id,
 
96
                                       line_no + 1,
 
97
                                       author,
 
98
                                       revno,
 
99
                                       None,
 
100
                                       line.rstrip("\r\n")
 
101
                                      ])
 
102
                self.annotations.append(revision)
 
103
 
 
104
            if not self.plain:
 
105
                now = time.time()
 
106
                self.annomodel.foreach(self._highlight_annotation, now)
 
107
        finally:
 
108
            branch.repository.unlock()
 
109
            branch.unlock()
 
110
 
 
111
        self.annoview.set_model(self.annomodel)
 
112
        self.annoview.grab_focus()
 
113
 
 
114
    def jump_to_line(self, lineno):
 
115
        if lineno > len(self.annomodel) or lineno < 1:
 
116
            row = 0
 
117
            # FIXME:should really deal with this in the gui. Perhaps a status
 
118
            # bar?
 
119
            print("gannotate: Line number %d does't exist. Defaulting to "
 
120
                  "line 1." % lineno)
 
121
            return
 
122
        else:
 
123
            row = lineno - 1
 
124
 
 
125
        self.annoview.set_cursor(row)
 
126
        self.annoview.scroll_to_cell(row, use_align=True)
 
127
 
 
128
    def _dotted_revnos(self, repository, revision_id):
 
129
        """Return a dict of revision_id -> dotted revno
 
130
        
 
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):
 
157
            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 = "?"
 
170
 
 
171
            yield revision, revno, text
 
172
 
 
173
    def _highlight_annotation(self, model, path, iter, now):
 
174
        revision_id, = model.get(iter, REVISION_ID_COL)
 
175
        revision = self.revisions[revision_id]
 
176
        model.set(iter, HIGHLIGHT_COLOR_COL,
 
177
                  self.annotate_colormap.get_color(revision, now))
 
178
 
 
179
    def _selected_revision(self):
 
180
        (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)
 
197
 
 
198
    def _create(self):
 
199
        self.logview = self._create_log_view()
 
200
        self.annoview = self._create_annotate_view()
 
201
 
 
202
        vbox = gtk.VBox(False)
 
203
        vbox.show()
 
204
 
 
205
        sw = gtk.ScrolledWindow()
 
206
        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
207
        sw.set_shadow_type(gtk.SHADOW_IN)
 
208
        sw.add(self.annoview)
 
209
        self.annoview.gwindow = self
 
210
        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
        
 
224
        self.pane = pane = gtk.VPaned()
 
225
        pane.add1(swbox)
 
226
        pane.add2(self.logview)
 
227
        pane.show()
 
228
        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)
 
240
 
 
241
        self.add(vbox)
 
242
 
 
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
    def _create_annotate_view(self):
 
272
        tv = gtk.TreeView()
 
273
        tv.set_rules_hint(False)
 
274
        tv.connect("cursor-changed", self._activate_selected_revision)
 
275
        tv.show()
 
276
        tv.connect("row-activated", self.row_diff)
 
277
 
 
278
        cell = gtk.CellRendererText()
 
279
        cell.set_property("xalign", 1.0)
 
280
        cell.set_property("ypad", 0)
 
281
        cell.set_property("family", "Monospace")
 
282
        cell.set_property("cell-background-gdk",
 
283
                          tv.get_style().bg[gtk.STATE_NORMAL])
 
284
        col = gtk.TreeViewColumn()
 
285
        col.set_resizable(False)
 
286
        col.pack_start(cell, expand=True)
 
287
        col.add_attribute(cell, "text", LINE_NUM_COL)
 
288
        tv.append_column(col)
 
289
 
 
290
        cell = gtk.CellRendererText()
 
291
        cell.set_property("ypad", 0)
 
292
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
 
293
        cell.set_property("cell-background-gdk",
 
294
                          self.get_style().bg[gtk.STATE_NORMAL])
 
295
        col = gtk.TreeViewColumn("Committer")
 
296
        col.set_resizable(True)
 
297
        col.pack_start(cell, expand=True)
 
298
        col.add_attribute(cell, "text", COMMITTER_COL)
 
299
        tv.append_column(col)
 
300
 
 
301
        cell = gtk.CellRendererText()
 
302
        cell.set_property("xalign", 1.0)
 
303
        cell.set_property("ypad", 0)
 
304
        cell.set_property("cell-background-gdk",
 
305
                          self.get_style().bg[gtk.STATE_NORMAL])
 
306
        col = gtk.TreeViewColumn("Revno")
 
307
        col.set_resizable(False)
 
308
        col.pack_start(cell, expand=True)
 
309
        col.add_attribute(cell, "markup", REVNO_COL)
 
310
        tv.append_column(col)
 
311
 
 
312
        cell = gtk.CellRendererText()
 
313
        cell.set_property("ypad", 0)
 
314
        cell.set_property("family", "Monospace")
 
315
        col = gtk.TreeViewColumn()
 
316
        col.set_resizable(False)
 
317
        col.pack_start(cell, expand=True)
 
318
#        col.add_attribute(cell, "foreground", HIGHLIGHT_COLOR_COL)
 
319
        col.add_attribute(cell, "background", HIGHLIGHT_COLOR_COL)
 
320
        col.add_attribute(cell, "text", TEXT_LINE_COL)
 
321
        tv.append_column(col)
 
322
 
 
323
        # FIXME: Now that C-f is now used for search by text we
 
324
        # may as well disable the auto search.
 
325
        tv.set_search_column(LINE_NUM_COL)
 
326
 
 
327
        return tv
 
328
 
 
329
    def _create_log_view(self):
 
330
        lv = LogView()
 
331
        lv.show()
 
332
        return lv
 
333
 
 
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:
 
418
    """ A fake revision.
 
419
 
 
420
    For when a revision is referenced but not present.
 
421
    """
 
422
 
 
423
    def __init__(self, revision_id, committer='?', nick=None):
 
424
        self.revision_id = revision_id
 
425
        self.parent_ids = []
 
426
        self.committer = committer
 
427
        self.message = "?"
 
428
        self.timestamp = 0.0
 
429
        self.timezone = 0
 
430
        self.properties = {}
 
431
 
 
432
    def get_apparent_author(self):
 
433
        return self.committer
 
434
 
 
435
 
 
436
class RevisionCache(object):
 
437
    """A caching revision source"""
 
438
    def __init__(self, real_source, seed_cache=None):
 
439
        self.__real_source = real_source
 
440
        if seed_cache is None:
 
441
            self.__cache = {}
 
442
        else:
 
443
            self.__cache = dict(seed_cache)
 
444
 
 
445
    def get_revision(self, revision_id):
 
446
        if revision_id not in self.__cache:
 
447
            revision = self.__real_source.get_revision(revision_id)
 
448
            self.__cache[revision_id] = revision
 
449
        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