/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 revisionview.py

  • Committer: Aaron Bentley
  • Date: 2008-05-22 02:33:40 UTC
  • mfrom: (487.2.8 save-patch)
  • Revision ID: aaron@aaronbentley.com-20080522023340-3x585s8593j6a409
Allow saving from patch window, refactoring, testing.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright (C) 2005 Dan Loda <danloda@gmail.com>
 
2
# Copyright (C) 2007 Jelmer Vernooij <jelmer@samba.org>
2
3
 
3
4
# This program is free software; you can redistribute it and/or modify
4
5
# it under the terms of the GNU General Public License as published by
18
19
pygtk.require("2.0")
19
20
import gtk
20
21
import pango
 
22
import gobject
 
23
import subprocess
21
24
 
 
25
from bzrlib.plugins.gtk import icon_path
22
26
from bzrlib.osutils import format_date
23
 
 
24
 
 
25
 
class LogView(gtk.ScrolledWindow):
 
27
from bzrlib.util.bencode import bdecode
 
28
 
 
29
try:
 
30
    from bzrlib.plugins.gtk import seahorse
 
31
except ImportError:
 
32
    has_seahorse = False
 
33
else:
 
34
    has_seahorse = True
 
35
 
 
36
PAGE_GENERAL = 0
 
37
PAGE_RELATIONS = 1
 
38
PAGE_SIGNATURE = 2
 
39
PAGE_BUGS = 3
 
40
 
 
41
def _open_link(widget, uri):
 
42
    subprocess.Popen(['sensible-browser', uri], close_fds=True)
 
43
 
 
44
gtk.link_button_set_uri_hook(_open_link)
 
45
 
 
46
class BugsTab(gtk.VBox):
 
47
 
 
48
    def __init__(self):
 
49
        super(BugsTab, self).__init__(False, 6)
 
50
    
 
51
        table = gtk.Table(rows=2, columns=2)
 
52
 
 
53
        table.set_row_spacings(6)
 
54
        table.set_col_spacing(0, 16)
 
55
 
 
56
        image = gtk.Image()
 
57
        image.set_from_file(icon_path("bug.png"))
 
58
        table.attach(image, 0, 1, 0, 1, gtk.FILL)
 
59
 
 
60
        align = gtk.Alignment(0.0, 0.1)
 
61
        self.label = gtk.Label()
 
62
        align.add(self.label)
 
63
        table.attach(align, 1, 2, 0, 1, gtk.FILL)
 
64
 
 
65
        treeview = self.construct_treeview()
 
66
        table.attach(treeview, 1, 2, 1, 2, gtk.FILL | gtk.EXPAND)
 
67
 
 
68
        self.set_border_width(6)
 
69
        self.pack_start(table, expand=False)
 
70
 
 
71
        self.clear()
 
72
        self.show_all()
 
73
 
 
74
    def set_revision(self, revision):
 
75
        if revision is None:
 
76
            return
 
77
 
 
78
        self.clear()
 
79
        bugs_text = revision.properties.get('bugs', '')
 
80
        for bugline in bugs_text.splitlines():
 
81
                (url, status) = bugline.split(" ")
 
82
                if status == "fixed":
 
83
                    self.add_bug(url, status)
 
84
        
 
85
        if self.num_bugs == 0:
 
86
            return
 
87
        elif self.num_bugs == 1:
 
88
            label = "bug"
 
89
        else:
 
90
            label = "bugs"
 
91
 
 
92
        self.label.set_markup("<b>Bugs fixed</b>\n" +
 
93
                              "This revision claims to fix " +
 
94
                              "%d %s." % (self.num_bugs, label))
 
95
 
 
96
    def construct_treeview(self):
 
97
        self.bugs = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
 
98
        self.treeview = gtk.TreeView(self.bugs)
 
99
        self.treeview.set_headers_visible(False)
 
100
 
 
101
        uri_column = gtk.TreeViewColumn('Bug URI', gtk.CellRendererText(), text=0)
 
102
        self.treeview.append_column(uri_column)
 
103
 
 
104
        self.treeview.connect('row-activated', self.on_row_activated)
 
105
 
 
106
        win = gtk.ScrolledWindow()
 
107
        win.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
108
        win.set_shadow_type(gtk.SHADOW_IN)
 
109
        win.add(self.treeview)
 
110
 
 
111
        return win
 
112
 
 
113
    def clear(self):
 
114
        self.num_bugs = 0
 
115
        self.bugs.clear()
 
116
        self.set_sensitive(False)
 
117
        self.label.set_markup("<b>No bugs fixed</b>\n" +
 
118
                              "This revision does not claim to fix any bugs.")
 
119
 
 
120
    def add_bug(self, url, status):
 
121
        self.num_bugs += 1
 
122
        self.bugs.append([url, status])
 
123
        self.set_sensitive(True)
 
124
 
 
125
    def get_num_bugs(self):
 
126
        return self.num_bugs
 
127
 
 
128
    def on_row_activated(self, treeview, path, column):
 
129
        uri = self.bugs.get_value(self.bugs.get_iter(path), 0)
 
130
        _open_link(self, uri)
 
131
 
 
132
 
 
133
class SignatureTab(gtk.VBox):
 
134
 
 
135
    def __init__(self, repository):
 
136
        self.key = None
 
137
        self.revision = None
 
138
        self.repository = repository
 
139
 
 
140
        super(SignatureTab, self).__init__(False, 6)
 
141
        signature_box = gtk.Table(rows=3, columns=3)
 
142
        signature_box.set_col_spacing(0, 16)
 
143
        signature_box.set_col_spacing(1, 12)
 
144
        signature_box.set_row_spacings(6)
 
145
 
 
146
        self.signature_image = gtk.Image()
 
147
        signature_box.attach(self.signature_image, 0, 1, 0, 1, gtk.FILL)
 
148
 
 
149
        align = gtk.Alignment(0.0, 0.1)
 
150
        self.signature_label = gtk.Label()
 
151
        align.add(self.signature_label)
 
152
        signature_box.attach(align, 1, 3, 0, 1, gtk.FILL)
 
153
 
 
154
        align = gtk.Alignment(0.0, 0.5)
 
155
        self.signature_key_id_label = gtk.Label()
 
156
        self.signature_key_id_label.set_markup("<b>Key Id:</b>")
 
157
        align.add(self.signature_key_id_label)
 
158
        signature_box.attach(align, 1, 2, 1, 2, gtk.FILL, gtk.FILL)
 
159
 
 
160
        align = gtk.Alignment(0.0, 0.5)
 
161
        self.signature_key_id = gtk.Label()
 
162
        self.signature_key_id.set_selectable(True)
 
163
        align.add(self.signature_key_id)
 
164
        signature_box.attach(align, 2, 3, 1, 2, gtk.EXPAND | gtk.FILL, gtk.FILL)
 
165
 
 
166
        align = gtk.Alignment(0.0, 0.5)
 
167
        self.signature_fingerprint_label = gtk.Label()
 
168
        self.signature_fingerprint_label.set_markup("<b>Fingerprint:</b>")
 
169
        align.add(self.signature_fingerprint_label)
 
170
        signature_box.attach(align, 1, 2, 2, 3, gtk.FILL, gtk.FILL)
 
171
 
 
172
        align = gtk.Alignment(0.0, 0.5)
 
173
        self.signature_fingerprint = gtk.Label()
 
174
        self.signature_fingerprint.set_selectable(True)
 
175
        align.add(self.signature_fingerprint)
 
176
        signature_box.attach(align, 2, 3, 2, 3, gtk.EXPAND | gtk.FILL, gtk.FILL)
 
177
 
 
178
        align = gtk.Alignment(0.0, 0.5)
 
179
        self.signature_trust_label = gtk.Label()
 
180
        self.signature_trust_label.set_markup("<b>Trust:</b>")
 
181
        align.add(self.signature_trust_label)
 
182
        signature_box.attach(align, 1, 2, 3, 4, gtk.FILL, gtk.FILL)
 
183
 
 
184
        align = gtk.Alignment(0.0, 0.5)
 
185
        self.signature_trust = gtk.Label()
 
186
        self.signature_trust.set_selectable(True)
 
187
        align.add(self.signature_trust)
 
188
        signature_box.attach(align, 2, 3, 3, 4, gtk.EXPAND | gtk.FILL, gtk.FILL)
 
189
 
 
190
        self.set_border_width(6)
 
191
        self.pack_start(signature_box, expand=False)
 
192
        self.show_all()
 
193
 
 
194
    def set_revision(self, revision):
 
195
        self.revision = revision
 
196
        revid = revision.revision_id
 
197
 
 
198
        if self.repository.has_signature_for_revision_id(revid):
 
199
            crypttext = self.repository.get_signature_text(revid)
 
200
            self.show_signature(crypttext)
 
201
        else:
 
202
            self.show_no_signature()
 
203
 
 
204
    def show_no_signature(self):
 
205
        self.signature_key_id_label.hide()
 
206
        self.signature_key_id.set_text("")
 
207
 
 
208
        self.signature_fingerprint_label.hide()
 
209
        self.signature_fingerprint.set_text("")
 
210
 
 
211
        self.signature_trust_label.hide()
 
212
        self.signature_trust.set_text("")
 
213
 
 
214
        self.signature_image.set_from_file(icon_path("sign-unknown.png"))
 
215
        self.signature_label.set_markup("<b>Authenticity unknown</b>\n" +
 
216
                                        "This revision has not been signed.")
 
217
 
 
218
    def show_signature(self, crypttext):
 
219
        key = seahorse.verify(crypttext)
 
220
 
 
221
        if key and key.is_available():
 
222
            if key.is_trusted():
 
223
                if key.get_display_name() == self.revision.committer:
 
224
                    self.signature_image.set_from_file(icon_path("sign-ok.png"))
 
225
                    self.signature_label.set_markup("<b>Authenticity confirmed</b>\n" +
 
226
                                                    "This revision has been signed with " +
 
227
                                                    "a trusted key.")
 
228
                else:
 
229
                    self.signature_image.set_from_file(icon_path("sign-bad.png"))
 
230
                    self.signature_label.set_markup("<b>Authenticity cannot be confirmed</b>\n" +
 
231
                                                    "Revision committer is not the same as signer.")
 
232
            else:
 
233
                self.signature_image.set_from_file(icon_path("sign-bad.png"))
 
234
                self.signature_label.set_markup("<b>Authenticity cannot be confirmed</b>\n" +
 
235
                                                "This revision has been signed, but the " +
 
236
                                                "key is not trusted.")
 
237
        else:
 
238
            self.show_no_signature()
 
239
            self.signature_image.set_from_file(icon_path("sign-bad.png"))
 
240
            self.signature_label.set_markup("<b>Authenticity cannot be confirmed</b>\n" +
 
241
                                            "Signature key not available.")
 
242
            return
 
243
 
 
244
        trust = key.get_trust()
 
245
 
 
246
        if trust <= seahorse.TRUST_NEVER:
 
247
            trust_text = 'never trusted'
 
248
        elif trust == seahorse.TRUST_UNKNOWN:
 
249
            trust_text = 'not trusted'
 
250
        elif trust == seahorse.TRUST_MARGINAL:
 
251
            trust_text = 'marginally trusted'
 
252
        elif trust == seahorse.TRUST_FULL:
 
253
            trust_text = 'fully trusted'
 
254
        elif trust == seahorse.TRUST_ULTIMATE:
 
255
            trust_text = 'ultimately trusted'
 
256
 
 
257
        self.signature_key_id_label.show()
 
258
        self.signature_key_id.set_text(key.get_id())
 
259
 
 
260
        fingerprint = key.get_fingerprint()
 
261
        if fingerprint == "":
 
262
            fingerprint = '<span foreground="dim grey">N/A</span>'
 
263
 
 
264
        self.signature_fingerprint_label.show()
 
265
        self.signature_fingerprint.set_markup(fingerprint)
 
266
 
 
267
        self.signature_trust_label.show()
 
268
        self.signature_trust.set_text('This key is ' + trust_text)
 
269
 
 
270
 
 
271
class RevisionView(gtk.Notebook):
26
272
    """ Custom widget for commit log details.
27
273
 
28
274
    A variety of bzr tools may need to implement such a thing. This is a
29
275
    start.
30
276
    """
31
277
 
32
 
    def __init__(self, revision=None):
33
 
        gtk.ScrolledWindow.__init__(self)
34
 
        self.parent_id_widgets = []
35
 
        self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
36
 
        self.set_shadow_type(gtk.SHADOW_NONE)
37
 
        self._create()
38
 
 
39
 
        if revision is not None:
40
 
            self.set_revision(revision)
 
278
    __gproperties__ = {
 
279
        'branch': (
 
280
            gobject.TYPE_PYOBJECT,
 
281
            'Branch',
 
282
            'The branch holding the revision being displayed',
 
283
            gobject.PARAM_CONSTRUCT_ONLY | gobject.PARAM_WRITABLE
 
284
        ),
 
285
 
 
286
        'revision': (
 
287
            gobject.TYPE_PYOBJECT,
 
288
            'Revision',
 
289
            'The revision being displayed',
 
290
            gobject.PARAM_READWRITE
 
291
        ),
 
292
 
 
293
        'children': (
 
294
            gobject.TYPE_PYOBJECT,
 
295
            'Children',
 
296
            'Child revisions',
 
297
            gobject.PARAM_READWRITE
 
298
        ),
 
299
 
 
300
        'file-id': (
 
301
            gobject.TYPE_PYOBJECT,
 
302
            'File Id',
 
303
            'The file id',
 
304
            gobject.PARAM_READWRITE
 
305
        )
 
306
    }
 
307
 
 
308
    def __init__(self, branch=None, repository=None):
 
309
        gtk.Notebook.__init__(self)
 
310
 
 
311
        self._revision = None
 
312
        self._branch = branch
 
313
        if branch is not None:
 
314
            self._repository = branch.repository
 
315
        else:
 
316
            self._repository = repository
 
317
 
 
318
        self._create_general()
 
319
        self._create_relations()
 
320
        # Disabled because testaments aren't verified yet:
 
321
        # if has_seahorse:
 
322
        #    self._create_signature()
 
323
        self._create_file_info_view()
 
324
        self._create_bugs()
 
325
 
 
326
        self.set_current_page(PAGE_GENERAL)
 
327
        self.connect_after('switch-page', self._switch_page_cb)
 
328
        
 
329
        self._show_callback = None
 
330
        self._clicked_callback = None
 
331
 
 
332
        self._revision = None
 
333
        self._branch = branch
 
334
 
 
335
        self.update_tags()
 
336
 
 
337
        self.set_file_id(None)
 
338
 
 
339
    def do_get_property(self, property):
 
340
        if property.name == 'branch':
 
341
            return self._branch
 
342
        elif property.name == 'revision':
 
343
            return self._revision
 
344
        elif property.name == 'children':
 
345
            return self._children
 
346
        elif property.name == 'file-id':
 
347
            return self._file_id
 
348
        else:
 
349
            raise AttributeError, 'unknown property %s' % property.name
 
350
 
 
351
    def do_set_property(self, property, value):
 
352
        if property.name == 'branch':
 
353
            self._branch = value
 
354
        elif property.name == 'revision':
 
355
            self._set_revision(value)
 
356
        elif property.name == 'children':
 
357
            self.set_children(value)
 
358
        elif property.name == 'file-id':
 
359
            self._file_id = value
 
360
        else:
 
361
            raise AttributeError, 'unknown property %s' % property.name
 
362
 
 
363
    def set_show_callback(self, callback):
 
364
        self._show_callback = callback
 
365
 
 
366
    def set_file_id(self, file_id):
 
367
        """Set a specific file id that we want to track.
 
368
 
 
369
        This just effects the display of a per-file commit message.
 
370
        If it is set to None, then all commit messages will be shown.
 
371
        """
 
372
        self.set_property('file-id', file_id)
41
373
 
42
374
    def set_revision(self, revision):
43
 
        self.revision_id.set_text(revision.revision_id)
44
 
        self.committer.set_text(revision.committer)
45
 
        self.timestamp.set_text(format_date(revision.timestamp,
46
 
                                            revision.timezone))
47
 
        self.message_buffer.set_text(revision.message)
48
 
        self._add_parents(revision.parent_ids)
49
 
 
50
 
    def _add_parents(self, parent_ids):
51
 
        for widget in self.parent_id_widgets:
52
 
            self.table.remove(widget)
 
375
        if revision != self._revision:
 
376
            self.set_property('revision', revision)
 
377
 
 
378
    def get_revision(self):
 
379
        return self.get_property('revision')
 
380
 
 
381
    def _set_revision(self, revision):
 
382
        if revision is None: return
 
383
 
 
384
        self._revision = revision
 
385
        if revision.committer is not None:
 
386
            self.committer.set_text(revision.committer)
 
387
        else:
 
388
            self.committer.set_text("")
 
389
        author = revision.properties.get('author', '')
 
390
        if author != '':
 
391
            self.author.set_text(author)
 
392
            self.author.show()
 
393
            self.author_label.show()
 
394
        else:
 
395
            self.author.hide()
 
396
            self.author_label.hide()
 
397
 
 
398
        if revision.timestamp is not None:
 
399
            self.timestamp.set_text(format_date(revision.timestamp,
 
400
                                                revision.timezone))
 
401
        try:
 
402
            self.branchnick_label.set_text(revision.properties['branch-nick'])
 
403
        except KeyError:
 
404
            self.branchnick_label.set_text("")
 
405
 
 
406
        self._add_parents_or_children(revision.parent_ids,
 
407
                                      self.parents_widgets,
 
408
                                      self.parents_table)
 
409
        
 
410
        file_info = revision.properties.get('file-info', None)
 
411
        if file_info is not None:
 
412
            file_info = bdecode(file_info.encode('UTF-8'))
 
413
 
 
414
        if file_info:
 
415
            if self._file_id is None:
 
416
                text = []
 
417
                for fi in file_info:
 
418
                    text.append('%(path)s\n%(message)s' % fi)
 
419
                self.file_info_buffer.set_text('\n'.join(text))
 
420
                self.file_info_box.show()
 
421
            else:
 
422
                text = []
 
423
                for fi in file_info:
 
424
                    if fi['file_id'] == self._file_id:
 
425
                        text.append(fi['message'])
 
426
                if text:
 
427
                    self.file_info_buffer.set_text('\n'.join(text))
 
428
                    self.file_info_box.show()
 
429
                else:
 
430
                    self.file_info_box.hide()
 
431
        else:
 
432
            self.file_info_box.hide()
 
433
 
 
434
    def update_tags(self):
 
435
        if self._branch is not None and self._branch.supports_tags():
 
436
            self._tagdict = self._branch.tags.get_reverse_tag_dict()
 
437
        else:
 
438
            self._tagdict = {}
 
439
 
 
440
        self._add_tags()
 
441
 
 
442
    def _update_signature(self, widget, param):
 
443
        if self.get_current_page() == PAGE_SIGNATURE:
 
444
            self.signature_table.set_revision(self._revision)
 
445
 
 
446
    def _update_bugs(self, widget, param):
 
447
        self.bugs_page.set_revision(self._revision)
 
448
        label = self.get_tab_label(self.bugs_page)
 
449
        label.set_sensitive(self.bugs_page.get_num_bugs() != 0)
 
450
 
 
451
    def set_children(self, children):
 
452
        self._add_parents_or_children(children,
 
453
                                      self.children_widgets,
 
454
                                      self.children_table)
 
455
 
 
456
    def _switch_page_cb(self, notebook, page, page_num):
 
457
        if page_num == PAGE_SIGNATURE:
 
458
            self.signature_table.set_revision(self._revision)
 
459
 
 
460
 
 
461
 
 
462
    def _show_clicked_cb(self, widget, revid, parentid):
 
463
        """Callback for when the show button for a parent is clicked."""
 
464
        self._show_callback(revid, parentid)
 
465
 
 
466
    def _go_clicked_cb(self, widget, revid):
 
467
        """Callback for when the go button for a parent is clicked."""
 
468
 
 
469
    def _add_tags(self, *args):
 
470
        if self._revision is None:
 
471
            return
 
472
 
 
473
        if self._tagdict.has_key(self._revision.revision_id):
 
474
            tags = self._tagdict[self._revision.revision_id]
 
475
        else:
 
476
            tags = []
53
477
            
54
 
        self.parent_id_widgets = []
55
 
 
56
 
        if len(parent_ids):
57
 
            self.table.resize(4 + len(parent_ids), 2)
58
 
 
59
 
            align = gtk.Alignment(1.0, 0.5)
 
478
        if tags == []:
 
479
            self.tags_list.hide()
 
480
            self.tags_label.hide()
 
481
            return
 
482
 
 
483
        self.tags_list.set_text(", ".join(tags))
 
484
 
 
485
        self.tags_list.show_all()
 
486
        self.tags_label.show_all()
 
487
        
 
488
    def _add_parents_or_children(self, revids, widgets, table):
 
489
        while len(widgets) > 0:
 
490
            widget = widgets.pop()
 
491
            table.remove(widget)
 
492
        
 
493
        table.resize(max(len(revids), 1), 2)
 
494
 
 
495
        for idx, revid in enumerate(revids):
 
496
            align = gtk.Alignment(0.0, 0.0)
 
497
            widgets.append(align)
 
498
            table.attach(align, 1, 2, idx, idx + 1,
 
499
                                      gtk.EXPAND | gtk.FILL, gtk.FILL)
60
500
            align.show()
61
 
            self.table.attach(align, 0, 1, 3, 4, gtk.FILL, gtk.FILL)
62
 
            self.parent_id_widgets.append(align)
63
 
 
64
 
            label = gtk.Label()
65
 
            if len(parent_ids) > 1:
66
 
                label.set_markup("<b>Parent Ids:</b>")
67
 
            else:
68
 
                label.set_markup("<b>Parent Id:</b>")
69
 
            label.show()
70
 
            align.add(label)
71
 
 
72
 
            for i, parent_id in enumerate(parent_ids):
73
 
                align = gtk.Alignment(0.0, 0.5)
74
 
                self.parent_id_widgets.append(align)
75
 
                self.table.attach(align, 1, 2, i + 3, i + 4,
76
 
                                  gtk.EXPAND | gtk.FILL, gtk.FILL)
77
 
                align.show()
78
 
                label = gtk.Label(parent_id)
79
 
                label.set_selectable(True)
80
 
                label.show()
81
 
                align.add(label)
82
 
 
83
 
    def _create(self):
 
501
 
 
502
            hbox = gtk.HBox(False, spacing=6)
 
503
            align.add(hbox)
 
504
            hbox.show()
 
505
 
 
506
            image = gtk.Image()
 
507
            image.set_from_stock(
 
508
                gtk.STOCK_FIND, gtk.ICON_SIZE_SMALL_TOOLBAR)
 
509
            image.show()
 
510
 
 
511
            if self._show_callback is not None:
 
512
                button = gtk.Button()
 
513
                button.add(image)
 
514
                button.connect("clicked", self._show_clicked_cb,
 
515
                               self._revision.revision_id, revid)
 
516
                hbox.pack_start(button, expand=False, fill=True)
 
517
                button.show()
 
518
 
 
519
            button = gtk.Button(revid)
 
520
            button.connect("clicked",
 
521
                    lambda w, r: self.set_revision(self._repository.get_revision(r)), revid)
 
522
            button.set_use_underline(False)
 
523
            hbox.pack_start(button, expand=False, fill=True)
 
524
            button.show()
 
525
 
 
526
    def _create_general(self):
84
527
        vbox = gtk.VBox(False, 6)
85
528
        vbox.set_border_width(6)
86
529
        vbox.pack_start(self._create_headers(), expand=False, fill=True)
87
530
        vbox.pack_start(self._create_message_view())
88
 
        self.add_with_viewport(vbox)
89
 
        vbox.show()
 
531
        self.append_page(vbox, tab_label=gtk.Label("General"))
 
532
        vbox.show()
 
533
 
 
534
    def _create_relations(self):
 
535
        vbox = gtk.VBox(False, 6)
 
536
        vbox.set_border_width(6)
 
537
        vbox.pack_start(self._create_parents(), expand=False, fill=True)
 
538
        vbox.pack_start(self._create_children(), expand=False, fill=True)
 
539
        self.append_page(vbox, tab_label=gtk.Label("Relations"))
 
540
        vbox.show()
 
541
 
 
542
    def _create_signature(self):
 
543
        self.signature_table = SignatureTab(self._repository)
 
544
        self.append_page(self.signature_table, tab_label=gtk.Label('Signature'))
 
545
        self.connect_after('notify::revision', self._update_signature)
90
546
 
91
547
    def _create_headers(self):
92
 
        self.table = gtk.Table(rows=4, columns=2)
 
548
        self.table = gtk.Table(rows=5, columns=2)
93
549
        self.table.set_row_spacings(6)
94
550
        self.table.set_col_spacings(6)
95
551
        self.table.show()
96
 
        
 
552
 
 
553
        align = gtk.Alignment(1.0, 0.5)
 
554
        label = gtk.Label()
 
555
        label.set_markup("<b>Revision Id:</b>")
 
556
        align.add(label)
 
557
        self.table.attach(align, 0, 1, 0, 1, gtk.FILL, gtk.FILL)
 
558
        align.show()
 
559
        label.show()
 
560
 
 
561
        align = gtk.Alignment(0.0, 0.5)
 
562
        revision_id = gtk.Label()
 
563
        revision_id.set_selectable(True)
 
564
        self.connect('notify::revision', 
 
565
                lambda w, p: revision_id.set_text(self._revision.revision_id))
 
566
        align.add(revision_id)
 
567
        self.table.attach(align, 1, 2, 0, 1, gtk.EXPAND | gtk.FILL, gtk.FILL)
 
568
        align.show()
 
569
        revision_id.show()
 
570
 
 
571
        align = gtk.Alignment(1.0, 0.5)
 
572
        self.author_label = gtk.Label()
 
573
        self.author_label.set_markup("<b>Author:</b>")
 
574
        align.add(self.author_label)
 
575
        self.table.attach(align, 0, 1, 1, 2, gtk.FILL, gtk.FILL)
 
576
        align.show()
 
577
        self.author_label.show()
 
578
 
 
579
        align = gtk.Alignment(0.0, 0.5)
 
580
        self.author = gtk.Label()
 
581
        self.author.set_selectable(True)
 
582
        align.add(self.author)
 
583
        self.table.attach(align, 1, 2, 1, 2, gtk.EXPAND | gtk.FILL, gtk.FILL)
 
584
        align.show()
 
585
        self.author.show()
 
586
        self.author.hide()
 
587
 
97
588
        align = gtk.Alignment(1.0, 0.5)
98
589
        label = gtk.Label()
99
590
        label.set_markup("<b>Committer:</b>")
100
591
        align.add(label)
101
 
        self.table.attach(align, 0, 1, 0, 1, gtk.FILL, gtk.FILL)
 
592
        self.table.attach(align, 0, 1, 2, 3, gtk.FILL, gtk.FILL)
102
593
        align.show()
103
594
        label.show()
104
595
 
106
597
        self.committer = gtk.Label()
107
598
        self.committer.set_selectable(True)
108
599
        align.add(self.committer)
109
 
        self.table.attach(align, 1, 2, 0, 1, gtk.EXPAND | gtk.FILL, gtk.FILL)
 
600
        self.table.attach(align, 1, 2, 2, 3, gtk.EXPAND | gtk.FILL, gtk.FILL)
110
601
        align.show()
111
602
        self.committer.show()
112
603
 
 
604
        align = gtk.Alignment(0.0, 0.5)
 
605
        label = gtk.Label()
 
606
        label.set_markup("<b>Branch nick:</b>")
 
607
        align.add(label)
 
608
        self.table.attach(align, 0, 1, 3, 4, gtk.FILL, gtk.FILL)
 
609
        label.show()
 
610
        align.show()
 
611
 
 
612
        align = gtk.Alignment(0.0, 0.5)
 
613
        self.branchnick_label = gtk.Label()
 
614
        self.branchnick_label.set_selectable(True)
 
615
        align.add(self.branchnick_label)
 
616
        self.table.attach(align, 1, 2, 3, 4, gtk.EXPAND | gtk.FILL, gtk.FILL)
 
617
        self.branchnick_label.show()
 
618
        align.show()
 
619
 
113
620
        align = gtk.Alignment(1.0, 0.5)
114
621
        label = gtk.Label()
115
622
        label.set_markup("<b>Timestamp:</b>")
116
623
        align.add(label)
117
 
        self.table.attach(align, 0, 1, 1, 2, gtk.FILL, gtk.FILL)
 
624
        self.table.attach(align, 0, 1, 4, 5, gtk.FILL, gtk.FILL)
118
625
        align.show()
119
626
        label.show()
120
627
 
122
629
        self.timestamp = gtk.Label()
123
630
        self.timestamp.set_selectable(True)
124
631
        align.add(self.timestamp)
125
 
        self.table.attach(align, 1, 2, 1, 2, gtk.EXPAND | gtk.FILL, gtk.FILL)
 
632
        self.table.attach(align, 1, 2, 4, 5, gtk.EXPAND | gtk.FILL, gtk.FILL)
126
633
        align.show()
127
634
        self.timestamp.show()
128
635
 
129
636
        align = gtk.Alignment(1.0, 0.5)
130
 
        label = gtk.Label()
131
 
        label.set_markup("<b>Revision Id:</b>")
132
 
        align.add(label)
133
 
        self.table.attach(align, 0, 1, 2, 3, gtk.FILL, gtk.FILL)
 
637
        self.tags_label = gtk.Label()
 
638
        self.tags_label.set_markup("<b>Tags:</b>")
 
639
        align.add(self.tags_label)
134
640
        align.show()
135
 
        label.show()
 
641
        self.table.attach(align, 0, 1, 5, 6, gtk.FILL, gtk.FILL)
 
642
        self.tags_label.show()
136
643
 
137
644
        align = gtk.Alignment(0.0, 0.5)
138
 
        self.revision_id = gtk.Label()
139
 
        self.revision_id.set_selectable(True)
140
 
        align.add(self.revision_id)
141
 
        self.table.attach(align, 1, 2, 2, 3, gtk.EXPAND | gtk.FILL, gtk.FILL)
 
645
        self.tags_list = gtk.Label()
 
646
        align.add(self.tags_list)
 
647
        self.table.attach(align, 1, 2, 5, 6, gtk.EXPAND | gtk.FILL, gtk.FILL)
142
648
        align.show()
143
 
        self.revision_id.show()
 
649
        self.tags_list.show()
 
650
 
 
651
        self.connect('notify::revision', self._add_tags)
144
652
 
145
653
        return self.table
 
654
    
 
655
    def _create_parents(self):
 
656
        hbox = gtk.HBox(True, 3)
 
657
        
 
658
        self.parents_table = self._create_parents_or_children_table(
 
659
            "<b>Parents:</b>")
 
660
        self.parents_widgets = []
 
661
        hbox.pack_start(self.parents_table)
 
662
 
 
663
        hbox.show()
 
664
        return hbox
 
665
 
 
666
    def _create_children(self):
 
667
        hbox = gtk.HBox(True, 3)
 
668
        self.children_table = self._create_parents_or_children_table(
 
669
            "<b>Children:</b>")
 
670
        self.children_widgets = []
 
671
        hbox.pack_start(self.children_table)
 
672
        hbox.show()
 
673
        return hbox
 
674
        
 
675
    def _create_parents_or_children_table(self, text):
 
676
        table = gtk.Table(rows=1, columns=2)
 
677
        table.set_row_spacings(3)
 
678
        table.set_col_spacings(6)
 
679
        table.show()
 
680
 
 
681
        label = gtk.Label()
 
682
        label.set_markup(text)
 
683
        align = gtk.Alignment(0.0, 0.5)
 
684
        align.add(label)
 
685
        table.attach(align, 0, 1, 0, 1, gtk.FILL, gtk.FILL)
 
686
        label.show()
 
687
        align.show()
 
688
 
 
689
        return table
146
690
 
147
691
    def _create_message_view(self):
148
 
        self.message_buffer = gtk.TextBuffer()
149
 
        tv = gtk.TextView(self.message_buffer)
150
 
        tv.set_editable(False)
151
 
        tv.set_wrap_mode(gtk.WRAP_WORD)
152
 
        tv.modify_font(pango.FontDescription("Monospace"))
153
 
        tv.show()
154
 
        return tv
 
692
        msg_buffer = gtk.TextBuffer()
 
693
        self.connect('notify::revision',
 
694
                lambda w, p: msg_buffer.set_text(self._revision.message))
 
695
        window = gtk.ScrolledWindow()
 
696
        window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
 
697
        window.set_shadow_type(gtk.SHADOW_IN)
 
698
        tv = gtk.TextView(msg_buffer)
 
699
        tv.set_editable(False)
 
700
        tv.set_wrap_mode(gtk.WRAP_WORD)
 
701
 
 
702
        tv.modify_font(pango.FontDescription("Monospace"))
 
703
        tv.show()
 
704
        window.add(tv)
 
705
        window.show()
 
706
        return window
 
707
 
 
708
    def _create_bugs(self):
 
709
        self.bugs_page = BugsTab()
 
710
        self.connect_after('notify::revision', self._update_bugs) 
 
711
        self.append_page(self.bugs_page, tab_label=gtk.Label('Bugs'))
 
712
 
 
713
    def _create_file_info_view(self):
 
714
        self.file_info_box = gtk.VBox(False, 6)
 
715
        self.file_info_box.set_border_width(6)
 
716
        self.file_info_buffer = gtk.TextBuffer()
 
717
        window = gtk.ScrolledWindow()
 
718
        window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
 
719
        window.set_shadow_type(gtk.SHADOW_IN)
 
720
        tv = gtk.TextView(self.file_info_buffer)
 
721
        tv.set_editable(False)
 
722
        tv.set_wrap_mode(gtk.WRAP_WORD)
 
723
        tv.modify_font(pango.FontDescription("Monospace"))
 
724
        tv.show()
 
725
        window.add(tv)
 
726
        window.show()
 
727
        self.file_info_box.pack_start(window)
 
728
        self.file_info_box.hide() # Only shown when there are per-file messages
 
729
        self.append_page(self.file_info_box, tab_label=gtk.Label('Per-file'))
155
730