1
# -*- coding: UTF-8 -*-
4
This module contains the code to manage the branch information window,
5
which contains both the revision graph and details panes.
8
__copyright__ = "Copyright © 2005 Canonical Ltd."
9
__author__ = "Scott James Remnant <scott@ubuntu.com>"
16
from bzrlib.plugins.gtk import icon_path
17
from bzrlib.plugins.gtk.branchview import TreeView, treemodel
18
from bzrlib.plugins.gtk.tags import AddTagDialog
19
from bzrlib.plugins.gtk.preferences import PreferencesWindow
20
from bzrlib.plugins.gtk.revisionmenu import RevisionMenu
21
from bzrlib.plugins.gtk.window import Window
23
from bzrlib.config import BranchConfig, GlobalConfig
24
from bzrlib.revision import Revision, NULL_REVISION
25
from bzrlib.trace import mutter
27
class BranchWindow(Window):
30
This object represents and manages a single window containing information
31
for a particular branch.
34
def __init__(self, branch, start_revs, maxnum, parent=None):
35
"""Create a new BranchWindow.
37
:param branch: Branch object for branch to show.
38
:param start_revs: Revision ids of top revisions.
39
:param maxnum: Maximum number of revisions to display,
43
Window.__init__(self, parent=parent)
44
self.set_border_width(0)
47
self.start_revs = start_revs
49
self.config = GlobalConfig()
51
if self.config.get_user_option('viz-compact-view') == 'yes':
52
self.compact_view = True
54
self.compact_view = False
56
self.set_title(branch.nick + " - revision history")
58
# Use three-quarters of the screen by default
59
screen = self.get_screen()
60
monitor = screen.get_monitor_geometry(0)
61
width = int(monitor.width * 0.75)
62
height = int(monitor.height * 0.75)
63
self.set_default_size(width, height)
66
icon = self.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)
69
gtk.accel_map_add_entry("<viz>/Go/Next Revision", gtk.keysyms.Up, gtk.gdk.MOD1_MASK)
70
gtk.accel_map_add_entry("<viz>/Go/Previous Revision", gtk.keysyms.Down, gtk.gdk.MOD1_MASK)
71
gtk.accel_map_add_entry("<viz>/View/Refresh", gtk.keysyms.F5, 0)
73
self.accel_group = gtk.AccelGroup()
74
self.add_accel_group(self.accel_group)
76
gtk.Action.set_tool_item_type(gtk.MenuToolButton)
78
self.prev_rev_action = gtk.Action("prev-rev", "_Previous Revision", "Go to the previous revision", gtk.STOCK_GO_DOWN)
79
self.prev_rev_action.set_accel_path("<viz>/Go/Previous Revision")
80
self.prev_rev_action.set_accel_group(self.accel_group)
81
self.prev_rev_action.connect("activate", self._back_clicked_cb)
82
self.prev_rev_action.connect_accelerator()
84
self.next_rev_action = gtk.Action("next-rev", "_Next Revision", "Go to the next revision", gtk.STOCK_GO_UP)
85
self.next_rev_action.set_accel_path("<viz>/Go/Next Revision")
86
self.next_rev_action.set_accel_group(self.accel_group)
87
self.next_rev_action.connect("activate", self._fwd_clicked_cb)
88
self.next_rev_action.connect_accelerator()
90
self.refresh_action = gtk.Action("refresh", "_Refresh", "Refresh view", gtk.STOCK_REFRESH)
91
self.refresh_action.set_accel_path("<viz>/View/Refresh")
92
self.refresh_action.set_accel_group(self.accel_group)
93
self.refresh_action.connect("activate", self._refresh_clicked)
94
self.refresh_action.connect_accelerator()
98
def set_revision(self, revid):
99
self.treeview.set_revision_id(revid)
102
"""Construct the window contents."""
103
vbox = gtk.VBox(spacing=0)
106
self.paned = gtk.VPaned()
107
self.paned.pack1(self.construct_top(), resize=True, shrink=False)
108
self.paned.pack2(self.construct_bottom(), resize=False, shrink=True)
111
vbox.pack_start(self.construct_menubar(), expand=False, fill=True)
112
vbox.pack_start(self.construct_navigation(), expand=False, fill=True)
114
vbox.pack_start(self.paned, expand=True, fill=True)
115
vbox.set_focus_child(self.paned)
119
def construct_menubar(self):
120
menubar = gtk.MenuBar()
122
file_menu = gtk.Menu()
123
file_menuitem = gtk.MenuItem("_File")
124
file_menuitem.set_submenu(file_menu)
126
file_menu_close = gtk.ImageMenuItem(gtk.STOCK_CLOSE, self.accel_group)
127
file_menu_close.connect('activate', lambda x: self.destroy())
129
file_menu_quit = gtk.ImageMenuItem(gtk.STOCK_QUIT, self.accel_group)
130
file_menu_quit.connect('activate', lambda x: gtk.main_quit())
132
if self._parent is not None:
133
file_menu.add(file_menu_close)
134
file_menu.add(file_menu_quit)
136
edit_menu = gtk.Menu()
137
edit_menuitem = gtk.MenuItem("_Edit")
138
edit_menuitem.set_submenu(edit_menu)
140
edit_menu_find = gtk.ImageMenuItem(gtk.STOCK_FIND)
142
edit_menu_branchopts = gtk.MenuItem("Branch Settings")
143
edit_menu_branchopts.connect('activate', lambda x: PreferencesWindow(self.branch.get_config()).show())
145
edit_menu_globopts = gtk.MenuItem("Global Settings")
146
edit_menu_globopts.connect('activate', lambda x: PreferencesWindow().show())
148
edit_menu.add(edit_menu_find)
149
edit_menu.add(edit_menu_branchopts)
150
edit_menu.add(edit_menu_globopts)
152
view_menu = gtk.Menu()
153
view_menuitem = gtk.MenuItem("_View")
154
view_menuitem.set_submenu(view_menu)
156
view_menu_refresh = self.refresh_action.create_menu_item()
157
view_menu_refresh.connect('activate', self._refresh_clicked)
159
view_menu.add(view_menu_refresh)
160
view_menu.add(gtk.SeparatorMenuItem())
162
view_menu_toolbar = gtk.CheckMenuItem("Show Toolbar")
163
view_menu_toolbar.set_active(True)
164
view_menu_toolbar.connect('toggled', self._toolbar_visibility_changed)
166
view_menu_compact = gtk.CheckMenuItem("Show Compact Graph")
167
view_menu_compact.set_active(self.compact_view)
168
view_menu_compact.connect('activate', self._brokenlines_toggled_cb)
170
view_menu.add(view_menu_toolbar)
171
view_menu.add(view_menu_compact)
172
view_menu.add(gtk.SeparatorMenuItem())
174
self.mnu_show_revno_column = gtk.CheckMenuItem("Show Revision _Number Column")
175
self.mnu_show_date_column = gtk.CheckMenuItem("Show _Date Column")
177
# Revision numbers are pointless if there are multiple branches
178
if len(self.start_revs) > 1:
179
self.mnu_show_revno_column.set_sensitive(False)
180
self.treeview.set_property('revno-column-visible', False)
182
for (col, name) in [(self.mnu_show_revno_column, "revno"),
183
(self.mnu_show_date_column, "date")]:
184
col.set_active(self.treeview.get_property(name + "-column-visible"))
185
col.connect('toggled', self._col_visibility_changed, name)
189
go_menu.set_accel_group(self.accel_group)
190
go_menuitem = gtk.MenuItem("_Go")
191
go_menuitem.set_submenu(go_menu)
193
go_menu_next = self.next_rev_action.create_menu_item()
194
go_menu_prev = self.prev_rev_action.create_menu_item()
196
tag_image = gtk.Image()
197
tag_image.set_from_file(icon_path("tag-16.png"))
198
self.go_menu_tags = gtk.ImageMenuItem("_Tags")
199
self.go_menu_tags.set_image(tag_image)
202
go_menu.add(go_menu_next)
203
go_menu.add(go_menu_prev)
204
go_menu.add(gtk.SeparatorMenuItem())
205
go_menu.add(self.go_menu_tags)
207
self.revision_menu = RevisionMenu(self.branch.repository, [], self.branch, parent=self)
208
revision_menuitem = gtk.MenuItem("_Revision")
209
revision_menuitem.set_submenu(self.revision_menu)
211
branch_menu = gtk.Menu()
212
branch_menuitem = gtk.MenuItem("_Branch")
213
branch_menuitem.set_submenu(branch_menu)
215
branch_menu.add(gtk.MenuItem("Pu_ll Revisions"))
216
branch_menu.add(gtk.MenuItem("Pu_sh Revisions"))
219
from bzrlib.plugins import search
221
mutter("Didn't find search plugin")
223
branch_menu.add(gtk.SeparatorMenuItem())
225
branch_index_menuitem = gtk.MenuItem("_Index")
226
branch_index_menuitem.connect('activate', self._branch_index_cb)
227
branch_menu.add(branch_index_menuitem)
229
branch_search_menuitem = gtk.MenuItem("_Search")
230
branch_search_menuitem.connect('activate', self._branch_search_cb)
231
branch_menu.add(branch_search_menuitem)
233
help_menu = gtk.Menu()
234
help_menuitem = gtk.MenuItem("_Help")
235
help_menuitem.set_submenu(help_menu)
237
help_about_menuitem = gtk.ImageMenuItem(gtk.STOCK_ABOUT, self.accel_group)
238
help_about_menuitem.connect('activate', self._about_dialog_cb)
240
help_menu.add(help_about_menuitem)
242
menubar.add(file_menuitem)
243
menubar.add(edit_menuitem)
244
menubar.add(view_menuitem)
245
menubar.add(go_menuitem)
246
menubar.add(revision_menuitem)
247
menubar.add(branch_menuitem)
248
menubar.add(help_menuitem)
253
def construct_top(self):
254
"""Construct the top-half of the window."""
255
# FIXME: Make broken_line_length configurable
257
self.treeview = TreeView(self.branch, self.start_revs, self.maxnum, self.compact_view)
259
self.treeview.connect('revision-selected',
260
self._treeselection_changed_cb)
261
self.treeview.connect('revision-activated',
262
self._tree_revision_activated)
264
self.treeview.connect('tag-added', lambda w, t, r: self._update_tags())
266
for col in ["revno", "date"]:
267
option = self.config.get_user_option(col + '-column-visible')
268
if option is not None:
269
self.treeview.set_property(col + '-column-visible', option == 'True')
271
self.treeview.set_property(col + '-column-visible', False)
275
align = gtk.Alignment(0.0, 0.0, 1.0, 1.0)
276
align.set_padding(5, 0, 0, 0)
277
align.add(self.treeview)
282
def construct_navigation(self):
283
"""Construct the navigation buttons."""
284
self.toolbar = gtk.Toolbar()
285
self.toolbar.set_style(gtk.TOOLBAR_BOTH_HORIZ)
287
self.prev_button = self.prev_rev_action.create_tool_item()
288
self.toolbar.insert(self.prev_button, -1)
290
self.next_button = self.next_rev_action.create_tool_item()
291
self.toolbar.insert(self.next_button, -1)
293
self.toolbar.insert(gtk.SeparatorToolItem(), -1)
295
refresh_button = gtk.ToolButton(gtk.STOCK_REFRESH)
296
refresh_button.connect('clicked', self._refresh_clicked)
297
self.toolbar.insert(refresh_button, -1)
299
self.toolbar.show_all()
303
def construct_bottom(self):
304
"""Construct the bottom half of the window."""
305
from bzrlib.plugins.gtk.revisionview import RevisionView
306
self.revisionview = RevisionView(branch=self.branch)
307
(width, height) = self.get_size()
308
self.revisionview.set_size_request(width, int(height / 2.5))
309
self.revisionview.show()
310
self.revisionview.set_show_callback(self._show_clicked_cb)
311
self.revisionview.connect('notify::revision', self._go_clicked_cb)
312
self.treeview.connect('tag-added', lambda w, t, r: self.revisionview.update_tags())
313
return self.revisionview
315
def _tag_selected_cb(self, menuitem, revid):
316
self.treeview.set_revision_id(revid)
318
def _treeselection_changed_cb(self, selection, *args):
319
"""callback for when the treeview changes."""
320
revision = self.treeview.get_revision()
321
parents = self.treeview.get_parents()
322
children = self.treeview.get_children()
324
self.revision_menu.set_revision_ids([revision.revision_id])
326
if revision and revision != NULL_REVISION:
327
prev_menu = gtk.Menu()
329
self.prev_rev_action.set_sensitive(True)
330
for parent_id in parents:
331
if parent_id and parent_id != NULL_REVISION:
332
parent = self.branch.repository.get_revision(parent_id)
334
str = ' (' + parent.properties['branch-nick'] + ')'
338
item = gtk.MenuItem(parent.message.split("\n")[0] + str)
339
item.connect('activate', self._set_revision_cb, parent_id)
343
self.prev_rev_action.set_sensitive(False)
346
self.prev_button.set_menu(prev_menu)
348
next_menu = gtk.Menu()
349
if len(children) > 0:
350
self.next_rev_action.set_sensitive(True)
351
for child_id in children:
352
child = self.branch.repository.get_revision(child_id)
354
str = ' (' + child.properties['branch-nick'] + ')'
358
item = gtk.MenuItem(child.message.split("\n")[0] + str)
359
item.connect('activate', self._set_revision_cb, child_id)
363
self.next_rev_action.set_sensitive(False)
366
self.next_button.set_menu(next_menu)
368
self.revisionview.set_revision(revision)
369
self.revisionview.set_children(children)
371
def _tree_revision_activated(self, widget, path, col):
372
# TODO: more than one parent
373
"""Callback for when a treeview row gets activated."""
374
revision = self.treeview.get_revision()
375
parents = self.treeview.get_parents()
377
if len(parents) == 0:
380
parent_id = parents[0]
382
self.show_diff(revision.revision_id, parent_id)
383
self.treeview.grab_focus()
385
def _back_clicked_cb(self, *args):
386
"""Callback for when the back button is clicked."""
389
def _fwd_clicked_cb(self, *args):
390
"""Callback for when the forward button is clicked."""
391
self.treeview.forward()
393
def _go_clicked_cb(self, w, p):
394
"""Callback for when the go button for a parent is clicked."""
395
if self.revisionview.get_revision() is not None:
396
self.treeview.set_revision(self.revisionview.get_revision())
398
def _show_clicked_cb(self, revid, parentid):
399
"""Callback for when the show button for a parent is clicked."""
400
self.show_diff(revid, parentid)
401
self.treeview.grab_focus()
403
def _set_revision_cb(self, w, revision_id):
404
self.treeview.set_revision_id(revision_id)
406
def _brokenlines_toggled_cb(self, button):
407
self.compact_view = button.get_active()
409
if self.compact_view:
414
self.config.set_user_option('viz-compact-view', option)
415
self.treeview.set_property('compact', self.compact_view)
416
self.treeview.refresh()
418
def _branch_index_cb(self, w):
419
from bzrlib.plugins.search import index as _mod_index
420
_mod_index.index_url(self.branch.base)
422
def _branch_search_cb(self, w):
423
from bzrlib.plugins.gtk.search import SearchDialog
424
dialog = SearchDialog(self.branch)
426
if dialog.run() == gtk.RESPONSE_OK:
427
self.set_revision(dialog.get_revision())
431
def _about_dialog_cb(self, w):
432
from bzrlib.plugins.gtk.about import AboutDialog
436
def _col_visibility_changed(self, col, property):
437
self.config.set_user_option(property + '-column-visible', col.get_active())
438
self.treeview.set_property(property + '-column-visible', col.get_active())
440
def _toolbar_visibility_changed(self, col):
446
def _show_about_cb(self, w):
447
dialog = AboutDialog()
448
dialog.connect('response', lambda d,r: d.destroy())
451
def _refresh_clicked(self, w):
452
self.treeview.refresh()
454
def _update_tags(self):
457
if self.branch.supports_tags():
458
tags = self.branch.tags.get_tag_dict().items()
461
for tag, revid in tags:
462
tag_image = gtk.Image()
463
tag_image.set_from_file(icon_path('tag-16.png'))
464
tag_item = gtk.ImageMenuItem(tag.replace('_', '__'))
465
tag_item.set_image(tag_image)
466
tag_item.connect('activate', self._tag_selected_cb, revid)
468
self.go_menu_tags.set_submenu(menu)
470
self.go_menu_tags.set_sensitive(len(tags) != 0)
472
self.go_menu_tags.set_sensitive(False)
474
self.go_menu_tags.show_all()
476
def show_diff(self, revid=None, parentid=None):
477
"""Open a new window to show a diff between the given revisions."""
478
from bzrlib.plugins.gtk.diff import DiffWindow
479
window = DiffWindow(parent=self)
482
parentid = NULL_REVISION
484
rev_tree = self.branch.repository.revision_tree(revid)
485
parent_tree = self.branch.repository.revision_tree(parentid)
487
description = revid + " - " + self.branch.nick
488
window.set_diff(description, rev_tree, parent_tree)