/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz
0.8.43 by Szilveszter Farkas (Phanatic)
Implemented Log functionality (via bzrk).
1
# Modified for use with Olive:
2
# Copyright (C) 2006 by Szilveszter Farkas (Phanatic) <szilveszter.farkas@gmail.com>
3
# Original copyright holder:
4
# Copyright (C) 2005 by Canonical Ltd. (Scott James Remnant <scott@ubuntu.com>)
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
20
"""Branch window.
21
22
This module contains the code to manage the branch information window,
23
which contains both the revision graph and details panes.
24
"""
25
26
__copyright__ = "Copyright © 2005 Canonical Ltd."
27
__author__    = "Scott James Remnant <scott@ubuntu.com>"
28
29
30
import gtk
31
import gobject
32
import pango
33
34
from bzrlib.osutils import format_date
35
36
from graph import distances, graph, same_branch
37
from graphcell import CellRendererGraph
38
39
40
class BranchWindow(gtk.Window):
41
    """Branch window.
42
43
    This object represents and manages a single window containing information
44
    for a particular branch.
45
    """
46
47
    def __init__(self, app=None):
48
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
49
        self.set_border_width(0)
50
        self.set_title("bzrk")
51
52
        self.app = app
53
54
        # Use three-quarters of the screen by default
55
        screen = self.get_screen()
56
        monitor = screen.get_monitor_geometry(0)
57
        width = int(monitor.width * 0.75)
58
        height = int(monitor.height * 0.75)
59
        self.set_default_size(width, height)
60
61
        # FIXME AndyFitz!
62
        icon = self.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)
63
        self.set_icon(icon)
64
65
        self.accel_group = gtk.AccelGroup()
66
        self.add_accel_group(self.accel_group)
67
68
        self.construct()
69
70
    def construct(self):
71
        """Construct the window contents."""
72
        vbox = gtk.VBox(spacing=0)
73
        self.add(vbox)
74
75
        vbox.pack_start(self.construct_navigation(), expand=False, fill=True)
76
77
        paned = gtk.VPaned()
78
        paned.pack1(self.construct_top(), resize=True, shrink=False)
79
        paned.pack2(self.construct_bottom(), resize=False, shrink=True)
80
        paned.show()
81
        vbox.pack_start(paned, expand=True, fill=True)
82
        vbox.set_focus_child(paned)
83
84
        vbox.show()
85
86
    def construct_top(self):
87
        """Construct the top-half of the window."""
88
        scrollwin = gtk.ScrolledWindow()
89
        scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
90
        scrollwin.set_shadow_type(gtk.SHADOW_IN)
91
        scrollwin.show()
92
93
        self.treeview = gtk.TreeView()
94
        self.treeview.set_rules_hint(True)
95
        self.treeview.set_search_column(4)
96
        self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
97
        self.treeview.connect("row-activated", self._treeview_row_activated_cb)
98
        scrollwin.add(self.treeview)
99
        self.treeview.show()
100
101
        cell = CellRendererGraph()
102
        column = gtk.TreeViewColumn()
103
        column.set_resizable(True)
104
        column.pack_start(cell, expand=False)
105
        column.add_attribute(cell, "node", 1)
106
        column.add_attribute(cell, "in-lines", 2)
107
        column.add_attribute(cell, "out-lines", 3)
108
        self.treeview.append_column(column)
109
110
        cell = gtk.CellRendererText()
111
        cell.set_property("width-chars", 40)
112
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
113
        column = gtk.TreeViewColumn("Message")
114
        column.set_resizable(True)
115
        column.pack_start(cell, expand=True)
116
        column.add_attribute(cell, "text", 4)
117
        self.treeview.append_column(column)
118
119
        cell = gtk.CellRendererText()
120
        cell.set_property("width-chars", 40)
121
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
122
        column = gtk.TreeViewColumn("Committer")
123
        column.set_resizable(True)
124
        column.pack_start(cell, expand=True)
125
        column.add_attribute(cell, "text", 5)
126
        self.treeview.append_column(column)
127
128
        cell = gtk.CellRendererText()
129
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
130
        column = gtk.TreeViewColumn("Date")
131
        column.set_resizable(True)
132
        column.pack_start(cell, expand=True)
133
        column.add_attribute(cell, "text", 6)
134
        self.treeview.append_column(column)
135
136
        return scrollwin
137
138
    def construct_navigation(self):
139
        """Construct the navigation buttons."""
140
        frame = gtk.Frame()
141
        frame.set_shadow_type(gtk.SHADOW_OUT)
142
        frame.show()
143
        
144
        hbox = gtk.HBox(spacing=12)
145
        frame.add(hbox)
146
        hbox.show()
147
148
        self.back_button = gtk.Button(stock=gtk.STOCK_GO_BACK)
149
        self.back_button.set_relief(gtk.RELIEF_NONE)
150
        self.back_button.add_accelerator("clicked", self.accel_group, ord('['),
151
                                         0, 0)
152
        self.back_button.connect("clicked", self._back_clicked_cb)
153
        hbox.pack_start(self.back_button, expand=False, fill=True)
154
        self.back_button.show()
155
156
        self.fwd_button = gtk.Button(stock=gtk.STOCK_GO_FORWARD)
157
        self.fwd_button.set_relief(gtk.RELIEF_NONE)
158
        self.fwd_button.add_accelerator("clicked", self.accel_group, ord(']'),
159
                                        0, 0)
160
        self.fwd_button.connect("clicked", self._fwd_clicked_cb)
161
        hbox.pack_start(self.fwd_button, expand=False, fill=True)
162
        self.fwd_button.show()
163
164
        return frame
165
166
    def construct_bottom(self):
167
        """Construct the bottom half of the window."""
168
        scrollwin = gtk.ScrolledWindow()
169
        scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
170
        scrollwin.set_shadow_type(gtk.SHADOW_NONE)
171
        (width, height) = self.get_size()
172
        scrollwin.set_size_request(width, int(height / 2.5))
173
        scrollwin.show()
174
175
        vbox = gtk.VBox(False, spacing=6)
176
        vbox.set_border_width(6)
177
        scrollwin.add_with_viewport(vbox)
178
        vbox.show()
179
180
        table = gtk.Table(rows=4, columns=2)
181
        table.set_row_spacings(6)
182
        table.set_col_spacings(6)
183
        vbox.pack_start(table, expand=False, fill=True)
184
        table.show()
185
186
        align = gtk.Alignment(0.0, 0.5)
187
        label = gtk.Label()
188
        label.set_markup("<b>Revision:</b>")
189
        align.add(label)
190
        table.attach(align, 0, 1, 0, 1, gtk.FILL, gtk.FILL)
191
        label.show()
192
        align.show()
193
194
        align = gtk.Alignment(0.0, 0.5)
195
        self.revid_label = gtk.Label()
196
        self.revid_label.set_selectable(True)
197
        align.add(self.revid_label)
198
        table.attach(align, 1, 2, 0, 1, gtk.EXPAND | gtk.FILL, gtk.FILL)
199
        self.revid_label.show()
200
        align.show()
201
202
        align = gtk.Alignment(0.0, 0.5)
203
        label = gtk.Label()
204
        label.set_markup("<b>Committer:</b>")
205
        align.add(label)
206
        table.attach(align, 0, 1, 1, 2, gtk.FILL, gtk.FILL)
207
        label.show()
208
        align.show()
209
210
        align = gtk.Alignment(0.0, 0.5)
211
        self.committer_label = gtk.Label()
212
        self.committer_label.set_selectable(True)
213
        align.add(self.committer_label)
214
        table.attach(align, 1, 2, 1, 2, gtk.EXPAND | gtk.FILL, gtk.FILL)
215
        self.committer_label.show()
216
        align.show()
217
218
        align = gtk.Alignment(0.0, 0.5)
219
        label = gtk.Label()
220
        label.set_markup("<b>Branch nick:</b>")
221
        align.add(label)
222
        table.attach(align, 0, 1, 2, 3, gtk.FILL, gtk.FILL)
223
        label.show()
224
        align.show()
225
226
        align = gtk.Alignment(0.0, 0.5)
227
        self.branchnick_label = gtk.Label()
228
        self.branchnick_label.set_selectable(True)
229
        align.add(self.branchnick_label)
230
        table.attach(align, 1, 2, 2, 3, gtk.EXPAND | gtk.FILL, gtk.FILL)
231
        self.branchnick_label.show()
232
        align.show()
233
234
        align = gtk.Alignment(0.0, 0.5)
235
        label = gtk.Label()
236
        label.set_markup("<b>Timestamp:</b>")
237
        align.add(label)
238
        table.attach(align, 0, 1, 3, 4, gtk.FILL, gtk.FILL)
239
        label.show()
240
        align.show()
241
242
        align = gtk.Alignment(0.0, 0.5)
243
        self.timestamp_label = gtk.Label()
244
        self.timestamp_label.set_selectable(True)
245
        align.add(self.timestamp_label)
246
        table.attach(align, 1, 2, 3, 4, gtk.EXPAND | gtk.FILL, gtk.FILL)
247
        self.timestamp_label.show()
248
        align.show()
249
250
        self.parents_table = gtk.Table(rows=1, columns=2)
251
        self.parents_table.set_row_spacings(3)
252
        self.parents_table.set_col_spacings(6)
253
        self.parents_table.show()
254
        vbox.pack_start(self.parents_table, expand=False, fill=True)
255
        self.parents_widgets = []
256
257
        label = gtk.Label()
258
        label.set_markup("<b>Parents:</b>")
259
        align = gtk.Alignment(0.0, 0.5)
260
        align.add(label)
261
        self.parents_table.attach(align, 0, 1, 0, 1, gtk.FILL, gtk.FILL)
262
        label.show()
263
        align.show()
264
265
        self.message_buffer = gtk.TextBuffer()
266
        textview = gtk.TextView(self.message_buffer)
267
        textview.set_editable(False)
268
        textview.set_wrap_mode(gtk.WRAP_WORD)
269
        textview.modify_font(pango.FontDescription("Monospace"))
270
        vbox.pack_start(textview, expand=True, fill=True)
271
        textview.show()
272
273
        return scrollwin
274
275
    def set_branch(self, branch, start, maxnum):
276
        """Set the branch and start position for this window.
277
278
        Creates a new TreeModel and populates it with information about
279
        the new branch before updating the window title and model of the
280
        treeview itself.
281
        """
282
        self.branch = branch
283
284
        # [ revision, node, last_lines, lines, message, committer, timestamp ]
285
        self.model = gtk.ListStore(gobject.TYPE_PYOBJECT,
286
                                   gobject.TYPE_PYOBJECT,
287
                                   gobject.TYPE_PYOBJECT,
288
                                   gobject.TYPE_PYOBJECT,
289
                                   str, str, str)
290
        self.index = {}
291
        index = 0
292
293
        last_lines = []
294
        (self.revisions, colours, self.children, self.parent_ids,
295
         merge_sorted) = distances(branch, start)
296
        for (index, (revision, node, lines)) in enumerate(graph(
297
                self.revisions, colours, merge_sorted)):
298
            # FIXME: at this point we should be able to show the graph order
299
            # and lines with no message or commit data - and then incrementally
300
            # fill the timestamp, committer etc data as desired.
301
            message = revision.message.split("\n")[0]
302
            if revision.committer is not None:
303
                timestamp = format_date(revision.timestamp, revision.timezone)
304
            else:
305
                timestamp = None
306
            self.model.append([revision, node, last_lines, lines,
307
                               message, revision.committer, timestamp])
308
            self.index[revision] = index
309
            last_lines = lines
310
            if maxnum is not None and index > maxnum:
311
                break
312
313
        self.set_title(branch.nick + " - bzrk")
314
        self.treeview.set_model(self.model)
315
316
    def _treeview_cursor_cb(self, *args):
317
        """Callback for when the treeview cursor changes."""
318
        (path, col) = self.treeview.get_cursor()
319
        revision = self.model[path][0]
320
321
        self.back_button.set_sensitive(len(self.parent_ids[revision]) > 0)
322
        self.fwd_button.set_sensitive(len(self.children[revision]) > 0)
323
324
        if revision.committer is not None:
325
            branchnick = ""
326
            committer = revision.committer
327
            timestamp = format_date(revision.timestamp, revision.timezone)
328
            message = revision.message
329
            try:
330
                branchnick = revision.properties['branch-nick']
331
            except KeyError:
332
                pass
333
334
        else:
335
            committer = ""
336
            timestamp = ""
337
            message = ""
338
            branchnick = ""
339
340
        self.revid_label.set_text(revision.revision_id)
341
        self.branchnick_label.set_text(branchnick)
342
343
        self.committer_label.set_text(committer)
344
        self.timestamp_label.set_text(timestamp)
345
        self.message_buffer.set_text(message)
346
347
        for widget in self.parents_widgets:
348
            self.parents_table.remove(widget)
349
350
        self.parents_widgets = []
351
        self.parents_table.resize(max(len(self.parent_ids[revision]), 1), 2)
352
        
353
        for idx, parent_id in enumerate(self.parent_ids[revision]):
354
            align = gtk.Alignment(0.0, 0.0)
355
            self.parents_widgets.append(align)
356
            self.parents_table.attach(align, 1, 2, idx, idx + 1,
357
                                      gtk.EXPAND | gtk.FILL, gtk.FILL)
358
            align.show()
359
360
            hbox = gtk.HBox(False, spacing=6)
361
            align.add(hbox)
362
            hbox.show()
363
364
            image = gtk.Image()
365
            image.set_from_stock(
366
                gtk.STOCK_FIND, gtk.ICON_SIZE_SMALL_TOOLBAR)
367
            image.show()
368
369
            button = gtk.Button()
370
            button.add(image)
371
            button.set_sensitive(self.app is not None)
372
            button.connect("clicked", self._show_clicked_cb,
373
                           revision.revision_id, parent_id)
374
            hbox.pack_start(button, expand=False, fill=True)
375
            button.show()
376
377
            button = gtk.Button(parent_id)
378
            button.set_use_underline(False)
379
            button.connect("clicked", self._go_clicked_cb, parent_id)
380
            hbox.pack_start(button, expand=False, fill=True)
381
            button.show()
382
383
384
    def _back_clicked_cb(self, *args):
385
        """Callback for when the back button is clicked."""
386
        (path, col) = self.treeview.get_cursor()
387
        revision = self.model[path][0]
388
        if not len(self.parent_ids[revision]):
389
            return
390
391
        for parent_id in self.parent_ids[revision]:
392
            parent = self.revisions[parent_id]
393
            if same_branch(revision, parent):
394
                self.treeview.set_cursor(self.index[parent])
395
                break
396
        else:
397
            next = self.revisions[self.parent_ids[revision][0]]
398
            self.treeview.set_cursor(self.index[next])
399
        self.treeview.grab_focus()
400
401
    def _fwd_clicked_cb(self, *args):
402
        """Callback for when the forward button is clicked."""
403
        (path, col) = self.treeview.get_cursor()
404
        revision = self.model[path][0]
405
        if not len(self.children[revision]):
406
            return
407
408
        for child in self.children[revision]:
409
            if same_branch(child, revision):
410
                self.treeview.set_cursor(self.index[child])
411
                break
412
        else:
413
            prev = list(self.children[revision])[0]
414
            self.treeview.set_cursor(self.index[prev])
415
        self.treeview.grab_focus()
416
417
    def _go_clicked_cb(self, widget, revid):
418
        """Callback for when the go button for a parent is clicked."""
419
        self.treeview.set_cursor(self.index[self.revisions[revid]])
420
        self.treeview.grab_focus()
421
422
    def _show_clicked_cb(self, widget, revid, parentid):
423
        """Callback for when the show button for a parent is clicked."""
424
        if self.app is not None:
425
            self.app.show_diff(self.branch, revid, parentid)
426
        self.treeview.grab_focus()
427
428
    def _treeview_row_activated_cb(self, widget, path, col):
429
        # TODO: more than one parent
430
        """Callback for when a treeview row gets activated."""
431
        revision = self.model[path][0]
432
        parent_id = self.parent_ids[revision][0]
433
        if self.app is not None:
434
            self.app.show_diff(self.branch, revision.revision_id, parent_id)
435
        self.treeview.grab_focus()