1
# -*- coding: UTF-8 -*-
3
4
This module contains the code to manage the branch information window,
4
5
which contains both the revision graph and details panes.
7
__copyright__ = "Copyright (c) 2005 Canonical Ltd."
8
__copyright__ = "Copyright © 2005 Canonical Ltd."
8
9
__author__ = "Scott James Remnant <scott@ubuntu.com>"
13
from bzrlib.plugins.gtk import icon_path
14
from bzrlib.plugins.gtk.branchview import TreeView
16
from bzrlib.plugins.gtk.window import Window
17
from bzrlib.plugins.gtk.tags import AddTagDialog
15
18
from bzrlib.plugins.gtk.preferences import PreferencesWindow
16
from bzrlib.plugins.gtk.revisionmenu import RevisionMenu
17
from bzrlib.plugins.gtk.window import Window
19
from bzrlib.plugins.gtk.branchview import TreeView, treemodel
20
from bzrlib.revision import Revision, NULL_REVISION
21
from bzrlib.config import BranchConfig
19
22
from bzrlib.config import GlobalConfig
20
from bzrlib.revision import NULL_REVISION
21
from bzrlib.trace import mutter
24
24
class BranchWindow(Window):
28
28
for a particular branch.
31
def __init__(self, branch, start_revs, maxnum, parent=None):
31
def __init__(self, branch, start, maxnum, parent=None):
32
32
"""Create a new BranchWindow.
34
34
:param branch: Branch object for branch to show.
35
:param start_revs: Revision ids of top revisions.
35
:param start: Revision id of top revision.
36
36
:param maxnum: Maximum number of revisions to display,
51
51
self.compact_view = False
53
self.set_title(branch._get_nick(local=True) + " - revision history")
53
self.set_title(branch.nick + " - revision history")
55
# user-configured window size
56
size = self._load_size('viz-window-size')
60
# Use three-quarters of the screen by default
61
screen = self.get_screen()
62
monitor = screen.get_monitor_geometry(0)
63
width = int(monitor.width * 0.75)
64
height = int(monitor.height * 0.75)
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)
65
60
self.set_default_size(width, height)
66
self.set_size_request(width/3, height/3)
67
self._save_size_on_destroy(self, 'viz-window-size')
70
63
icon = self.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)
73
66
gtk.accel_map_add_entry("<viz>/Go/Next Revision", gtk.keysyms.Up, gtk.gdk.MOD1_MASK)
74
67
gtk.accel_map_add_entry("<viz>/Go/Previous Revision", gtk.keysyms.Down, gtk.gdk.MOD1_MASK)
75
gtk.accel_map_add_entry("<viz>/View/Refresh", gtk.keysyms.F5, 0)
77
69
self.accel_group = gtk.AccelGroup()
78
70
self.add_accel_group(self.accel_group)
80
if getattr(gtk.Action, 'set_tool_item_type', None) is not None:
81
# Not available before PyGtk-2.10
82
gtk.Action.set_tool_item_type(gtk.MenuToolButton)
72
gtk.Action.set_tool_item_type(gtk.MenuToolButton)
84
74
self.prev_rev_action = gtk.Action("prev-rev", "_Previous Revision", "Go to the previous revision", gtk.STOCK_GO_DOWN)
85
75
self.prev_rev_action.set_accel_path("<viz>/Go/Previous Revision")
93
83
self.next_rev_action.connect("activate", self._fwd_clicked_cb)
94
84
self.next_rev_action.connect_accelerator()
96
self.refresh_action = gtk.Action("refresh", "_Refresh", "Refresh view", gtk.STOCK_REFRESH)
97
self.refresh_action.set_accel_path("<viz>/View/Refresh")
98
self.refresh_action.set_accel_group(self.accel_group)
99
self.refresh_action.connect("activate", self._refresh_clicked)
100
self.refresh_action.connect_accelerator()
104
def _save_size_on_destroy(self, widget, config_name):
105
"""Creates a hook that saves the size of widget to config option
106
config_name when the window is destroyed/closed."""
108
width, height = widget.allocation.width, widget.allocation.height
109
value = '%sx%s' % (width, height)
110
self.config.set_user_option(config_name, value)
111
self.connect("destroy", save_size)
113
88
def set_revision(self, revid):
114
89
self.treeview.set_revision_id(revid)
121
96
self.paned = gtk.VPaned()
122
self.paned.pack1(self.construct_top(), resize=False, shrink=True)
123
self.paned.pack2(self.construct_bottom(), resize=True, shrink=False)
97
self.paned.pack1(self.construct_top(), resize=True, shrink=False)
98
self.paned.pack2(self.construct_bottom(), resize=False, shrink=True)
126
nav = self.construct_navigation()
127
menubar = self.construct_menubar()
128
vbox.pack_start(menubar, expand=False, fill=True)
129
vbox.pack_start(nav, expand=False, fill=True)
101
vbox.pack_start(self.construct_menubar(), expand=False, fill=True)
102
vbox.pack_start(self.construct_navigation(), expand=False, fill=True)
131
104
vbox.pack_start(self.paned, expand=True, fill=True)
132
105
vbox.set_focus_child(self.paned)
134
self.treeview.connect('revision-selected',
135
self._treeselection_changed_cb)
136
self.treeview.connect('revision-activated',
137
self._tree_revision_activated)
139
self.treeview.connect('tag-added', lambda w, t, r: self._update_tags())
142
109
def construct_menubar(self):
149
116
file_menu_close = gtk.ImageMenuItem(gtk.STOCK_CLOSE, self.accel_group)
150
117
file_menu_close.connect('activate', lambda x: self.destroy())
152
119
file_menu_quit = gtk.ImageMenuItem(gtk.STOCK_QUIT, self.accel_group)
153
120
file_menu_quit.connect('activate', lambda x: gtk.main_quit())
155
122
if self._parent is not None:
156
123
file_menu.add(file_menu_close)
157
124
file_menu.add(file_menu_quit)
160
127
edit_menuitem = gtk.MenuItem("_Edit")
161
128
edit_menuitem.set_submenu(edit_menu)
130
edit_menu_find = gtk.ImageMenuItem(gtk.STOCK_FIND)
163
132
edit_menu_branchopts = gtk.MenuItem("Branch Settings")
164
133
edit_menu_branchopts.connect('activate', lambda x: PreferencesWindow(self.branch.get_config()).show())
166
135
edit_menu_globopts = gtk.MenuItem("Global Settings")
167
136
edit_menu_globopts.connect('activate', lambda x: PreferencesWindow().show())
138
edit_menu.add(edit_menu_find)
169
139
edit_menu.add(edit_menu_branchopts)
170
140
edit_menu.add(edit_menu_globopts)
173
143
view_menuitem = gtk.MenuItem("_View")
174
144
view_menuitem.set_submenu(view_menu)
176
view_menu_refresh = self.refresh_action.create_menu_item()
177
view_menu_refresh.connect('activate', self._refresh_clicked)
179
view_menu.add(view_menu_refresh)
180
view_menu.add(gtk.SeparatorMenuItem())
182
146
view_menu_toolbar = gtk.CheckMenuItem("Show Toolbar")
183
147
view_menu_toolbar.set_active(True)
184
if self.config.get_user_option('viz-toolbar-visible') == 'False':
185
view_menu_toolbar.set_active(False)
187
148
view_menu_toolbar.connect('toggled', self._toolbar_visibility_changed)
189
150
view_menu_compact = gtk.CheckMenuItem("Show Compact Graph")
190
151
view_menu_compact.set_active(self.compact_view)
191
152
view_menu_compact.connect('activate', self._brokenlines_toggled_cb)
193
view_menu_diffs = gtk.CheckMenuItem("Show Diffs")
194
view_menu_diffs.set_active(False)
195
if self.config.get_user_option('viz-show-diffs') == 'True':
196
view_menu_diffs.set_active(True)
197
view_menu_diffs.connect('toggled', self._diff_visibility_changed)
199
view_menu_wide_diffs = gtk.CheckMenuItem("Wide Diffs")
200
view_menu_wide_diffs.set_active(False)
201
if self.config.get_user_option('viz-wide-diffs') == 'True':
202
view_menu_wide_diffs.set_active(True)
203
view_menu_wide_diffs.connect('toggled', self._diff_placement_changed)
205
view_menu_wrap_diffs = gtk.CheckMenuItem("Wrap _Long Lines in Diffs")
206
view_menu_wrap_diffs.set_active(False)
207
if self.config.get_user_option('viz-wrap-diffs') == 'True':
208
view_menu_wrap_diffs.set_active(True)
209
view_menu_wrap_diffs.connect('toggled', self._diff_wrap_changed)
211
154
view_menu.add(view_menu_toolbar)
212
155
view_menu.add(view_menu_compact)
213
156
view_menu.add(gtk.SeparatorMenuItem())
214
view_menu.add(view_menu_diffs)
215
view_menu.add(view_menu_wide_diffs)
216
view_menu.add(view_menu_wrap_diffs)
217
view_menu.add(gtk.SeparatorMenuItem())
219
self.mnu_show_revno_column = gtk.CheckMenuItem("Show Revision _Number Column")
220
self.mnu_show_date_column = gtk.CheckMenuItem("Show _Date Column")
222
# Revision numbers are pointless if there are multiple branches
223
if len(self.start_revs) > 1:
224
self.mnu_show_revno_column.set_sensitive(False)
225
self.treeview.set_property('revno-column-visible', False)
227
for (col, name) in [(self.mnu_show_revno_column, "revno"),
228
(self.mnu_show_date_column, "date")]:
158
for (label, name) in [("Revision _Number", "revno"), ("_Date", "date")]:
159
col = gtk.CheckMenuItem("Show " + label + " Column")
229
160
col.set_active(self.treeview.get_property(name + "-column-visible"))
230
161
col.connect('toggled', self._col_visibility_changed, name)
231
162
view_menu.add(col)
234
165
go_menu.set_accel_group(self.accel_group)
235
166
go_menuitem = gtk.MenuItem("_Go")
236
167
go_menuitem.set_submenu(go_menu)
238
169
go_menu_next = self.next_rev_action.create_menu_item()
239
170
go_menu_prev = self.prev_rev_action.create_menu_item()
241
tag_image = gtk.Image()
242
tag_image.set_from_file(icon_path("tag-16.png"))
243
self.go_menu_tags = gtk.ImageMenuItem("_Tags")
244
self.go_menu_tags.set_image(tag_image)
245
self.treeview.connect('refreshed', lambda w: self._update_tags())
172
self.go_menu_tags = gtk.MenuItem("_Tags")
247
175
go_menu.add(go_menu_next)
248
176
go_menu.add(go_menu_prev)
249
177
go_menu.add(gtk.SeparatorMenuItem())
250
178
go_menu.add(self.go_menu_tags)
252
self.revision_menu = RevisionMenu(self.branch.repository, [], self.branch, parent=self)
180
revision_menu = gtk.Menu()
253
181
revision_menuitem = gtk.MenuItem("_Revision")
254
revision_menuitem.set_submenu(self.revision_menu)
182
revision_menuitem.set_submenu(revision_menu)
184
revision_menu_diff = gtk.MenuItem("View Changes")
185
revision_menu_diff.connect('activate',
186
lambda w: self.treeview.show_diff())
188
revision_menu_tag = gtk.MenuItem("Tag Revision")
189
revision_menu_tag.connect('activate', self._tag_revision_cb)
191
revision_menu.add(revision_menu_tag)
192
revision_menu.add(revision_menu_diff)
256
194
branch_menu = gtk.Menu()
257
195
branch_menuitem = gtk.MenuItem("_Branch")
260
198
branch_menu.add(gtk.MenuItem("Pu_ll Revisions"))
261
199
branch_menu.add(gtk.MenuItem("Pu_sh Revisions"))
264
from bzrlib.plugins import search
266
mutter("Didn't find search plugin")
268
branch_menu.add(gtk.SeparatorMenuItem())
270
branch_index_menuitem = gtk.MenuItem("_Index")
271
branch_index_menuitem.connect('activate', self._branch_index_cb)
272
branch_menu.add(branch_index_menuitem)
274
branch_search_menuitem = gtk.MenuItem("_Search")
275
branch_search_menuitem.connect('activate', self._branch_search_cb)
276
branch_menu.add(branch_search_menuitem)
278
201
help_menu = gtk.Menu()
279
202
help_menuitem = gtk.MenuItem("_Help")
280
203
help_menuitem.set_submenu(help_menu)
299
222
"""Construct the top-half of the window."""
300
223
# FIXME: Make broken_line_length configurable
302
self.treeview = TreeView(self.branch, self.start_revs, self.maxnum, self.compact_view)
225
self.treeview = TreeView(self.branch, self.start, self.maxnum, self.compact_view)
227
self.treeview.connect('revision-selected',
228
self._treeselection_changed_cb)
229
self.treeview.connect('revision-activated',
230
self._tree_revision_activated)
232
self.treeview.connect('tag-added', lambda w, t, r: self._update_tags())
304
234
for col in ["revno", "date"]:
305
235
option = self.config.get_user_option(col + '-column-visible')
306
236
if option is not None:
307
237
self.treeview.set_property(col + '-column-visible', option == 'True')
309
self.treeview.set_property(col + '-column-visible', False)
311
239
self.treeview.show()
313
241
align = gtk.Alignment(0.0, 0.0, 1.0, 1.0)
314
242
align.set_padding(5, 0, 0, 0)
315
243
align.add(self.treeview)
316
# user-configured size
317
size = self._load_size('viz-graph-size')
320
align.set_size_request(width, height)
322
(width, height) = self.get_size()
323
align.set_size_request(width, int(height / 2.5))
324
self._save_size_on_destroy(align, 'viz-graph-size')
350
269
def construct_bottom(self):
351
270
"""Construct the bottom half of the window."""
352
if self.config.get_user_option('viz-wide-diffs') == 'True':
353
self.diff_paned = gtk.VPaned()
355
self.diff_paned = gtk.HPaned()
356
(width, height) = self.get_size()
357
self.diff_paned.set_size_request(20, 20) # shrinkable
359
271
from bzrlib.plugins.gtk.revisionview import RevisionView
360
272
self.revisionview = RevisionView(branch=self.branch)
361
self.revisionview.set_size_request(width/3, int(height / 2.5))
362
# user-configured size
363
size = self._load_size('viz-revisionview-size')
366
self.revisionview.set_size_request(width, height)
367
self._save_size_on_destroy(self.revisionview, 'viz-revisionview-size')
273
(width, height) = self.get_size()
274
self.revisionview.set_size_request(width, int(height / 2.5))
368
275
self.revisionview.show()
369
276
self.revisionview.set_show_callback(self._show_clicked_cb)
370
277
self.revisionview.connect('notify::revision', self._go_clicked_cb)
371
278
self.treeview.connect('tag-added', lambda w, t, r: self.revisionview.update_tags())
372
self.diff_paned.pack1(self.revisionview)
374
from bzrlib.plugins.gtk.diff import DiffWidget
375
self.diff = DiffWidget()
376
self.diff_paned.pack2(self.diff)
378
self.diff_paned.show_all()
379
if self.config.get_user_option('viz-show-diffs') != 'True':
382
return self.diff_paned
279
return self.revisionview
384
281
def _tag_selected_cb(self, menuitem, revid):
385
282
self.treeview.set_revision_id(revid)
390
287
parents = self.treeview.get_parents()
391
288
children = self.treeview.get_children()
393
self.revision_menu.set_revision_ids([revision.revision_id])
395
if revision and revision != NULL_REVISION:
290
if revision is not None:
396
291
prev_menu = gtk.Menu()
397
292
if len(parents) > 0:
398
293
self.prev_rev_action.set_sensitive(True)
399
294
for parent_id in parents:
400
if parent_id and parent_id != NULL_REVISION:
401
parent = self.branch.repository.get_revision(parent_id)
403
str = ' (' + parent.properties['branch-nick'] + ')'
295
parent = self.branch.repository.get_revision(parent_id)
297
str = ' (' + parent.properties['branch-nick'] + ')'
407
item = gtk.MenuItem(parent.message.split("\n")[0] + str)
408
item.connect('activate', self._set_revision_cb, parent_id)
301
item = gtk.MenuItem(parent.message.split("\n")[0] + str)
302
item.connect('activate', self._set_revision_cb, parent_id)
410
304
prev_menu.show_all()
412
306
self.prev_rev_action.set_sensitive(False)
415
if getattr(self.prev_button, 'set_menu', None) is not None:
416
self.prev_button.set_menu(prev_menu)
309
self.prev_button.set_menu(prev_menu)
418
311
next_menu = gtk.Menu()
419
312
if len(children) > 0:
433
326
self.next_rev_action.set_sensitive(False)
436
if getattr(self.next_button, 'set_menu', None) is not None:
437
self.next_button.set_menu(next_menu)
329
self.next_button.set_menu(next_menu)
439
331
self.revisionview.set_revision(revision)
440
332
self.revisionview.set_children(children)
441
self.update_diff_panel(revision, parents)
443
334
def _tree_revision_activated(self, widget, path, col):
444
335
# TODO: more than one parent
445
336
"""Callback for when a treeview row gets activated."""
447
338
parents = self.treeview.get_parents()
449
340
if len(parents) == 0:
450
parent_id = NULL_REVISION
452
343
parent_id = parents[0]
454
345
self.show_diff(revision.revision_id, parent_id)
455
346
self.treeview.grab_focus()
457
349
def _back_clicked_cb(self, *args):
458
350
"""Callback for when the back button is clicked."""
459
351
self.treeview.back()
461
353
def _fwd_clicked_cb(self, *args):
462
354
"""Callback for when the forward button is clicked."""
463
355
self.treeview.forward()
487
379
self.treeview.set_property('compact', self.compact_view)
488
380
self.treeview.refresh()
490
def _branch_index_cb(self, w):
491
from bzrlib.plugins.search import index as _mod_index
492
_mod_index.index_url(self.branch.base)
494
def _branch_search_cb(self, w):
495
from bzrlib.plugins.search import index as _mod_index
496
from bzrlib.plugins.gtk.search import SearchDialog
497
from bzrlib.plugins.search import errors as search_errors
382
def _tag_revision_cb(self, w):
500
index = _mod_index.open_index_url(self.branch.base)
501
except search_errors.NoSearchIndex:
502
dialog = gtk.MessageDialog(self, type=gtk.MESSAGE_QUESTION,
503
buttons=gtk.BUTTONS_OK_CANCEL,
504
message_format="This branch has not been indexed yet. "
506
if dialog.run() == gtk.RESPONSE_OK:
508
index = _mod_index.index_url(self.branch.base)
513
dialog = SearchDialog(index)
515
if dialog.run() == gtk.RESPONSE_OK:
516
self.set_revision(dialog.get_revision())
384
self.treeview.set_sensitive(False)
385
dialog = AddTagDialog(self.branch.repository, self.treeview.get_revision().revision_id, self.branch)
386
response = dialog.run()
387
if response != gtk.RESPONSE_NONE:
390
if response == gtk.RESPONSE_OK:
391
self.treeview.add_tag(dialog.tagname, dialog._revid)
396
self.treeview.set_sensitive(True)
520
398
def _about_dialog_cb(self, w):
521
399
from bzrlib.plugins.gtk.about import AboutDialog
522
401
AboutDialog().run()
524
403
def _col_visibility_changed(self, col, property):
528
407
def _toolbar_visibility_changed(self, col):
529
408
if col.get_active():
532
411
self.toolbar.hide()
533
self.config.set_user_option('viz-toolbar-visible', col.get_active())
535
def _make_diff_nonzero_size(self):
536
"""make sure the diff isn't zero-width or zero-height"""
537
alloc = self.diff.get_allocation()
538
if (alloc.width < 10) or (alloc.height < 10):
539
width, height = self.get_size()
540
self.revisionview.set_size_request(width/3, int(height / 2.5))
542
def _diff_visibility_changed(self, col):
543
"""Hide or show the diff panel."""
546
self._make_diff_nonzero_size()
549
self.config.set_user_option('viz-show-diffs', str(col.get_active()))
550
self.update_diff_panel()
552
def _diff_placement_changed(self, col):
553
"""Toggle the diff panel's position."""
554
self.config.set_user_option('viz-wide-diffs', str(col.get_active()))
556
old = self.paned.get_child2()
557
self.paned.remove(old)
558
self.paned.pack2(self.construct_bottom(), resize=True, shrink=False)
559
self._make_diff_nonzero_size()
561
self.treeview.emit('revision-selected')
563
def _diff_wrap_changed(self, widget):
564
"""Toggle word wrap in the diff widget."""
565
self.config.set_user_option('viz-wrap-diffs', widget.get_active())
566
self.diff._on_wraplines_toggled(widget)
413
def _show_about_cb(self, w):
414
dialog = AboutDialog()
415
dialog.connect('response', lambda d,r: d.destroy())
568
418
def _refresh_clicked(self, w):
569
419
self.treeview.refresh()
574
424
if self.branch.supports_tags():
575
425
tags = self.branch.tags.get_tag_dict().items()
576
tags.sort(reverse=True)
577
428
for tag, revid in tags:
578
tag_image = gtk.Image()
579
tag_image.set_from_file(icon_path('tag-16.png'))
580
tag_item = gtk.ImageMenuItem(tag.replace('_', '__'))
581
tag_item.set_image(tag_image)
429
tag_item = gtk.MenuItem(tag, use_underline=False)
582
430
tag_item.connect('activate', self._tag_selected_cb, revid)
583
tag_item.set_sensitive(self.treeview.has_revision_id(revid))
584
431
menu.add(tag_item)
585
432
self.go_menu_tags.set_submenu(menu)
591
438
self.go_menu_tags.show_all()
593
def _load_size(self, name):
594
"""Read and parse 'name' from self.config.
595
The value is a string, formatted as WIDTHxHEIGHT
596
Returns None, or (width, height)
598
size = self.config.get_user_option(name)
600
width, height = [int(num) for num in size.split('x')]
601
# avoid writing config every time we start
605
def show_diff(self, revid=None, parentid=NULL_REVISION):
440
def show_diff(self, revid=None, parentid=None):
606
441
"""Open a new window to show a diff between the given revisions."""
607
442
from bzrlib.plugins.gtk.diff import DiffWindow
608
443
window = DiffWindow(parent=self)
446
parentid = NULL_REVISION
610
448
rev_tree = self.branch.repository.revision_tree(revid)
611
449
parent_tree = self.branch.repository.revision_tree(parentid)
613
description = revid + " - " + self.branch._get_nick(local=True)
451
description = revid + " - " + self.branch.nick
614
452
window.set_diff(description, rev_tree, parent_tree)
617
def update_diff_panel(self, revision=None, parents=None):
618
"""Show the current revision in the diff panel."""
619
if self.config.get_user_option('viz-show-diffs') != 'True':
622
if not revision: # default to selected row
623
revision = self.treeview.get_revision()
624
if revision == NULL_REVISION:
627
if not parents: # default to selected row's parents
628
parents = self.treeview.get_parents()
629
if len(parents) == 0:
630
parent_id = NULL_REVISION
632
parent_id = parents[0]
634
rev_tree = self.branch.repository.revision_tree(revision.revision_id)
635
parent_tree = self.branch.repository.revision_tree(parent_id)
637
self.diff.set_diff(rev_tree, parent_tree)
638
if self.config.get_user_option('viz-wrap-diffs') == 'True':
639
self.diff._on_wraplines_toggled(wrap=True)