/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: 2008-06-10 15:25:47 UTC
  • mto: This revision was merged to the branch mainline in revision 504.
  • Revision ID: v.ladeuil+lp@free.fr-20080610152547-dwmil1p8pd0mfpnl
Fix third failing test (thanks to jam).

* tests/test_commit.py:
(TestPendingRevisions.test_pending_revisions_multi_merge): Fix
provided by jam: bzr we now filter the pending merges so that only
the 'heads()' are included. We just ensure that the pending merges
contain the unique tips for the ancestries.

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