/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-02-18 13:01:03 UTC
  • Revision ID: jelmer@samba.org-20110218130103-fiyk203auk28thpn
Remove some unused imports, fix some formatting.

Show diffs side-by-side

added added

removed removed

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