/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: Szilveszter Farkas (Phanatic)
  • Date: 2006-08-07 16:51:21 UTC
  • mto: (0.14.1 main) (93.1.1 win32.bialix)
  • mto: This revision was merged to the branch mainline in revision 83.
  • Revision ID: Szilveszter.Farkas@gmail.com-20060807165121-10fe27c374bbdffd
Added new artwork.

2006-08-07  Szilveszter Farkas <Szilveszter.Farkas@gmail.com>

    * olive.galde: added custom artwork (icons)
    * icons/*: new icons for the toolbar
    * setup.py: install the icons

Show diffs side-by-side

added added

removed removed

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