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

Merged an approved request (single clicking bookmark).

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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.plugins.gtk.window import Window
 
17
from bzrlib.plugins.gtk.tags import AddTagDialog
 
18
from bzrlib.plugins.gtk.preferences import PreferencesWindow
 
19
from bzrlib.plugins.gtk.branchview import TreeView, treemodel
 
20
from bzrlib.revision import Revision, NULL_REVISION
 
21
from bzrlib.config import BranchConfig
 
22
from bzrlib.config import GlobalConfig
 
23
 
 
24
class BranchWindow(Window):
 
25
    """Branch window.
 
26
 
 
27
    This object represents and manages a single window containing information
 
28
    for a particular branch.
 
29
    """
 
30
 
 
31
    def __init__(self, branch, start_revs, maxnum, parent=None):
 
32
        """Create a new BranchWindow.
 
33
 
 
34
        :param branch: Branch object for branch to show.
 
35
        :param start_revs: Revision ids of top revisions.
 
36
        :param maxnum: Maximum number of revisions to display, 
 
37
                       None for no limit.
 
38
        """
 
39
 
 
40
        Window.__init__(self, parent=parent)
 
41
        self.set_border_width(0)
 
42
 
 
43
        self.branch      = branch
 
44
        self.start_revs  = start_revs
 
45
        self.maxnum      = maxnum
 
46
        self.config      = GlobalConfig()
 
47
 
 
48
        if self.config.get_user_option('viz-compact-view') == 'yes':
 
49
            self.compact_view = True
 
50
        else:
 
51
            self.compact_view = False
 
52
 
 
53
        self.set_title(branch.nick + " - revision history")
 
54
 
 
55
        # Use three-quarters of the screen by default
 
56
        screen = self.get_screen()
 
57
        monitor = screen.get_monitor_geometry(0)
 
58
        width = int(monitor.width * 0.75)
 
59
        height = int(monitor.height * 0.75)
 
60
        self.set_default_size(width, height)
 
61
 
 
62
        # FIXME AndyFitz!
 
63
        icon = self.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)
 
64
        self.set_icon(icon)
 
65
 
 
66
        gtk.accel_map_add_entry("<viz>/Go/Next Revision", gtk.keysyms.Up, gtk.gdk.MOD1_MASK)
 
67
        gtk.accel_map_add_entry("<viz>/Go/Previous Revision", gtk.keysyms.Down, gtk.gdk.MOD1_MASK)
 
68
 
 
69
        self.accel_group = gtk.AccelGroup()
 
70
        self.add_accel_group(self.accel_group)
 
71
 
 
72
        gtk.Action.set_tool_item_type(gtk.MenuToolButton)
 
73
 
 
74
        self.prev_rev_action = gtk.Action("prev-rev", "_Previous Revision", "Go to the previous revision", gtk.STOCK_GO_DOWN)
 
75
        self.prev_rev_action.set_accel_path("<viz>/Go/Previous Revision")
 
76
        self.prev_rev_action.set_accel_group(self.accel_group)
 
77
        self.prev_rev_action.connect("activate", self._back_clicked_cb)
 
78
        self.prev_rev_action.connect_accelerator()
 
79
 
 
80
        self.next_rev_action = gtk.Action("next-rev", "_Next Revision", "Go to the next revision", gtk.STOCK_GO_UP)
 
81
        self.next_rev_action.set_accel_path("<viz>/Go/Next Revision")
 
82
        self.next_rev_action.set_accel_group(self.accel_group)
 
83
        self.next_rev_action.connect("activate", self._fwd_clicked_cb)
 
84
        self.next_rev_action.connect_accelerator()
 
85
 
 
86
        self.construct()
 
87
 
 
88
    def set_revision(self, revid):
 
89
        self.treeview.set_revision_id(revid)
 
90
 
 
91
    def construct(self):
 
92
        """Construct the window contents."""
 
93
        vbox = gtk.VBox(spacing=0)
 
94
        self.add(vbox)
 
95
 
 
96
        self.paned = gtk.VPaned()
 
97
        self.paned.pack1(self.construct_top(), resize=True, shrink=False)
 
98
        self.paned.pack2(self.construct_bottom(), resize=False, shrink=True)
 
99
        self.paned.show()
 
100
 
 
101
        vbox.pack_start(self.construct_menubar(), expand=False, fill=True)
 
102
        vbox.pack_start(self.construct_navigation(), expand=False, fill=True)
 
103
        
 
104
        vbox.pack_start(self.paned, expand=True, fill=True)
 
105
        vbox.set_focus_child(self.paned)
 
106
 
 
107
        vbox.show()
 
108
 
 
109
    def construct_menubar(self):
 
110
        menubar = gtk.MenuBar()
 
111
 
 
112
        file_menu = gtk.Menu()
 
113
        file_menuitem = gtk.MenuItem("_File")
 
114
        file_menuitem.set_submenu(file_menu)
 
115
 
 
116
        file_menu_close = gtk.ImageMenuItem(gtk.STOCK_CLOSE, self.accel_group)
 
117
        file_menu_close.connect('activate', lambda x: self.destroy())
 
118
        
 
119
        file_menu_quit = gtk.ImageMenuItem(gtk.STOCK_QUIT, self.accel_group)
 
120
        file_menu_quit.connect('activate', lambda x: gtk.main_quit())
 
121
        
 
122
        if self._parent is not None:
 
123
            file_menu.add(file_menu_close)
 
124
        file_menu.add(file_menu_quit)
 
125
 
 
126
        edit_menu = gtk.Menu()
 
127
        edit_menuitem = gtk.MenuItem("_Edit")
 
128
        edit_menuitem.set_submenu(edit_menu)
 
129
 
 
130
        edit_menu_find = gtk.ImageMenuItem(gtk.STOCK_FIND)
 
131
 
 
132
        edit_menu_branchopts = gtk.MenuItem("Branch Settings")
 
133
        edit_menu_branchopts.connect('activate', lambda x: PreferencesWindow(self.branch.get_config()).show())
 
134
 
 
135
        edit_menu_globopts = gtk.MenuItem("Global Settings")
 
136
        edit_menu_globopts.connect('activate', lambda x: PreferencesWindow().show())
 
137
 
 
138
        edit_menu.add(edit_menu_find)
 
139
        edit_menu.add(edit_menu_branchopts)
 
140
        edit_menu.add(edit_menu_globopts)
 
141
 
 
142
        view_menu = gtk.Menu()
 
143
        view_menuitem = gtk.MenuItem("_View")
 
144
        view_menuitem.set_submenu(view_menu)
 
145
 
 
146
        view_menu_toolbar = gtk.CheckMenuItem("Show Toolbar")
 
147
        view_menu_toolbar.set_active(True)
 
148
        view_menu_toolbar.connect('toggled', self._toolbar_visibility_changed)
 
149
 
 
150
        view_menu_compact = gtk.CheckMenuItem("Show Compact Graph")
 
151
        view_menu_compact.set_active(self.compact_view)
 
152
        view_menu_compact.connect('activate', self._brokenlines_toggled_cb)
 
153
 
 
154
        view_menu.add(view_menu_toolbar)
 
155
        view_menu.add(view_menu_compact)
 
156
        view_menu.add(gtk.SeparatorMenuItem())
 
157
 
 
158
        self.mnu_show_revno_column = gtk.CheckMenuItem("Show Revision _Number Column")
 
159
        self.mnu_show_date_column = gtk.CheckMenuItem("Show _Date Column")
 
160
 
 
161
        # Revision numbers are pointless if there are multiple branches
 
162
        if len(self.start_revs) > 1:
 
163
            self.mnu_show_revno_column.set_sensitive(False)
 
164
            self.treeview.set_property('revno-column-visible', False)
 
165
 
 
166
        for (col, name) in [(self.mnu_show_revno_column, "revno"), 
 
167
                            (self.mnu_show_date_column, "date")]:
 
168
            col.set_active(self.treeview.get_property(name + "-column-visible"))
 
169
            col.connect('toggled', self._col_visibility_changed, name)
 
170
            view_menu.add(col)
 
171
 
 
172
        go_menu = gtk.Menu()
 
173
        go_menu.set_accel_group(self.accel_group)
 
174
        go_menuitem = gtk.MenuItem("_Go")
 
175
        go_menuitem.set_submenu(go_menu)
 
176
        
 
177
        go_menu_next = self.next_rev_action.create_menu_item()
 
178
        go_menu_prev = self.prev_rev_action.create_menu_item()
 
179
 
 
180
        self.go_menu_tags = gtk.MenuItem("_Tags")
 
181
        self._update_tags()
 
182
 
 
183
        go_menu.add(go_menu_next)
 
184
        go_menu.add(go_menu_prev)
 
185
        go_menu.add(gtk.SeparatorMenuItem())
 
186
        go_menu.add(self.go_menu_tags)
 
187
 
 
188
        revision_menu = gtk.Menu()
 
189
        revision_menuitem = gtk.MenuItem("_Revision")
 
190
        revision_menuitem.set_submenu(revision_menu)
 
191
 
 
192
        revision_menu_diff = gtk.MenuItem("View Changes")
 
193
        revision_menu_diff.connect('activate', 
 
194
                lambda w: self.treeview.show_diff())
 
195
 
 
196
        revision_menu_tag = gtk.MenuItem("Tag Revision")
 
197
        revision_menu_tag.connect('activate', self._tag_revision_cb)
 
198
 
 
199
        revision_menu.add(revision_menu_tag)
 
200
        revision_menu.add(revision_menu_diff)
 
201
 
 
202
        branch_menu = gtk.Menu()
 
203
        branch_menuitem = gtk.MenuItem("_Branch")
 
204
        branch_menuitem.set_submenu(branch_menu)
 
205
 
 
206
        branch_menu.add(gtk.MenuItem("Pu_ll Revisions"))
 
207
        branch_menu.add(gtk.MenuItem("Pu_sh Revisions"))
 
208
 
 
209
        help_menu = gtk.Menu()
 
210
        help_menuitem = gtk.MenuItem("_Help")
 
211
        help_menuitem.set_submenu(help_menu)
 
212
 
 
213
        help_about_menuitem = gtk.ImageMenuItem(gtk.STOCK_ABOUT, self.accel_group)
 
214
        help_about_menuitem.connect('activate', self._about_dialog_cb)
 
215
 
 
216
        help_menu.add(help_about_menuitem)
 
217
 
 
218
        menubar.add(file_menuitem)
 
219
        menubar.add(edit_menuitem)
 
220
        menubar.add(view_menuitem)
 
221
        menubar.add(go_menuitem)
 
222
        menubar.add(revision_menuitem)
 
223
        menubar.add(branch_menuitem)
 
224
        menubar.add(help_menuitem)
 
225
        menubar.show_all()
 
226
 
 
227
        return menubar
 
228
 
 
229
    def construct_top(self):
 
230
        """Construct the top-half of the window."""
 
231
        # FIXME: Make broken_line_length configurable
 
232
 
 
233
        self.treeview = TreeView(self.branch, self.start_revs, self.maxnum, self.compact_view)
 
234
 
 
235
        self.treeview.connect('revision-selected',
 
236
                self._treeselection_changed_cb)
 
237
        self.treeview.connect('revision-activated',
 
238
                self._tree_revision_activated)
 
239
 
 
240
        self.treeview.connect('tag-added', lambda w, t, r: self._update_tags())
 
241
 
 
242
        for col in ["revno", "date"]:
 
243
            option = self.config.get_user_option(col + '-column-visible')
 
244
            if option is not None:
 
245
                self.treeview.set_property(col + '-column-visible', option == 'True')
 
246
 
 
247
        self.treeview.set_property(col + '-column-visible', False)
 
248
        self.treeview.show()
 
249
 
 
250
        align = gtk.Alignment(0.0, 0.0, 1.0, 1.0)
 
251
        align.set_padding(5, 0, 0, 0)
 
252
        align.add(self.treeview)
 
253
        align.show()
 
254
 
 
255
        return align
 
256
 
 
257
    def construct_navigation(self):
 
258
        """Construct the navigation buttons."""
 
259
        self.toolbar = gtk.Toolbar()
 
260
        self.toolbar.set_style(gtk.TOOLBAR_BOTH_HORIZ)
 
261
 
 
262
        self.prev_button = self.prev_rev_action.create_tool_item()
 
263
        self.toolbar.insert(self.prev_button, -1)
 
264
 
 
265
        self.next_button = self.next_rev_action.create_tool_item()
 
266
        self.toolbar.insert(self.next_button, -1)
 
267
 
 
268
        self.toolbar.insert(gtk.SeparatorToolItem(), -1)
 
269
 
 
270
        refresh_button = gtk.ToolButton(gtk.STOCK_REFRESH)
 
271
        refresh_button.connect('clicked', self._refresh_clicked)
 
272
        self.toolbar.insert(refresh_button, -1)
 
273
 
 
274
        self.toolbar.show_all()
 
275
 
 
276
        return self.toolbar
 
277
 
 
278
    def construct_bottom(self):
 
279
        """Construct the bottom half of the window."""
 
280
        from bzrlib.plugins.gtk.revisionview import RevisionView
 
281
        self.revisionview = RevisionView(branch=self.branch)
 
282
        (width, height) = self.get_size()
 
283
        self.revisionview.set_size_request(width, int(height / 2.5))
 
284
        self.revisionview.show()
 
285
        self.revisionview.set_show_callback(self._show_clicked_cb)
 
286
        self.revisionview.connect('notify::revision', self._go_clicked_cb)
 
287
        self.treeview.connect('tag-added', lambda w, t, r: self.revisionview.update_tags())
 
288
        return self.revisionview
 
289
 
 
290
    def _tag_selected_cb(self, menuitem, revid):
 
291
        self.treeview.set_revision_id(revid)
 
292
 
 
293
    def _treeselection_changed_cb(self, selection, *args):
 
294
        """callback for when the treeview changes."""
 
295
        revision = self.treeview.get_revision()
 
296
        parents  = self.treeview.get_parents()
 
297
        children = self.treeview.get_children()
 
298
 
 
299
        if revision is not None:
 
300
            prev_menu = gtk.Menu()
 
301
            if len(parents) > 0:
 
302
                self.prev_rev_action.set_sensitive(True)
 
303
                for parent_id in parents:
 
304
                    parent = self.branch.repository.get_revision(parent_id)
 
305
                    try:
 
306
                        str = ' (' + parent.properties['branch-nick'] + ')'
 
307
                    except KeyError:
 
308
                        str = ""
 
309
 
 
310
                    item = gtk.MenuItem(parent.message.split("\n")[0] + str)
 
311
                    item.connect('activate', self._set_revision_cb, parent_id)
 
312
                    prev_menu.add(item)
 
313
                prev_menu.show_all()
 
314
            else:
 
315
                self.prev_rev_action.set_sensitive(False)
 
316
                prev_menu.hide()
 
317
 
 
318
            self.prev_button.set_menu(prev_menu)
 
319
 
 
320
            next_menu = gtk.Menu()
 
321
            if len(children) > 0:
 
322
                self.next_rev_action.set_sensitive(True)
 
323
                for child_id in children:
 
324
                    child = self.branch.repository.get_revision(child_id)
 
325
                    try:
 
326
                        str = ' (' + child.properties['branch-nick'] + ')'
 
327
                    except KeyError:
 
328
                        str = ""
 
329
 
 
330
                    item = gtk.MenuItem(child.message.split("\n")[0] + str)
 
331
                    item.connect('activate', self._set_revision_cb, child_id)
 
332
                    next_menu.add(item)
 
333
                next_menu.show_all()
 
334
            else:
 
335
                self.next_rev_action.set_sensitive(False)
 
336
                next_menu.hide()
 
337
 
 
338
            self.next_button.set_menu(next_menu)
 
339
 
 
340
            self.revisionview.set_revision(revision)
 
341
            self.revisionview.set_children(children)
 
342
    
 
343
    def _tree_revision_activated(self, widget, path, col):
 
344
        # TODO: more than one parent
 
345
        """Callback for when a treeview row gets activated."""
 
346
        revision = self.treeview.get_revision()
 
347
        parents  = self.treeview.get_parents()
 
348
 
 
349
        if len(parents) == 0:
 
350
            parent_id = None
 
351
        else:
 
352
            parent_id = parents[0]
 
353
 
 
354
        self.show_diff(revision.revision_id, parent_id)
 
355
        self.treeview.grab_focus()
 
356
    
 
357
 
 
358
    def _back_clicked_cb(self, *args):
 
359
        """Callback for when the back button is clicked."""
 
360
        self.treeview.back()
 
361
        
 
362
    def _fwd_clicked_cb(self, *args):
 
363
        """Callback for when the forward button is clicked."""
 
364
        self.treeview.forward()
 
365
 
 
366
    def _go_clicked_cb(self, w, p):
 
367
        """Callback for when the go button for a parent is clicked."""
 
368
        if self.revisionview.get_revision() is not None:
 
369
            self.treeview.set_revision(self.revisionview.get_revision())
 
370
 
 
371
    def _show_clicked_cb(self, revid, parentid):
 
372
        """Callback for when the show button for a parent is clicked."""
 
373
        self.show_diff(revid, parentid)
 
374
        self.treeview.grab_focus()
 
375
 
 
376
    def _set_revision_cb(self, w, revision_id):
 
377
        self.treeview.set_revision_id(revision_id)
 
378
 
 
379
    def _brokenlines_toggled_cb(self, button):
 
380
        self.compact_view = button.get_active()
 
381
 
 
382
        if self.compact_view:
 
383
            option = 'yes'
 
384
        else:
 
385
            option = 'no'
 
386
 
 
387
        self.config.set_user_option('viz-compact-view', option)
 
388
        self.treeview.set_property('compact', self.compact_view)
 
389
        self.treeview.refresh()
 
390
 
 
391
    def _tag_revision_cb(self, w):
 
392
        try:
 
393
            self.treeview.set_sensitive(False)
 
394
            dialog = AddTagDialog(self.branch.repository, self.treeview.get_revision().revision_id, self.branch)
 
395
            response = dialog.run()
 
396
            if response != gtk.RESPONSE_NONE:
 
397
                dialog.hide()
 
398
            
 
399
                if response == gtk.RESPONSE_OK:
 
400
                    self.treeview.add_tag(dialog.tagname, dialog._revid)
 
401
                
 
402
                dialog.destroy()
 
403
 
 
404
        finally:
 
405
            self.treeview.set_sensitive(True)
 
406
 
 
407
    def _about_dialog_cb(self, w):
 
408
        from bzrlib.plugins.gtk.about import AboutDialog
 
409
 
 
410
        AboutDialog().run()
 
411
 
 
412
    def _col_visibility_changed(self, col, property):
 
413
        self.config.set_user_option(property + '-column-visible', col.get_active())
 
414
        self.treeview.set_property(property + '-column-visible', col.get_active())
 
415
 
 
416
    def _toolbar_visibility_changed(self, col):
 
417
        if col.get_active():
 
418
            self.toolbar.show() 
 
419
        else:
 
420
            self.toolbar.hide()
 
421
 
 
422
    def _show_about_cb(self, w):
 
423
        dialog = AboutDialog()
 
424
        dialog.connect('response', lambda d,r: d.destroy())
 
425
        dialog.run()
 
426
 
 
427
    def _refresh_clicked(self, w):
 
428
        self.treeview.refresh()
 
429
 
 
430
    def _update_tags(self):
 
431
        menu = gtk.Menu()
 
432
 
 
433
        if self.branch.supports_tags():
 
434
            tags = self.branch.tags.get_tag_dict().items()
 
435
            tags.sort()
 
436
            tags.reverse()
 
437
            for tag, revid in tags:
 
438
                tag_item = gtk.MenuItem(tag, use_underline=False)
 
439
                tag_item.connect('activate', self._tag_selected_cb, revid)
 
440
                menu.add(tag_item)
 
441
            self.go_menu_tags.set_submenu(menu)
 
442
 
 
443
            self.go_menu_tags.set_sensitive(len(tags) != 0)
 
444
        else:
 
445
            self.go_menu_tags.set_sensitive(False)
 
446
 
 
447
        self.go_menu_tags.show_all()
 
448
 
 
449
    def show_diff(self, revid=None, parentid=None):
 
450
        """Open a new window to show a diff between the given revisions."""
 
451
        from bzrlib.plugins.gtk.diff import DiffWindow
 
452
        window = DiffWindow(parent=self)
 
453
 
 
454
        if parentid is None:
 
455
            parentid = NULL_REVISION
 
456
 
 
457
        rev_tree    = self.branch.repository.revision_tree(revid)
 
458
        parent_tree = self.branch.repository.revision_tree(parentid)
 
459
 
 
460
        description = revid + " - " + self.branch.nick
 
461
        window.set_diff(description, rev_tree, parent_tree)
 
462
        window.show()
 
463
 
 
464