/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 olive/frontend/gtk/viz/branchwin.py

  • Committer: Szilveszter Farkas (Phanatic)
  • Date: 2006-08-10 17:25:30 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-20060810172530-d63fde9240356aab
Implemented Log functionality (via bzrk).

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

    * olive/frontend/gtk/log.py: implemented OliveLog (calls BzrkApp)
    * olive/frontend/gtk/viz/diffwin.py: made it bzrlib 0.9 API compatible
    * olive/frontend/gtk/viz/bzrkapp.py: don't quit main loop
    * olive/frontend/gtk/viz: added bzrk codebase for Log functionality
    * setup.py: added olive.frontend.gtk.viz package

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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()