1
# -*- coding: UTF-8 -*-
4
3
This module contains the code to manage the branch information window,
5
4
which contains both the revision graph and details panes.
8
__copyright__ = "Copyright © 2005 Canonical Ltd."
7
__copyright__ = "Copyright (c) 2005 Canonical Ltd."
9
8
__author__ = "Scott James Remnant <scott@ubuntu.com>"
16
from bzrlib.plugins.gtk.window import Window
17
13
from bzrlib.plugins.gtk import icon_path
18
from bzrlib.plugins.gtk.tags import AddTagDialog
14
from bzrlib.plugins.gtk.branchview import TreeView
19
15
from bzrlib.plugins.gtk.preferences import PreferencesWindow
20
from bzrlib.plugins.gtk.branchview import TreeView, treemodel
16
from bzrlib.plugins.gtk.revisionmenu import RevisionMenu
17
from bzrlib.plugins.gtk.window import Window
22
from bzrlib.config import BranchConfig, GlobalConfig
23
from bzrlib.revision import Revision, NULL_REVISION
19
from bzrlib.config import GlobalConfig
20
from bzrlib.revision import NULL_REVISION
24
21
from bzrlib.trace import mutter
26
24
class BranchWindow(Window):
53
51
self.compact_view = False
55
self.set_title(branch.nick + " - revision history")
53
self.set_title(branch._get_nick(local=True) + " - revision history")
57
# Use three-quarters of the screen by default
58
screen = self.get_screen()
59
monitor = screen.get_monitor_geometry(0)
60
width = int(monitor.width * 0.75)
61
height = int(monitor.height * 0.75)
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)
62
65
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')
65
70
icon = self.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)
72
77
self.accel_group = gtk.AccelGroup()
73
78
self.add_accel_group(self.accel_group)
75
gtk.Action.set_tool_item_type(gtk.MenuToolButton)
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)
77
84
self.prev_rev_action = gtk.Action("prev-rev", "_Previous Revision", "Go to the previous revision", gtk.STOCK_GO_DOWN)
78
85
self.prev_rev_action.set_accel_path("<viz>/Go/Previous Revision")
92
99
self.refresh_action.connect("activate", self._refresh_clicked)
93
100
self.refresh_action.connect_accelerator()
102
self.vbox = self.construct()
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)
97
113
def set_revision(self, revid):
98
114
self.treeview.set_revision_id(revid)
102
118
vbox = gtk.VBox(spacing=0)
105
self.paned = gtk.VPaned()
106
self.paned.pack1(self.construct_top(), resize=True, shrink=False)
107
self.paned.pack2(self.construct_bottom(), resize=False, shrink=True)
121
# order is important here
122
paned = self.construct_paned()
123
nav = self.construct_navigation()
124
menubar = self.construct_menubar()
126
vbox.pack_start(menubar, expand=False, fill=True)
127
vbox.pack_start(nav, expand=False, fill=True)
128
vbox.pack_start(paned, expand=True, fill=True)
129
vbox.set_focus_child(paned)
136
def construct_paned(self):
137
"""Construct the main HPaned/VPaned contents."""
138
if self.config.get_user_option('viz-vertical') == 'True':
139
self.paned = gtk.HPaned()
141
self.paned = gtk.VPaned()
143
self.paned.pack1(self.construct_top(), resize=False, shrink=True)
144
self.paned.pack2(self.construct_bottom(), resize=True, shrink=False)
108
145
self.paned.show()
110
vbox.pack_start(self.construct_menubar(), expand=False, fill=True)
111
vbox.pack_start(self.construct_navigation(), expand=False, fill=True)
113
vbox.pack_start(self.paned, expand=True, fill=True)
114
vbox.set_focus_child(self.paned)
118
149
def construct_menubar(self):
119
150
menubar = gtk.MenuBar()
125
156
file_menu_close = gtk.ImageMenuItem(gtk.STOCK_CLOSE, self.accel_group)
126
157
file_menu_close.connect('activate', lambda x: self.destroy())
128
159
file_menu_quit = gtk.ImageMenuItem(gtk.STOCK_QUIT, self.accel_group)
129
160
file_menu_quit.connect('activate', lambda x: gtk.main_quit())
131
162
if self._parent is not None:
132
163
file_menu.add(file_menu_close)
133
164
file_menu.add(file_menu_quit)
136
167
edit_menuitem = gtk.MenuItem("_Edit")
137
168
edit_menuitem.set_submenu(edit_menu)
139
edit_menu_find = gtk.ImageMenuItem(gtk.STOCK_FIND)
141
170
edit_menu_branchopts = gtk.MenuItem("Branch Settings")
142
171
edit_menu_branchopts.connect('activate', lambda x: PreferencesWindow(self.branch.get_config()).show())
144
173
edit_menu_globopts = gtk.MenuItem("Global Settings")
145
174
edit_menu_globopts.connect('activate', lambda x: PreferencesWindow().show())
147
edit_menu.add(edit_menu_find)
148
176
edit_menu.add(edit_menu_branchopts)
149
177
edit_menu.add(edit_menu_globopts)
161
189
view_menu_toolbar = gtk.CheckMenuItem("Show Toolbar")
162
190
view_menu_toolbar.set_active(True)
191
if self.config.get_user_option('viz-toolbar-visible') == 'False':
192
view_menu_toolbar.set_active(False)
163
194
view_menu_toolbar.connect('toggled', self._toolbar_visibility_changed)
165
196
view_menu_compact = gtk.CheckMenuItem("Show Compact Graph")
166
197
view_menu_compact.set_active(self.compact_view)
167
198
view_menu_compact.connect('activate', self._brokenlines_toggled_cb)
200
view_menu_vertical = gtk.CheckMenuItem("Side-by-side Layout")
201
view_menu_vertical.set_active(False)
202
if self.config.get_user_option('viz-vertical') == 'True':
203
view_menu_vertical.set_active(True)
204
view_menu_vertical.connect('toggled', self._vertical_layout)
206
view_menu_diffs = gtk.CheckMenuItem("Show Diffs")
207
view_menu_diffs.set_active(False)
208
if self.config.get_user_option('viz-show-diffs') == 'True':
209
view_menu_diffs.set_active(True)
210
view_menu_diffs.connect('toggled', self._diff_visibility_changed)
212
view_menu_wide_diffs = gtk.CheckMenuItem("Wide Diffs")
213
view_menu_wide_diffs.set_active(False)
214
if self.config.get_user_option('viz-wide-diffs') == 'True':
215
view_menu_wide_diffs.set_active(True)
216
view_menu_wide_diffs.connect('toggled', self._diff_placement_changed)
218
view_menu_wrap_diffs = gtk.CheckMenuItem("Wrap _Long Lines in Diffs")
219
view_menu_wrap_diffs.set_active(False)
220
if self.config.get_user_option('viz-wrap-diffs') == 'True':
221
view_menu_wrap_diffs.set_active(True)
222
view_menu_wrap_diffs.connect('toggled', self._diff_wrap_changed)
169
224
view_menu.add(view_menu_toolbar)
170
225
view_menu.add(view_menu_compact)
226
view_menu.add(view_menu_vertical)
227
view_menu.add(gtk.SeparatorMenuItem())
228
view_menu.add(view_menu_diffs)
229
view_menu.add(view_menu_wide_diffs)
230
view_menu.add(view_menu_wrap_diffs)
171
231
view_menu.add(gtk.SeparatorMenuItem())
173
233
self.mnu_show_revno_column = gtk.CheckMenuItem("Show Revision _Number Column")
196
256
tag_image.set_from_file(icon_path("tag-16.png"))
197
257
self.go_menu_tags = gtk.ImageMenuItem("_Tags")
198
258
self.go_menu_tags.set_image(tag_image)
259
self.treeview.connect('refreshed', lambda w: self._update_tags())
201
261
go_menu.add(go_menu_next)
202
262
go_menu.add(go_menu_prev)
203
263
go_menu.add(gtk.SeparatorMenuItem())
204
264
go_menu.add(self.go_menu_tags)
206
revision_menu = gtk.Menu()
266
self.revision_menu = RevisionMenu(self.branch.repository, [],
267
self.branch, parent=self)
207
268
revision_menuitem = gtk.MenuItem("_Revision")
208
revision_menuitem.set_submenu(revision_menu)
210
revision_menu_diff = gtk.MenuItem("View Changes")
211
revision_menu_diff.connect('activate',
214
revision_menu_compare = gtk.MenuItem("Compare with...")
215
revision_menu_compare.connect('activate',
216
self._compare_with_cb)
218
revision_menu_tag = gtk.MenuItem("Tag Revision")
219
revision_menu_tag.connect('activate', self._tag_revision_cb)
221
revision_menu.add(revision_menu_tag)
222
revision_menu.add(revision_menu_diff)
223
revision_menu.add(revision_menu_compare)
269
revision_menuitem.set_submenu(self.revision_menu)
225
271
branch_menu = gtk.Menu()
226
272
branch_menuitem = gtk.MenuItem("_Branch")
234
280
except ImportError:
235
281
mutter("Didn't find search plugin")
283
branch_menu.add(gtk.SeparatorMenuItem())
237
285
branch_index_menuitem = gtk.MenuItem("_Index")
238
286
branch_index_menuitem.connect('activate', self._branch_index_cb)
239
287
branch_menu.add(branch_index_menuitem)
289
branch_search_menuitem = gtk.MenuItem("_Search")
290
branch_search_menuitem.connect('activate', self._branch_search_cb)
291
branch_menu.add(branch_search_menuitem)
241
293
help_menu = gtk.Menu()
242
294
help_menuitem = gtk.MenuItem("_Help")
243
295
help_menuitem.set_submenu(help_menu)
245
help_about_menuitem = gtk.ImageMenuItem(gtk.STOCK_ABOUT, self.accel_group)
297
help_about_menuitem = gtk.ImageMenuItem(gtk.STOCK_ABOUT,
246
299
help_about_menuitem.connect('activate', self._about_dialog_cb)
248
301
help_menu.add(help_about_menuitem)
262
315
"""Construct the top-half of the window."""
263
316
# FIXME: Make broken_line_length configurable
265
self.treeview = TreeView(self.branch, self.start_revs, self.maxnum, self.compact_view)
267
self.treeview.connect('revision-selected',
268
self._treeselection_changed_cb)
269
self.treeview.connect('revision-activated',
270
self._tree_revision_activated)
272
self.treeview.connect('tag-added', lambda w, t, r: self._update_tags())
318
self.treeview = TreeView(self.branch, self.start_revs, self.maxnum,
274
321
for col in ["revno", "date"]:
275
322
option = self.config.get_user_option(col + '-column-visible')
276
323
if option is not None:
277
self.treeview.set_property(col + '-column-visible', option == 'True')
324
self.treeview.set_property(col + '-column-visible',
279
327
self.treeview.set_property(col + '-column-visible', False)
283
331
align = gtk.Alignment(0.0, 0.0, 1.0, 1.0)
284
332
align.set_padding(5, 0, 0, 0)
285
333
align.add(self.treeview)
334
# user-configured size
335
size = self._load_size('viz-graph-size')
338
align.set_size_request(width, height)
340
(width, height) = self.get_size()
341
align.set_size_request(width, int(height / 2.5))
342
self._save_size_on_destroy(align, 'viz-graph-size')
311
368
def construct_bottom(self):
312
369
"""Construct the bottom half of the window."""
370
if self.config.get_user_option('viz-wide-diffs') == 'True':
371
self.diff_paned = gtk.VPaned()
373
self.diff_paned = gtk.HPaned()
374
(width, height) = self.get_size()
375
self.diff_paned.set_size_request(20, 20) # shrinkable
313
377
from bzrlib.plugins.gtk.revisionview import RevisionView
314
378
self.revisionview = RevisionView(branch=self.branch)
315
(width, height) = self.get_size()
316
self.revisionview.set_size_request(width, int(height / 2.5))
379
self.revisionview.set_size_request(width/3, int(height / 2.5))
380
# user-configured size
381
size = self._load_size('viz-revisionview-size')
384
self.revisionview.set_size_request(width, height)
385
self._save_size_on_destroy(self.revisionview, 'viz-revisionview-size')
317
386
self.revisionview.show()
318
387
self.revisionview.set_show_callback(self._show_clicked_cb)
319
388
self.revisionview.connect('notify::revision', self._go_clicked_cb)
320
self.treeview.connect('tag-added', lambda w, t, r: self.revisionview.update_tags())
321
return self.revisionview
389
self.treeview.connect('tag-added',
390
lambda w, t, r: self.revisionview.update_tags())
391
self.treeview.connect('revision-selected',
392
self._treeselection_changed_cb)
393
self.treeview.connect('revision-activated',
394
self._tree_revision_activated)
395
self.diff_paned.pack1(self.revisionview)
397
from bzrlib.plugins.gtk.diff import DiffWidget
398
self.diff = DiffWidget()
399
self.diff_paned.pack2(self.diff)
401
self.diff_paned.show_all()
402
if self.config.get_user_option('viz-show-diffs') != 'True':
405
return self.diff_paned
323
407
def _tag_selected_cb(self, menuitem, revid):
324
408
self.treeview.set_revision_id(revid)
369
455
self.next_rev_action.set_sensitive(False)
372
self.next_button.set_menu(next_menu)
458
if getattr(self.next_button, 'set_menu', None) is not None:
459
self.next_button.set_menu(next_menu)
374
461
self.revisionview.set_revision(revision)
375
462
self.revisionview.set_children(children)
463
self.update_diff_panel(revision, parents)
377
465
def _tree_revision_activated(self, widget, path, col):
378
466
# TODO: more than one parent
379
467
"""Callback for when a treeview row gets activated."""
381
469
parents = self.treeview.get_parents()
383
471
if len(parents) == 0:
472
parent_id = NULL_REVISION
386
474
parent_id = parents[0]
388
476
self.show_diff(revision.revision_id, parent_id)
389
477
self.treeview.grab_focus()
391
def _menu_diff_cb(self,w):
392
(path, focus) = self.treeview.treeview.get_cursor()
393
revid = self.treeview.model[path][treemodel.REVID]
395
parentids = self.branch.repository.revision_parents(revid)
397
if len(parentids) == 0:
398
parentid = NULL_REVISION
400
parentid = parentids[0]
402
self.show_diff(revid,parentid)
404
479
def _back_clicked_cb(self, *args):
405
480
"""Callback for when the back button is clicked."""
406
481
self.treeview.back()
408
483
def _fwd_clicked_cb(self, *args):
409
484
"""Callback for when the forward button is clicked."""
410
485
self.treeview.forward()
419
494
self.show_diff(revid, parentid)
420
495
self.treeview.grab_focus()
422
def _compare_with_cb(self,w):
423
"""Callback for revision 'compare with' menu. Will show a small
424
dialog with branch revisions to compare with selected revision in TreeView"""
426
from bzrlib.plugins.gtk.revbrowser import RevisionBrowser
428
rb = RevisionBrowser(self.branch,self)
431
if ret == gtk.RESPONSE_OK:
432
(path, focus) = self.treeview.treeview.get_cursor()
433
revid = self.treeview.model[path][treemodel.REVID]
434
self.show_diff(revid, rb.selected_revid)
438
497
def _set_revision_cb(self, w, revision_id):
439
498
self.treeview.set_revision_id(revision_id)
450
509
self.treeview.set_property('compact', self.compact_view)
451
510
self.treeview.refresh()
453
def _tag_revision_cb(self, w):
455
self.treeview.set_sensitive(False)
456
dialog = AddTagDialog(self.branch.repository, self.treeview.get_revision().revision_id, self.branch)
457
response = dialog.run()
458
if response != gtk.RESPONSE_NONE:
461
if response == gtk.RESPONSE_OK:
462
self.treeview.add_tag(dialog.tagname, dialog._revid)
467
self.treeview.set_sensitive(True)
469
512
def _branch_index_cb(self, w):
470
513
from bzrlib.plugins.search import index as _mod_index
471
514
_mod_index.index_url(self.branch.base)
516
def _branch_search_cb(self, w):
517
from bzrlib.plugins.search import (
519
errors as search_errors,
521
from bzrlib.plugins.gtk.search import SearchDialog
524
index = _mod_index.open_index_url(self.branch.base)
525
except search_errors.NoSearchIndex:
526
dialog = gtk.MessageDialog(self, type=gtk.MESSAGE_QUESTION,
527
buttons=gtk.BUTTONS_OK_CANCEL,
528
message_format="This branch has not been indexed yet. "
530
if dialog.run() == gtk.RESPONSE_OK:
532
index = _mod_index.index_url(self.branch.base)
537
dialog = SearchDialog(index)
539
if dialog.run() == gtk.RESPONSE_OK:
540
self.set_revision(dialog.get_revision())
473
544
def _about_dialog_cb(self, w):
474
545
from bzrlib.plugins.gtk.about import AboutDialog
476
546
AboutDialog().run()
478
548
def _col_visibility_changed(self, col, property):
482
552
def _toolbar_visibility_changed(self, col):
483
553
if col.get_active():
486
556
self.toolbar.hide()
488
def _show_about_cb(self, w):
489
dialog = AboutDialog()
490
dialog.connect('response', lambda d,r: d.destroy())
557
self.config.set_user_option('viz-toolbar-visible', col.get_active())
559
def _vertical_layout(self, col):
560
"""Toggle the layout vertical/horizontal"""
561
self.config.set_user_option('viz-vertical', str(col.get_active()))
564
self.vbox.remove(old)
565
self.vbox.pack_start(self.construct_paned(), expand=True, fill=True)
566
self._make_diff_paned_nonzero_size()
567
self._make_diff_nonzero_size()
569
self.treeview.emit('revision-selected')
571
def _make_diff_paned_nonzero_size(self):
572
"""make sure the diff/revision pane isn't zero-width or zero-height"""
573
alloc = self.diff_paned.get_allocation()
574
if (alloc.width < 10) or (alloc.height < 10):
575
width, height = self.get_size()
576
self.diff_paned.set_size_request(width/3, int(height / 2.5))
578
def _make_diff_nonzero_size(self):
579
"""make sure the diff isn't zero-width or zero-height"""
580
alloc = self.diff.get_allocation()
581
if (alloc.width < 10) or (alloc.height < 10):
582
width, height = self.get_size()
583
self.revisionview.set_size_request(width/3, int(height / 2.5))
585
def _diff_visibility_changed(self, col):
586
"""Hide or show the diff panel."""
589
self._make_diff_nonzero_size()
592
self.config.set_user_option('viz-show-diffs', str(col.get_active()))
593
self.update_diff_panel()
595
def _diff_placement_changed(self, col):
596
"""Toggle the diff panel's position."""
597
self.config.set_user_option('viz-wide-diffs', str(col.get_active()))
599
old = self.paned.get_child2()
600
self.paned.remove(old)
601
self.paned.pack2(self.construct_bottom(), resize=True, shrink=False)
602
self._make_diff_nonzero_size()
604
self.treeview.emit('revision-selected')
606
def _diff_wrap_changed(self, widget):
607
"""Toggle word wrap in the diff widget."""
608
self.config.set_user_option('viz-wrap-diffs', widget.get_active())
609
self.diff._on_wraplines_toggled(widget)
493
611
def _refresh_clicked(self, w):
494
612
self.treeview.refresh()
499
617
if self.branch.supports_tags():
500
618
tags = self.branch.tags.get_tag_dict().items()
619
tags.sort(reverse=True)
503
620
for tag, revid in tags:
504
621
tag_image = gtk.Image()
505
622
tag_image.set_from_file(icon_path('tag-16.png'))
506
623
tag_item = gtk.ImageMenuItem(tag.replace('_', '__'))
507
624
tag_item.set_image(tag_image)
508
625
tag_item.connect('activate', self._tag_selected_cb, revid)
626
tag_item.set_sensitive(self.treeview.has_revision_id(revid))
509
627
menu.add(tag_item)
510
628
self.go_menu_tags.set_submenu(menu)
516
634
self.go_menu_tags.show_all()
518
def show_diff(self, revid=None, parentid=None):
636
def _load_size(self, name):
637
"""Read and parse 'name' from self.config.
638
The value is a string, formatted as WIDTHxHEIGHT
639
Returns None, or (width, height)
641
size = self.config.get_user_option(name)
643
width, height = [int(num) for num in size.split('x')]
644
# avoid writing config every time we start
648
def show_diff(self, revid=None, parentid=NULL_REVISION):
519
649
"""Open a new window to show a diff between the given revisions."""
520
650
from bzrlib.plugins.gtk.diff import DiffWindow
521
651
window = DiffWindow(parent=self)
524
parentid = NULL_REVISION
526
653
rev_tree = self.branch.repository.revision_tree(revid)
527
654
parent_tree = self.branch.repository.revision_tree(parentid)
529
description = revid + " - " + self.branch.nick
656
description = revid + " - " + self.branch._get_nick(local=True)
530
657
window.set_diff(description, rev_tree, parent_tree)
660
def update_diff_panel(self, revision=None, parents=None):
661
"""Show the current revision in the diff panel."""
662
if self.config.get_user_option('viz-show-diffs') != 'True':
665
if not revision: # default to selected row
666
revision = self.treeview.get_revision()
667
if revision == NULL_REVISION:
670
if not parents: # default to selected row's parents
671
parents = self.treeview.get_parents()
672
if len(parents) == 0:
673
parent_id = NULL_REVISION
675
parent_id = parents[0]
677
rev_tree = self.branch.repository.revision_tree(revision.revision_id)
678
parent_tree = self.branch.repository.revision_tree(parent_id)
680
self.diff.set_diff(rev_tree, parent_tree)
681
if self.config.get_user_option('viz-wrap-diffs') == 'True':
682
self.diff._on_wraplines_toggled(wrap=True)