/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

Merged an approved request (single clicking bookmark).

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright (C) 2005 Dan Loda <danloda@gmail.com>
 
2
# Copyright (C) 2007 Jelmer Vernooij <jelmer@samba.org>
2
3
 
3
4
# This program is free software; you can redistribute it and/or modify
4
5
# it under the terms of the GNU General Public License as published by
18
19
pygtk.require("2.0")
19
20
import gtk
20
21
import pango
 
22
import gobject
 
23
import subprocess
21
24
 
22
25
from bzrlib.osutils import format_date
23
 
 
24
 
 
25
 
class LogView(gtk.ScrolledWindow):
 
26
from bzrlib.util.bencode import bdecode
 
27
 
 
28
def _open_link(widget, uri):
 
29
    subprocess.Popen(['sensible-browser', uri], close_fds=True)
 
30
 
 
31
gtk.link_button_set_uri_hook(_open_link)
 
32
 
 
33
class BugsTab(gtk.Table):
 
34
    def __init__(self):
 
35
        super(BugsTab, self).__init__(rows=5, columns=2)
 
36
        self.set_row_spacings(6)
 
37
        self.set_col_spacings(6)
 
38
        self.clear()
 
39
 
 
40
    def clear(self):
 
41
        for c in self.get_children():
 
42
            self.remove(c)
 
43
        self.count = 0
 
44
        self.hide_all() # Only shown when there are bugs
 
45
 
 
46
    def add_bug(self, url, status):
 
47
        button = gtk.LinkButton(url, url)
 
48
        self.attach(button, 0, 1, self.count, self.count + 1,
 
49
                              gtk.EXPAND | gtk.FILL, gtk.FILL)
 
50
        status_label = gtk.Label(status)
 
51
        self.attach(status_label, 1, 2, self.count, self.count + 1,
 
52
                              gtk.EXPAND | gtk.FILL, gtk.FILL)
 
53
        self.count += 1
 
54
        self.show_all()
 
55
 
 
56
 
 
57
class RevisionView(gtk.Notebook):
26
58
    """ Custom widget for commit log details.
27
59
 
28
60
    A variety of bzr tools may need to implement such a thing. This is a
29
61
    start.
30
62
    """
31
63
 
32
 
    def __init__(self, revision=None):
33
 
        gtk.ScrolledWindow.__init__(self)
34
 
        self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
35
 
        self.set_shadow_type(gtk.SHADOW_NONE)
36
 
        self._create()
 
64
    __gproperties__ = {
 
65
        'branch': (
 
66
            gobject.TYPE_PYOBJECT,
 
67
            'Branch',
 
68
            'The branch holding the revision being displayed',
 
69
            gobject.PARAM_CONSTRUCT_ONLY | gobject.PARAM_WRITABLE
 
70
        ),
 
71
 
 
72
        'revision': (
 
73
            gobject.TYPE_PYOBJECT,
 
74
            'Revision',
 
75
            'The revision being displayed',
 
76
            gobject.PARAM_READWRITE
 
77
        ),
 
78
 
 
79
        'children': (
 
80
            gobject.TYPE_PYOBJECT,
 
81
            'Children',
 
82
            'Child revisions',
 
83
            gobject.PARAM_READWRITE
 
84
        ),
 
85
 
 
86
        'file-id': (
 
87
            gobject.TYPE_PYOBJECT,
 
88
            'File Id',
 
89
            'The file id',
 
90
            gobject.PARAM_READWRITE
 
91
        )
 
92
    }
 
93
 
 
94
 
 
95
    def __init__(self, branch=None):
 
96
        gtk.Notebook.__init__(self)
 
97
 
 
98
        self._create_general()
 
99
        self._create_relations()
 
100
        self._create_file_info_view()
 
101
        self._create_bugs()
 
102
 
 
103
        self.set_current_page(0)
 
104
        
37
105
        self._show_callback = None
38
 
        self._go_callback = None
39
106
        self._clicked_callback = None
40
107
 
41
 
        if revision is not None:
42
 
            self.set_revision(revision)
 
108
        self._revision = None
 
109
        self._branch = branch
 
110
 
 
111
        self.update_tags()
 
112
 
 
113
        self.set_file_id(None)
 
114
 
 
115
    def do_get_property(self, property):
 
116
        if property.name == 'branch':
 
117
            return self._branch
 
118
        elif property.name == 'revision':
 
119
            return self._revision
 
120
        elif property.name == 'children':
 
121
            return self._children
 
122
        elif property.name == 'file-id':
 
123
            return self._file_id
 
124
        else:
 
125
            raise AttributeError, 'unknown property %s' % property.name
 
126
 
 
127
    def do_set_property(self, property, value):
 
128
        if property.name == 'branch':
 
129
            self._branch = value
 
130
        elif property.name == 'revision':
 
131
            self._set_revision(value)
 
132
        elif property.name == 'children':
 
133
            self.set_children(value)
 
134
        elif property.name == 'file-id':
 
135
            self._file_id = value
 
136
        else:
 
137
            raise AttributeError, 'unknown property %s' % property.name
43
138
 
44
139
    def set_show_callback(self, callback):
45
140
        self._show_callback = callback
46
141
 
47
 
    def set_go_callback(self, callback):
48
 
        self._go_callback = callback
 
142
    def set_file_id(self, file_id):
 
143
        """Set a specific file id that we want to track.
 
144
 
 
145
        This just effects the display of a per-file commit message.
 
146
        If it is set to None, then all commit messages will be shown.
 
147
        """
 
148
        self.set_property('file-id', file_id)
49
149
 
50
150
    def set_revision(self, revision):
 
151
        if revision != self._revision:
 
152
            self.set_property('revision', revision)
 
153
 
 
154
    def get_revision(self):
 
155
        return self.get_property('revision')
 
156
 
 
157
    def _set_revision(self, revision):
 
158
        if revision is None: return
 
159
 
51
160
        self._revision = revision
52
 
        self.revision_id.set_text(revision.revision_id)
53
 
        self.committer.set_text(revision.committer)
54
 
        self.timestamp.set_text(format_date(revision.timestamp,
55
 
                                            revision.timezone))
56
 
        self.message_buffer.set_text(revision.message)
 
161
        if revision.committer is not None:
 
162
            self.committer.set_text(revision.committer)
 
163
        else:
 
164
            self.committer.set_text("")
 
165
        author = revision.properties.get('author', '')
 
166
        if author != '':
 
167
            self.author.set_text(author)
 
168
            self.author.show()
 
169
            self.author_label.show()
 
170
        else:
 
171
            self.author.hide()
 
172
            self.author_label.hide()
 
173
 
 
174
        if revision.timestamp is not None:
 
175
            self.timestamp.set_text(format_date(revision.timestamp,
 
176
                                                revision.timezone))
57
177
        try:
58
178
            self.branchnick_label.set_text(revision.properties['branch-nick'])
59
179
        except KeyError:
60
180
            self.branchnick_label.set_text("")
61
181
 
62
 
        self._add_parents(revision.parent_ids)
 
182
        self._add_parents_or_children(revision.parent_ids,
 
183
                                      self.parents_widgets,
 
184
                                      self.parents_table)
 
185
        
 
186
        file_info = revision.properties.get('file-info', None)
 
187
        if file_info is not None:
 
188
            file_info = bdecode(file_info.encode('UTF-8'))
 
189
 
 
190
        if file_info:
 
191
            if self._file_id is None:
 
192
                text = []
 
193
                for fi in file_info:
 
194
                    text.append('%(path)s\n%(message)s' % fi)
 
195
                self.file_info_buffer.set_text('\n'.join(text))
 
196
                self.file_info_box.show()
 
197
            else:
 
198
                text = []
 
199
                for fi in file_info:
 
200
                    if fi['file_id'] == self._file_id:
 
201
                        text.append(fi['message'])
 
202
                if text:
 
203
                    self.file_info_buffer.set_text('\n'.join(text))
 
204
                    self.file_info_box.show()
 
205
                else:
 
206
                    self.file_info_box.hide()
 
207
        else:
 
208
            self.file_info_box.hide()
 
209
 
 
210
        self.bugs_table.clear()
 
211
        bugs_text = revision.properties.get('bugs', None)
 
212
        if bugs_text:
 
213
            for bugline in bugs_text.splitlines():
 
214
                (url, status) = bugline.split(" ")
 
215
                self.bugs_table.add_bug(url, status)
 
216
 
 
217
    def update_tags(self):
 
218
        if self._branch is not None and self._branch.supports_tags():
 
219
            self._tagdict = self._branch.tags.get_reverse_tag_dict()
 
220
        else:
 
221
            self._tagdict = {}
 
222
 
 
223
        self._add_tags()
 
224
 
 
225
    def set_children(self, children):
 
226
        self._add_parents_or_children(children,
 
227
                                      self.children_widgets,
 
228
                                      self.children_table)
63
229
 
64
230
    def _show_clicked_cb(self, widget, revid, parentid):
65
231
        """Callback for when the show button for a parent is clicked."""
67
233
 
68
234
    def _go_clicked_cb(self, widget, revid):
69
235
        """Callback for when the go button for a parent is clicked."""
70
 
        self._go_callback(revid)
71
 
 
72
 
    def _add_parents(self, parent_ids):
73
 
        for widget in self.parents_widgets:
74
 
            self.parents_table.remove(widget)
 
236
 
 
237
    def _add_tags(self, *args):
 
238
        if self._revision is None: return
 
239
 
 
240
        if self._tagdict.has_key(self._revision.revision_id):
 
241
            tags = self._tagdict[self._revision.revision_id]
 
242
        else:
 
243
            tags = []
75
244
            
76
 
        self.parents_widgets = []
77
 
        self.parents_table.resize(max(len(parent_ids), 1), 2)
78
 
 
79
 
        for idx, parent_id in enumerate(parent_ids):
 
245
        if tags == []:
 
246
            self.tags_list.hide()
 
247
            self.tags_label.hide()
 
248
            return
 
249
 
 
250
        self.tags_list.set_text(", ".join(tags))
 
251
 
 
252
        self.tags_list.show_all()
 
253
        self.tags_label.show_all()
 
254
        
 
255
    def _add_parents_or_children(self, revids, widgets, table):
 
256
        while len(widgets) > 0:
 
257
            widget = widgets.pop()
 
258
            table.remove(widget)
 
259
        
 
260
        table.resize(max(len(revids), 1), 2)
 
261
 
 
262
        for idx, revid in enumerate(revids):
80
263
            align = gtk.Alignment(0.0, 0.0)
81
 
            self.parents_widgets.append(align)
82
 
            self.parents_table.attach(align, 1, 2, idx, idx + 1,
 
264
            widgets.append(align)
 
265
            table.attach(align, 1, 2, idx, idx + 1,
83
266
                                      gtk.EXPAND | gtk.FILL, gtk.FILL)
84
267
            align.show()
85
268
 
96
279
                button = gtk.Button()
97
280
                button.add(image)
98
281
                button.connect("clicked", self._show_clicked_cb,
99
 
                               self._revision.revision_id, parent_id)
 
282
                               self._revision.revision_id, revid)
100
283
                hbox.pack_start(button, expand=False, fill=True)
101
284
                button.show()
102
285
 
103
 
            if self._go_callback is not None:
104
 
                button = gtk.Button(parent_id)
105
 
                button.connect("clicked", self._go_clicked_cb, parent_id)
106
 
            else:
107
 
                button = gtk.Label(parent_id)
 
286
            button = gtk.Button(revid)
 
287
            button.connect("clicked",
 
288
                    lambda w, r: self.set_revision(self._branch.repository.get_revision(r)), revid)
108
289
            button.set_use_underline(False)
109
290
            hbox.pack_start(button, expand=False, fill=True)
110
291
            button.show()
111
292
 
112
 
    def _create(self):
 
293
    def _create_general(self):
113
294
        vbox = gtk.VBox(False, 6)
114
295
        vbox.set_border_width(6)
115
296
        vbox.pack_start(self._create_headers(), expand=False, fill=True)
116
 
        vbox.pack_start(self._create_parents_table(), expand=False, fill=True)
117
297
        vbox.pack_start(self._create_message_view())
118
 
        self.add_with_viewport(vbox)
 
298
        self.append_page(vbox, tab_label=gtk.Label("General"))
 
299
        vbox.show()
 
300
 
 
301
    def _create_relations(self):
 
302
        vbox = gtk.VBox(False, 6)
 
303
        vbox.set_border_width(6)
 
304
        vbox.pack_start(self._create_parents(), expand=False, fill=True)
 
305
        vbox.pack_start(self._create_children(), expand=False, fill=True)
 
306
        self.append_page(vbox, tab_label=gtk.Label("Relations"))
119
307
        vbox.show()
120
308
 
121
309
    def _create_headers(self):
122
 
        self.table = gtk.Table(rows=4, columns=2)
 
310
        self.table = gtk.Table(rows=5, columns=2)
123
311
        self.table.set_row_spacings(6)
124
312
        self.table.set_col_spacings(6)
125
313
        self.table.show()
133
321
        label.show()
134
322
 
135
323
        align = gtk.Alignment(0.0, 0.5)
136
 
        self.revision_id = gtk.Label()
137
 
        self.revision_id.set_selectable(True)
138
 
        align.add(self.revision_id)
 
324
        revision_id = gtk.Label()
 
325
        revision_id.set_selectable(True)
 
326
        self.connect('notify::revision', 
 
327
                lambda w, p: revision_id.set_text(self._revision.revision_id))
 
328
        align.add(revision_id)
139
329
        self.table.attach(align, 1, 2, 0, 1, gtk.EXPAND | gtk.FILL, gtk.FILL)
140
330
        align.show()
141
 
        self.revision_id.show()
 
331
        revision_id.show()
 
332
 
 
333
        align = gtk.Alignment(1.0, 0.5)
 
334
        self.author_label = gtk.Label()
 
335
        self.author_label.set_markup("<b>Author:</b>")
 
336
        align.add(self.author_label)
 
337
        self.table.attach(align, 0, 1, 1, 2, gtk.FILL, gtk.FILL)
 
338
        align.show()
 
339
        self.author_label.show()
 
340
 
 
341
        align = gtk.Alignment(0.0, 0.5)
 
342
        self.author = gtk.Label()
 
343
        self.author.set_selectable(True)
 
344
        align.add(self.author)
 
345
        self.table.attach(align, 1, 2, 1, 2, gtk.EXPAND | gtk.FILL, gtk.FILL)
 
346
        align.show()
 
347
        self.author.show()
 
348
        self.author.hide()
142
349
 
143
350
        align = gtk.Alignment(1.0, 0.5)
144
351
        label = gtk.Label()
145
352
        label.set_markup("<b>Committer:</b>")
146
353
        align.add(label)
147
 
        self.table.attach(align, 0, 1, 1, 2, gtk.FILL, gtk.FILL)
 
354
        self.table.attach(align, 0, 1, 2, 3, gtk.FILL, gtk.FILL)
148
355
        align.show()
149
356
        label.show()
150
357
 
152
359
        self.committer = gtk.Label()
153
360
        self.committer.set_selectable(True)
154
361
        align.add(self.committer)
155
 
        self.table.attach(align, 1, 2, 1, 2, gtk.EXPAND | gtk.FILL, gtk.FILL)
 
362
        self.table.attach(align, 1, 2, 2, 3, gtk.EXPAND | gtk.FILL, gtk.FILL)
156
363
        align.show()
157
364
        self.committer.show()
158
365
 
160
367
        label = gtk.Label()
161
368
        label.set_markup("<b>Branch nick:</b>")
162
369
        align.add(label)
163
 
        self.table.attach(align, 0, 1, 2, 3, gtk.FILL, gtk.FILL)
 
370
        self.table.attach(align, 0, 1, 3, 4, gtk.FILL, gtk.FILL)
164
371
        label.show()
165
372
        align.show()
166
373
 
168
375
        self.branchnick_label = gtk.Label()
169
376
        self.branchnick_label.set_selectable(True)
170
377
        align.add(self.branchnick_label)
171
 
        self.table.attach(align, 1, 2, 2, 3, gtk.EXPAND | gtk.FILL, gtk.FILL)
 
378
        self.table.attach(align, 1, 2, 3, 4, gtk.EXPAND | gtk.FILL, gtk.FILL)
172
379
        self.branchnick_label.show()
173
380
        align.show()
174
381
 
176
383
        label = gtk.Label()
177
384
        label.set_markup("<b>Timestamp:</b>")
178
385
        align.add(label)
179
 
        self.table.attach(align, 0, 1, 3, 4, gtk.FILL, gtk.FILL)
 
386
        self.table.attach(align, 0, 1, 4, 5, gtk.FILL, gtk.FILL)
180
387
        align.show()
181
388
        label.show()
182
389
 
184
391
        self.timestamp = gtk.Label()
185
392
        self.timestamp.set_selectable(True)
186
393
        align.add(self.timestamp)
187
 
        self.table.attach(align, 1, 2, 3, 4, gtk.EXPAND | gtk.FILL, gtk.FILL)
 
394
        self.table.attach(align, 1, 2, 4, 5, gtk.EXPAND | gtk.FILL, gtk.FILL)
188
395
        align.show()
189
396
        self.timestamp.show()
190
397
 
 
398
        align = gtk.Alignment(1.0, 0.5)
 
399
        self.tags_label = gtk.Label()
 
400
        self.tags_label.set_markup("<b>Tags:</b>")
 
401
        align.add(self.tags_label)
 
402
        align.show()
 
403
        self.table.attach(align, 0, 1, 5, 6, gtk.FILL, gtk.FILL)
 
404
        self.tags_label.show()
 
405
 
 
406
        align = gtk.Alignment(0.0, 0.5)
 
407
        self.tags_list = gtk.Label()
 
408
        align.add(self.tags_list)
 
409
        self.table.attach(align, 1, 2, 5, 6, gtk.EXPAND | gtk.FILL, gtk.FILL)
 
410
        align.show()
 
411
        self.tags_list.show()
 
412
 
 
413
        self.connect('notify::revision', self._add_tags)
 
414
 
191
415
        return self.table
192
416
 
193
 
    def _create_parents_table(self):
194
 
        self.parents_table = gtk.Table(rows=1, columns=2)
195
 
        self.parents_table.set_row_spacings(3)
196
 
        self.parents_table.set_col_spacings(6)
197
 
        self.parents_table.show()
 
417
    
 
418
    def _create_parents(self):
 
419
        hbox = gtk.HBox(True, 3)
 
420
        
 
421
        self.parents_table = self._create_parents_or_children_table(
 
422
            "<b>Parents:</b>")
198
423
        self.parents_widgets = []
 
424
        hbox.pack_start(self.parents_table)
 
425
 
 
426
        hbox.show()
 
427
        return hbox
 
428
 
 
429
    def _create_children(self):
 
430
        hbox = gtk.HBox(True, 3)
 
431
        self.children_table = self._create_parents_or_children_table(
 
432
            "<b>Children:</b>")
 
433
        self.children_widgets = []
 
434
        hbox.pack_start(self.children_table)
 
435
        hbox.show()
 
436
        return hbox
 
437
        
 
438
    def _create_parents_or_children_table(self, text):
 
439
        table = gtk.Table(rows=1, columns=2)
 
440
        table.set_row_spacings(3)
 
441
        table.set_col_spacings(6)
 
442
        table.show()
199
443
 
200
444
        label = gtk.Label()
201
 
        label.set_markup("<b>Parents:</b>")
 
445
        label.set_markup(text)
202
446
        align = gtk.Alignment(0.0, 0.5)
203
447
        align.add(label)
204
 
        self.parents_table.attach(align, 0, 1, 0, 1, gtk.FILL, gtk.FILL)
 
448
        table.attach(align, 0, 1, 0, 1, gtk.FILL, gtk.FILL)
205
449
        label.show()
206
450
        align.show()
207
451
 
208
 
        return self.parents_table
 
452
        return table
209
453
 
210
454
    def _create_message_view(self):
211
 
        self.message_buffer = gtk.TextBuffer()
212
 
        tv = gtk.TextView(self.message_buffer)
213
 
        tv.set_editable(False)
214
 
        tv.set_wrap_mode(gtk.WRAP_WORD)
215
 
        tv.modify_font(pango.FontDescription("Monospace"))
216
 
        tv.show()
217
 
        return tv
 
455
        msg_buffer = gtk.TextBuffer()
 
456
        self.connect('notify::revision',
 
457
                lambda w, p: msg_buffer.set_text(self._revision.message))
 
458
        window = gtk.ScrolledWindow()
 
459
        window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
 
460
        window.set_shadow_type(gtk.SHADOW_IN)
 
461
        tv = gtk.TextView(msg_buffer)
 
462
        tv.set_editable(False)
 
463
        tv.set_wrap_mode(gtk.WRAP_WORD)
 
464
        tv.modify_font(pango.FontDescription("Monospace"))
 
465
        tv.show()
 
466
        window.add(tv)
 
467
        window.show()
 
468
        return window
 
469
 
 
470
    def _create_bugs(self):
 
471
        self.bugs_table = BugsTab()
 
472
        self.append_page(self.bugs_table, tab_label=gtk.Label('Bugs'))
 
473
 
 
474
    def _create_file_info_view(self):
 
475
        self.file_info_box = gtk.VBox(False, 6)
 
476
        self.file_info_box.set_border_width(6)
 
477
        self.file_info_buffer = gtk.TextBuffer()
 
478
        window = gtk.ScrolledWindow()
 
479
        window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
 
480
        window.set_shadow_type(gtk.SHADOW_IN)
 
481
        tv = gtk.TextView(self.file_info_buffer)
 
482
        tv.set_editable(False)
 
483
        tv.set_wrap_mode(gtk.WRAP_WORD)
 
484
        tv.modify_font(pango.FontDescription("Monospace"))
 
485
        tv.show()
 
486
        window.add(tv)
 
487
        window.show()
 
488
        self.file_info_box.pack_start(window)
 
489
        self.file_info_box.hide() # Only shown when there are per-file messages
 
490
        self.append_page(self.file_info_box, tab_label=gtk.Label('Per-file'))
218
491