/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

Commit messages never contain config options

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