16
from bzrlib.plugins.gtk.window import Window
16
from bzrlib.plugins.gtk import icon_path
17
from bzrlib.plugins.gtk.branchview import TreeView, treemodel
17
18
from bzrlib.plugins.gtk.tags import AddTagDialog
18
19
from bzrlib.plugins.gtk.preferences import PreferencesWindow
19
from bzrlib.plugins.gtk.branchview import TreeView
20
from bzrlib.revision import Revision
21
from bzrlib.config import BranchConfig
22
from bzrlib.config import GlobalConfig
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
24
27
class BranchWindow(Window):
28
31
for a particular branch.
31
def __init__(self, branch, start, maxnum, parent=None):
34
def __init__(self, branch, start_revs, maxnum, parent=None):
32
35
"""Create a new BranchWindow.
34
37
:param branch: Branch object for branch to show.
35
:param start: Revision id of top revision.
38
:param start_revs: Revision ids of top revisions.
36
39
:param maxnum: Maximum number of revisions to display,
53
58
self.set_title(branch.nick + " - revision history")
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
# user-configured window size
61
size = self._load_size('viz-window-size')
65
# Use three-quarters of the screen by default
66
screen = self.get_screen()
67
monitor = screen.get_monitor_geometry(0)
68
width = int(monitor.width * 0.75)
69
height = int(monitor.height * 0.75)
60
70
self.set_default_size(width, height)
71
self.set_size_request(width/3, height/3)
72
self.connect("size-allocate", self._on_size_allocate, 'viz-window-size')
63
75
icon = self.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)
83
96
self.next_rev_action.connect("activate", self._fwd_clicked_cb)
84
97
self.next_rev_action.connect_accelerator()
99
self.refresh_action = gtk.Action("refresh", "_Refresh", "Refresh view", gtk.STOCK_REFRESH)
100
self.refresh_action.set_accel_path("<viz>/View/Refresh")
101
self.refresh_action.set_accel_group(self.accel_group)
102
self.refresh_action.connect("activate", self._refresh_clicked)
103
self.refresh_action.connect_accelerator()
88
107
def set_revision(self, revid):
96
115
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)
116
self.paned.pack1(self.construct_top(), resize=False, shrink=True)
117
self.paned.pack2(self.construct_bottom(), resize=True, shrink=False)
101
vbox.pack_start(self.construct_menubar(), expand=False, fill=True)
102
vbox.pack_start(self.construct_navigation(), expand=False, fill=True)
103
vbox.pack_start(self.construct_loading_msg(), expand=False, fill=True)
120
nav = self.construct_navigation()
121
menubar = self.construct_menubar()
122
vbox.pack_start(menubar, expand=False, fill=True)
123
vbox.pack_start(nav, expand=False, fill=True)
105
125
vbox.pack_start(self.paned, expand=True, fill=True)
106
126
vbox.set_focus_child(self.paned)
110
130
def construct_menubar(self):
111
131
menubar = gtk.MenuBar()
128
148
edit_menuitem = gtk.MenuItem("_Edit")
129
149
edit_menuitem.set_submenu(edit_menu)
131
edit_menu_find = gtk.ImageMenuItem(gtk.STOCK_FIND)
133
151
edit_menu_branchopts = gtk.MenuItem("Branch Settings")
134
152
edit_menu_branchopts.connect('activate', lambda x: PreferencesWindow(self.branch.get_config()).show())
136
154
edit_menu_globopts = gtk.MenuItem("Global Settings")
137
155
edit_menu_globopts.connect('activate', lambda x: PreferencesWindow().show())
139
edit_menu.add(edit_menu_find)
140
157
edit_menu.add(edit_menu_branchopts)
141
158
edit_menu.add(edit_menu_globopts)
144
161
view_menuitem = gtk.MenuItem("_View")
145
162
view_menuitem.set_submenu(view_menu)
164
view_menu_refresh = self.refresh_action.create_menu_item()
165
view_menu_refresh.connect('activate', self._refresh_clicked)
167
view_menu.add(view_menu_refresh)
168
view_menu.add(gtk.SeparatorMenuItem())
147
170
view_menu_toolbar = gtk.CheckMenuItem("Show Toolbar")
148
171
view_menu_toolbar.set_active(True)
172
if self.config.get_user_option('viz-toolbar-visible') == 'False':
173
view_menu_toolbar.set_active(False)
149
175
view_menu_toolbar.connect('toggled', self._toolbar_visibility_changed)
151
177
view_menu_compact = gtk.CheckMenuItem("Show Compact Graph")
152
178
view_menu_compact.set_active(self.compact_view)
153
179
view_menu_compact.connect('activate', self._brokenlines_toggled_cb)
181
view_menu_diffs = gtk.CheckMenuItem("Show Diffs")
182
view_menu_diffs.set_active(False)
183
if self.config.get_user_option('viz-show-diffs') == 'True':
184
view_menu_diffs.set_active(True)
185
view_menu_diffs.connect('toggled', self._diff_visibility_changed)
187
view_menu_wide_diffs = gtk.CheckMenuItem("Wide Diffs")
188
view_menu_wide_diffs.set_active(False)
189
if self.config.get_user_option('viz-wide-diffs') == 'True':
190
view_menu_wide_diffs.set_active(True)
191
view_menu_wide_diffs.connect('toggled', self._diff_placement_changed)
193
view_menu_wrap_diffs = gtk.CheckMenuItem("Wrap _Long Lines in Diffs")
194
view_menu_wrap_diffs.set_active(False)
195
if self.config.get_user_option('viz-wrap-diffs') == 'True':
196
view_menu_wrap_diffs.set_active(True)
197
view_menu_wrap_diffs.connect('toggled', self._diff_wrap_changed)
155
199
view_menu.add(view_menu_toolbar)
156
200
view_menu.add(view_menu_compact)
157
201
view_menu.add(gtk.SeparatorMenuItem())
159
for (label, name) in [("Revision _Number", "revno"), ("_Date", "date")]:
160
col = gtk.CheckMenuItem("Show " + label + " Column")
202
view_menu.add(view_menu_diffs)
203
view_menu.add(view_menu_wide_diffs)
204
view_menu.add(view_menu_wrap_diffs)
205
view_menu.add(gtk.SeparatorMenuItem())
207
self.mnu_show_revno_column = gtk.CheckMenuItem("Show Revision _Number Column")
208
self.mnu_show_date_column = gtk.CheckMenuItem("Show _Date Column")
210
# Revision numbers are pointless if there are multiple branches
211
if len(self.start_revs) > 1:
212
self.mnu_show_revno_column.set_sensitive(False)
213
self.treeview.set_property('revno-column-visible', False)
215
for (col, name) in [(self.mnu_show_revno_column, "revno"),
216
(self.mnu_show_date_column, "date")]:
161
217
col.set_active(self.treeview.get_property(name + "-column-visible"))
162
218
col.connect('toggled', self._col_visibility_changed, name)
163
219
view_menu.add(col)
178
237
go_menu.add(gtk.SeparatorMenuItem())
179
238
go_menu.add(self.go_menu_tags)
181
revision_menu = gtk.Menu()
240
self.revision_menu = RevisionMenu(self.branch.repository, [], self.branch, parent=self)
182
241
revision_menuitem = gtk.MenuItem("_Revision")
183
revision_menuitem.set_submenu(revision_menu)
185
revision_menu_diff = gtk.MenuItem("View Changes")
186
revision_menu_diff.connect('activate',
187
lambda w: self.treeview.show_diff())
189
revision_menu_tag = gtk.MenuItem("Tag Revision")
190
revision_menu_tag.connect('activate', self._tag_revision_cb)
192
revision_menu.add(revision_menu_tag)
193
revision_menu.add(revision_menu_diff)
242
revision_menuitem.set_submenu(self.revision_menu)
195
244
branch_menu = gtk.Menu()
196
245
branch_menuitem = gtk.MenuItem("_Branch")
199
248
branch_menu.add(gtk.MenuItem("Pu_ll Revisions"))
200
249
branch_menu.add(gtk.MenuItem("Pu_sh Revisions"))
252
from bzrlib.plugins import search
254
mutter("Didn't find search plugin")
256
branch_menu.add(gtk.SeparatorMenuItem())
258
branch_index_menuitem = gtk.MenuItem("_Index")
259
branch_index_menuitem.connect('activate', self._branch_index_cb)
260
branch_menu.add(branch_index_menuitem)
262
branch_search_menuitem = gtk.MenuItem("_Search")
263
branch_search_menuitem.connect('activate', self._branch_search_cb)
264
branch_menu.add(branch_search_menuitem)
266
help_menu = gtk.Menu()
267
help_menuitem = gtk.MenuItem("_Help")
268
help_menuitem.set_submenu(help_menu)
270
help_about_menuitem = gtk.ImageMenuItem(gtk.STOCK_ABOUT, self.accel_group)
271
help_about_menuitem.connect('activate', self._about_dialog_cb)
273
help_menu.add(help_about_menuitem)
202
275
menubar.add(file_menuitem)
203
276
menubar.add(edit_menuitem)
204
277
menubar.add(view_menuitem)
205
278
menubar.add(go_menuitem)
206
279
menubar.add(revision_menuitem)
207
280
menubar.add(branch_menuitem)
281
menubar.add(help_menuitem)
208
282
menubar.show_all()
212
def construct_loading_msg(self):
213
image_loading = gtk.image_new_from_stock(gtk.STOCK_REFRESH,
214
gtk.ICON_SIZE_BUTTON)
217
label_loading = gtk.Label(_("Please wait, loading ancestral graph..."))
218
label_loading.set_alignment(0.0, 0.5)
221
self.loading_msg_box = gtk.HBox()
222
self.loading_msg_box.set_spacing(5)
223
self.loading_msg_box.set_border_width(5)
224
self.loading_msg_box.pack_start(image_loading, False, False)
225
self.loading_msg_box.pack_start(label_loading, True, True)
226
self.loading_msg_box.show()
228
return self.loading_msg_box
230
286
def construct_top(self):
231
287
"""Construct the top-half of the window."""
232
288
# FIXME: Make broken_line_length configurable
234
self.treeview = TreeView(self.branch, self.start, self.maxnum, self.compact_view)
290
self.treeview = TreeView(self.branch, self.start_revs, self.maxnum, self.compact_view)
236
292
self.treeview.connect('revision-selected',
237
293
self._treeselection_changed_cb)
239
self.treeview.connect('revisions-loaded',
240
lambda x: self.loading_msg_box.hide())
294
self.treeview.connect('revision-activated',
295
self._tree_revision_activated)
242
297
self.treeview.connect('tag-added', lambda w, t, r: self._update_tags())
245
300
option = self.config.get_user_option(col + '-column-visible')
246
301
if option is not None:
247
302
self.treeview.set_property(col + '-column-visible', option == 'True')
304
self.treeview.set_property(col + '-column-visible', False)
249
306
self.treeview.show()
251
308
align = gtk.Alignment(0.0, 0.0, 1.0, 1.0)
252
309
align.set_padding(5, 0, 0, 0)
253
310
align.add(self.treeview)
311
# user-configured size
312
size = self._load_size('viz-graph-size')
315
align.set_size_request(width, height)
317
(width, height) = self.get_size()
318
align.set_size_request(width, int(height / 2.5))
319
align.connect('size-allocate', self._on_size_allocate, 'viz-graph-size')
279
345
def construct_bottom(self):
280
346
"""Construct the bottom half of the window."""
347
if self.config.get_user_option('viz-wide-diffs') == 'True':
348
self.diff_paned = gtk.VPaned()
350
self.diff_paned = gtk.HPaned()
351
(width, height) = self.get_size()
352
self.diff_paned.set_size_request(20, 20) # shrinkable
281
354
from bzrlib.plugins.gtk.revisionview import RevisionView
282
355
self.revisionview = RevisionView(branch=self.branch)
283
(width, height) = self.get_size()
284
self.revisionview.set_size_request(width, int(height / 2.5))
356
self.revisionview.set_size_request(width/3, int(height / 2.5))
357
# user-configured size
358
size = self._load_size('viz-revisionview-size')
361
self.revisionview.set_size_request(width, height)
362
self.revisionview.connect('size-allocate', self._on_size_allocate, 'viz-revisionview-size')
285
363
self.revisionview.show()
286
364
self.revisionview.set_show_callback(self._show_clicked_cb)
287
365
self.revisionview.connect('notify::revision', self._go_clicked_cb)
288
366
self.treeview.connect('tag-added', lambda w, t, r: self.revisionview.update_tags())
289
return self.revisionview
367
self.diff_paned.pack1(self.revisionview)
369
from bzrlib.plugins.gtk.diff import DiffWidget
370
self.diff = DiffWidget()
371
self.diff_paned.pack2(self.diff)
373
self.diff_paned.show_all()
374
if self.config.get_user_option('viz-show-diffs') != 'True':
377
return self.diff_paned
291
379
def _tag_selected_cb(self, menuitem, revid):
292
380
self.treeview.set_revision_id(revid)
297
385
parents = self.treeview.get_parents()
298
386
children = self.treeview.get_children()
300
if revision is not None:
388
self.revision_menu.set_revision_ids([revision.revision_id])
390
if revision and revision != NULL_REVISION:
301
391
prev_menu = gtk.Menu()
302
392
if len(parents) > 0:
303
393
self.prev_rev_action.set_sensitive(True)
304
394
for parent_id in parents:
305
parent = self.branch.repository.get_revision(parent_id)
307
str = ' (' + parent.properties['branch-nick'] + ')'
395
if parent_id and parent_id != NULL_REVISION:
396
parent = self.branch.repository.get_revision(parent_id)
398
str = ' (' + parent.properties['branch-nick'] + ')'
311
item = gtk.MenuItem(parent.message.split("\n")[0] + str)
312
item.connect('activate', self._set_revision_cb, parent_id)
402
item = gtk.MenuItem(parent.message.split("\n")[0] + str)
403
item.connect('activate', self._set_revision_cb, parent_id)
314
405
prev_menu.show_all()
316
407
self.prev_rev_action.set_sensitive(False)
341
432
self.revisionview.set_revision(revision)
342
433
self.revisionview.set_children(children)
434
self.update_diff_panel(revision, parents)
436
def _tree_revision_activated(self, widget, path, col):
437
# TODO: more than one parent
438
"""Callback for when a treeview row gets activated."""
439
revision = self.treeview.get_revision()
440
parents = self.treeview.get_parents()
442
if len(parents) == 0:
445
parent_id = parents[0]
447
self.show_diff(revision.revision_id, parent_id)
448
self.treeview.grab_focus()
344
450
def _back_clicked_cb(self, *args):
345
451
"""Callback for when the back button is clicked."""
346
452
self.treeview.back()
357
463
def _show_clicked_cb(self, revid, parentid):
358
464
"""Callback for when the show button for a parent is clicked."""
359
self.treeview.show_diff(revid, parentid)
465
self.show_diff(revid, parentid)
360
466
self.treeview.grab_focus()
362
468
def _set_revision_cb(self, w, revision_id):
374
480
self.treeview.set_property('compact', self.compact_view)
375
481
self.treeview.refresh()
377
def _tag_revision_cb(self, w):
483
def _branch_index_cb(self, w):
484
from bzrlib.plugins.search import index as _mod_index
485
_mod_index.index_url(self.branch.base)
487
def _branch_search_cb(self, w):
488
from bzrlib.plugins.search import index as _mod_index
489
from bzrlib.plugins.gtk.search import SearchDialog
490
from bzrlib.plugins.search import errors as search_errors
379
self.treeview.set_sensitive(False)
380
dialog = AddTagDialog(self.branch.repository, self.treeview.get_revision().revision_id, self.branch)
381
response = dialog.run()
382
if response != gtk.RESPONSE_NONE:
385
if response == gtk.RESPONSE_OK:
386
self.treeview.add_tag(dialog.tagname, dialog._revid)
391
self.treeview.set_sensitive(True)
493
index = _mod_index.open_index_url(self.branch.base)
494
except search_errors.NoSearchIndex:
495
dialog = gtk.MessageDialog(self, type=gtk.MESSAGE_QUESTION,
496
buttons=gtk.BUTTONS_OK_CANCEL,
497
message_format="This branch has not been indexed yet. "
499
if dialog.run() == gtk.RESPONSE_OK:
501
index = _mod_index.index_url(self.branch.base)
506
dialog = SearchDialog(index)
508
if dialog.run() == gtk.RESPONSE_OK:
509
self.set_revision(dialog.get_revision())
513
def _about_dialog_cb(self, w):
514
from bzrlib.plugins.gtk.about import AboutDialog
393
518
def _col_visibility_changed(self, col, property):
394
519
self.config.set_user_option(property + '-column-visible', col.get_active())
397
522
def _toolbar_visibility_changed(self, col):
398
523
if col.get_active():
401
526
self.toolbar.hide()
527
self.config.set_user_option('viz-toolbar-visible', col.get_active())
529
def _make_diff_nonzero_size(self):
530
"""make sure the diff isn't zero-width or zero-height"""
531
alloc = self.diff.get_allocation()
532
if (alloc.width < 10) or (alloc.height < 10):
533
width, height = self.get_size()
534
self.revisionview.set_size_request(width/3, int(height / 2.5))
536
def _diff_visibility_changed(self, col):
537
"""Hide or show the diff panel."""
540
self._make_diff_nonzero_size()
543
self.config.set_user_option('viz-show-diffs', str(col.get_active()))
544
self.update_diff_panel()
546
def _diff_placement_changed(self, col):
547
"""Toggle the diff panel's position."""
548
self.config.set_user_option('viz-wide-diffs', str(col.get_active()))
550
old = self.paned.get_child2()
551
self.paned.remove(old)
552
self.paned.pack2(self.construct_bottom(), resize=True, shrink=False)
553
self._make_diff_nonzero_size()
555
self.treeview.emit('revision-selected')
557
def _diff_wrap_changed(self, widget):
558
"""Toggle word wrap in the diff widget."""
559
self.config.set_user_option('viz-wrap-diffs', widget.get_active())
560
self.diff._on_wraplines_toggled(widget)
403
562
def _show_about_cb(self, w):
404
563
dialog = AboutDialog()
405
564
dialog.connect('response', lambda d,r: d.destroy())
408
567
def _refresh_clicked(self, w):
409
self.treeview.update()
568
self.treeview.refresh()
411
570
def _update_tags(self):
412
571
menu = gtk.Menu()
428
590
self.go_menu_tags.show_all()
592
def _load_size(self, name):
593
"""Read and parse 'name' from self.config.
594
The value is a string, formatted as WIDTHxHEIGHT
595
Returns None, or (width, height)
597
size = self.config.get_user_option(name)
599
width, height = [int(num) for num in size.split('x')]
600
# avoid writing config every time we start
601
self._sizes[name] = (width, height)
605
def _on_size_allocate(self, widget, allocation, name):
606
"""When window has been resized, save the new size."""
608
if name in self._sizes:
609
width, height = self._sizes[name]
611
size_changed = (width != allocation.width) or \
612
(height != allocation.height)
615
width, height = allocation.width, allocation.height
616
self._sizes[name] = (width, height)
617
value = '%sx%s' % (width, height)
618
self.config.set_user_option(name, value)
620
def show_diff(self, revid=None, parentid=None):
621
"""Open a new window to show a diff between the given revisions."""
622
from bzrlib.plugins.gtk.diff import DiffWindow
623
window = DiffWindow(parent=self)
626
parentid = NULL_REVISION
628
rev_tree = self.branch.repository.revision_tree(revid)
629
parent_tree = self.branch.repository.revision_tree(parentid)
631
description = revid + " - " + self.branch.nick
632
window.set_diff(description, rev_tree, parent_tree)
635
def update_diff_panel(self, revision=None, parents=None):
636
"""Show the current revision in the diff panel."""
637
if self.config.get_user_option('viz-show-diffs') != 'True':
640
if not revision: # default to selected row
641
revision = self.treeview.get_revision()
642
if (not revision) or (revision == NULL_REVISION):
645
if not parents: # default to selected row's parents
646
parents = self.treeview.get_parents()
647
if len(parents) == 0:
650
parent_id = parents[0]
652
rev_tree = self.branch.repository.revision_tree(revision.revision_id)
653
parent_tree = self.branch.repository.revision_tree(parent_id)
655
self.diff.set_diff(rev_tree, parent_tree)
656
if self.config.get_user_option('viz-wrap-diffs') == 'True':
657
self.diff._on_wraplines_toggled(wrap=True)