/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: Martin Albisetti
  • Date: 2008-06-20 03:33:17 UTC
  • mfrom: (498.1.6 gtk)
  • Revision ID: argentina@gmail.com-20080620033317-14hiurwurhhb98ze
(Elliot Murphy) Fix dbus importing

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