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")
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)
105
121
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)
122
self.paned.pack1(self.construct_top(), resize=False, shrink=True)
123
self.paned.pack2(self.construct_bottom(), resize=True, shrink=False)
108
124
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)
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)
113
131
vbox.pack_start(self.paned, expand=True, fill=True)
114
132
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())
118
142
def construct_menubar(self):
125
149
file_menu_close = gtk.ImageMenuItem(gtk.STOCK_CLOSE, self.accel_group)
126
150
file_menu_close.connect('activate', lambda x: self.destroy())
128
152
file_menu_quit = gtk.ImageMenuItem(gtk.STOCK_QUIT, self.accel_group)
129
153
file_menu_quit.connect('activate', lambda x: gtk.main_quit())
131
155
if self._parent is not None:
132
156
file_menu.add(file_menu_close)
133
157
file_menu.add(file_menu_quit)
136
160
edit_menuitem = gtk.MenuItem("_Edit")
137
161
edit_menuitem.set_submenu(edit_menu)
139
edit_menu_find = gtk.ImageMenuItem(gtk.STOCK_FIND)
141
163
edit_menu_branchopts = gtk.MenuItem("Branch Settings")
142
164
edit_menu_branchopts.connect('activate', lambda x: PreferencesWindow(self.branch.get_config()).show())
144
166
edit_menu_globopts = gtk.MenuItem("Global Settings")
145
167
edit_menu_globopts.connect('activate', lambda x: PreferencesWindow().show())
147
edit_menu.add(edit_menu_find)
148
169
edit_menu.add(edit_menu_branchopts)
149
170
edit_menu.add(edit_menu_globopts)
161
182
view_menu_toolbar = gtk.CheckMenuItem("Show Toolbar")
162
183
view_menu_toolbar.set_active(True)
184
if self.config.get_user_option('viz-toolbar-visible') == 'False':
185
view_menu_toolbar.set_active(False)
163
187
view_menu_toolbar.connect('toggled', self._toolbar_visibility_changed)
165
189
view_menu_compact = gtk.CheckMenuItem("Show Compact Graph")
166
190
view_menu_compact.set_active(self.compact_view)
167
191
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)
169
211
view_menu.add(view_menu_toolbar)
170
212
view_menu.add(view_menu_compact)
171
213
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())
173
219
self.mnu_show_revno_column = gtk.CheckMenuItem("Show Revision _Number Column")
174
220
self.mnu_show_date_column = gtk.CheckMenuItem("Show _Date Column")
188
234
go_menu.set_accel_group(self.accel_group)
189
235
go_menuitem = gtk.MenuItem("_Go")
190
236
go_menuitem.set_submenu(go_menu)
192
238
go_menu_next = self.next_rev_action.create_menu_item()
193
239
go_menu_prev = self.prev_rev_action.create_menu_item()
196
242
tag_image.set_from_file(icon_path("tag-16.png"))
197
243
self.go_menu_tags = gtk.ImageMenuItem("_Tags")
198
244
self.go_menu_tags.set_image(tag_image)
245
self.treeview.connect('refreshed', lambda w: self._update_tags())
201
247
go_menu.add(go_menu_next)
202
248
go_menu.add(go_menu_prev)
203
249
go_menu.add(gtk.SeparatorMenuItem())
204
250
go_menu.add(self.go_menu_tags)
206
revision_menu = gtk.Menu()
252
self.revision_menu = RevisionMenu(self.branch.repository, [], self.branch, parent=self)
207
253
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)
254
revision_menuitem.set_submenu(self.revision_menu)
225
256
branch_menu = gtk.Menu()
226
257
branch_menuitem = gtk.MenuItem("_Branch")
234
265
except ImportError:
235
266
mutter("Didn't find search plugin")
268
branch_menu.add(gtk.SeparatorMenuItem())
237
270
branch_index_menuitem = gtk.MenuItem("_Index")
238
271
branch_index_menuitem.connect('activate', self._branch_index_cb)
239
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)
241
278
help_menu = gtk.Menu()
242
279
help_menuitem = gtk.MenuItem("_Help")
243
280
help_menuitem.set_submenu(help_menu)
265
302
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())
274
304
for col in ["revno", "date"]:
275
305
option = self.config.get_user_option(col + '-column-visible')
276
306
if option is not None:
283
313
align = gtk.Alignment(0.0, 0.0, 1.0, 1.0)
284
314
align.set_padding(5, 0, 0, 0)
285
315
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')
311
350
def construct_bottom(self):
312
351
"""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
313
359
from bzrlib.plugins.gtk.revisionview import RevisionView
314
360
self.revisionview = RevisionView(branch=self.branch)
315
(width, height) = self.get_size()
316
self.revisionview.set_size_request(width, int(height / 2.5))
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')
317
368
self.revisionview.show()
318
369
self.revisionview.set_show_callback(self._show_clicked_cb)
319
370
self.revisionview.connect('notify::revision', self._go_clicked_cb)
320
371
self.treeview.connect('tag-added', lambda w, t, r: self.revisionview.update_tags())
321
return self.revisionview
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
323
384
def _tag_selected_cb(self, menuitem, revid):
324
385
self.treeview.set_revision_id(revid)
349
412
self.prev_rev_action.set_sensitive(False)
352
self.prev_button.set_menu(prev_menu)
415
if getattr(self.prev_button, 'set_menu', None) is not None:
416
self.prev_button.set_menu(prev_menu)
354
418
next_menu = gtk.Menu()
355
419
if len(children) > 0:
369
433
self.next_rev_action.set_sensitive(False)
372
self.next_button.set_menu(next_menu)
436
if getattr(self.next_button, 'set_menu', None) is not None:
437
self.next_button.set_menu(next_menu)
374
439
self.revisionview.set_revision(revision)
375
440
self.revisionview.set_children(children)
441
self.update_diff_panel(revision, parents)
377
443
def _tree_revision_activated(self, widget, path, col):
378
444
# TODO: more than one parent
379
445
"""Callback for when a treeview row gets activated."""
381
447
parents = self.treeview.get_parents()
383
449
if len(parents) == 0:
450
parent_id = NULL_REVISION
386
452
parent_id = parents[0]
388
454
self.show_diff(revision.revision_id, parent_id)
389
455
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
457
def _back_clicked_cb(self, *args):
405
458
"""Callback for when the back button is clicked."""
406
459
self.treeview.back()
408
461
def _fwd_clicked_cb(self, *args):
409
462
"""Callback for when the forward button is clicked."""
410
463
self.treeview.forward()
419
472
self.show_diff(revid, parentid)
420
473
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
475
def _set_revision_cb(self, w, revision_id):
439
476
self.treeview.set_revision_id(revision_id)
450
487
self.treeview.set_property('compact', self.compact_view)
451
488
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
490
def _branch_index_cb(self, w):
470
491
from bzrlib.plugins.search import index as _mod_index
471
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
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())
473
520
def _about_dialog_cb(self, w):
474
521
from bzrlib.plugins.gtk.about import AboutDialog
476
522
AboutDialog().run()
478
524
def _col_visibility_changed(self, col, property):
482
528
def _toolbar_visibility_changed(self, col):
483
529
if col.get_active():
486
532
self.toolbar.hide()
488
def _show_about_cb(self, w):
489
dialog = AboutDialog()
490
dialog.connect('response', lambda d,r: d.destroy())
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)
493
568
def _refresh_clicked(self, w):
494
569
self.treeview.refresh()
499
574
if self.branch.supports_tags():
500
575
tags = self.branch.tags.get_tag_dict().items()
576
tags.sort(reverse=True)
503
577
for tag, revid in tags:
504
578
tag_image = gtk.Image()
505
579
tag_image.set_from_file(icon_path('tag-16.png'))
506
580
tag_item = gtk.ImageMenuItem(tag.replace('_', '__'))
507
581
tag_item.set_image(tag_image)
508
582
tag_item.connect('activate', self._tag_selected_cb, revid)
583
tag_item.set_sensitive(self.treeview.has_revision_id(revid))
509
584
menu.add(tag_item)
510
585
self.go_menu_tags.set_submenu(menu)
516
591
self.go_menu_tags.show_all()
518
def show_diff(self, revid=None, parentid=None):
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):
519
606
"""Open a new window to show a diff between the given revisions."""
520
607
from bzrlib.plugins.gtk.diff import DiffWindow
521
608
window = DiffWindow(parent=self)
524
parentid = NULL_REVISION
526
610
rev_tree = self.branch.repository.revision_tree(revid)
527
611
parent_tree = self.branch.repository.revision_tree(parentid)
529
description = revid + " - " + self.branch.nick
613
description = revid + " - " + self.branch._get_nick(local=True)
530
614
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)