/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-09-03 01:25:04 UTC
  • mto: This revision was merged to the branch mainline in revision 741.
  • Revision ID: sinzui.is@verizon.net-20110903012504-0jr4diz9033g5df2
Menu fixes.

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__(homogeneous=False, spacing=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, 0.0, 0.0)
 
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, 0.0, 0.0)
 
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, 0.0, 0.0)
 
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, 0.0, 0.0)
 
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, 0.0, 0.0)
 
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, 0.0, 0.0)
 
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, 0.0, 0.0)
 
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
        Gtk.Notebook.__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
        self.signature_table = None
 
339
 
 
340
        self._create_general()
 
341
        self._create_relations()
 
342
        # Disabled because testaments aren't verified yet:
 
343
        if has_seahorse:
 
344
            self._create_signature()
 
345
        self._create_file_info_view()
 
346
        self._create_bugs()
 
347
 
 
348
        self.set_current_page(PAGE_GENERAL)
 
349
        self.connect_after('switch-page', self._switch_page_cb)
 
350
        
 
351
        self._show_callback = None
 
352
        self._clicked_callback = None
 
353
 
 
354
        self._revision = None
 
355
        self._branch = branch
 
356
 
 
357
        self.update_tags()
 
358
 
 
359
        self.set_file_id(None)
 
360
 
 
361
    def do_get_property(self, property):
 
362
        if property.name == 'branch':
 
363
            return self._branch
 
364
        elif property.name == 'revision':
 
365
            return self._revision
 
366
        elif property.name == 'children':
 
367
            return self._children
 
368
        elif property.name == 'file-id':
 
369
            return self._file_id
 
370
        else:
 
371
            raise AttributeError, 'unknown property %s' % property.name
 
372
 
 
373
    def do_set_property(self, property, value):
 
374
        if property.name == 'branch':
 
375
            self._branch = value
 
376
        elif property.name == 'revision':
 
377
            self._set_revision(value)
 
378
        elif property.name == 'children':
 
379
            self.set_children(value)
 
380
        elif property.name == 'file-id':
 
381
            self._file_id = value
 
382
        else:
 
383
            raise AttributeError, 'unknown property %s' % property.name
 
384
 
 
385
    def set_show_callback(self, callback):
 
386
        self._show_callback = callback
 
387
 
 
388
    def set_file_id(self, file_id):
 
389
        """Set a specific file id that we want to track.
 
390
 
 
391
        This just effects the display of a per-file commit message.
 
392
        If it is set to None, then all commit messages will be shown.
 
393
        """
 
394
        self.set_property('file-id', file_id)
 
395
 
 
396
    def set_revision(self, revision):
 
397
        if revision != self._revision:
 
398
            self.set_property('revision', revision)
 
399
 
 
400
    def get_revision(self):
 
401
        return self.get_property('revision')
 
402
 
 
403
    def _set_revision(self, revision):
 
404
        if revision is None: return
 
405
        
 
406
        self.avatarsbox.reset()
 
407
        
 
408
        self._revision = revision
 
409
        if revision.committer is not None:
 
410
            self.committer.set_text(revision.committer)
 
411
            self.avatarsbox.add(revision.committer, "committer")
 
412
        else:
 
413
            self.committer.set_text("")
 
414
            self.avatarsbox.hide()
 
415
        author = revision.properties.get('author', '')
 
416
        self.avatarsbox.merge(revision.get_apparent_authors(), "author")
 
417
        if author != '':
 
418
            self.author.set_text(author)
 
419
            self.author.show()
 
420
            self.author_label.show()
 
421
        else:
 
422
            self.author.hide()
 
423
            self.author_label.hide()
 
424
 
 
425
        if revision.timestamp is not None:
 
426
            self.timestamp.set_text(format_date(revision.timestamp,
 
427
                                                revision.timezone))
 
428
        try:
 
429
            self.branchnick.show()
 
430
            self.branchnick_label.show()
 
431
            self.branchnick.set_text(revision.properties['branch-nick'])
 
432
        except KeyError:
 
433
            self.branchnick.hide()
 
434
            self.branchnick_label.hide()
 
435
 
 
436
        self._add_parents_or_children(revision.parent_ids,
 
437
                                      self.parents_widgets,
 
438
                                      self.parents_table)
 
439
 
 
440
        file_info = revision.properties.get('file-info', None)
 
441
        if file_info is not None:
 
442
            try:
 
443
                file_info = bdecode(file_info.encode('UTF-8'))
 
444
            except ValueError:
 
445
                trace.note('Invalid per-file info for revision:%s, value: %r',
 
446
                           revision.revision_id, file_info)
 
447
                file_info = None
 
448
 
 
449
        if file_info:
 
450
            if self._file_id is None:
 
451
                text = []
 
452
                for fi in file_info:
 
453
                    text.append('%(path)s\n%(message)s' % fi)
 
454
                self.file_info_buffer.set_text('\n'.join(text))
 
455
                self.file_info_box.show()
 
456
            else:
 
457
                text = []
 
458
                for fi in file_info:
 
459
                    if fi['file_id'] == self._file_id:
 
460
                        text.append(fi['message'])
 
461
                if text:
 
462
                    self.file_info_buffer.set_text('\n'.join(text))
 
463
                    self.file_info_box.show()
 
464
                else:
 
465
                    self.file_info_box.hide()
 
466
        else:
 
467
            self.file_info_box.hide()
 
468
 
 
469
    def update_tags(self):
 
470
        if self._branch is not None and self._branch.supports_tags():
 
471
            self._tagdict = self._branch.tags.get_reverse_tag_dict()
 
472
        else:
 
473
            self._tagdict = {}
 
474
 
 
475
        self._add_tags()
 
476
 
 
477
    def _update_signature(self, widget, param):
 
478
        if not has_seahorse:
 
479
            return
 
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 not has_seahorse:
 
495
            return
 
496
        if page_num == PAGE_SIGNATURE:
 
497
            self.signature_table.set_revision(self._revision)
 
498
 
 
499
 
 
500
 
 
501
    def _show_clicked_cb(self, widget, revid, parentid):
 
502
        """Callback for when the show button for a parent is clicked."""
 
503
        self._show_callback(revid, parentid)
 
504
 
 
505
    def _go_clicked_cb(self, widget, revid):
 
506
        """Callback for when the go button for a parent is clicked."""
 
507
 
 
508
    def _add_tags(self, *args):
 
509
        if self._revision is None:
 
510
            return
 
511
 
 
512
        if self._tagdict.has_key(self._revision.revision_id):
 
513
            tags = self._tagdict[self._revision.revision_id]
 
514
        else:
 
515
            tags = []
 
516
            
 
517
        if tags == []:
 
518
            self.tags_list.hide()
 
519
            self.tags_label.hide()
 
520
            return
 
521
 
 
522
        self.tags_list.set_text(", ".join(tags))
 
523
 
 
524
        self.tags_list.show_all()
 
525
        self.tags_label.show_all()
 
526
        
 
527
    def _add_parents_or_children(self, revids, widgets, table):
 
528
        while len(widgets) > 0:
 
529
            widget = widgets.pop()
 
530
            table.remove(widget)
 
531
        
 
532
        table.resize(max(len(revids), 1), 2)
 
533
 
 
534
        for idx, revid in enumerate(revids):
 
535
            align = Gtk.Alignment.new(0.0, 0.0, 1, 1)
 
536
            widgets.append(align)
 
537
            table.attach(align, 1, 2, idx, idx + 1,
 
538
                                      Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, Gtk.AttachOptions.FILL)
 
539
            align.show()
 
540
 
 
541
            hbox = Gtk.HBox(homogeneous=False, spacing=6)
 
542
            align.add(hbox)
 
543
            hbox.show()
 
544
 
 
545
            image = Gtk.Image()
 
546
            image.set_from_stock(
 
547
                Gtk.STOCK_FIND, Gtk.IconSize.SMALL_TOOLBAR)
 
548
            image.show()
 
549
 
 
550
            if self._show_callback is not None:
 
551
                button = Gtk.Button()
 
552
                button.add(image)
 
553
                button.connect("clicked", self._show_clicked_cb,
 
554
                               self._revision.revision_id, revid)
 
555
                hbox.pack_start(button, False, True, 0)
 
556
                button.show()
 
557
 
 
558
            button = Gtk.Button()
 
559
            revid_label = Gtk.Label(label=str(revid))
 
560
            revid_label.set_ellipsize(Pango.EllipsizeMode.MIDDLE)
 
561
            revid_label.set_alignment(0.0, 0.5)
 
562
            button.add(revid_label)
 
563
            button.connect("clicked",
 
564
                    lambda w, r: self.set_revision(
 
565
                        self._repository.get_revision(r)), revid)
 
566
            button.set_use_underline(False)
 
567
            hbox.pack_start(button, True, True, 0)
 
568
            button.show_all()
 
569
 
 
570
    def _create_general(self):
 
571
        vbox = Gtk.VBox(homogeneous=False, spacing=6)
 
572
        vbox.set_border_width(6)
 
573
        vbox.pack_start(self._create_headers(), False, True, 0)
 
574
        vbox.pack_start(self._create_message_view(), True, True, 0)
 
575
        self.append_page(vbox, Gtk.Label(label="General"))
 
576
        vbox.show()
 
577
 
 
578
    def _create_relations(self):
 
579
        vbox = Gtk.VBox(homogeneous=False, spacing=6)
 
580
        vbox.set_border_width(6)
 
581
        vbox.pack_start(self._create_parents(), False, True, 0)
 
582
        vbox.pack_start(self._create_children(), False, True, 0)
 
583
        self.append_page(vbox, Gtk.Label(label="Relations"))
 
584
        vbox.show()
 
585
 
 
586
    def _create_signature(self):
 
587
        self.signature_table = SignatureTab(self._repository)
 
588
        self.append_page(
 
589
            self.signature_table, Gtk.Label(label='Signature'))
 
590
        self.connect_after('notify::revision', self._update_signature)
 
591
 
 
592
    def _create_headers(self):
 
593
        self.avatarsbox = AvatarsBox()
 
594
        
 
595
        self.table = Gtk.Table(rows=5, columns=2)
 
596
        self.table.set_row_spacings(6)
 
597
        self.table.set_col_spacings(6)
 
598
        self.table.show()
 
599
        
 
600
        self.avatarsbox.pack_start(self.table, True, True, 0)
 
601
        self.avatarsbox.show()
 
602
 
 
603
        row = 0
 
604
 
 
605
        label = Gtk.Label()
 
606
        label.set_alignment(1.0, 0.5)
 
607
        label.set_markup("<b>Revision Id:</b>")
 
608
        self.table.attach(label, 0, 1, row, row+1, Gtk.AttachOptions.FILL, Gtk.AttachOptions.FILL)
 
609
        label.show()
 
610
 
 
611
        revision_id = Gtk.Label()
 
612
        revision_id.set_ellipsize(Pango.EllipsizeMode.MIDDLE)
 
613
        revision_id.set_alignment(0.0, 0.5)
 
614
        revision_id.set_selectable(True)
 
615
        self.connect('notify::revision', 
 
616
                lambda w, p: revision_id.set_text(self._revision.revision_id))
 
617
        self.table.attach(revision_id, 1, 2, row, row+1, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, Gtk.AttachOptions.FILL)
 
618
        revision_id.show()
 
619
 
 
620
        row += 1
 
621
        self.author_label = Gtk.Label()
 
622
        self.author_label.set_alignment(1.0, 0.5)
 
623
        self.author_label.set_markup("<b>Author:</b>")
 
624
        self.table.attach(self.author_label, 0, 1, row, row+1, Gtk.AttachOptions.FILL, Gtk.AttachOptions.FILL)
 
625
        self.author_label.show()
 
626
 
 
627
        self.author = Gtk.Label()
 
628
        self.author.set_ellipsize(Pango.EllipsizeMode.END)
 
629
        self.author.set_alignment(0.0, 0.5)
 
630
        self.author.set_selectable(True)
 
631
        self.table.attach(self.author, 1, 2, row, row+1, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, Gtk.AttachOptions.FILL)
 
632
        self.author.show()
 
633
        self.author.hide()
 
634
 
 
635
        row += 1
 
636
        label = Gtk.Label()
 
637
        label.set_alignment(1.0, 0.5)
 
638
        label.set_markup("<b>Committer:</b>")
 
639
        self.table.attach(label, 0, 1, row, row+1, Gtk.AttachOptions.FILL, Gtk.AttachOptions.FILL)
 
640
        label.show()
 
641
 
 
642
        self.committer = Gtk.Label()
 
643
        self.committer.set_ellipsize(Pango.EllipsizeMode.END)
 
644
        self.committer.set_alignment(0.0, 0.5)
 
645
        self.committer.set_selectable(True)
 
646
        self.table.attach(self.committer, 1, 2, row, row+1, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, Gtk.AttachOptions.FILL)
 
647
        self.committer.show()
 
648
 
 
649
        row += 1
 
650
        self.branchnick_label = Gtk.Label()
 
651
        self.branchnick_label.set_alignment(1.0, 0.5)
 
652
        self.branchnick_label.set_markup("<b>Branch nick:</b>")
 
653
        self.table.attach(self.branchnick_label, 0, 1, row, row+1, Gtk.AttachOptions.FILL, Gtk.AttachOptions.FILL)
 
654
        self.branchnick_label.show()
 
655
 
 
656
        self.branchnick = Gtk.Label()
 
657
        self.branchnick.set_ellipsize(Pango.EllipsizeMode.MIDDLE)
 
658
        self.branchnick.set_alignment(0.0, 0.5)
 
659
        self.branchnick.set_selectable(True)
 
660
        self.table.attach(self.branchnick, 1, 2, row, row+1, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, Gtk.AttachOptions.FILL)
 
661
        self.branchnick.show()
 
662
 
 
663
        row += 1
 
664
        label = Gtk.Label()
 
665
        label.set_alignment(1.0, 0.5)
 
666
        label.set_markup("<b>Timestamp:</b>")
 
667
        self.table.attach(label, 0, 1, row, row+1, Gtk.AttachOptions.FILL, Gtk.AttachOptions.FILL)
 
668
        label.show()
 
669
 
 
670
        self.timestamp = Gtk.Label()
 
671
        self.timestamp.set_ellipsize(Pango.EllipsizeMode.END)
 
672
        self.timestamp.set_alignment(0.0, 0.5)
 
673
        self.timestamp.set_selectable(True)
 
674
        self.table.attach(self.timestamp, 1, 2, row, row+1, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, Gtk.AttachOptions.FILL)
 
675
        self.timestamp.show()
 
676
 
 
677
        row += 1
 
678
        self.tags_label = Gtk.Label()
 
679
        self.tags_label.set_alignment(1.0, 0.5)
 
680
        self.tags_label.set_markup("<b>Tags:</b>")
 
681
        self.table.attach(self.tags_label, 0, 1, row, row+1, Gtk.AttachOptions.FILL, Gtk.AttachOptions.FILL)
 
682
        self.tags_label.show()
 
683
 
 
684
        self.tags_list = Gtk.Label()
 
685
        self.tags_list.set_ellipsize(Pango.EllipsizeMode.MIDDLE)
 
686
        self.tags_list.set_alignment(0.0, 0.5)
 
687
        self.table.attach(self.tags_list, 1, 2, row, row+1, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, Gtk.AttachOptions.FILL)
 
688
        self.tags_list.show()
 
689
 
 
690
        self.connect('notify::revision', self._add_tags)
 
691
 
 
692
        return self.avatarsbox
 
693
    
 
694
    def _create_parents(self):
 
695
        hbox = Gtk.HBox(homogeneous=True, spacing=3)
 
696
        
 
697
        self.parents_table = self._create_parents_or_children_table(
 
698
            "<b>Parents:</b>")
 
699
        self.parents_widgets = []
 
700
        hbox.pack_start(self.parents_table, True, True, 0)
 
701
 
 
702
        hbox.show()
 
703
        return hbox
 
704
 
 
705
    def _create_children(self):
 
706
        hbox = Gtk.HBox(homogeneous=True, spacing=3)
 
707
        self.children_table = self._create_parents_or_children_table(
 
708
            "<b>Children:</b>")
 
709
        self.children_widgets = []
 
710
        hbox.pack_start(self.children_table, True, True, 0)
 
711
        hbox.show()
 
712
        return hbox
 
713
        
 
714
    def _create_parents_or_children_table(self, text):
 
715
        table = Gtk.Table(rows=1, columns=2)
 
716
        table.set_row_spacings(3)
 
717
        table.set_col_spacings(6)
 
718
        table.show()
 
719
 
 
720
        label = Gtk.Label()
 
721
        label.set_markup(text)
 
722
        align = Gtk.Alignment.new(0.0, 0.5, 0, 0)
 
723
        align.add(label)
 
724
        table.attach(align, 0, 1, 0, 1, Gtk.AttachOptions.FILL, Gtk.AttachOptions.FILL)
 
725
        label.show()
 
726
        align.show()
 
727
 
 
728
        return table
 
729
 
 
730
    def _create_message_view(self):
 
731
        msg_buffer = Gtk.TextBuffer()
 
732
        self.connect('notify::revision',
 
733
                lambda w, p: msg_buffer.set_text(self._revision.message))
 
734
        window = Gtk.ScrolledWindow()
 
735
        window.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
 
736
        window.set_shadow_type(Gtk.ShadowType.IN)
 
737
        tv = Gtk.TextView(buffer=msg_buffer)
 
738
        tv.set_editable(False)
 
739
        tv.set_wrap_mode(Gtk.WrapMode.WORD)
 
740
 
 
741
        tv.modify_font(Pango.FontDescription("Monospace"))
 
742
        tv.show()
 
743
        window.add(tv)
 
744
        window.show()
 
745
        return window
 
746
 
 
747
    def _create_bugs(self):
 
748
        self.bugs_page = BugsTab()
 
749
        self.connect_after('notify::revision', self._update_bugs) 
 
750
        self.append_page(self.bugs_page, Gtk.Label(label='Bugs'))
 
751
 
 
752
    def _create_file_info_view(self):
 
753
        self.file_info_box = Gtk.VBox(homogeneous=False, spacing=6)
 
754
        self.file_info_box.set_border_width(6)
 
755
        self.file_info_buffer = Gtk.TextBuffer()
 
756
        window = Gtk.ScrolledWindow()
 
757
        window.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
 
758
        window.set_shadow_type(Gtk.ShadowType.IN)
 
759
        tv = Gtk.TextView(buffer=self.file_info_buffer)
 
760
        tv.set_editable(False)
 
761
        tv.set_wrap_mode(Gtk.WrapMode.WORD)
 
762
        tv.modify_font(Pango.FontDescription("Monospace"))
 
763
        tv.show()
 
764
        window.add(tv)
 
765
        window.show()
 
766
        self.file_info_box.pack_start(window, True, True, 0)
 
767
        self.file_info_box.hide() # Only shown when there are per-file messages
 
768
        self.append_page(self.file_info_box, Gtk.Label(label='Per-file'))
 
769