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
23
class BranchWindow(Window):
53
50
self.compact_view = False
55
self.set_title(branch.nick + " - revision history")
52
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)
54
# user-configured window size
55
size = self._load_size('viz-window-size')
59
# Use three-quarters of the screen by default
60
screen = self.get_screen()
61
monitor = screen.get_monitor_geometry(0)
62
width = int(monitor.width * 0.75)
63
height = int(monitor.height * 0.75)
62
64
self.set_default_size(width, height)
65
self.set_size_request(width/3, height/3)
66
self._save_size_on_destroy(self, 'viz-window-size')
65
69
icon = self.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)
72
76
self.accel_group = gtk.AccelGroup()
73
77
self.add_accel_group(self.accel_group)
75
gtk.Action.set_tool_item_type(gtk.MenuToolButton)
79
if getattr(gtk.Action, 'set_tool_item_type', None) is not None:
80
# Not available before PyGtk-2.10
81
gtk.Action.set_tool_item_type(gtk.MenuToolButton)
77
83
self.prev_rev_action = gtk.Action("prev-rev", "_Previous Revision", "Go to the previous revision", gtk.STOCK_GO_DOWN)
78
84
self.prev_rev_action.set_accel_path("<viz>/Go/Previous Revision")
103
def _save_size_on_destroy(self, widget, config_name):
104
"""Creates a hook that saves the size of widget to config option
105
config_name when the window is destroyed/closed."""
107
width, height = widget.allocation.width, widget.allocation.height
108
value = '%sx%s' % (width, height)
109
self.config.set_user_option(config_name, value)
110
self.connect("destroy", save_size)
97
112
def set_revision(self, revid):
98
113
self.treeview.set_revision_id(revid)
105
120
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
self.paned.pack1(self.construct_top(), resize=False, shrink=True)
122
self.paned.pack2(self.construct_bottom(), resize=True, shrink=False)
108
123
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)
125
nav = self.construct_navigation()
126
menubar = self.construct_menubar()
127
vbox.pack_start(menubar, expand=False, fill=True)
128
vbox.pack_start(nav, expand=False, fill=True)
113
130
vbox.pack_start(self.paned, expand=True, fill=True)
114
131
vbox.set_focus_child(self.paned)
133
self.treeview.connect('revision-selected',
134
self._treeselection_changed_cb)
135
self.treeview.connect('revision-activated',
136
self._tree_revision_activated)
138
self.treeview.connect('tag-added', lambda w, t, r: self._update_tags())
118
141
def construct_menubar(self):
125
148
file_menu_close = gtk.ImageMenuItem(gtk.STOCK_CLOSE, self.accel_group)
126
149
file_menu_close.connect('activate', lambda x: self.destroy())
128
151
file_menu_quit = gtk.ImageMenuItem(gtk.STOCK_QUIT, self.accel_group)
129
152
file_menu_quit.connect('activate', lambda x: gtk.main_quit())
131
154
if self._parent is not None:
132
155
file_menu.add(file_menu_close)
133
156
file_menu.add(file_menu_quit)
136
159
edit_menuitem = gtk.MenuItem("_Edit")
137
160
edit_menuitem.set_submenu(edit_menu)
139
edit_menu_find = gtk.ImageMenuItem(gtk.STOCK_FIND)
141
162
edit_menu_branchopts = gtk.MenuItem("Branch Settings")
142
163
edit_menu_branchopts.connect('activate', lambda x: PreferencesWindow(self.branch.get_config()).show())
144
165
edit_menu_globopts = gtk.MenuItem("Global Settings")
145
166
edit_menu_globopts.connect('activate', lambda x: PreferencesWindow().show())
147
edit_menu.add(edit_menu_find)
148
168
edit_menu.add(edit_menu_branchopts)
149
169
edit_menu.add(edit_menu_globopts)
161
181
view_menu_toolbar = gtk.CheckMenuItem("Show Toolbar")
162
182
view_menu_toolbar.set_active(True)
183
if self.config.get_user_option('viz-toolbar-visible') == 'False':
184
view_menu_toolbar.set_active(False)
163
186
view_menu_toolbar.connect('toggled', self._toolbar_visibility_changed)
165
188
view_menu_compact = gtk.CheckMenuItem("Show Compact Graph")
166
189
view_menu_compact.set_active(self.compact_view)
167
190
view_menu_compact.connect('activate', self._brokenlines_toggled_cb)
192
view_menu_diffs = gtk.CheckMenuItem("Show Diffs")
193
view_menu_diffs.set_active(False)
194
if self.config.get_user_option('viz-show-diffs') == 'True':
195
view_menu_diffs.set_active(True)
196
view_menu_diffs.connect('toggled', self._diff_visibility_changed)
198
view_menu_wide_diffs = gtk.CheckMenuItem("Wide Diffs")
199
view_menu_wide_diffs.set_active(False)
200
if self.config.get_user_option('viz-wide-diffs') == 'True':
201
view_menu_wide_diffs.set_active(True)
202
view_menu_wide_diffs.connect('toggled', self._diff_placement_changed)
204
view_menu_wrap_diffs = gtk.CheckMenuItem("Wrap _Long Lines in Diffs")
205
view_menu_wrap_diffs.set_active(False)
206
if self.config.get_user_option('viz-wrap-diffs') == 'True':
207
view_menu_wrap_diffs.set_active(True)
208
view_menu_wrap_diffs.connect('toggled', self._diff_wrap_changed)
169
210
view_menu.add(view_menu_toolbar)
170
211
view_menu.add(view_menu_compact)
171
212
view_menu.add(gtk.SeparatorMenuItem())
213
view_menu.add(view_menu_diffs)
214
view_menu.add(view_menu_wide_diffs)
215
view_menu.add(view_menu_wrap_diffs)
216
view_menu.add(gtk.SeparatorMenuItem())
173
218
self.mnu_show_revno_column = gtk.CheckMenuItem("Show Revision _Number Column")
174
219
self.mnu_show_date_column = gtk.CheckMenuItem("Show _Date Column")
188
233
go_menu.set_accel_group(self.accel_group)
189
234
go_menuitem = gtk.MenuItem("_Go")
190
235
go_menuitem.set_submenu(go_menu)
192
237
go_menu_next = self.next_rev_action.create_menu_item()
193
238
go_menu_prev = self.prev_rev_action.create_menu_item()
196
241
tag_image.set_from_file(icon_path("tag-16.png"))
197
242
self.go_menu_tags = gtk.ImageMenuItem("_Tags")
198
243
self.go_menu_tags.set_image(tag_image)
244
self.treeview.connect('refreshed', lambda w: self._update_tags())
201
246
go_menu.add(go_menu_next)
202
247
go_menu.add(go_menu_prev)
203
248
go_menu.add(gtk.SeparatorMenuItem())
204
249
go_menu.add(self.go_menu_tags)
206
revision_menu = gtk.Menu()
251
self.revision_menu = RevisionMenu(self.branch.repository, [], self.branch, parent=self)
207
252
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)
253
revision_menuitem.set_submenu(self.revision_menu)
225
255
branch_menu = gtk.Menu()
226
256
branch_menuitem = gtk.MenuItem("_Branch")
234
264
except ImportError:
235
265
mutter("Didn't find search plugin")
267
branch_menu.add(gtk.SeparatorMenuItem())
237
269
branch_index_menuitem = gtk.MenuItem("_Index")
238
270
branch_index_menuitem.connect('activate', self._branch_index_cb)
239
271
branch_menu.add(branch_index_menuitem)
273
branch_search_menuitem = gtk.MenuItem("_Search")
274
branch_search_menuitem.connect('activate', self._branch_search_cb)
275
branch_menu.add(branch_search_menuitem)
241
277
help_menu = gtk.Menu()
242
278
help_menuitem = gtk.MenuItem("_Help")
243
279
help_menuitem.set_submenu(help_menu)
265
301
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
303
for col in ["revno", "date"]:
275
304
option = self.config.get_user_option(col + '-column-visible')
276
305
if option is not None:
283
312
align = gtk.Alignment(0.0, 0.0, 1.0, 1.0)
284
313
align.set_padding(5, 0, 0, 0)
285
314
align.add(self.treeview)
315
# user-configured size
316
size = self._load_size('viz-graph-size')
319
align.set_size_request(width, height)
321
(width, height) = self.get_size()
322
align.set_size_request(width, int(height / 2.5))
323
self._save_size_on_destroy(align, 'viz-graph-size')
311
349
def construct_bottom(self):
312
350
"""Construct the bottom half of the window."""
351
if self.config.get_user_option('viz-wide-diffs') == 'True':
352
self.diff_paned = gtk.VPaned()
354
self.diff_paned = gtk.HPaned()
355
(width, height) = self.get_size()
356
self.diff_paned.set_size_request(20, 20) # shrinkable
313
358
from bzrlib.plugins.gtk.revisionview import RevisionView
314
359
self.revisionview = RevisionView(branch=self.branch)
315
(width, height) = self.get_size()
316
self.revisionview.set_size_request(width, int(height / 2.5))
360
self.revisionview.set_size_request(width/3, int(height / 2.5))
361
# user-configured size
362
size = self._load_size('viz-revisionview-size')
365
self.revisionview.set_size_request(width, height)
366
self._save_size_on_destroy(self.revisionview, 'viz-revisionview-size')
317
367
self.revisionview.show()
318
368
self.revisionview.set_show_callback(self._show_clicked_cb)
319
369
self.revisionview.connect('notify::revision', self._go_clicked_cb)
320
370
self.treeview.connect('tag-added', lambda w, t, r: self.revisionview.update_tags())
321
return self.revisionview
371
self.diff_paned.pack1(self.revisionview)
373
from bzrlib.plugins.gtk.diff import DiffWidget
374
self.diff = DiffWidget()
375
self.diff_paned.pack2(self.diff)
377
self.diff_paned.show_all()
378
if self.config.get_user_option('viz-show-diffs') != 'True':
381
return self.diff_paned
323
383
def _tag_selected_cb(self, menuitem, revid):
324
384
self.treeview.set_revision_id(revid)
349
411
self.prev_rev_action.set_sensitive(False)
352
self.prev_button.set_menu(prev_menu)
414
if getattr(self.prev_button, 'set_menu', None) is not None:
415
self.prev_button.set_menu(prev_menu)
354
417
next_menu = gtk.Menu()
355
418
if len(children) > 0:
369
432
self.next_rev_action.set_sensitive(False)
372
self.next_button.set_menu(next_menu)
435
if getattr(self.next_button, 'set_menu', None) is not None:
436
self.next_button.set_menu(next_menu)
374
438
self.revisionview.set_revision(revision)
375
439
self.revisionview.set_children(children)
440
self.update_diff_panel(revision, parents)
377
442
def _tree_revision_activated(self, widget, path, col):
378
443
# TODO: more than one parent
379
444
"""Callback for when a treeview row gets activated."""
381
446
parents = self.treeview.get_parents()
383
448
if len(parents) == 0:
449
parent_id = NULL_REVISION
386
451
parent_id = parents[0]
388
453
self.show_diff(revision.revision_id, parent_id)
389
454
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
456
def _back_clicked_cb(self, *args):
405
457
"""Callback for when the back button is clicked."""
406
458
self.treeview.back()
408
460
def _fwd_clicked_cb(self, *args):
409
461
"""Callback for when the forward button is clicked."""
410
462
self.treeview.forward()
419
471
self.show_diff(revid, parentid)
420
472
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
474
def _set_revision_cb(self, w, revision_id):
439
475
self.treeview.set_revision_id(revision_id)
450
486
self.treeview.set_property('compact', self.compact_view)
451
487
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
489
def _branch_index_cb(self, w):
470
490
from bzrlib.plugins.search import index as _mod_index
471
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
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())
473
519
def _about_dialog_cb(self, w):
474
520
from bzrlib.plugins.gtk.about import AboutDialog
476
521
AboutDialog().run()
478
523
def _col_visibility_changed(self, col, property):
482
527
def _toolbar_visibility_changed(self, col):
483
528
if col.get_active():
486
531
self.toolbar.hide()
488
def _show_about_cb(self, w):
489
dialog = AboutDialog()
490
dialog.connect('response', lambda d,r: d.destroy())
532
self.config.set_user_option('viz-toolbar-visible', col.get_active())
534
def _make_diff_nonzero_size(self):
535
"""make sure the diff isn't zero-width or zero-height"""
536
alloc = self.diff.get_allocation()
537
if (alloc.width < 10) or (alloc.height < 10):
538
width, height = self.get_size()
539
self.revisionview.set_size_request(width/3, int(height / 2.5))
541
def _diff_visibility_changed(self, col):
542
"""Hide or show the diff panel."""
545
self._make_diff_nonzero_size()
548
self.config.set_user_option('viz-show-diffs', str(col.get_active()))
549
self.update_diff_panel()
551
def _diff_placement_changed(self, col):
552
"""Toggle the diff panel's position."""
553
self.config.set_user_option('viz-wide-diffs', str(col.get_active()))
555
old = self.paned.get_child2()
556
self.paned.remove(old)
557
self.paned.pack2(self.construct_bottom(), resize=True, shrink=False)
558
self._make_diff_nonzero_size()
560
self.treeview.emit('revision-selected')
562
def _diff_wrap_changed(self, widget):
563
"""Toggle word wrap in the diff widget."""
564
self.config.set_user_option('viz-wrap-diffs', widget.get_active())
565
self.diff._on_wraplines_toggled(widget)
493
567
def _refresh_clicked(self, w):
494
568
self.treeview.refresh()
499
573
if self.branch.supports_tags():
500
574
tags = self.branch.tags.get_tag_dict().items()
575
tags.sort(reverse=True)
503
576
for tag, revid in tags:
504
577
tag_image = gtk.Image()
505
578
tag_image.set_from_file(icon_path('tag-16.png'))
506
579
tag_item = gtk.ImageMenuItem(tag.replace('_', '__'))
507
580
tag_item.set_image(tag_image)
508
581
tag_item.connect('activate', self._tag_selected_cb, revid)
582
tag_item.set_sensitive(self.treeview.has_revision_id(revid))
509
583
menu.add(tag_item)
510
584
self.go_menu_tags.set_submenu(menu)
516
590
self.go_menu_tags.show_all()
518
def show_diff(self, revid=None, parentid=None):
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
604
def show_diff(self, revid=None, parentid=NULL_REVISION):
519
605
"""Open a new window to show a diff between the given revisions."""
520
606
from bzrlib.plugins.gtk.diff import DiffWindow
521
607
window = DiffWindow(parent=self)
524
parentid = NULL_REVISION
526
609
rev_tree = self.branch.repository.revision_tree(revid)
527
610
parent_tree = self.branch.repository.revision_tree(parentid)
529
description = revid + " - " + self.branch.nick
612
description = revid + " - " + self.branch._get_nick(local=True)
530
613
window.set_diff(description, rev_tree, parent_tree)
616
def update_diff_panel(self, revision=None, parents=None):
617
"""Show the current revision in the diff panel."""
618
if self.config.get_user_option('viz-show-diffs') != 'True':
621
if not revision: # default to selected row
622
revision = self.treeview.get_revision()
623
if revision == NULL_REVISION:
626
if not parents: # default to selected row's parents
627
parents = self.treeview.get_parents()
628
if len(parents) == 0:
629
parent_id = NULL_REVISION
631
parent_id = parents[0]
633
rev_tree = self.branch.repository.revision_tree(revision.revision_id)
634
parent_tree = self.branch.repository.revision_tree(parent_id)
636
self.diff.set_diff(rev_tree, parent_tree)
637
if self.config.get_user_option('viz-wrap-diffs') == 'True':
638
self.diff._on_wraplines_toggled(wrap=True)