/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz

« back to all changes in this revision

Viewing changes to annotate/gannotate.py

  • Committer: Jelmer Vernooij
  • Date: 2007-04-03 17:16:07 UTC
  • mfrom: (187 trunk)
  • mto: This revision was merged to the branch mainline in revision 188.
  • Revision ID: jelmer@samba.org-20070403171607-0zaskazouokrm4cq
Tags: bzr-gtk-0.15.2
PrepareĀ forĀ 0.15.2

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 Gtk
21
 
from gi.repository import Pango
 
19
import pygtk
 
20
pygtk.require("2.0")
 
21
import gobject
 
22
import gtk
 
23
import pango
22
24
import re
23
25
 
24
 
from bzrlib import patiencediff
 
26
from bzrlib import patiencediff, tsort
25
27
from bzrlib.errors import NoSuchRevision
26
28
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
27
29
 
28
 
from bzrlib.plugins.gtk.annotate.colormap import AnnotateColorSaturation
29
 
from bzrlib.plugins.gtk.revisionview import RevisionView
30
 
from bzrlib.plugins.gtk.window import Window
 
30
from colormap import AnnotateColorMap, AnnotateColorSaturation
 
31
from bzrlib.plugins.gtk.logview import LogView
31
32
 
32
33
 
33
34
(
40
41
) = range(6)
41
42
 
42
43
 
43
 
class GAnnotateWindow(Window):
 
44
class GAnnotateWindow(gtk.Window):
44
45
    """Annotate window."""
45
46
 
46
 
    def __init__(self, all=False, plain=False, parent=None, branch=None):
 
47
    def __init__(self, all=False, plain=False):
47
48
        self.all = all
48
49
        self.plain = plain
49
 
        self._branch = branch
50
 
 
51
 
        Window.__init__(self, parent)
52
 
 
53
 
        self.set_icon(self.render_icon(Gtk.STOCK_FIND, Gtk.IconSize.BUTTON))
 
50
        
 
51
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
 
52
        
 
53
        self.set_icon(self.render_icon(gtk.STOCK_FIND, gtk.ICON_SIZE_BUTTON))
54
54
        self.annotate_colormap = AnnotateColorSaturation()
55
55
 
56
56
        self._create()
63
63
        self.branch = branch
64
64
        self.tree = tree
65
65
        self.file_id = file_id
66
 
        self.revisionview.set_file_id(file_id)
67
66
        self.revision_id = getattr(tree, 'get_revision_id', 
68
67
                                   lambda: CURRENT_REVISION)()
69
 
 
70
 
        # [revision id, line number, author, revno, highlight color, line]
71
 
        self.annomodel = Gtk.ListStore(GObject.TYPE_STRING,
72
 
                                       GObject.TYPE_INT,
73
 
                                       GObject.TYPE_STRING,
74
 
                                       GObject.TYPE_STRING,
75
 
                                       GObject.TYPE_STRING,
76
 
                                       GObject.TYPE_STRING)
77
 
 
 
68
        
 
69
        # [revision id, line number, committer, revno, highlight color, line]
 
70
        self.annomodel = gtk.ListStore(gobject.TYPE_STRING,
 
71
                                       gobject.TYPE_STRING,
 
72
                                       gobject.TYPE_STRING,
 
73
                                       gobject.TYPE_STRING,
 
74
                                       gobject.TYPE_STRING,
 
75
                                       gobject.TYPE_STRING)
 
76
        
78
77
        last_seen = None
79
78
        try:
80
79
            branch.lock_read()
81
80
            branch.repository.lock_read()
82
 
            self.dotted = {}
83
 
            revno_map = self.branch.get_revision_id_to_revno_map()
84
 
            for revision_id, revno in revno_map.iteritems():
85
 
                self.dotted[revision_id] = '.'.join(str(num) for num in revno)
86
81
            for line_no, (revision, revno, line)\
87
 
                in enumerate(self._annotate(tree, file_id)):
 
82
                    in enumerate(self._annotate(tree, file_id)):
88
83
                if revision.revision_id == last_seen and not self.all:
89
 
                    revno = author = ""
 
84
                    revno = committer = ""
90
85
                else:
91
86
                    last_seen = revision.revision_id
92
 
                    author = ", ".join(revision.get_apparent_authors())
 
87
                    committer = revision.committer
93
88
 
94
89
                if revision.revision_id not in self.revisions:
95
90
                    self.revisions[revision.revision_id] = revision
96
91
 
97
92
                self.annomodel.append([revision.revision_id,
98
93
                                       line_no + 1,
99
 
                                       author,
 
94
                                       committer,
100
95
                                       revno,
101
96
                                       None,
102
97
                                       line.rstrip("\r\n")
103
 
                                       ])
 
98
                                      ])
104
99
                self.annotations.append(revision)
105
100
 
106
101
            if not self.plain:
112
107
 
113
108
        self.annoview.set_model(self.annomodel)
114
109
        self.annoview.grab_focus()
115
 
        my_revno = self.dotted.get(self.revision_id, 'current')
116
 
        title = '%s (%s) - gannotate' % (self.tree.id2path(file_id), my_revno)
117
 
        self.set_title(title)
118
110
 
119
111
    def jump_to_line(self, lineno):
120
112
        if lineno > len(self.annomodel) or lineno < 1:
123
115
            # bar?
124
116
            print("gannotate: Line number %d does't exist. Defaulting to "
125
117
                  "line 1." % lineno)
126
 
            return
 
118
            return
127
119
        else:
128
120
            row = lineno - 1
129
121
 
130
122
        self.annoview.set_cursor(row)
131
123
        self.annoview.scroll_to_cell(row, use_align=True)
132
124
 
 
125
    def _dotted_revnos(self, repository, revision_id):
 
126
        """Return a dict of revision_id -> dotted revno
 
127
        
 
128
        :param repository: The repository to get the graph from
 
129
        :param revision_id: The last revision for which this info is needed
 
130
        """
 
131
        graph = repository.get_revision_graph(revision_id)
 
132
        dotted = {}
 
133
        for n, revision_id, d, revno, e in tsort.merge_sort(graph, 
 
134
            revision_id, generate_revno=True):
 
135
            dotted[revision_id] = '.'.join(str(num) for num in revno)
 
136
        return dotted
133
137
 
134
138
    def _annotate(self, tree, file_id):
135
139
        current_revision = FakeRevision(CURRENT_REVISION)
137
141
        current_revision.timestamp = time.time()
138
142
        current_revision.message = '[Not yet committed]'
139
143
        current_revision.parent_ids = tree.get_parent_ids()
140
 
        current_revision.properties['branch-nick'] = self.branch._get_nick(local=True)
 
144
        current_revision.properties['branch-nick'] = self.branch.nick
141
145
        current_revno = '%d?' % (self.branch.revno() + 1)
142
146
        repository = self.branch.repository
143
147
        if self.revision_id == CURRENT_REVISION:
144
148
            revision_id = self.branch.last_revision()
145
149
        else:
146
150
            revision_id = self.revision_id
 
151
        dotted = self._dotted_revnos(repository, revision_id)
147
152
        revision_cache = RevisionCache(repository, self.revisions)
148
153
        for origin, text in tree.annotate_iter(file_id):
149
154
            rev_id = origin
153
158
            else:
154
159
                try:
155
160
                    revision = revision_cache.get_revision(rev_id)
156
 
                    revno = self.dotted.get(rev_id, 'merge')
 
161
                    revno = dotted.get(rev_id, 'merge')
157
162
                    if len(revno) > 15:
158
163
                        revno = 'merge'
159
164
                except NoSuchRevision:
176
181
 
177
182
    def _activate_selected_revision(self, w):
178
183
        rev_id = self._selected_revision()
179
 
        if not rev_id or rev_id == NULL_REVISION:
 
184
        if rev_id is None:
180
185
            return
181
186
        selected = self.revisions[rev_id]
182
 
        self.revisionview.set_revision(selected)
 
187
        self.logview.set_revision(selected)
183
188
        if (len(selected.parent_ids) != 0 and selected.parent_ids[0] not in
184
189
            self._no_back):
185
190
            enable_back = True
188
193
        self.back_button.set_sensitive(enable_back)
189
194
 
190
195
    def _create(self):
191
 
        self.revisionview = self._create_log_view()
 
196
        self.logview = self._create_log_view()
192
197
        self.annoview = self._create_annotate_view()
193
198
 
194
 
        vbox = Gtk.VBox(False)
 
199
        vbox = gtk.VBox(False)
195
200
        vbox.show()
196
201
 
197
 
        sw = Gtk.ScrolledWindow()
198
 
        sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
199
 
        sw.set_shadow_type(Gtk.ShadowType.IN)
 
202
        sw = gtk.ScrolledWindow()
 
203
        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
204
        sw.set_shadow_type(gtk.SHADOW_IN)
200
205
        sw.add(self.annoview)
201
206
        self.annoview.gwindow = self
202
207
        sw.show()
203
208
 
204
 
        swbox = Gtk.VBox()
205
 
        swbox.pack_start(sw, True, True, 0)
 
209
        swbox = gtk.VBox()
 
210
        swbox.pack_start(sw)
206
211
        swbox.show()
207
212
 
208
 
        hbox = Gtk.HBox(False, 6)
 
213
        hbox = gtk.HBox(False, 6)
209
214
        self.back_button = self._create_back_button()
210
215
        hbox.pack_start(self.back_button, expand=False, fill=True)
211
216
        self.forward_button = self._create_forward_button()
212
217
        hbox.pack_start(self.forward_button, expand=False, fill=True)
213
 
        self.find_button = self._create_find_button()
214
 
        hbox.pack_start(self.find_button, expand=False, fill=True)
215
 
        self.goto_button = self._create_goto_button()
216
 
        hbox.pack_start(self.goto_button, expand=False, fill=True)
217
218
        hbox.show()
218
219
        vbox.pack_start(hbox, expand=False, fill=True)
219
 
 
220
 
        self.pane = pane = Gtk.VPaned()
 
220
        
 
221
        self.pane = pane = gtk.VPaned()
221
222
        pane.add1(swbox)
222
 
        pane.add2(self.revisionview)
 
223
        pane.add2(self.logview)
223
224
        pane.show()
224
225
        vbox.pack_start(pane, expand=True, fill=True)
225
226
 
226
227
        self._search = SearchBox()
227
228
        swbox.pack_start(self._search, expand=False, fill=True)
228
 
        accels = Gtk.AccelGroup()
229
 
        accels.connect_group(Gdk.KEY_f, Gdk.EventMask.CONTROL_MASK,
230
 
                             Gtk.ACCEL_LOCKED,
 
229
        accels = gtk.AccelGroup()
 
230
        accels.connect_group(gtk.keysyms.f, gtk.gdk.CONTROL_MASK,
 
231
                             gtk.ACCEL_LOCKED,
231
232
                             self._search_by_text)
232
 
        accels.connect_group(Gdk.KEY_g, Gdk.EventMask.CONTROL_MASK,
233
 
                             Gtk.ACCEL_LOCKED,
 
233
        accels.connect_group(gtk.keysyms.g, gtk.gdk.CONTROL_MASK,
 
234
                             gtk.ACCEL_LOCKED,
234
235
                             self._search_by_line)
235
236
        self.add_accel_group(accels)
236
237
 
237
238
        self.add(vbox)
238
239
 
239
 
    def _search_by_text(self, *ignored): # (accel_group, window, key, modifiers):
 
240
    def _search_by_text(self, accel_group, window, key, modifiers):
240
241
        self._search.show_for('text')
241
242
        self._search.set_target(self.annoview, TEXT_LINE_COL)
242
243
 
243
 
    def _search_by_line(self, *ignored): # accel_group, window, key, modifiers):
 
244
    def _search_by_line(self, accel_group, window, key, modifiers):
244
245
        self._search.show_for('line')
245
246
        self._search.set_target(self.annoview, LINE_NUM_COL)
246
247
 
247
 
    def line_diff(self, tv, path, tvc):
 
248
    def row_diff(self, tv, path, tvc):
248
249
        row = path[0]
249
250
        revision = self.annotations[row]
250
251
        repository = self.branch.repository
258
259
            else:
259
260
                tree2 = repository.revision_tree(NULL_REVISION)
260
261
        from bzrlib.plugins.gtk.diff import DiffWindow
261
 
        window = DiffWindow(self)
262
 
        window.set_diff("Diff for line %d" % (row+1), tree1, tree2)
 
262
        window = DiffWindow()
 
263
        window.set_diff("Diff for row %d" % (row+1), tree1, tree2)
263
264
        window.set_file(tree1.id2path(self.file_id))
264
265
        window.show()
265
266
 
266
267
 
267
268
    def _create_annotate_view(self):
268
 
        tv = Gtk.TreeView()
 
269
        tv = gtk.TreeView()
269
270
        tv.set_rules_hint(False)
270
271
        tv.connect("cursor-changed", self._activate_selected_revision)
271
272
        tv.show()
272
 
        tv.connect("row-activated", self.line_diff)
 
273
        tv.connect("row-activated", self.row_diff)
273
274
 
274
 
        cell = Gtk.CellRendererText()
 
275
        cell = gtk.CellRendererText()
275
276
        cell.set_property("xalign", 1.0)
276
277
        cell.set_property("ypad", 0)
277
278
        cell.set_property("family", "Monospace")
278
279
        cell.set_property("cell-background-gdk",
279
 
                          tv.get_style().bg[Gtk.StateType.NORMAL])
280
 
        col = Gtk.TreeViewColumn()
 
280
                          tv.get_style().bg[gtk.STATE_NORMAL])
 
281
        col = gtk.TreeViewColumn()
281
282
        col.set_resizable(False)
282
 
        col.pack_start(cell, True, True, 0)
 
283
        col.pack_start(cell, expand=True)
283
284
        col.add_attribute(cell, "text", LINE_NUM_COL)
284
285
        tv.append_column(col)
285
286
 
286
 
        cell = Gtk.CellRendererText()
 
287
        cell = gtk.CellRendererText()
287
288
        cell.set_property("ypad", 0)
288
 
        cell.set_property("ellipsize", Pango.EllipsizeMode.END)
 
289
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
289
290
        cell.set_property("cell-background-gdk",
290
 
                          self.get_style().bg[Gtk.StateType.NORMAL])
291
 
        col = Gtk.TreeViewColumn("Committer")
 
291
                          self.get_style().bg[gtk.STATE_NORMAL])
 
292
        col = gtk.TreeViewColumn("Committer")
292
293
        col.set_resizable(True)
293
 
        col.pack_start(cell, True, True, 0)
 
294
        col.pack_start(cell, expand=True)
294
295
        col.add_attribute(cell, "text", COMMITTER_COL)
295
296
        tv.append_column(col)
296
297
 
297
 
        cell = Gtk.CellRendererText()
 
298
        cell = gtk.CellRendererText()
298
299
        cell.set_property("xalign", 1.0)
299
300
        cell.set_property("ypad", 0)
300
301
        cell.set_property("cell-background-gdk",
301
 
                          self.get_style().bg[Gtk.StateType.NORMAL])
302
 
        col = Gtk.TreeViewColumn("Revno")
 
302
                          self.get_style().bg[gtk.STATE_NORMAL])
 
303
        col = gtk.TreeViewColumn("Revno")
303
304
        col.set_resizable(False)
304
 
        col.pack_start(cell, True, True, 0)
 
305
        col.pack_start(cell, expand=True)
305
306
        col.add_attribute(cell, "markup", REVNO_COL)
306
307
        tv.append_column(col)
307
308
 
308
 
        cell = Gtk.CellRendererText()
 
309
        cell = gtk.CellRendererText()
309
310
        cell.set_property("ypad", 0)
310
311
        cell.set_property("family", "Monospace")
311
 
        col = Gtk.TreeViewColumn()
 
312
        col = gtk.TreeViewColumn()
312
313
        col.set_resizable(False)
313
 
        col.pack_start(cell, True, True, 0)
 
314
        col.pack_start(cell, expand=True)
314
315
#        col.add_attribute(cell, "foreground", HIGHLIGHT_COLOR_COL)
315
316
        col.add_attribute(cell, "background", HIGHLIGHT_COLOR_COL)
316
317
        col.add_attribute(cell, "text", TEXT_LINE_COL)
317
318
        tv.append_column(col)
318
319
 
319
 
        # interactive substring search
320
 
        def search_equal_func(model, column, key, iter):
321
 
            return model.get_value(iter, TEXT_LINE_COL).lower().find(key.lower()) == -1
322
 
 
323
 
        tv.set_enable_search(True)
324
 
        tv.set_search_equal_func(search_equal_func)
 
320
        # FIXME: Now that C-f is now used for search by text we
 
321
        # may as well disable the auto search.
 
322
        tv.set_search_column(LINE_NUM_COL)
325
323
 
326
324
        return tv
327
325
 
328
326
    def _create_log_view(self):
329
 
        lv = RevisionView(self._branch)
 
327
        lv = LogView()
330
328
        lv.show()
331
329
        return lv
332
330
 
333
331
    def _create_back_button(self):
334
 
        button = Gtk.Button()
 
332
        button = gtk.Button()
335
333
        button.set_use_stock(True)
336
334
        button.set_label("gtk-go-back")
337
335
        button.connect("clicked", lambda w: self.go_back())
338
 
        button.set_relief(Gtk.ReliefStyle.NONE)
 
336
        button.set_relief(gtk.RELIEF_NONE)
339
337
        button.show()
340
338
        return button
341
339
 
342
340
    def _create_forward_button(self):
343
 
        button = Gtk.Button()
 
341
        button = gtk.Button()
344
342
        button.set_use_stock(True)
345
343
        button.set_label("gtk-go-forward")
346
344
        button.connect("clicked", lambda w: self.go_forward())
347
 
        button.set_relief(Gtk.ReliefStyle.NONE)
 
345
        button.set_relief(gtk.RELIEF_NONE)
348
346
        button.show()
349
347
        button.set_sensitive(False)
350
348
        return button
351
349
 
352
 
    def _create_find_button(self):
353
 
        button = Gtk.Button()
354
 
        button.set_use_stock(True)
355
 
        button.set_label("gtk-find")
356
 
        button.set_tooltip_text("Search for text (Ctrl+F)")
357
 
        button.connect("clicked", self._search_by_text)
358
 
        button.set_relief(Gtk.ReliefStyle.NONE)
359
 
        button.show()
360
 
        button.set_sensitive(True)
361
 
        return button
362
 
 
363
 
    def _create_goto_button(self):
364
 
        button = Gtk.Button()
365
 
        button.set_label("Goto Line")
366
 
        button.set_tooltip_text("Scroll to a line by entering its number (Ctrl+G)")
367
 
        button.connect("clicked", self._search_by_line)
368
 
        button.set_relief(Gtk.ReliefStyle.NONE)
369
 
        button.show()
370
 
        button.set_sensitive(True)
371
 
        return button
372
 
 
373
350
    def go_back(self):
374
351
        last_tree = self.tree
375
352
        rev_id = self._selected_revision()
415
392
                return j - i
416
393
 
417
394
 
418
 
class FakeRevision(object):
 
395
 
 
396
class FakeRevision:
419
397
    """ A fake revision.
420
398
 
421
399
    For when a revision is referenced but not present.
430
408
        self.timezone = 0
431
409
        self.properties = {}
432
410
 
433
 
    def get_apparent_authors(self):
434
 
        return [self.committer]
435
 
 
436
411
 
437
412
class RevisionCache(object):
438
413
    """A caching revision source"""
439
 
 
440
414
    def __init__(self, real_source, seed_cache=None):
441
415
        self.__real_source = real_source
442
416
        if seed_cache is None:
450
424
            self.__cache[revision_id] = revision
451
425
        return self.__cache[revision_id]
452
426
 
453
 
class SearchBox(Gtk.HBox):
 
427
class SearchBox(gtk.HBox):
454
428
    """A button box for searching in text or lines of annotations"""
455
429
    def __init__(self):
456
 
        GObject.GObject.__init__(self, False, 6)
 
430
        gtk.HBox.__init__(self, False, 6)
457
431
 
458
432
        # Close button
459
 
        button = Gtk.Button()
460
 
        image = Gtk.Image()
461
 
        image.set_from_stock('gtk-stop', Gtk.IconSize.BUTTON)
 
433
        button = gtk.Button()
 
434
        image = gtk.Image()
 
435
        image.set_from_stock('gtk-stop', gtk.ICON_SIZE_BUTTON)
462
436
        button.set_image(image)
463
 
        button.set_relief(Gtk.ReliefStyle.NONE)
 
437
        button.set_relief(gtk.RELIEF_NONE)
464
438
        button.connect("clicked", lambda w: self.hide_all())
465
439
        self.pack_start(button, expand=False, fill=False)
466
440
 
467
441
        # Search entry
468
 
        label = Gtk.Label()
 
442
        label = gtk.Label()
469
443
        self._label = label
470
444
        self.pack_start(label, expand=False, fill=False)
471
445
 
472
 
        entry = Gtk.Entry()
 
446
        entry = gtk.Entry()
473
447
        self._entry = entry
474
448
        entry.connect("activate", lambda w, d: self._do_search(d),
475
449
                      'forward')
476
450
        self.pack_start(entry, expand=False, fill=False)
477
451
 
478
452
        # Next/previous buttons
479
 
        button = Gtk.Button('_Next')
480
 
        image = Gtk.Image()
481
 
        image.set_from_stock('gtk-go-forward', Gtk.IconSize.BUTTON)
 
453
        button = gtk.Button('_Next')
 
454
        image = gtk.Image()
 
455
        image.set_from_stock('gtk-go-forward', gtk.ICON_SIZE_BUTTON)
482
456
        button.set_image(image)
483
457
        button.connect("clicked", lambda w, d: self._do_search(d),
484
458
                       'forward')
485
459
        self.pack_start(button, expand=False, fill=False)
486
460
 
487
 
        button = Gtk.Button('_Previous')
488
 
        image = Gtk.Image()
489
 
        image.set_from_stock('gtk-go-back', Gtk.IconSize.BUTTON)
 
461
        button = gtk.Button('_Previous')
 
462
        image = gtk.Image()
 
463
        image.set_from_stock('gtk-go-back', gtk.ICON_SIZE_BUTTON)
490
464
        button.set_image(image)
491
465
        button.connect("clicked", lambda w, d: self._do_search(d),
492
466
                       'backward')
493
467
        self.pack_start(button, expand=False, fill=False)
494
468
 
495
469
        # Search options
496
 
        check = Gtk.CheckButton('Match case')
 
470
        check = gtk.CheckButton('Match case')
497
471
        self._match_case = check
498
472
        self.pack_start(check, expand=False, fill=False)
499
473
 
500
 
        check = Gtk.CheckButton('Regexp')
 
474
        check = gtk.CheckButton('Regexp')
501
475
        check.connect("toggled", lambda w: self._set_label())
502
476
        self._regexp = check
503
477
        self.pack_start(check, expand=False, fill=False)
533
507
 
534
508
    def _match(self, model, iterator, column):
535
509
        matching_case = self._match_case.get_active()
536
 
        cell_value, = model.get(iterator, column)
 
510
        string, = model.get(iterator, column)
537
511
        key = self._entry.get_text()
538
 
        if column == LINE_NUM_COL:
539
 
            # FIXME: For goto-line there are faster algorithms than searching 
540
 
            # every line til we find the right one! -- mbp 2011-01-27
541
 
            return key.strip() == str(cell_value)
542
 
        elif self._regexp.get_active():
 
512
        if self._regexp.get_active():
543
513
            if matching_case:
544
 
                match = re.compile(key).search(cell_value, 1)
 
514
                match = re.compile(key).search(string, 1)
545
515
            else:
546
 
                match = re.compile(key, re.I).search(cell_value, 1)
 
516
                match = re.compile(key, re.I).search(string, 1)
547
517
        else:
548
518
            if not matching_case:
549
 
                cell_value = cell_value.lower()
 
519
                string = string.lower()
550
520
                key = key.lower()
551
 
            match = cell_value.find(key) != -1
 
521
            match = string.find(key) != -1
552
522
 
553
523
        return match
554
524