/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: 2008-06-29 16:24:24 UTC
  • mto: This revision was merged to the branch mainline in revision 519.
  • Revision ID: jelmer@samba.org-20080629162424-48a6rrjmmpejfcyr
Stop emitting no longer used revisions-loaded message.

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
import time
18
18
 
19
 
from gi.repository import GObject
20
 
from gi.repository import Gdk
21
 
from gi.repository import Gtk
22
 
from gi.repository import Pango
 
19
import pygtk
 
20
pygtk.require("2.0")
 
21
import gobject
 
22
import gtk
 
23
import pango
23
24
import re
24
25
 
25
 
from bzrlib import patiencediff
 
26
from bzrlib import patiencediff, tsort
26
27
from bzrlib.errors import NoSuchRevision
27
28
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
28
29
 
29
 
from bzrlib.plugins.gtk.annotate.colormap import AnnotateColorSaturation
30
 
from bzrlib.plugins.gtk.i18n import _i18n
 
30
from colormap import AnnotateColorMap, AnnotateColorSaturation
31
31
from bzrlib.plugins.gtk.revisionview import RevisionView
32
32
from bzrlib.plugins.gtk.window import Window
33
33
 
49
49
        self.all = all
50
50
        self.plain = plain
51
51
        self._branch = branch
52
 
 
53
 
        super(GAnnotateWindow, self).__init__(parent=parent)
54
 
 
55
 
        self.set_icon(
56
 
            self.render_icon_pixbuf(Gtk.STOCK_FIND, Gtk.IconSize.BUTTON))
 
52
        
 
53
        Window.__init__(self, parent)
 
54
        
 
55
        self.set_icon(self.render_icon(gtk.STOCK_FIND, gtk.ICON_SIZE_BUTTON))
57
56
        self.annotate_colormap = AnnotateColorSaturation()
58
57
 
59
58
        self._create()
69
68
        self.revisionview.set_file_id(file_id)
70
69
        self.revision_id = getattr(tree, 'get_revision_id', 
71
70
                                   lambda: CURRENT_REVISION)()
72
 
 
 
71
        
73
72
        # [revision id, line number, author, revno, highlight color, line]
74
 
        self.annomodel = Gtk.ListStore(GObject.TYPE_STRING,
75
 
                                       GObject.TYPE_INT,
76
 
                                       GObject.TYPE_STRING,
77
 
                                       GObject.TYPE_STRING,
78
 
                                       GObject.TYPE_STRING,
79
 
                                       GObject.TYPE_STRING)
80
 
 
 
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
        
81
80
        last_seen = None
82
81
        try:
83
82
            branch.lock_read()
87
86
            for revision_id, revno in revno_map.iteritems():
88
87
                self.dotted[revision_id] = '.'.join(str(num) for num in revno)
89
88
            for line_no, (revision, revno, line)\
90
 
                in enumerate(self._annotate(tree, file_id)):
 
89
                    in enumerate(self._annotate(tree, file_id)):
91
90
                if revision.revision_id == last_seen and not self.all:
92
91
                    revno = author = ""
93
92
                else:
94
93
                    last_seen = revision.revision_id
95
 
                    author = ", ".join(revision.get_apparent_authors())
 
94
                    author = revision.get_apparent_author()
96
95
 
97
96
                if revision.revision_id not in self.revisions:
98
97
                    self.revisions[revision.revision_id] = revision
103
102
                                       revno,
104
103
                                       None,
105
104
                                       line.rstrip("\r\n")
106
 
                                       ])
 
105
                                      ])
107
106
                self.annotations.append(revision)
108
107
 
109
108
            if not self.plain:
126
125
            # bar?
127
126
            print("gannotate: Line number %d does't exist. Defaulting to "
128
127
                  "line 1." % lineno)
129
 
            return
 
128
            return
130
129
        else:
131
130
            row = lineno - 1
132
131
 
133
 
        tree_path = Gtk.TreePath(path=row)
134
 
        self.annoview.set_cursor(tree_path, None, False)
135
 
        self.annoview.scroll_to_cell(tree_path, use_align=True)
 
132
        self.annoview.set_cursor(row)
 
133
        self.annoview.scroll_to_cell(row, use_align=True)
 
134
 
136
135
 
137
136
    def _annotate(self, tree, file_id):
138
137
        current_revision = FakeRevision(CURRENT_REVISION)
140
139
        current_revision.timestamp = time.time()
141
140
        current_revision.message = '[Not yet committed]'
142
141
        current_revision.parent_ids = tree.get_parent_ids()
143
 
        current_revision.properties['branch-nick'] = self.branch._get_nick(local=True)
 
142
        current_revision.properties['branch-nick'] = self.branch.nick
144
143
        current_revno = '%d?' % (self.branch.revno() + 1)
145
144
        repository = self.branch.repository
146
145
        if self.revision_id == CURRENT_REVISION:
168
167
    def _highlight_annotation(self, model, path, iter, now):
169
168
        revision_id, = model.get(iter, REVISION_ID_COL)
170
169
        revision = self.revisions[revision_id]
171
 
        # XXX sinzui 2011-08-12: What does get_color return?
172
 
        color = self.annotate_colormap.get_color(revision, now)
173
 
        model.set_value(iter, HIGHLIGHT_COLOR_COL, color)
 
170
        model.set(iter, HIGHLIGHT_COLOR_COL,
 
171
                  self.annotate_colormap.get_color(revision, now))
174
172
 
175
173
    def _selected_revision(self):
176
174
        (path, col) = self.annoview.get_cursor()
195
193
        self.revisionview = self._create_log_view()
196
194
        self.annoview = self._create_annotate_view()
197
195
 
198
 
        vbox = Gtk.VBox(homogeneous=False, spacing=0)
 
196
        vbox = gtk.VBox(False)
199
197
        vbox.show()
200
198
 
201
 
        sw = Gtk.ScrolledWindow()
202
 
        sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
203
 
        sw.set_shadow_type(Gtk.ShadowType.IN)
 
199
        sw = gtk.ScrolledWindow()
 
200
        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
201
        sw.set_shadow_type(gtk.SHADOW_IN)
204
202
        sw.add(self.annoview)
205
203
        self.annoview.gwindow = self
206
204
        sw.show()
207
205
 
208
 
        swbox = Gtk.VBox()
209
 
        swbox.pack_start(sw, True, True, 0)
 
206
        swbox = gtk.VBox()
 
207
        swbox.pack_start(sw)
210
208
        swbox.show()
211
209
 
212
 
        hbox = Gtk.HBox(homogeneous=False, spacing=6)
 
210
        hbox = gtk.HBox(False, 6)
213
211
        self.back_button = self._create_back_button()
214
 
        hbox.pack_start(self.back_button, False, True, 0)
 
212
        hbox.pack_start(self.back_button, expand=False, fill=True)
215
213
        self.forward_button = self._create_forward_button()
216
 
        hbox.pack_start(self.forward_button, False, True, 0)
217
 
        self.find_button = self._create_find_button()
218
 
        hbox.pack_start(self.find_button, False, True, 0)
219
 
        self.goto_button = self._create_goto_button()
220
 
        hbox.pack_start(self.goto_button, False, True, 0)
 
214
        hbox.pack_start(self.forward_button, expand=False, fill=True)
221
215
        hbox.show()
222
 
        vbox.pack_start(hbox, False, True, 0)
223
 
 
224
 
        self.pane = pane = Gtk.Paned.new(Gtk.Orientation.VERTICAL)
 
216
        vbox.pack_start(hbox, expand=False, fill=True)
 
217
        
 
218
        self.pane = pane = gtk.VPaned()
225
219
        pane.add1(swbox)
226
220
        pane.add2(self.revisionview)
227
221
        pane.show()
228
 
        vbox.pack_start(pane, True, True, 0)
 
222
        vbox.pack_start(pane, expand=True, fill=True)
229
223
 
230
224
        self._search = SearchBox()
231
 
        swbox.pack_start(self._search, False, True, 0)
232
 
        accels = Gtk.AccelGroup()
233
 
        accels.connect(Gdk.KEY_f, Gdk.ModifierType.CONTROL_MASK,
234
 
                             Gtk.AccelFlags.LOCKED,
 
225
        swbox.pack_start(self._search, expand=False, fill=True)
 
226
        accels = gtk.AccelGroup()
 
227
        accels.connect_group(gtk.keysyms.f, gtk.gdk.CONTROL_MASK,
 
228
                             gtk.ACCEL_LOCKED,
235
229
                             self._search_by_text)
236
 
        accels.connect(Gdk.KEY_g, Gdk.ModifierType.CONTROL_MASK,
237
 
                             Gtk.AccelFlags.LOCKED,
 
230
        accels.connect_group(gtk.keysyms.g, gtk.gdk.CONTROL_MASK,
 
231
                             gtk.ACCEL_LOCKED,
238
232
                             self._search_by_line)
239
233
        self.add_accel_group(accels)
240
234
 
241
235
        self.add(vbox)
242
236
 
243
 
    def _search_by_text(self, *ignored): # (accel_group, window, key, modifiers):
 
237
    def _search_by_text(self, accel_group, window, key, modifiers):
244
238
        self._search.show_for('text')
245
239
        self._search.set_target(self.annoview, TEXT_LINE_COL)
246
240
 
247
 
    def _search_by_line(self, *ignored): # accel_group, window, key, modifiers):
 
241
    def _search_by_line(self, accel_group, window, key, modifiers):
248
242
        self._search.show_for('line')
249
243
        self._search.set_target(self.annoview, LINE_NUM_COL)
250
244
 
251
245
    def line_diff(self, tv, path, tvc):
252
 
        row = path.get_indices()[0]
 
246
        row = path[0]
253
247
        revision = self.annotations[row]
254
248
        repository = self.branch.repository
255
249
        if revision.revision_id == CURRENT_REVISION:
262
256
            else:
263
257
                tree2 = repository.revision_tree(NULL_REVISION)
264
258
        from bzrlib.plugins.gtk.diff import DiffWindow
265
 
        window = DiffWindow(self)
 
259
        window = DiffWindow()
266
260
        window.set_diff("Diff for line %d" % (row+1), tree1, tree2)
267
261
        window.set_file(tree1.id2path(self.file_id))
268
262
        window.show()
269
263
 
270
264
 
271
265
    def _create_annotate_view(self):
272
 
        tv = Gtk.TreeView()
 
266
        tv = gtk.TreeView()
273
267
        tv.set_rules_hint(False)
274
268
        tv.connect("cursor-changed", self._activate_selected_revision)
275
269
        tv.show()
276
270
        tv.connect("row-activated", self.line_diff)
277
271
 
278
 
        cell = Gtk.CellRendererText()
 
272
        cell = gtk.CellRendererText()
279
273
        cell.set_property("xalign", 1.0)
280
274
        cell.set_property("ypad", 0)
281
275
        cell.set_property("family", "Monospace")
282
 
        cell.set_property(
283
 
            "cell-background-rgba",
284
 
            tv.get_style_context().get_background_color(Gtk.StateType.NORMAL))
285
 
        col = Gtk.TreeViewColumn()
 
276
        cell.set_property("cell-background-gdk",
 
277
                          tv.get_style().bg[gtk.STATE_NORMAL])
 
278
        col = gtk.TreeViewColumn()
286
279
        col.set_resizable(False)
287
 
        col.pack_start(cell, True)
 
280
        col.pack_start(cell, expand=True)
288
281
        col.add_attribute(cell, "text", LINE_NUM_COL)
289
282
        tv.append_column(col)
290
283
 
291
 
        style_context = self.get_style_context()
292
 
 
293
 
        cell = Gtk.CellRendererText()
 
284
        cell = gtk.CellRendererText()
294
285
        cell.set_property("ypad", 0)
295
 
        cell.set_property("ellipsize", Pango.EllipsizeMode.END)
296
 
        cell.set_property(
297
 
            "cell-background-rgba",
298
 
            style_context.get_background_color(Gtk.StateType.NORMAL))
299
 
        col = Gtk.TreeViewColumn("Committer")
 
286
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
 
287
        cell.set_property("cell-background-gdk",
 
288
                          self.get_style().bg[gtk.STATE_NORMAL])
 
289
        col = gtk.TreeViewColumn("Committer")
300
290
        col.set_resizable(True)
301
 
        col.pack_start(cell, True)
 
291
        col.pack_start(cell, expand=True)
302
292
        col.add_attribute(cell, "text", COMMITTER_COL)
303
293
        tv.append_column(col)
304
294
 
305
 
        cell = Gtk.CellRendererText()
 
295
        cell = gtk.CellRendererText()
306
296
        cell.set_property("xalign", 1.0)
307
297
        cell.set_property("ypad", 0)
308
 
        cell.set_property(
309
 
            "cell-background-rgba",
310
 
            style_context.get_background_color(Gtk.StateType.NORMAL))
311
 
        col = Gtk.TreeViewColumn("Revno")
 
298
        cell.set_property("cell-background-gdk",
 
299
                          self.get_style().bg[gtk.STATE_NORMAL])
 
300
        col = gtk.TreeViewColumn("Revno")
312
301
        col.set_resizable(False)
313
 
        col.pack_start(cell, True)
 
302
        col.pack_start(cell, expand=True)
314
303
        col.add_attribute(cell, "markup", REVNO_COL)
315
304
        tv.append_column(col)
316
305
 
317
 
        cell = Gtk.CellRendererText()
 
306
        cell = gtk.CellRendererText()
318
307
        cell.set_property("ypad", 0)
319
308
        cell.set_property("family", "Monospace")
320
 
        col = Gtk.TreeViewColumn()
 
309
        col = gtk.TreeViewColumn()
321
310
        col.set_resizable(False)
322
 
        col.pack_start(cell, True)
 
311
        col.pack_start(cell, expand=True)
323
312
#        col.add_attribute(cell, "foreground", HIGHLIGHT_COLOR_COL)
324
313
        col.add_attribute(cell, "background", HIGHLIGHT_COLOR_COL)
325
314
        col.add_attribute(cell, "text", TEXT_LINE_COL)
326
315
        tv.append_column(col)
327
316
 
328
 
        # interactive substring search
329
 
        def search_equal_func(model, column, key, iter):
330
 
            return model.get_value(iter, TEXT_LINE_COL).lower().find(key.lower()) == -1
331
 
 
332
 
        tv.set_enable_search(True)
333
 
        tv.set_search_equal_func(search_equal_func, None)
 
317
        # FIXME: Now that C-f is now used for search by text we
 
318
        # may as well disable the auto search.
 
319
        tv.set_search_column(LINE_NUM_COL)
334
320
 
335
321
        return tv
336
322
 
340
326
        return lv
341
327
 
342
328
    def _create_back_button(self):
343
 
        button = Gtk.Button()
 
329
        button = gtk.Button()
344
330
        button.set_use_stock(True)
345
331
        button.set_label("gtk-go-back")
346
332
        button.connect("clicked", lambda w: self.go_back())
347
 
        button.set_relief(Gtk.ReliefStyle.NONE)
 
333
        button.set_relief(gtk.RELIEF_NONE)
348
334
        button.show()
349
335
        return button
350
336
 
351
337
    def _create_forward_button(self):
352
 
        button = Gtk.Button()
 
338
        button = gtk.Button()
353
339
        button.set_use_stock(True)
354
340
        button.set_label("gtk-go-forward")
355
341
        button.connect("clicked", lambda w: self.go_forward())
356
 
        button.set_relief(Gtk.ReliefStyle.NONE)
 
342
        button.set_relief(gtk.RELIEF_NONE)
357
343
        button.show()
358
344
        button.set_sensitive(False)
359
345
        return button
360
346
 
361
 
    def _create_find_button(self):
362
 
        button = Gtk.Button()
363
 
        button.set_use_stock(True)
364
 
        button.set_label("gtk-find")
365
 
        button.set_tooltip_text("Search for text (Ctrl+F)")
366
 
        button.connect("clicked", self._search_by_text)
367
 
        button.set_relief(Gtk.ReliefStyle.NONE)
368
 
        button.show()
369
 
        button.set_sensitive(True)
370
 
        return button
371
 
 
372
 
    def _create_goto_button(self):
373
 
        button = Gtk.Button()
374
 
        button.set_label("Goto Line")
375
 
        button.set_tooltip_text("Scroll to a line by entering its number (Ctrl+G)")
376
 
        button.connect("clicked", self._search_by_line)
377
 
        button.set_relief(Gtk.ReliefStyle.NONE)
378
 
        button.show()
379
 
        button.set_sensitive(True)
380
 
        return button
381
 
 
382
347
    def go_back(self):
383
348
        last_tree = self.tree
384
349
        rev_id = self._selected_revision()
403
368
        rev_id = self._selected_revision()
404
369
        if self.file_id in target_tree:
405
370
            offset = self.get_scroll_offset(target_tree)
406
 
            path, col = self.annoview.get_cursor()
407
 
            (row,) = path.get_indices()
 
371
            (row,), col = self.annoview.get_cursor()
408
372
            self.annotate(target_tree, self.branch, self.file_id)
409
373
            new_row = row+offset
410
374
            if new_row < 0:
411
375
                new_row = 0
412
 
            new_path = Gtk.TreePath(path=new_row)
413
 
            self.annoview.set_cursor(new_path, None, False)
 
376
            self.annoview.set_cursor(new_row)
414
377
            return True
415
378
        else:
416
379
            return False
418
381
    def get_scroll_offset(self, tree):
419
382
        old = self.tree.get_file(self.file_id)
420
383
        new = tree.get_file(self.file_id)
421
 
        path, col = self.annoview.get_cursor()
422
 
        (row,) = path.get_indices()
 
384
        (row,), col = self.annoview.get_cursor()
423
385
        matcher = patiencediff.PatienceSequenceMatcher(None, old.readlines(),
424
386
                                                       new.readlines())
425
387
        for i, j, n in matcher.get_matching_blocks():
427
389
                return j - i
428
390
 
429
391
 
430
 
class FakeRevision(object):
 
392
class FakeRevision:
431
393
    """ A fake revision.
432
394
 
433
395
    For when a revision is referenced but not present.
442
404
        self.timezone = 0
443
405
        self.properties = {}
444
406
 
445
 
    def get_apparent_authors(self):
446
 
        return [self.committer]
 
407
    def get_apparent_author(self):
 
408
        return self.committer
447
409
 
448
410
 
449
411
class RevisionCache(object):
450
412
    """A caching revision source"""
451
 
 
452
413
    def __init__(self, real_source, seed_cache=None):
453
414
        self.__real_source = real_source
454
415
        if seed_cache is None:
462
423
            self.__cache[revision_id] = revision
463
424
        return self.__cache[revision_id]
464
425
 
465
 
 
466
 
class SearchBox(Gtk.HBox):
 
426
class SearchBox(gtk.HBox):
467
427
    """A button box for searching in text or lines of annotations"""
468
428
    def __init__(self):
469
 
        super(SearchBox, self).__init__(homogeneous=False, spacing=6)
 
429
        gtk.HBox.__init__(self, False, 6)
470
430
 
471
431
        # Close button
472
 
        button = Gtk.Button()
473
 
        image = Gtk.Image()
474
 
        image.set_from_stock('gtk-stop', Gtk.IconSize.BUTTON)
 
432
        button = gtk.Button()
 
433
        image = gtk.Image()
 
434
        image.set_from_stock('gtk-stop', gtk.ICON_SIZE_BUTTON)
475
435
        button.set_image(image)
476
 
        button.set_relief(Gtk.ReliefStyle.NONE)
477
 
        button.connect("clicked", lambda w: self.hide())
478
 
        self.pack_start(button, False, False, 0)
 
436
        button.set_relief(gtk.RELIEF_NONE)
 
437
        button.connect("clicked", lambda w: self.hide_all())
 
438
        self.pack_start(button, expand=False, fill=False)
479
439
 
480
440
        # Search entry
481
 
        label = Gtk.Label()
 
441
        label = gtk.Label()
482
442
        self._label = label
483
 
        self.pack_start(label, False, False, 0)
 
443
        self.pack_start(label, expand=False, fill=False)
484
444
 
485
 
        entry = Gtk.Entry()
 
445
        entry = gtk.Entry()
486
446
        self._entry = entry
487
447
        entry.connect("activate", lambda w, d: self._do_search(d),
488
448
                      'forward')
489
 
        self.pack_start(entry, False, False, 0)
 
449
        self.pack_start(entry, expand=False, fill=False)
490
450
 
491
451
        # Next/previous buttons
492
 
        button = Gtk.Button(_i18n('_Next'), use_underline=True)
493
 
        image = Gtk.Image()
494
 
        image.set_from_stock('gtk-go-forward', Gtk.IconSize.BUTTON)
 
452
        button = gtk.Button('_Next')
 
453
        image = gtk.Image()
 
454
        image.set_from_stock('gtk-go-forward', gtk.ICON_SIZE_BUTTON)
495
455
        button.set_image(image)
496
456
        button.connect("clicked", lambda w, d: self._do_search(d),
497
457
                       'forward')
498
 
        self.pack_start(button, False, False, 0)
 
458
        self.pack_start(button, expand=False, fill=False)
499
459
 
500
 
        button = Gtk.Button(_i18n('_Previous'), use_underline=True)
501
 
        image = Gtk.Image()
502
 
        image.set_from_stock('gtk-go-back', Gtk.IconSize.BUTTON)
 
460
        button = gtk.Button('_Previous')
 
461
        image = gtk.Image()
 
462
        image.set_from_stock('gtk-go-back', gtk.ICON_SIZE_BUTTON)
503
463
        button.set_image(image)
504
464
        button.connect("clicked", lambda w, d: self._do_search(d),
505
465
                       'backward')
506
 
        self.pack_start(button, False, False, 0)
 
466
        self.pack_start(button, expand=False, fill=False)
507
467
 
508
468
        # Search options
509
 
        check = Gtk.CheckButton('Match case')
 
469
        check = gtk.CheckButton('Match case')
510
470
        self._match_case = check
511
 
        self.pack_start(check, False, False, 0)
 
471
        self.pack_start(check, expand=False, fill=False)
512
472
 
513
 
        check = Gtk.CheckButton('Regexp')
 
473
        check = gtk.CheckButton('Regexp')
514
474
        check.connect("toggled", lambda w: self._set_label())
515
475
        self._regexp = check
516
 
        self.pack_start(check, False, False, 0)
 
476
        self.pack_start(check, expand=False, fill=False)
517
477
 
518
478
        self._view = None
519
479
        self._column = None
546
506
 
547
507
    def _match(self, model, iterator, column):
548
508
        matching_case = self._match_case.get_active()
549
 
        cell_value, = model.get(iterator, column)
 
509
        string, = model.get(iterator, column)
550
510
        key = self._entry.get_text()
551
 
        if column == LINE_NUM_COL:
552
 
            # FIXME: For goto-line there are faster algorithms than searching 
553
 
            # every line til we find the right one! -- mbp 2011-01-27
554
 
            return key.strip() == str(cell_value)
555
 
        elif self._regexp.get_active():
 
511
        if self._regexp.get_active():
556
512
            if matching_case:
557
 
                match = re.compile(key).search(cell_value, 1)
 
513
                match = re.compile(key).search(string, 1)
558
514
            else:
559
 
                match = re.compile(key, re.I).search(cell_value, 1)
 
515
                match = re.compile(key, re.I).search(string, 1)
560
516
        else:
561
517
            if not matching_case:
562
 
                cell_value = cell_value.lower()
 
518
                string = string.lower()
563
519
                key = key.lower()
564
 
            match = cell_value.find(key) != -1
 
520
            match = string.find(key) != -1
565
521
 
566
522
        return match
567
523
 
599
555
        for row in iterate(model, start):
600
556
            if self._match(model, row, self._column):
601
557
                path = model.get_path(row)
602
 
                self._view.set_cursor(path, None, False)
 
558
                self._view.set_cursor(path)
603
559
                self._view.scroll_to_cell(path, use_align=True)
604
560
                break