/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-12-11 17:14:12 UTC
  • Revision ID: jelmer@samba.org-20111211171412-cgcn0yas3zlcahzg
StartĀ onĀ 0.104.0.

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