/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: Curtis Hovey
  • Date: 2011-08-12 20:25:28 UTC
  • mto: This revision was merged to the branch mainline in revision 741.
  • Revision ID: sinzui.is@verizon.net-20110812202528-4xf4a2t23urx50d2
Updated gst to gtk3.

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