/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: Jelmer Vernooij
  • Date: 2011-04-10 18:44:39 UTC
  • mto: This revision was merged to the branch mainline in revision 730.
  • Revision ID: jelmer@samba.org-20110410184439-g7hqaacexqtviq13
Move i18n support to a separate file, so gettext files aren't loaded unless bzr-gtk is used.

Show diffs side-by-side

added added

removed removed

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