16
from bzrlib.plugins.gtk import icon_path
17
from bzrlib.plugins.gtk.branchview import TreeView, treemodel
16
from bzrlib.plugins.gtk.window import Window
18
17
from bzrlib.plugins.gtk.tags import AddTagDialog
19
18
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
19
from bzrlib.revision import Revision
20
from bzrlib.config import BranchConfig
21
from bzrlib.config import GlobalConfig
22
from treeview import TreeView
23
from about import AboutDialog
27
25
class BranchWindow(Window):
54
52
self.compact_view = False
56
self.set_title(branch._get_nick(local=True) + " - revision history")
54
self.set_title(branch.nick + " - revision history")
58
# user-configured window size
59
size = self._load_size('viz-window-size')
63
# Use three-quarters of the screen by default
64
screen = self.get_screen()
65
monitor = screen.get_monitor_geometry(0)
66
width = int(monitor.width * 0.75)
67
height = int(monitor.height * 0.75)
56
# Use three-quarters of the screen by default
57
screen = self.get_screen()
58
monitor = screen.get_monitor_geometry(0)
59
width = int(monitor.width * 0.75)
60
height = int(monitor.height * 0.75)
68
61
self.set_default_size(width, height)
69
self.set_size_request(width/3, height/3)
70
self._save_size_on_destroy(self, 'viz-window-size')
73
64
icon = self.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)
94
84
self.next_rev_action.connect("activate", self._fwd_clicked_cb)
95
85
self.next_rev_action.connect_accelerator()
97
self.refresh_action = gtk.Action("refresh", "_Refresh", "Refresh view", gtk.STOCK_REFRESH)
98
self.refresh_action.set_accel_path("<viz>/View/Refresh")
99
self.refresh_action.set_accel_group(self.accel_group)
100
self.refresh_action.connect("activate", self._refresh_clicked)
101
self.refresh_action.connect_accelerator()
105
def _save_size_on_destroy(self, widget, config_name):
106
"""Creates a hook that saves the size of widget to config option
107
config_name when the window is destroyed/closed."""
109
width, height = widget.allocation.width, widget.allocation.height
110
value = '%sx%s' % (width, height)
111
self.config.set_user_option(config_name, value)
112
self.connect("destroy", save_size)
114
89
def set_revision(self, revid):
115
90
self.treeview.set_revision_id(revid)
122
97
self.paned = gtk.VPaned()
123
self.paned.pack1(self.construct_top(), resize=False, shrink=True)
124
self.paned.pack2(self.construct_bottom(), resize=True, shrink=False)
98
self.paned.pack1(self.construct_top(), resize=True, shrink=False)
99
self.paned.pack2(self.construct_bottom(), resize=False, shrink=True)
125
100
self.paned.show()
127
nav = self.construct_navigation()
128
menubar = self.construct_menubar()
129
vbox.pack_start(menubar, expand=False, fill=True)
130
vbox.pack_start(nav, expand=False, fill=True)
102
vbox.pack_start(self.construct_menubar(), expand=False, fill=True)
103
vbox.pack_start(self.construct_navigation(), expand=False, fill=True)
104
vbox.pack_start(self.construct_loading_msg(), expand=False, fill=True)
132
106
vbox.pack_start(self.paned, expand=True, fill=True)
133
107
vbox.set_focus_child(self.paned)
135
self.treeview.connect('revision-selected',
136
self._treeselection_changed_cb)
137
self.treeview.connect('revision-activated',
138
self._tree_revision_activated)
140
self.treeview.connect('tag-added', lambda w, t, r: self._update_tags())
143
111
def construct_menubar(self):
144
112
menubar = gtk.MenuBar()
174
145
view_menuitem = gtk.MenuItem("_View")
175
146
view_menuitem.set_submenu(view_menu)
177
view_menu_refresh = self.refresh_action.create_menu_item()
178
view_menu_refresh.connect('activate', self._refresh_clicked)
180
view_menu.add(view_menu_refresh)
181
view_menu.add(gtk.SeparatorMenuItem())
183
148
view_menu_toolbar = gtk.CheckMenuItem("Show Toolbar")
184
149
view_menu_toolbar.set_active(True)
185
if self.config.get_user_option('viz-toolbar-visible') == 'False':
186
view_menu_toolbar.set_active(False)
188
150
view_menu_toolbar.connect('toggled', self._toolbar_visibility_changed)
190
152
view_menu_compact = gtk.CheckMenuItem("Show Compact Graph")
191
153
view_menu_compact.set_active(self.compact_view)
192
154
view_menu_compact.connect('activate', self._brokenlines_toggled_cb)
194
view_menu_diffs = gtk.CheckMenuItem("Show Diffs")
195
view_menu_diffs.set_active(False)
196
if self.config.get_user_option('viz-show-diffs') == 'True':
197
view_menu_diffs.set_active(True)
198
view_menu_diffs.connect('toggled', self._diff_visibility_changed)
200
view_menu_wide_diffs = gtk.CheckMenuItem("Wide Diffs")
201
view_menu_wide_diffs.set_active(False)
202
if self.config.get_user_option('viz-wide-diffs') == 'True':
203
view_menu_wide_diffs.set_active(True)
204
view_menu_wide_diffs.connect('toggled', self._diff_placement_changed)
206
view_menu_wrap_diffs = gtk.CheckMenuItem("Wrap _Long Lines in Diffs")
207
view_menu_wrap_diffs.set_active(False)
208
if self.config.get_user_option('viz-wrap-diffs') == 'True':
209
view_menu_wrap_diffs.set_active(True)
210
view_menu_wrap_diffs.connect('toggled', self._diff_wrap_changed)
212
156
view_menu.add(view_menu_toolbar)
213
157
view_menu.add(view_menu_compact)
214
158
view_menu.add(gtk.SeparatorMenuItem())
215
view_menu.add(view_menu_diffs)
216
view_menu.add(view_menu_wide_diffs)
217
view_menu.add(view_menu_wrap_diffs)
218
view_menu.add(gtk.SeparatorMenuItem())
220
self.mnu_show_revno_column = gtk.CheckMenuItem("Show Revision _Number Column")
221
self.mnu_show_date_column = gtk.CheckMenuItem("Show _Date Column")
223
# Revision numbers are pointless if there are multiple branches
224
if len(self.start_revs) > 1:
225
self.mnu_show_revno_column.set_sensitive(False)
226
self.treeview.set_property('revno-column-visible', False)
228
for (col, name) in [(self.mnu_show_revno_column, "revno"),
229
(self.mnu_show_date_column, "date")]:
160
for (label, name) in [("Revision _Number", "revno"), ("_Date", "date")]:
161
col = gtk.CheckMenuItem("Show " + label + " Column")
230
162
col.set_active(self.treeview.get_property(name + "-column-visible"))
231
163
col.connect('toggled', self._col_visibility_changed, name)
232
164
view_menu.add(col)
239
171
go_menu_next = self.next_rev_action.create_menu_item()
240
172
go_menu_prev = self.prev_rev_action.create_menu_item()
242
tag_image = gtk.Image()
243
tag_image.set_from_file(icon_path("tag-16.png"))
244
self.go_menu_tags = gtk.ImageMenuItem("_Tags")
245
self.go_menu_tags.set_image(tag_image)
246
self.treeview.connect('refreshed', lambda w: self._update_tags())
174
tags_menu = gtk.Menu()
175
go_menu_tags = gtk.MenuItem("_Tags")
176
go_menu_tags.set_submenu(tags_menu)
178
if self.branch.supports_tags():
179
tags = self.branch.tags.get_tag_dict().items()
182
for tag, revid in tags:
183
tag_item = gtk.MenuItem(tag)
184
tag_item.connect('activate', self._tag_selected_cb, revid)
185
tags_menu.add(tag_item)
187
go_menu_tags.set_sensitive(len(tags) != 0)
189
go_menu_tags.set_sensitive(False)
248
191
go_menu.add(go_menu_next)
249
192
go_menu.add(go_menu_prev)
250
193
go_menu.add(gtk.SeparatorMenuItem())
251
go_menu.add(self.go_menu_tags)
194
go_menu.add(go_menu_tags)
253
self.revision_menu = RevisionMenu(self.branch.repository, [], self.branch, parent=self)
196
revision_menu = gtk.Menu()
254
197
revision_menuitem = gtk.MenuItem("_Revision")
255
revision_menuitem.set_submenu(self.revision_menu)
198
revision_menuitem.set_submenu(revision_menu)
200
revision_menu_diff = gtk.MenuItem("View Changes")
201
revision_menu_diff.connect('activate',
202
lambda w: self.treeview.show_diff())
204
revision_menu_tag = gtk.MenuItem("Tag Revision")
205
revision_menu_tag.connect('activate', self._tag_revision_cb)
207
revision_menu.add(revision_menu_tag)
208
revision_menu.add(revision_menu_diff)
257
210
branch_menu = gtk.Menu()
258
211
branch_menuitem = gtk.MenuItem("_Branch")
261
214
branch_menu.add(gtk.MenuItem("Pu_ll Revisions"))
262
215
branch_menu.add(gtk.MenuItem("Pu_sh Revisions"))
265
from bzrlib.plugins import search
267
mutter("Didn't find search plugin")
269
branch_menu.add(gtk.SeparatorMenuItem())
271
branch_index_menuitem = gtk.MenuItem("_Index")
272
branch_index_menuitem.connect('activate', self._branch_index_cb)
273
branch_menu.add(branch_index_menuitem)
275
branch_search_menuitem = gtk.MenuItem("_Search")
276
branch_search_menuitem.connect('activate', self._branch_search_cb)
277
branch_menu.add(branch_search_menuitem)
279
help_menu = gtk.Menu()
280
help_menuitem = gtk.MenuItem("_Help")
281
help_menuitem.set_submenu(help_menu)
283
help_about_menuitem = gtk.ImageMenuItem(gtk.STOCK_ABOUT, self.accel_group)
284
help_about_menuitem.connect('activate', self._about_dialog_cb)
286
help_menu.add(help_about_menuitem)
288
217
menubar.add(file_menuitem)
289
218
menubar.add(edit_menuitem)
290
219
menubar.add(view_menuitem)
291
220
menubar.add(go_menuitem)
292
221
menubar.add(revision_menuitem)
293
222
menubar.add(branch_menuitem)
294
menubar.add(help_menuitem)
295
223
menubar.show_all()
227
def construct_loading_msg(self):
228
image_loading = gtk.image_new_from_stock(gtk.STOCK_REFRESH,
229
gtk.ICON_SIZE_BUTTON)
232
label_loading = gtk.Label(_("Please wait, loading ancestral graph..."))
233
label_loading.set_alignment(0.0, 0.5)
236
self.loading_msg_box = gtk.HBox()
237
self.loading_msg_box.set_spacing(5)
238
self.loading_msg_box.set_border_width(5)
239
self.loading_msg_box.pack_start(image_loading, False, False)
240
self.loading_msg_box.pack_start(label_loading, True, True)
241
self.loading_msg_box.show()
243
return self.loading_msg_box
299
245
def construct_top(self):
300
246
"""Construct the top-half of the window."""
301
247
# FIXME: Make broken_line_length configurable
303
self.treeview = TreeView(self.branch, self.start_revs, self.maxnum, self.compact_view)
249
self.treeview = TreeView(self.branch, self.start, self.maxnum, self.compact_view)
251
self.treeview.connect('revision-selected',
252
self._treeselection_changed_cb)
254
self.treeview.connect('revisions-loaded',
255
lambda x: self.loading_msg_box.hide())
305
257
for col in ["revno", "date"]:
306
258
option = self.config.get_user_option(col + '-column-visible')
307
259
if option is not None:
308
260
self.treeview.set_property(col + '-column-visible', option == 'True')
310
self.treeview.set_property(col + '-column-visible', False)
312
262
self.treeview.show()
314
264
align = gtk.Alignment(0.0, 0.0, 1.0, 1.0)
315
265
align.set_padding(5, 0, 0, 0)
316
266
align.add(self.treeview)
317
# user-configured size
318
size = self._load_size('viz-graph-size')
321
align.set_size_request(width, height)
323
(width, height) = self.get_size()
324
align.set_size_request(width, int(height / 2.5))
325
self._save_size_on_destroy(align, 'viz-graph-size')
351
292
def construct_bottom(self):
352
293
"""Construct the bottom half of the window."""
353
if self.config.get_user_option('viz-wide-diffs') == 'True':
354
self.diff_paned = gtk.VPaned()
356
self.diff_paned = gtk.HPaned()
294
from bzrlib.plugins.gtk.revisionview import RevisionView
295
self.revisionview = RevisionView(None, tags=[], show_children=True, branch=self.branch)
357
296
(width, height) = self.get_size()
358
self.diff_paned.set_size_request(20, 20) # shrinkable
360
from bzrlib.plugins.gtk.revisionview import RevisionView
361
self.revisionview = RevisionView(branch=self.branch)
362
self.revisionview.set_size_request(width/3, int(height / 2.5))
363
# user-configured size
364
size = self._load_size('viz-revisionview-size')
367
self.revisionview.set_size_request(width, height)
368
self._save_size_on_destroy(self.revisionview, 'viz-revisionview-size')
297
self.revisionview.set_size_request(width, int(height / 2.5))
369
298
self.revisionview.show()
370
299
self.revisionview.set_show_callback(self._show_clicked_cb)
371
self.revisionview.connect('notify::revision', self._go_clicked_cb)
372
self.treeview.connect('tag-added', lambda w, t, r: self.revisionview.update_tags())
373
self.diff_paned.pack1(self.revisionview)
375
from bzrlib.plugins.gtk.diff import DiffWidget
376
self.diff = DiffWidget()
377
self.diff_paned.pack2(self.diff)
379
self.diff_paned.show_all()
380
if self.config.get_user_option('viz-show-diffs') != 'True':
383
return self.diff_paned
300
self.revisionview.set_go_callback(self._go_clicked_cb)
301
return self.revisionview
385
303
def _tag_selected_cb(self, menuitem, revid):
386
304
self.treeview.set_revision_id(revid)
391
309
parents = self.treeview.get_parents()
392
310
children = self.treeview.get_children()
394
self.revision_menu.set_revision_ids([revision.revision_id])
396
if revision and revision != NULL_REVISION:
312
if revision is not None:
397
313
prev_menu = gtk.Menu()
398
314
if len(parents) > 0:
399
315
self.prev_rev_action.set_sensitive(True)
400
316
for parent_id in parents:
401
if parent_id and parent_id != NULL_REVISION:
402
parent = self.branch.repository.get_revision(parent_id)
404
str = ' (' + parent.properties['branch-nick'] + ')'
317
parent = self.branch.repository.get_revision(parent_id)
319
str = ' (' + parent.properties['branch-nick'] + ')'
408
item = gtk.MenuItem(parent.message.split("\n")[0] + str)
409
item.connect('activate', self._set_revision_cb, parent_id)
323
item = gtk.MenuItem(parent.message.split("\n")[0] + str)
324
item.connect('activate', self._set_revision_cb, parent_id)
411
326
prev_menu.show_all()
413
328
self.prev_rev_action.set_sensitive(False)
436
351
self.next_button.set_menu(next_menu)
438
self.revisionview.set_revision(revision)
439
self.revisionview.set_children(children)
440
self.update_diff_panel(revision, parents)
442
def _tree_revision_activated(self, widget, path, col):
443
# TODO: more than one parent
444
"""Callback for when a treeview row gets activated."""
445
revision = self.treeview.get_revision()
446
parents = self.treeview.get_parents()
448
if len(parents) == 0:
449
parent_id = NULL_REVISION
451
parent_id = parents[0]
453
self.show_diff(revision.revision_id, parent_id)
454
self.treeview.grab_focus()
354
if self.branch.supports_tags():
355
tagdict = self.branch.tags.get_reverse_tag_dict()
356
if tagdict.has_key(revision.revision_id):
357
tags = tagdict[revision.revision_id]
358
self.revisionview.set_revision(revision, tags, children)
456
360
def _back_clicked_cb(self, *args):
457
361
"""Callback for when the back button is clicked."""
458
362
self.treeview.back()
461
365
"""Callback for when the forward button is clicked."""
462
366
self.treeview.forward()
464
def _go_clicked_cb(self, w, p):
368
def _go_clicked_cb(self, revid):
465
369
"""Callback for when the go button for a parent is clicked."""
466
if self.revisionview.get_revision() is not None:
467
self.treeview.set_revision(self.revisionview.get_revision())
370
self.treeview.set_revision_id(revid)
469
372
def _show_clicked_cb(self, revid, parentid):
470
373
"""Callback for when the show button for a parent is clicked."""
471
self.show_diff(revid, parentid)
374
self.treeview.show_diff(revid, parentid)
472
375
self.treeview.grab_focus()
474
377
def _set_revision_cb(self, w, revision_id):
486
389
self.treeview.set_property('compact', self.compact_view)
487
390
self.treeview.refresh()
489
def _branch_index_cb(self, w):
490
from bzrlib.plugins.search import index as _mod_index
491
_mod_index.index_url(self.branch.base)
493
def _branch_search_cb(self, w):
494
from bzrlib.plugins.search import index as _mod_index
495
from bzrlib.plugins.gtk.search import SearchDialog
496
from bzrlib.plugins.search import errors as search_errors
392
def _tag_revision_cb(self, w):
499
index = _mod_index.open_index_url(self.branch.base)
500
except search_errors.NoSearchIndex:
501
dialog = gtk.MessageDialog(self, type=gtk.MESSAGE_QUESTION,
502
buttons=gtk.BUTTONS_OK_CANCEL,
503
message_format="This branch has not been indexed yet. "
505
if dialog.run() == gtk.RESPONSE_OK:
507
index = _mod_index.index_url(self.branch.base)
512
dialog = SearchDialog(index)
514
if dialog.run() == gtk.RESPONSE_OK:
515
self.set_revision(dialog.get_revision())
519
def _about_dialog_cb(self, w):
520
from bzrlib.plugins.gtk.about import AboutDialog
394
self.treeview.set_sensitive(False)
396
dialog = AddTagDialog(self.branch.repository, self.treeview.get_revision().revision_id, self.branch)
397
response = dialog.run()
398
if response != gtk.RESPONSE_NONE:
401
if response == gtk.RESPONSE_OK:
403
self.branch.lock_write()
404
self.branch.tags.set_tag(dialog.tagname, dialog._revid)
411
self.branch.lock_read()
412
self.treeview.emit("revision-selected")
413
self.treeview.set_sensitive(True)
524
415
def _col_visibility_changed(self, col, property):
525
416
self.config.set_user_option(property + '-column-visible', col.get_active())
528
419
def _toolbar_visibility_changed(self, col):
529
420
if col.get_active():
532
423
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)
568
425
def _show_about_cb(self, w):
569
426
dialog = AboutDialog()
570
427
dialog.connect('response', lambda d,r: d.destroy())
573
430
def _refresh_clicked(self, w):
574
self.treeview.refresh()
576
def _update_tags(self):
579
if self.branch.supports_tags():
580
tags = self.branch.tags.get_tag_dict().items()
581
tags.sort(reverse=True)
582
for tag, revid in tags:
583
tag_image = gtk.Image()
584
tag_image.set_from_file(icon_path('tag-16.png'))
585
tag_item = gtk.ImageMenuItem(tag.replace('_', '__'))
586
tag_item.set_image(tag_image)
587
tag_item.connect('activate', self._tag_selected_cb, revid)
588
tag_item.set_sensitive(self.treeview.has_revision_id(revid))
590
self.go_menu_tags.set_submenu(menu)
592
self.go_menu_tags.set_sensitive(len(tags) != 0)
594
self.go_menu_tags.set_sensitive(False)
596
self.go_menu_tags.show_all()
598
def _load_size(self, name):
599
"""Read and parse 'name' from self.config.
600
The value is a string, formatted as WIDTHxHEIGHT
601
Returns None, or (width, height)
603
size = self.config.get_user_option(name)
605
width, height = [int(num) for num in size.split('x')]
606
# avoid writing config every time we start
610
def show_diff(self, revid=None, parentid=NULL_REVISION):
611
"""Open a new window to show a diff between the given revisions."""
612
from bzrlib.plugins.gtk.diff import DiffWindow
613
window = DiffWindow(parent=self)
615
rev_tree = self.branch.repository.revision_tree(revid)
616
parent_tree = self.branch.repository.revision_tree(parentid)
618
description = revid + " - " + self.branch._get_nick(local=True)
619
window.set_diff(description, rev_tree, parent_tree)
622
def update_diff_panel(self, revision=None, parents=None):
623
"""Show the current revision in the diff panel."""
624
if self.config.get_user_option('viz-show-diffs') != 'True':
627
if not revision: # default to selected row
628
revision = self.treeview.get_revision()
629
if revision == NULL_REVISION:
632
if not parents: # default to selected row's parents
633
parents = self.treeview.get_parents()
634
if len(parents) == 0:
635
parent_id = NULL_REVISION
637
parent_id = parents[0]
639
rev_tree = self.branch.repository.revision_tree(revision.revision_id)
640
parent_tree = self.branch.repository.revision_tree(parent_id)
642
self.diff.set_diff(rev_tree, parent_tree)
643
if self.config.get_user_option('viz-wrap-diffs') == 'True':
644
self.diff._on_wraplines_toggled(wrap=True)
431
self.treeview.update()