/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: Daniel Schierbeck
  • Date: 2008-04-07 20:34:51 UTC
  • mfrom: (450.6.13 bugs)
  • mto: (463.2.1 bug.78765)
  • mto: This revision was merged to the branch mainline in revision 462.
  • Revision ID: daniel.schierbeck@gmail.com-20080407203451-2i6el7jf9t0k9y64
Merged bug page improvements.

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