/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: Vincent Ladeuil
  • Date: 2009-05-06 11:51:38 UTC
  • mfrom: (628.1.2 gtk)
  • Revision ID: v.ladeuil+lp@free.fr-20090506115138-7abxvg2qz4gzraup
UpgradeĀ COMPATIBLE_BZR_VERSIONS

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