/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 18:12:29 UTC
  • mto: This revision was merged to the branch mainline in revision 519.
  • Revision ID: jelmer@samba.org-20080629181229-1l2m4cf7vvbyh8qg
Simplify progress bar code, use embedded progress bar inside viz window.

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 colormap import AnnotateColorMap, AnnotateColorSaturation
30
31
from bzrlib.plugins.gtk.revisionview import RevisionView
31
32
from bzrlib.plugins.gtk.window import Window
32
33
 
48
49
        self.all = all
49
50
        self.plain = plain
50
51
        self._branch = branch
51
 
 
52
 
        super(GAnnotateWindow, self).__init__(parent=parent)
53
 
 
54
 
        self.set_icon(
55
 
            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))
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_INT,
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_STRING,
 
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 = ", ".join(revision.get_apparent_authors())
 
94
                    author = revision.get_apparent_author()
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
 
        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)
 
132
        self.annoview.set_cursor(row)
 
133
        self.annoview.scroll_to_cell(row, use_align=True)
 
134
 
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._get_nick(local=True)
 
142
        current_revision.properties['branch-nick'] = self.branch.nick
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
 
        # 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)
 
170
        model.set(iter, HIGHLIGHT_COLOR_COL,
 
171
                  self.annotate_colormap.get_color(revision, now))
173
172
 
174
173
    def _selected_revision(self):
175
174
        (path, col) = self.annoview.get_cursor()
194
193
        self.revisionview = self._create_log_view()
195
194
        self.annoview = self._create_annotate_view()
196
195
 
197
 
        vbox = Gtk.VBox(homogeneous=False, spacing=0)
 
196
        vbox = gtk.VBox(False)
198
197
        vbox.show()
199
198
 
200
 
        sw = Gtk.ScrolledWindow()
201
 
        sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
202
 
        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)
203
202
        sw.add(self.annoview)
204
203
        self.annoview.gwindow = self
205
204
        sw.show()
206
205
 
207
 
        swbox = Gtk.VBox()
208
 
        swbox.pack_start(sw, True, True, 0)
 
206
        swbox = gtk.VBox()
 
207
        swbox.pack_start(sw)
209
208
        swbox.show()
210
209
 
211
 
        hbox = Gtk.HBox(homogeneous=False, spacing=6)
 
210
        hbox = gtk.HBox(False, 6)
212
211
        self.back_button = self._create_back_button()
213
 
        hbox.pack_start(self.back_button, False, True, 0)
 
212
        hbox.pack_start(self.back_button, expand=False, fill=True)
214
213
        self.forward_button = self._create_forward_button()
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)
 
214
        hbox.pack_start(self.forward_button, expand=False, fill=True)
220
215
        hbox.show()
221
 
        vbox.pack_start(hbox, False, True, 0)
222
 
 
223
 
        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()
224
219
        pane.add1(swbox)
225
220
        pane.add2(self.revisionview)
226
221
        pane.show()
227
 
        vbox.pack_start(pane, True, True, 0)
 
222
        vbox.pack_start(pane, expand=True, fill=True)
228
223
 
229
224
        self._search = SearchBox()
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,
 
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,
234
229
                             self._search_by_text)
235
 
        accels.connect(Gdk.KEY_g, Gdk.ModifierType.CONTROL_MASK,
236
 
                             Gtk.AccelFlags.LOCKED,
 
230
        accels.connect_group(gtk.keysyms.g, gtk.gdk.CONTROL_MASK,
 
231
                             gtk.ACCEL_LOCKED,
237
232
                             self._search_by_line)
238
233
        self.add_accel_group(accels)
239
234
 
240
235
        self.add(vbox)
241
236
 
242
 
    def _search_by_text(self, *ignored): # (accel_group, window, key, modifiers):
 
237
    def _search_by_text(self, accel_group, window, key, modifiers):
243
238
        self._search.show_for('text')
244
239
        self._search.set_target(self.annoview, TEXT_LINE_COL)
245
240
 
246
 
    def _search_by_line(self, *ignored): # accel_group, window, key, modifiers):
 
241
    def _search_by_line(self, accel_group, window, key, modifiers):
247
242
        self._search.show_for('line')
248
243
        self._search.set_target(self.annoview, LINE_NUM_COL)
249
244
 
250
245
    def line_diff(self, tv, path, tvc):
251
 
        row = path.get_indices()[0]
 
246
        row = path[0]
252
247
        revision = self.annotations[row]
253
248
        repository = self.branch.repository
254
249
        if revision.revision_id == CURRENT_REVISION:
261
256
            else:
262
257
                tree2 = repository.revision_tree(NULL_REVISION)
263
258
        from bzrlib.plugins.gtk.diff import DiffWindow
264
 
        window = DiffWindow(self)
 
259
        window = DiffWindow()
265
260
        window.set_diff("Diff for line %d" % (row+1), tree1, tree2)
266
261
        window.set_file(tree1.id2path(self.file_id))
267
262
        window.show()
268
263
 
269
264
 
270
265
    def _create_annotate_view(self):
271
 
        tv = Gtk.TreeView()
 
266
        tv = gtk.TreeView()
272
267
        tv.set_rules_hint(False)
273
268
        tv.connect("cursor-changed", self._activate_selected_revision)
274
269
        tv.show()
275
270
        tv.connect("row-activated", self.line_diff)
276
271
 
277
 
        cell = Gtk.CellRendererText()
 
272
        cell = gtk.CellRendererText()
278
273
        cell.set_property("xalign", 1.0)
279
274
        cell.set_property("ypad", 0)
280
275
        cell.set_property("family", "Monospace")
281
276
        cell.set_property("cell-background-gdk",
282
 
                          tv.get_style().bg[Gtk.StateType.NORMAL])
283
 
        col = Gtk.TreeViewColumn()
 
277
                          tv.get_style().bg[gtk.STATE_NORMAL])
 
278
        col = gtk.TreeViewColumn()
284
279
        col.set_resizable(False)
285
 
        col.pack_start(cell, True)
 
280
        col.pack_start(cell, expand=True)
286
281
        col.add_attribute(cell, "text", LINE_NUM_COL)
287
282
        tv.append_column(col)
288
283
 
289
 
        cell = Gtk.CellRendererText()
 
284
        cell = gtk.CellRendererText()
290
285
        cell.set_property("ypad", 0)
291
 
        cell.set_property("ellipsize", Pango.EllipsizeMode.END)
 
286
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
292
287
        cell.set_property("cell-background-gdk",
293
 
                          self.get_style().bg[Gtk.StateType.NORMAL])
294
 
        col = Gtk.TreeViewColumn("Committer")
 
288
                          self.get_style().bg[gtk.STATE_NORMAL])
 
289
        col = gtk.TreeViewColumn("Committer")
295
290
        col.set_resizable(True)
296
 
        col.pack_start(cell, True)
 
291
        col.pack_start(cell, expand=True)
297
292
        col.add_attribute(cell, "text", COMMITTER_COL)
298
293
        tv.append_column(col)
299
294
 
300
 
        cell = Gtk.CellRendererText()
 
295
        cell = gtk.CellRendererText()
301
296
        cell.set_property("xalign", 1.0)
302
297
        cell.set_property("ypad", 0)
303
298
        cell.set_property("cell-background-gdk",
304
 
                          self.get_style().bg[Gtk.StateType.NORMAL])
305
 
        col = Gtk.TreeViewColumn("Revno")
 
299
                          self.get_style().bg[gtk.STATE_NORMAL])
 
300
        col = gtk.TreeViewColumn("Revno")
306
301
        col.set_resizable(False)
307
 
        col.pack_start(cell, True)
 
302
        col.pack_start(cell, expand=True)
308
303
        col.add_attribute(cell, "markup", REVNO_COL)
309
304
        tv.append_column(col)
310
305
 
311
 
        cell = Gtk.CellRendererText()
 
306
        cell = gtk.CellRendererText()
312
307
        cell.set_property("ypad", 0)
313
308
        cell.set_property("family", "Monospace")
314
 
        col = Gtk.TreeViewColumn()
 
309
        col = gtk.TreeViewColumn()
315
310
        col.set_resizable(False)
316
 
        col.pack_start(cell, True)
 
311
        col.pack_start(cell, expand=True)
317
312
#        col.add_attribute(cell, "foreground", HIGHLIGHT_COLOR_COL)
318
313
        col.add_attribute(cell, "background", HIGHLIGHT_COLOR_COL)
319
314
        col.add_attribute(cell, "text", TEXT_LINE_COL)
320
315
        tv.append_column(col)
321
316
 
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)
 
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
320
 
329
321
        return tv
330
322
 
334
326
        return lv
335
327
 
336
328
    def _create_back_button(self):
337
 
        button = Gtk.Button()
 
329
        button = gtk.Button()
338
330
        button.set_use_stock(True)
339
331
        button.set_label("gtk-go-back")
340
332
        button.connect("clicked", lambda w: self.go_back())
341
 
        button.set_relief(Gtk.ReliefStyle.NONE)
 
333
        button.set_relief(gtk.RELIEF_NONE)
342
334
        button.show()
343
335
        return button
344
336
 
345
337
    def _create_forward_button(self):
346
 
        button = Gtk.Button()
 
338
        button = gtk.Button()
347
339
        button.set_use_stock(True)
348
340
        button.set_label("gtk-go-forward")
349
341
        button.connect("clicked", lambda w: self.go_forward())
350
 
        button.set_relief(Gtk.ReliefStyle.NONE)
 
342
        button.set_relief(gtk.RELIEF_NONE)
351
343
        button.show()
352
344
        button.set_sensitive(False)
353
345
        return button
354
346
 
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
 
 
376
347
    def go_back(self):
377
348
        last_tree = self.tree
378
349
        rev_id = self._selected_revision()
397
368
        rev_id = self._selected_revision()
398
369
        if self.file_id in target_tree:
399
370
            offset = self.get_scroll_offset(target_tree)
400
 
            path, col = self.annoview.get_cursor()
401
 
            (row,) = path.get_indices()
 
371
            (row,), col = self.annoview.get_cursor()
402
372
            self.annotate(target_tree, self.branch, self.file_id)
403
373
            new_row = row+offset
404
374
            if new_row < 0:
405
375
                new_row = 0
406
 
            new_path = Gtk.TreePath(path=new_row)
407
 
            self.annoview.set_cursor(new_path, None, False)
 
376
            self.annoview.set_cursor(new_row)
408
377
            return True
409
378
        else:
410
379
            return False
412
381
    def get_scroll_offset(self, tree):
413
382
        old = self.tree.get_file(self.file_id)
414
383
        new = tree.get_file(self.file_id)
415
 
        path, col = self.annoview.get_cursor()
416
 
        (row,) = path.get_indices()
 
384
        (row,), col = self.annoview.get_cursor()
417
385
        matcher = patiencediff.PatienceSequenceMatcher(None, old.readlines(),
418
386
                                                       new.readlines())
419
387
        for i, j, n in matcher.get_matching_blocks():
421
389
                return j - i
422
390
 
423
391
 
424
 
class FakeRevision(object):
 
392
class FakeRevision:
425
393
    """ A fake revision.
426
394
 
427
395
    For when a revision is referenced but not present.
436
404
        self.timezone = 0
437
405
        self.properties = {}
438
406
 
439
 
    def get_apparent_authors(self):
440
 
        return [self.committer]
 
407
    def get_apparent_author(self):
 
408
        return self.committer
441
409
 
442
410
 
443
411
class RevisionCache(object):
444
412
    """A caching revision source"""
445
 
 
446
413
    def __init__(self, real_source, seed_cache=None):
447
414
        self.__real_source = real_source
448
415
        if seed_cache is None:
456
423
            self.__cache[revision_id] = revision
457
424
        return self.__cache[revision_id]
458
425
 
459
 
 
460
 
class SearchBox(Gtk.HBox):
 
426
class SearchBox(gtk.HBox):
461
427
    """A button box for searching in text or lines of annotations"""
462
428
    def __init__(self):
463
 
        super(SearchBox, self).__init__(homogeneous=False, spacing=6)
 
429
        gtk.HBox.__init__(self, False, 6)
464
430
 
465
431
        # Close button
466
 
        button = Gtk.Button()
467
 
        image = Gtk.Image()
468
 
        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)
469
435
        button.set_image(image)
470
 
        button.set_relief(Gtk.ReliefStyle.NONE)
471
 
        button.connect("clicked", lambda w: self.hide())
472
 
        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)
473
439
 
474
440
        # Search entry
475
 
        label = Gtk.Label()
 
441
        label = gtk.Label()
476
442
        self._label = label
477
 
        self.pack_start(label, False, False, 0)
 
443
        self.pack_start(label, expand=False, fill=False)
478
444
 
479
 
        entry = Gtk.Entry()
 
445
        entry = gtk.Entry()
480
446
        self._entry = entry
481
447
        entry.connect("activate", lambda w, d: self._do_search(d),
482
448
                      'forward')
483
 
        self.pack_start(entry, False, False, 0)
 
449
        self.pack_start(entry, expand=False, fill=False)
484
450
 
485
451
        # Next/previous buttons
486
 
        button = Gtk.Button('_Next')
487
 
        image = Gtk.Image()
488
 
        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)
489
455
        button.set_image(image)
490
456
        button.connect("clicked", lambda w, d: self._do_search(d),
491
457
                       'forward')
492
 
        self.pack_start(button, False, False, 0)
 
458
        self.pack_start(button, expand=False, fill=False)
493
459
 
494
 
        button = Gtk.Button('_Previous')
495
 
        image = Gtk.Image()
496
 
        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)
497
463
        button.set_image(image)
498
464
        button.connect("clicked", lambda w, d: self._do_search(d),
499
465
                       'backward')
500
 
        self.pack_start(button, False, False, 0)
 
466
        self.pack_start(button, expand=False, fill=False)
501
467
 
502
468
        # Search options
503
 
        check = Gtk.CheckButton('Match case')
 
469
        check = gtk.CheckButton('Match case')
504
470
        self._match_case = check
505
 
        self.pack_start(check, False, False, 0)
 
471
        self.pack_start(check, expand=False, fill=False)
506
472
 
507
 
        check = Gtk.CheckButton('Regexp')
 
473
        check = gtk.CheckButton('Regexp')
508
474
        check.connect("toggled", lambda w: self._set_label())
509
475
        self._regexp = check
510
 
        self.pack_start(check, False, False, 0)
 
476
        self.pack_start(check, expand=False, fill=False)
511
477
 
512
478
        self._view = None
513
479
        self._column = None
540
506
 
541
507
    def _match(self, model, iterator, column):
542
508
        matching_case = self._match_case.get_active()
543
 
        cell_value, = model.get(iterator, column)
 
509
        string, = model.get(iterator, column)
544
510
        key = self._entry.get_text()
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():
 
511
        if self._regexp.get_active():
550
512
            if matching_case:
551
 
                match = re.compile(key).search(cell_value, 1)
 
513
                match = re.compile(key).search(string, 1)
552
514
            else:
553
 
                match = re.compile(key, re.I).search(cell_value, 1)
 
515
                match = re.compile(key, re.I).search(string, 1)
554
516
        else:
555
517
            if not matching_case:
556
 
                cell_value = cell_value.lower()
 
518
                string = string.lower()
557
519
                key = key.lower()
558
 
            match = cell_value.find(key) != -1
 
520
            match = string.find(key) != -1
559
521
 
560
522
        return match
561
523
 
593
555
        for row in iterate(model, start):
594
556
            if self._match(model, row, self._column):
595
557
                path = model.get_path(row)
596
 
                self._view.set_cursor(path, None, False)
 
558
                self._view.set_cursor(path)
597
559
                self._view.scroll_to_cell(path, use_align=True)
598
560
                break