/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz
1 by Scott James Remnant
Commit the first version of bzrk.
1
#!/usr/bin/python
2
# -*- coding: UTF-8 -*-
3
"""Branch window.
4
5
This module contains the code to manage the branch information window,
6
which contains both the revision graph and details panes.
7
"""
8
9
__copyright__ = "Copyright © 2005 Canonical Ltd."
10
__author__    = "Scott James Remnant <scott@ubuntu.com>"
11
12
13
import os
14
15
import gtk
16
import gobject
17
import pango
18
19
from bzrlib.osutils import format_date
20
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
21
from graph import distances, graph, same_branch
1 by Scott James Remnant
Commit the first version of bzrk.
22
from graphcell import CellRendererGraph
23
24
25
class BranchWindow(gtk.Window):
26
    """Branch window.
27
28
    This object represents and manages a single window containing information
29
    for a particular branch.
30
    """
31
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
32
    def __init__(self, app=None):
1 by Scott James Remnant
Commit the first version of bzrk.
33
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
34
        self.set_border_width(0)
35
        self.set_title("bzrk")
36
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
37
        self.app = app
38
1 by Scott James Remnant
Commit the first version of bzrk.
39
        # Use three-quarters of the screen by default
40
        screen = self.get_screen()
4 by Scott James Remnant
Use the size of the first monitor rather than the entire screen
41
        monitor = screen.get_monitor_geometry(0)
42
        width = int(monitor.width * 0.75)
43
        height = int(monitor.height * 0.75)
1 by Scott James Remnant
Commit the first version of bzrk.
44
        self.set_default_size(width, height)
45
46
        # FIXME AndyFitz!
47
        icon = self.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)
48
        self.set_icon(icon)
49
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
50
        self.accel_group = gtk.AccelGroup()
51
        self.add_accel_group(self.accel_group)
52
1 by Scott James Remnant
Commit the first version of bzrk.
53
        self.construct()
54
55
    def construct(self):
56
        """Construct the window contents."""
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
57
        paned = gtk.VPaned()
58
        paned.pack1(self.construct_top(), resize=True, shrink=False)
7 by Scott James Remnant
Put some information about the highlighted revision in the bottom pane,
59
        paned.pack2(self.construct_bottom(), resize=False, shrink=True)
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
60
        self.add(paned)
61
        paned.show()
62
63
    def construct_top(self):
64
        """Construct the top-half of the window."""
65
        vbox = gtk.VBox(spacing=6)
66
        vbox.set_border_width(12)
67
        vbox.show()
68
1 by Scott James Remnant
Commit the first version of bzrk.
69
        scrollwin = gtk.ScrolledWindow()
70
        scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
71
        scrollwin.set_shadow_type(gtk.SHADOW_IN)
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
72
        vbox.pack_start(scrollwin, expand=True, fill=True)
1 by Scott James Remnant
Commit the first version of bzrk.
73
        scrollwin.show()
74
75
        self.treeview = gtk.TreeView()
76
        self.treeview.set_rules_hint(True)
77
        self.treeview.set_search_column(4)
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
78
        self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
1 by Scott James Remnant
Commit the first version of bzrk.
79
        scrollwin.add(self.treeview)
80
        self.treeview.show()
81
82
        cell = CellRendererGraph()
83
        column = gtk.TreeViewColumn()
84
        column.set_resizable(False)
85
        column.pack_start(cell, expand=False)
86
        column.add_attribute(cell, "node", 1)
87
        column.add_attribute(cell, "in-lines", 2)
88
        column.add_attribute(cell, "out-lines", 3)
89
        self.treeview.append_column(column)
90
91
        cell = gtk.CellRendererText()
92
        cell.set_property("width-chars", 40)
93
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
94
        column = gtk.TreeViewColumn("Message")
95
        column.set_resizable(True)
96
        column.pack_start(cell, expand=True)
97
        column.add_attribute(cell, "text", 4)
98
        self.treeview.append_column(column)
99
100
        cell = gtk.CellRendererText()
101
        cell.set_property("width-chars", 40)
102
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
103
        column = gtk.TreeViewColumn("Committer")
104
        column.set_resizable(True)
105
        column.pack_start(cell, expand=True)
106
        column.add_attribute(cell, "text", 5)
107
        self.treeview.append_column(column)
108
109
        cell = gtk.CellRendererText()
110
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
111
        column = gtk.TreeViewColumn("Date")
112
        column.set_resizable(True)
113
        column.pack_start(cell, expand=True)
114
        column.add_attribute(cell, "text", 6)
115
        self.treeview.append_column(column)
116
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
117
        hbox = gtk.HBox(False, spacing=6)
118
        vbox.pack_start(hbox, expand=False, fill=False)
119
        hbox.show()
120
121
        self.back_button = gtk.Button(stock=gtk.STOCK_GO_BACK)
122
        self.back_button.add_accelerator("clicked", self.accel_group, ord('['),
123
                                         0, 0)
124
        self.back_button.connect("clicked", self._back_clicked_cb)
125
        hbox.pack_start(self.back_button, expand=False, fill=True)
126
        self.back_button.show()
127
128
        self.fwd_button = gtk.Button(stock=gtk.STOCK_GO_FORWARD)
129
        self.fwd_button.add_accelerator("clicked", self.accel_group, ord(']'),
130
                                        0, 0)
131
        self.fwd_button.connect("clicked", self._fwd_clicked_cb)
132
        hbox.pack_start(self.fwd_button, expand=False, fill=True)
133
        self.fwd_button.show()
134
135
        return vbox
136
137
    def construct_bottom(self):
138
        """Construct the bottom half of the window."""
7 by Scott James Remnant
Put some information about the highlighted revision in the bottom pane,
139
        vbox = gtk.VBox(False, spacing=6)
140
        vbox.set_border_width(12)
8 by Scott James Remnant
Fiddle around with the alignment and size allocations a little
141
        (width, height) = self.get_size()
142
        vbox.set_size_request(width, int(height / 2.5))
7 by Scott James Remnant
Put some information about the highlighted revision in the bottom pane,
143
        vbox.show()
144
145
        self.table = gtk.Table(rows=4, columns=2)
146
        self.table.set_row_spacings(6)
147
        self.table.set_col_spacings(6)
148
        vbox.pack_start(self.table, expand=False, fill=True)
149
        self.table.show()
150
8 by Scott James Remnant
Fiddle around with the alignment and size allocations a little
151
        align = gtk.Alignment(0.0, 0.5)
7 by Scott James Remnant
Put some information about the highlighted revision in the bottom pane,
152
        label = gtk.Label()
153
        label.set_markup("<b>Revision:</b>")
154
        align.add(label)
155
        self.table.attach(align, 0, 1, 0, 1, gtk.FILL, gtk.FILL)
156
        label.show()
157
        align.show()
158
8 by Scott James Remnant
Fiddle around with the alignment and size allocations a little
159
        align = gtk.Alignment(0.0, 0.5)
7 by Scott James Remnant
Put some information about the highlighted revision in the bottom pane,
160
        self.revid_label = gtk.Label()
161
        self.revid_label.set_selectable(True)
162
        align.add(self.revid_label)
163
        self.table.attach(align, 1, 2, 0, 1, gtk.EXPAND | gtk.FILL, gtk.FILL)
164
        self.revid_label.show()
165
        align.show()
166
8 by Scott James Remnant
Fiddle around with the alignment and size allocations a little
167
        align = gtk.Alignment(0.0, 0.5)
7 by Scott James Remnant
Put some information about the highlighted revision in the bottom pane,
168
        label = gtk.Label()
169
        label.set_markup("<b>Committer:</b>")
170
        align.add(label)
171
        self.table.attach(align, 0, 1, 1, 2, gtk.FILL, gtk.FILL)
172
        label.show()
173
        align.show()
174
8 by Scott James Remnant
Fiddle around with the alignment and size allocations a little
175
        align = gtk.Alignment(0.0, 0.5)
7 by Scott James Remnant
Put some information about the highlighted revision in the bottom pane,
176
        self.committer_label = gtk.Label()
177
        self.committer_label.set_selectable(True)
178
        align.add(self.committer_label)
179
        self.table.attach(align, 1, 2, 1, 2, gtk.EXPAND | gtk.FILL, gtk.FILL)
180
        self.committer_label.show()
181
        align.show()
182
8 by Scott James Remnant
Fiddle around with the alignment and size allocations a little
183
        align = gtk.Alignment(0.0, 0.5)
7 by Scott James Remnant
Put some information about the highlighted revision in the bottom pane,
184
        label = gtk.Label()
185
        label.set_markup("<b>Timestamp:</b>")
186
        align.add(label)
187
        self.table.attach(align, 0, 1, 2, 3, gtk.FILL, gtk.FILL)
188
        label.show()
189
        align.show()
190
8 by Scott James Remnant
Fiddle around with the alignment and size allocations a little
191
        align = gtk.Alignment(0.0, 0.5)
7 by Scott James Remnant
Put some information about the highlighted revision in the bottom pane,
192
        self.timestamp_label = gtk.Label()
193
        self.timestamp_label.set_selectable(True)
194
        align.add(self.timestamp_label)
195
        self.table.attach(align, 1, 2, 2, 3, gtk.EXPAND | gtk.FILL, gtk.FILL)
196
        self.timestamp_label.show()
197
        align.show()
198
8 by Scott James Remnant
Fiddle around with the alignment and size allocations a little
199
        align = gtk.Alignment(0.0, 0.5)
7 by Scott James Remnant
Put some information about the highlighted revision in the bottom pane,
200
        label = gtk.Label()
201
        label.set_markup("<b>Parents:</b>")
202
        align.add(label)
203
        self.table.attach(align, 0, 1, 3, 4, gtk.FILL, gtk.FILL)
204
        label.show()
205
        align.show()
206
207
        self.parents_widgets = []
208
209
        scrollwin = gtk.ScrolledWindow()
210
        scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
211
        scrollwin.set_shadow_type(gtk.SHADOW_IN)
212
        vbox.pack_start(scrollwin, expand=True, fill=True)
213
        scrollwin.show()
214
215
        self.message_buffer = gtk.TextBuffer()
216
        textview = gtk.TextView(self.message_buffer)
217
        textview.set_editable(False)
218
        textview.set_wrap_mode(gtk.WRAP_WORD)
219
        textview.modify_font(pango.FontDescription("Monospace"))
220
        scrollwin.add(textview)
221
        textview.show()
222
223
        return vbox
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
224
1 by Scott James Remnant
Commit the first version of bzrk.
225
    def set_branch(self, branch, start):
226
        """Set the branch and start position for this window.
227
228
        Creates a new TreeModel and populates it with information about
229
        the new branch before updating the window title and model of the
230
        treeview itself.
231
        """
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
232
        self.branch = branch
233
1 by Scott James Remnant
Commit the first version of bzrk.
234
        # [ revision, node, last_lines, lines, message, committer, timestamp ]
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
235
        self.model = gtk.ListStore(gobject.TYPE_PYOBJECT,
236
                                   gobject.TYPE_PYOBJECT,
237
                                   gobject.TYPE_PYOBJECT,
238
                                   gobject.TYPE_PYOBJECT,
239
                                   str, str, str)
240
        self.index = {}
241
        index = 0
1 by Scott James Remnant
Commit the first version of bzrk.
242
243
        last_lines = []
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
244
        (revids, self.revisions, colours, self.children) \
245
                 = distances(branch, start)
246
        for revision, node, lines in graph(revids, self.revisions, colours):
1 by Scott James Remnant
Commit the first version of bzrk.
247
            message = revision.message.split("\n")[0]
248
            if revision.committer is not None:
249
                timestamp = format_date(revision.timestamp, revision.timezone)
250
            else:
251
                timestamp = None
252
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
253
            self.model.append([ revision, node, last_lines, lines,
254
                                message, revision.committer, timestamp ])
255
            self.index[revision] = index
256
            index += 1
257
1 by Scott James Remnant
Commit the first version of bzrk.
258
            last_lines = lines
259
260
        self.set_title(os.path.basename(branch.base) + " - bzrk")
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
261
        self.treeview.set_model(self.model)
262
263
    def _treeview_cursor_cb(self, *args):
264
        """Callback for when the treeview cursor changes."""
265
        (path, col) = self.treeview.get_cursor()
266
        revision = self.model[path][0]
267
5 by Scott James Remnant
Reverse the meaning of the Back and Forward buttons so Back actually
268
        self.back_button.set_sensitive(len(revision.parent_ids) > 0)
269
        self.fwd_button.set_sensitive(len(self.children[revision]) > 0)
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
270
7 by Scott James Remnant
Put some information about the highlighted revision in the bottom pane,
271
        if revision.committer is not None:
272
            committer = revision.committer
273
            timestamp = format_date(revision.timestamp, revision.timezone)
274
            message = revision.message
275
        else:
276
            committer = ""
277
            timestamp = ""
278
            message = ""
279
280
        self.revid_label.set_text(revision.revision_id)
281
        self.committer_label.set_text(committer)
282
        self.timestamp_label.set_text(timestamp)
283
        self.message_buffer.set_text(message)
284
285
        for widget in self.parents_widgets:
286
            self.table.remove(widget)
287
288
        self.parents_widgets = []
289
        self.table.resize(4 + len(revision.parent_ids) - 1, 2)
290
        for idx, parent_id in enumerate(revision.parent_ids):
291
            self.table.set_row_spacing(idx + 3, 0)
292
293
            align = gtk.Alignment(0.0, 0.0)
294
            self.parents_widgets.append(align)
295
            self.table.attach(align, 1, 2, idx + 3, idx + 4,
296
                              gtk.EXPAND | gtk.FILL, gtk.FILL)
297
            align.show()
298
299
            hbox = gtk.HBox(False, 0)
300
            align.add(hbox)
301
            hbox.show()
302
303
            label = gtk.Label(parent_id)
304
            label.set_selectable(True)
305
            hbox.pack_start(label, expand=False, fill=True)
306
            label.show()
307
308
            image = gtk.Image()
309
            image.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU)
310
            image.show()
311
312
            button = gtk.Button()
313
            button.add(image)
314
            button.set_relief(gtk.RELIEF_NONE)
315
            button.connect("clicked", self._go_clicked_cb, parent_id)
316
            hbox.pack_start(button, expand=False, fill=True)
317
            button.show()
318
319
            image = gtk.Image()
320
            image.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU)
321
            image.show()
322
323
            button = gtk.Button()
324
            button.add(image)
325
            button.set_relief(gtk.RELIEF_NONE)
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
326
            button.set_sensitive(self.app is not None)
327
            button.connect("clicked", self._show_clicked_cb,
328
                           revision.revision_id, parent_id)
7 by Scott James Remnant
Put some information about the highlighted revision in the bottom pane,
329
            hbox.pack_start(button, expand=False, fill=True)
330
            button.show()
331
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
332
    def _back_clicked_cb(self, *args):
333
        """Callback for when the back button is clicked."""
334
        (path, col) = self.treeview.get_cursor()
335
        revision = self.model[path][0]
336
        if not len(revision.parent_ids):
337
            return
338
339
        for parent_id in revision.parent_ids:
340
            parent = self.revisions[parent_id]
341
            if same_branch(revision, parent):
342
                self.treeview.set_cursor(self.index[parent])
343
                break
344
        else:
345
            next = self.revisions[revision.parent_ids[0]]
346
            self.treeview.set_cursor(self.index[next])
13 by Scott James Remnant
Keep the focus on the treeview
347
        self.treeview.grab_focus()
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
348
5 by Scott James Remnant
Reverse the meaning of the Back and Forward buttons so Back actually
349
    def _fwd_clicked_cb(self, *args):
350
        """Callback for when the forward button is clicked."""
351
        (path, col) = self.treeview.get_cursor()
352
        revision = self.model[path][0]
353
        if not len(self.children[revision]):
354
            return
355
356
        for child in self.children[revision]:
357
            if same_branch(child, revision):
358
                self.treeview.set_cursor(self.index[child])
359
                break
360
        else:
361
            prev = list(self.children[revision])[0]
362
            self.treeview.set_cursor(self.index[prev])
13 by Scott James Remnant
Keep the focus on the treeview
363
        self.treeview.grab_focus()
7 by Scott James Remnant
Put some information about the highlighted revision in the bottom pane,
364
365
    def _go_clicked_cb(self, widget, revid):
366
        """Callback for when the go button for a parent is clicked."""
367
        self.treeview.set_cursor(self.index[self.revisions[revid]])
13 by Scott James Remnant
Keep the focus on the treeview
368
        self.treeview.grab_focus()
7 by Scott James Remnant
Put some information about the highlighted revision in the bottom pane,
369
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
370
    def _show_clicked_cb(self, widget, revid, parentid):
7 by Scott James Remnant
Put some information about the highlighted revision in the bottom pane,
371
        """Callback for when the show button for a parent is clicked."""
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
372
        if self.app is not None:
373
            self.app.show_diff(self.branch, revid, parentid)
13 by Scott James Remnant
Keep the focus on the treeview
374
        self.treeview.grab_focus()