/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: 2012-02-19 15:00:34 UTC
  • Revision ID: jelmer@samba.org-20120219150034-2yt7lhd0o6zam8q5
Cope with AppIndicator3 being missing during test run.

Show diffs side-by-side

added added

removed removed

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