/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
# -*- coding: UTF-8 -*-
2
"""Branch window.
3
4
This module contains the code to manage the branch information window,
5
which contains both the revision graph and details panes.
6
"""
7
8
__copyright__ = "Copyright © 2005 Canonical Ltd."
9
__author__    = "Scott James Remnant <scott@ubuntu.com>"
10
11
12
import gtk
13
import gobject
14
import pango
15
16
from bzrlib.osutils import format_date
17
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
18
from graph import distances, graph, same_branch
1 by Scott James Remnant
Commit the first version of bzrk.
19
from graphcell import CellRendererGraph
20
21
22
class BranchWindow(gtk.Window):
23
    """Branch window.
24
25
    This object represents and manages a single window containing information
26
    for a particular branch.
27
    """
28
275.1.6 by Daniel Schierbeck
Made Ctrl-W call gtk.main_quit if the window has no parent.
29
    def __init__(self, parent=None):
1 by Scott James Remnant
Commit the first version of bzrk.
30
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
31
        self.set_border_width(0)
32
        self.set_title("bzrk")
33
275.1.6 by Daniel Schierbeck
Made Ctrl-W call gtk.main_quit if the window has no parent.
34
        self._parent = parent
35
36
        self.connect('key-press-event', self._on_key_pressed)
275.1.1 by Daniel Schierbeck
Close branch visualization window when 'destroy' message is received.
37
1 by Scott James Remnant
Commit the first version of bzrk.
38
        # Use three-quarters of the screen by default
39
        screen = self.get_screen()
4 by Scott James Remnant
Use the size of the first monitor rather than the entire screen
40
        monitor = screen.get_monitor_geometry(0)
41
        width = int(monitor.width * 0.75)
42
        height = int(monitor.height * 0.75)
1 by Scott James Remnant
Commit the first version of bzrk.
43
        self.set_default_size(width, height)
44
45
        # FIXME AndyFitz!
46
        icon = self.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)
47
        self.set_icon(icon)
48
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
49
        self.accel_group = gtk.AccelGroup()
50
        self.add_accel_group(self.accel_group)
51
1 by Scott James Remnant
Commit the first version of bzrk.
52
        self.construct()
53
54
    def construct(self):
55
        """Construct the window contents."""
44 by David Allouche
reorganise branch window
56
        vbox = gtk.VBox(spacing=0)
57
        self.add(vbox)
58
59
        vbox.pack_start(self.construct_navigation(), expand=False, fill=True)
60
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
61
        paned = gtk.VPaned()
62
        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,
63
        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
64
        paned.show()
44 by David Allouche
reorganise branch window
65
        vbox.pack_start(paned, expand=True, fill=True)
66
        vbox.set_focus_child(paned)
67
68
        vbox.show()
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
69
70
    def construct_top(self):
71
        """Construct the top-half of the window."""
1 by Scott James Remnant
Commit the first version of bzrk.
72
        scrollwin = gtk.ScrolledWindow()
73
        scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
74
        scrollwin.set_shadow_type(gtk.SHADOW_IN)
75
        scrollwin.show()
76
77
        self.treeview = gtk.TreeView()
78
        self.treeview.set_rules_hint(True)
79
        self.treeview.set_search_column(4)
280.1.2 by Daniel Schierbeck
Switched to handle the 'changed' signal from the treeview's treeselection instead of the 'cursor-changed' signal from the treeview itself, allowing more flexibility (particularly the ability to handle programmatic selections)
80
        self.treeview.get_selection().connect("changed", self._treeselection_changed_cb)
47 by Wouter van Heyst
rename _treeview_clicked_cb to correcter _treeview_row_activated_cb
81
        self.treeview.connect("row-activated", self._treeview_row_activated_cb)
226 by Jelmer Vernooij
Add context menu in bzrk.
82
        self.treeview.connect("button-release-event", 
83
                self._treeview_row_mouseclick)
1 by Scott James Remnant
Commit the first version of bzrk.
84
        scrollwin.add(self.treeview)
85
        self.treeview.show()
86
87
        cell = CellRendererGraph()
88
        column = gtk.TreeViewColumn()
45 by David Allouche
resizable graph column
89
        column.set_resizable(True)
1 by Scott James Remnant
Commit the first version of bzrk.
90
        column.pack_start(cell, expand=False)
91
        column.add_attribute(cell, "node", 1)
92
        column.add_attribute(cell, "in-lines", 2)
93
        column.add_attribute(cell, "out-lines", 3)
94
        self.treeview.append_column(column)
95
96
        cell = gtk.CellRendererText()
97
        cell.set_property("width-chars", 40)
98
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
99
        column = gtk.TreeViewColumn("Message")
100
        column.set_resizable(True)
101
        column.pack_start(cell, expand=True)
102
        column.add_attribute(cell, "text", 4)
103
        self.treeview.append_column(column)
104
105
        cell = gtk.CellRendererText()
106
        cell.set_property("width-chars", 40)
107
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
108
        column = gtk.TreeViewColumn("Committer")
109
        column.set_resizable(True)
110
        column.pack_start(cell, expand=True)
111
        column.add_attribute(cell, "text", 5)
112
        self.treeview.append_column(column)
113
114
        cell = gtk.CellRendererText()
115
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
116
        column = gtk.TreeViewColumn("Date")
117
        column.set_resizable(True)
118
        column.pack_start(cell, expand=True)
119
        column.add_attribute(cell, "text", 6)
120
        self.treeview.append_column(column)
121
44 by David Allouche
reorganise branch window
122
        return scrollwin
123
124
    def construct_navigation(self):
125
        """Construct the navigation buttons."""
126
        frame = gtk.Frame()
127
        frame.set_shadow_type(gtk.SHADOW_OUT)
128
        frame.show()
129
        
130
        hbox = gtk.HBox(spacing=12)
131
        frame.add(hbox)
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
132
        hbox.show()
133
134
        self.back_button = gtk.Button(stock=gtk.STOCK_GO_BACK)
44 by David Allouche
reorganise branch window
135
        self.back_button.set_relief(gtk.RELIEF_NONE)
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
136
        self.back_button.add_accelerator("clicked", self.accel_group, ord('['),
137
                                         0, 0)
138
        self.back_button.connect("clicked", self._back_clicked_cb)
139
        hbox.pack_start(self.back_button, expand=False, fill=True)
140
        self.back_button.show()
141
142
        self.fwd_button = gtk.Button(stock=gtk.STOCK_GO_FORWARD)
44 by David Allouche
reorganise branch window
143
        self.fwd_button.set_relief(gtk.RELIEF_NONE)
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
144
        self.fwd_button.add_accelerator("clicked", self.accel_group, ord(']'),
145
                                        0, 0)
146
        self.fwd_button.connect("clicked", self._fwd_clicked_cb)
147
        hbox.pack_start(self.fwd_button, expand=False, fill=True)
148
        self.fwd_button.show()
149
44 by David Allouche
reorganise branch window
150
        return frame
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
151
152
    def construct_bottom(self):
153
        """Construct the bottom half of the window."""
147 by Jelmer Vernooij
Remove a bunch of duplicate functionality.
154
        from bzrlib.plugins.gtk.logview import LogView
155
        self.logview = LogView()
44 by David Allouche
reorganise branch window
156
        (width, height) = self.get_size()
147 by Jelmer Vernooij
Remove a bunch of duplicate functionality.
157
        self.logview.set_size_request(width, int(height / 2.5))
158
        self.logview.show()
159
        self.logview.set_show_callback(self._show_clicked_cb)
160
        self.logview.set_go_callback(self._go_clicked_cb)
161
        return self.logview
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
162
40 by David Allouche
remove --robust, pyflakes fixes, update README
163
    def set_branch(self, branch, start, maxnum):
1 by Scott James Remnant
Commit the first version of bzrk.
164
        """Set the branch and start position for this window.
165
166
        Creates a new TreeModel and populates it with information about
167
        the new branch before updating the window title and model of the
168
        treeview itself.
169
        """
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
170
        self.branch = branch
171
1 by Scott James Remnant
Commit the first version of bzrk.
172
        # [ 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
173
        self.model = gtk.ListStore(gobject.TYPE_PYOBJECT,
174
                                   gobject.TYPE_PYOBJECT,
175
                                   gobject.TYPE_PYOBJECT,
176
                                   gobject.TYPE_PYOBJECT,
177
                                   str, str, str)
178
        self.index = {}
265.1.1 by Gary van der Merwe
Show bzr viz interface quickly.
179
        self.set_title(branch.nick + " - bzrk")
180
        gobject.idle_add(self.populate_model, start, maxnum)
181
182
    def populate_model(self, start, maxnum):
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
183
        index = 0
265.1.1 by Gary van der Merwe
Show bzr viz interface quickly.
184
        
1 by Scott James Remnant
Commit the first version of bzrk.
185
        last_lines = []
40 by David Allouche
remove --robust, pyflakes fixes, update README
186
        (self.revisions, colours, self.children, self.parent_ids,
278.1.1 by John Arbash Meinel
Pass in the maxnum value to the distances function.
187
            merge_sorted) = distances(self.branch.repository, start,
188
                                      maxnum=maxnum)
41 by David Allouche
restore --maxnum functionality, reflush some comments
189
        for (index, (revision, node, lines)) in enumerate(graph(
190
                self.revisions, colours, merge_sorted)):
191
            # FIXME: at this point we should be able to show the graph order
192
            # and lines with no message or commit data - and then incrementally
193
            # fill the timestamp, committer etc data as desired.
1 by Scott James Remnant
Commit the first version of bzrk.
194
            message = revision.message.split("\n")[0]
195
            if revision.committer is not None:
196
                timestamp = format_date(revision.timestamp, revision.timezone)
197
            else:
198
                timestamp = None
41 by David Allouche
restore --maxnum functionality, reflush some comments
199
            self.model.append([revision, node, last_lines, lines,
200
                               message, revision.committer, timestamp])
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
201
            self.index[revision] = index
1 by Scott James Remnant
Commit the first version of bzrk.
202
            last_lines = lines
41 by David Allouche
restore --maxnum functionality, reflush some comments
203
            if maxnum is not None and index > maxnum:
204
                break
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
205
        self.treeview.set_model(self.model)
280.1.1 by Daniel Schierbeck
Select the tip revision when opening the visualization window
206
        self.treeview.get_selection().select_path(0)
265.1.1 by Gary van der Merwe
Show bzr viz interface quickly.
207
        return False
208
    
275.1.5 by Daniel Schierbeck
Made windows close correctly on Ctrl-W and made the application quit on Ctrl-Q.
209
    def _on_key_pressed(self, widget, event):
210
        """ Key press event handler. """
211
        keyname = gtk.gdk.keyval_name(event.keyval)
212
        func = getattr(self, '_on_key_press_' + keyname, None)
213
        if func:
214
            return func(event)
215
216
    def _on_key_press_w(self, event):
217
        if event.state & gtk.gdk.CONTROL_MASK:
218
            self.destroy()
275.1.6 by Daniel Schierbeck
Made Ctrl-W call gtk.main_quit if the window has no parent.
219
            if self._parent is None:
220
                gtk.main_quit()
275.1.5 by Daniel Schierbeck
Made windows close correctly on Ctrl-W and made the application quit on Ctrl-Q.
221
222
    def _on_key_press_q(self, event):
223
        if event.state & gtk.gdk.CONTROL_MASK:
224
            gtk.main_quit()
280.1.2 by Daniel Schierbeck
Switched to handle the 'changed' signal from the treeview's treeselection instead of the 'cursor-changed' signal from the treeview itself, allowing more flexibility (particularly the ability to handle programmatic selections)
225
226
    def _treeselection_changed_cb(self, selection, *args):
227
        """Callback for when the treeview changes."""
228
        (model, selected_rows) = selection.get_selected_rows()
229
        if len(selected_rows) > 0:
230
            revision = self.model[selected_rows[0]][0]
231
232
            self.back_button.set_sensitive(len(self.parent_ids[revision]) > 0)
233
            self.fwd_button.set_sensitive(len(self.children[revision]) > 0)
234
            tags = []
235
            if self.branch.supports_tags():
236
                tagdict = self.branch.tags.get_reverse_tag_dict()
237
                if tagdict.has_key(revision.revision_id):
238
                    tags = tagdict[revision.revision_id]
239
            self.logview.set_revision(revision, tags)
7 by Scott James Remnant
Put some information about the highlighted revision in the bottom pane,
240
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
241
    def _back_clicked_cb(self, *args):
242
        """Callback for when the back button is clicked."""
243
        (path, col) = self.treeview.get_cursor()
244
        revision = self.model[path][0]
20 by David Allouche
ignore redundent parents
245
        if not len(self.parent_ids[revision]):
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
246
            return
247
20 by David Allouche
ignore redundent parents
248
        for parent_id in self.parent_ids[revision]:
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
249
            parent = self.revisions[parent_id]
250
            if same_branch(revision, parent):
251
                self.treeview.set_cursor(self.index[parent])
252
                break
253
        else:
20 by David Allouche
ignore redundent parents
254
            next = self.revisions[self.parent_ids[revision][0]]
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
255
            self.treeview.set_cursor(self.index[next])
13 by Scott James Remnant
Keep the focus on the treeview
256
        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
257
5 by Scott James Remnant
Reverse the meaning of the Back and Forward buttons so Back actually
258
    def _fwd_clicked_cb(self, *args):
259
        """Callback for when the forward button is clicked."""
260
        (path, col) = self.treeview.get_cursor()
261
        revision = self.model[path][0]
262
        if not len(self.children[revision]):
263
            return
264
265
        for child in self.children[revision]:
266
            if same_branch(child, revision):
267
                self.treeview.set_cursor(self.index[child])
268
                break
269
        else:
270
            prev = list(self.children[revision])[0]
271
            self.treeview.set_cursor(self.index[prev])
13 by Scott James Remnant
Keep the focus on the treeview
272
        self.treeview.grab_focus()
7 by Scott James Remnant
Put some information about the highlighted revision in the bottom pane,
273
147 by Jelmer Vernooij
Remove a bunch of duplicate functionality.
274
    def _go_clicked_cb(self, revid):
7 by Scott James Remnant
Put some information about the highlighted revision in the bottom pane,
275
        """Callback for when the go button for a parent is clicked."""
276
        self.treeview.set_cursor(self.index[self.revisions[revid]])
13 by Scott James Remnant
Keep the focus on the treeview
277
        self.treeview.grab_focus()
7 by Scott James Remnant
Put some information about the highlighted revision in the bottom pane,
278
152 by Jelmer Vernooij
Cleanup some more code.
279
    def show_diff(self, branch, revid, parentid):
280
        """Open a new window to show a diff between the given revisions."""
281
        from bzrlib.plugins.gtk.diff import DiffWindow
282
        window = DiffWindow()
226 by Jelmer Vernooij
Add context menu in bzrk.
283
        (parent_tree, rev_tree) = branch.repository.revision_trees([parentid, 
284
                                                                   revid])
152 by Jelmer Vernooij
Cleanup some more code.
285
        description = revid + " - " + branch.nick
286
        window.set_diff(description, rev_tree, parent_tree)
287
        window.show()
288
147 by Jelmer Vernooij
Remove a bunch of duplicate functionality.
289
    def _show_clicked_cb(self, revid, parentid):
7 by Scott James Remnant
Put some information about the highlighted revision in the bottom pane,
290
        """Callback for when the show button for a parent is clicked."""
152 by Jelmer Vernooij
Cleanup some more code.
291
        self.show_diff(self.branch, revid, parentid)
13 by Scott James Remnant
Keep the focus on the treeview
292
        self.treeview.grab_focus()
46 by Wouter van Heyst
show diff on row activation, LP# 44591
293
226 by Jelmer Vernooij
Add context menu in bzrk.
294
    def _treeview_row_mouseclick(self, widget, event):
295
        from bzrlib.plugins.gtk.revisionmenu import RevisionPopupMenu
228 by Jelmer Vernooij
Remove unused code, prefer questions to check boxes.
296
        if event.button == 3:
297
            menu = RevisionPopupMenu(self.branch.repository, 
230 by Jelmer Vernooij
Initial work towards supporting multiple revisions.
298
                [x.revision_id for x in self.selected_revisions()],
299
                self.branch)
228 by Jelmer Vernooij
Remove unused code, prefer questions to check boxes.
300
            menu.popup(None, None, None, event.button, event.get_time())
226 by Jelmer Vernooij
Add context menu in bzrk.
301
302
    def selected_revision(self, path):
303
        return self.model[path][0]
304
305
    def selected_revisions(self):
306
        return [self.selected_revision(path) for path in \
307
                self.treeview.get_selection().get_selected_rows()[1]]
308
47 by Wouter van Heyst
rename _treeview_clicked_cb to correcter _treeview_row_activated_cb
309
    def _treeview_row_activated_cb(self, widget, path, col):
46 by Wouter van Heyst
show diff on row activation, LP# 44591
310
        # TODO: more than one parent
47 by Wouter van Heyst
rename _treeview_clicked_cb to correcter _treeview_row_activated_cb
311
        """Callback for when a treeview row gets activated."""
229 by Jelmer Vernooij
Fix regression.
312
        revision = self.selected_revision(path)
74 by Jelmer Vernooij
Fix exception when double-clicking revision without parents.
313
        if len(self.parent_ids[revision]) == 0:
314
            # Ignore revisions without parent
315
            return
46 by Wouter van Heyst
show diff on row activation, LP# 44591
316
        parent_id = self.parent_ids[revision][0]
152 by Jelmer Vernooij
Cleanup some more code.
317
        self.show_diff(self.branch, revision.revision_id, parent_id)
46 by Wouter van Heyst
show diff on row activation, LP# 44591
318
        self.treeview.grab_focus()