1
# -*- coding: UTF-8 -*-
3
4
This module contains the code to manage the branch information window,
4
5
which contains both the revision graph and details panes.
7
__copyright__ = "Copyright (c) 2005 Canonical Ltd."
8
__copyright__ = "Copyright © 2005 Canonical Ltd."
8
9
__author__ = "Scott James Remnant <scott@ubuntu.com>"
16
from bzrlib.plugins.gtk.window import Window
15
17
from bzrlib.plugins.gtk import icon_path
16
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.revisionmenu import RevisionMenu
20
from bzrlib.plugins.gtk.window import Window
22
from bzrlib.config import BranchConfig, GlobalConfig
20
from bzrlib.plugins.gtk.branchview import TreeView, treemodel
23
21
from bzrlib.revision import Revision, NULL_REVISION
24
from bzrlib.trace import mutter
22
from bzrlib.config import BranchConfig
23
from bzrlib.config import GlobalConfig
26
25
class BranchWindow(Window):
53
52
self.compact_view = False
55
self.set_title(branch._get_nick(local=True) + " - revision history")
54
self.set_title(branch.nick + " - revision history")
57
# user-configured window size
58
size = self._load_size('viz-window-size')
62
# Use three-quarters of the screen by default
63
screen = self.get_screen()
64
monitor = screen.get_monitor_geometry(0)
65
width = int(monitor.width * 0.75)
66
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)
67
61
self.set_default_size(width, height)
68
self.set_size_request(width/3, height/3)
69
self._save_size_on_destroy(self, 'viz-window-size')
72
64
icon = self.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)
79
71
self.accel_group = gtk.AccelGroup()
80
72
self.add_accel_group(self.accel_group)
82
if getattr(gtk.Action, 'set_tool_item_type', None) is not None:
83
# Not available before PyGtk-2.10
84
gtk.Action.set_tool_item_type(gtk.MenuToolButton)
74
gtk.Action.set_tool_item_type(gtk.MenuToolButton)
86
76
self.prev_rev_action = gtk.Action("prev-rev", "_Previous Revision", "Go to the previous revision", gtk.STOCK_GO_DOWN)
87
77
self.prev_rev_action.set_accel_path("<viz>/Go/Previous Revision")
106
def _save_size_on_destroy(self, widget, config_name):
107
"""Creates a hook that saves the size of widget to config option
108
config_name when the window is destroyed/closed."""
110
width, height = widget.allocation.width, widget.allocation.height
111
value = '%sx%s' % (width, height)
112
self.config.set_user_option(config_name, value)
113
self.connect("destroy", save_size)
115
96
def set_revision(self, revid):
116
97
self.treeview.set_revision_id(revid)
123
104
self.paned = gtk.VPaned()
124
self.paned.pack1(self.construct_top(), resize=False, shrink=True)
125
self.paned.pack2(self.construct_bottom(), resize=True, shrink=False)
105
self.paned.pack1(self.construct_top(), resize=True, shrink=False)
106
self.paned.pack2(self.construct_bottom(), resize=False, shrink=True)
126
107
self.paned.show()
128
nav = self.construct_navigation()
129
menubar = self.construct_menubar()
130
vbox.pack_start(menubar, expand=False, fill=True)
131
vbox.pack_start(nav, expand=False, fill=True)
109
vbox.pack_start(self.construct_menubar(), expand=False, fill=True)
110
vbox.pack_start(self.construct_navigation(), expand=False, fill=True)
133
112
vbox.pack_start(self.paned, expand=True, fill=True)
134
113
vbox.set_focus_child(self.paned)
136
self.treeview.connect('revision-selected',
137
self._treeselection_changed_cb)
138
self.treeview.connect('revision-activated',
139
self._tree_revision_activated)
141
self.treeview.connect('tag-added', lambda w, t, r: self._update_tags())
144
117
def construct_menubar(self):
145
118
menubar = gtk.MenuBar()
162
135
edit_menuitem = gtk.MenuItem("_Edit")
163
136
edit_menuitem.set_submenu(edit_menu)
138
edit_menu_find = gtk.ImageMenuItem(gtk.STOCK_FIND)
165
140
edit_menu_branchopts = gtk.MenuItem("Branch Settings")
166
141
edit_menu_branchopts.connect('activate', lambda x: PreferencesWindow(self.branch.get_config()).show())
168
143
edit_menu_globopts = gtk.MenuItem("Global Settings")
169
144
edit_menu_globopts.connect('activate', lambda x: PreferencesWindow().show())
146
edit_menu.add(edit_menu_find)
171
147
edit_menu.add(edit_menu_branchopts)
172
148
edit_menu.add(edit_menu_globopts)
184
160
view_menu_toolbar = gtk.CheckMenuItem("Show Toolbar")
185
161
view_menu_toolbar.set_active(True)
186
if self.config.get_user_option('viz-toolbar-visible') == 'False':
187
view_menu_toolbar.set_active(False)
189
162
view_menu_toolbar.connect('toggled', self._toolbar_visibility_changed)
191
164
view_menu_compact = gtk.CheckMenuItem("Show Compact Graph")
192
165
view_menu_compact.set_active(self.compact_view)
193
166
view_menu_compact.connect('activate', self._brokenlines_toggled_cb)
195
view_menu_diffs = gtk.CheckMenuItem("Show Diffs")
196
view_menu_diffs.set_active(False)
197
if self.config.get_user_option('viz-show-diffs') == 'True':
198
view_menu_diffs.set_active(True)
199
view_menu_diffs.connect('toggled', self._diff_visibility_changed)
201
view_menu_wide_diffs = gtk.CheckMenuItem("Wide Diffs")
202
view_menu_wide_diffs.set_active(False)
203
if self.config.get_user_option('viz-wide-diffs') == 'True':
204
view_menu_wide_diffs.set_active(True)
205
view_menu_wide_diffs.connect('toggled', self._diff_placement_changed)
207
view_menu_wrap_diffs = gtk.CheckMenuItem("Wrap _Long Lines in Diffs")
208
view_menu_wrap_diffs.set_active(False)
209
if self.config.get_user_option('viz-wrap-diffs') == 'True':
210
view_menu_wrap_diffs.set_active(True)
211
view_menu_wrap_diffs.connect('toggled', self._diff_wrap_changed)
213
168
view_menu.add(view_menu_toolbar)
214
169
view_menu.add(view_menu_compact)
215
170
view_menu.add(gtk.SeparatorMenuItem())
216
view_menu.add(view_menu_diffs)
217
view_menu.add(view_menu_wide_diffs)
218
view_menu.add(view_menu_wrap_diffs)
219
view_menu.add(gtk.SeparatorMenuItem())
221
172
self.mnu_show_revno_column = gtk.CheckMenuItem("Show Revision _Number Column")
222
173
self.mnu_show_date_column = gtk.CheckMenuItem("Show _Date Column")
244
195
tag_image.set_from_file(icon_path("tag-16.png"))
245
196
self.go_menu_tags = gtk.ImageMenuItem("_Tags")
246
197
self.go_menu_tags.set_image(tag_image)
247
self.treeview.connect('refreshed', lambda w: self._update_tags())
249
200
go_menu.add(go_menu_next)
250
201
go_menu.add(go_menu_prev)
251
202
go_menu.add(gtk.SeparatorMenuItem())
252
203
go_menu.add(self.go_menu_tags)
254
self.revision_menu = RevisionMenu(self.branch.repository, [], self.branch, parent=self)
205
revision_menu = gtk.Menu()
255
206
revision_menuitem = gtk.MenuItem("_Revision")
256
revision_menuitem.set_submenu(self.revision_menu)
207
revision_menuitem.set_submenu(revision_menu)
209
revision_menu_diff = gtk.MenuItem("View Changes")
210
revision_menu_diff.connect('activate',
213
revision_menu_compare = gtk.MenuItem("Compare with...")
214
revision_menu_compare.connect('activate',
215
self._compare_with_cb)
217
revision_menu_tag = gtk.MenuItem("Tag Revision")
218
revision_menu_tag.connect('activate', self._tag_revision_cb)
220
revision_menu.add(revision_menu_tag)
221
revision_menu.add(revision_menu_diff)
222
revision_menu.add(revision_menu_compare)
258
224
branch_menu = gtk.Menu()
259
225
branch_menuitem = gtk.MenuItem("_Branch")
262
228
branch_menu.add(gtk.MenuItem("Pu_ll Revisions"))
263
229
branch_menu.add(gtk.MenuItem("Pu_sh Revisions"))
266
from bzrlib.plugins import search
268
mutter("Didn't find search plugin")
270
branch_menu.add(gtk.SeparatorMenuItem())
272
branch_index_menuitem = gtk.MenuItem("_Index")
273
branch_index_menuitem.connect('activate', self._branch_index_cb)
274
branch_menu.add(branch_index_menuitem)
276
branch_search_menuitem = gtk.MenuItem("_Search")
277
branch_search_menuitem.connect('activate', self._branch_search_cb)
278
branch_menu.add(branch_search_menuitem)
280
231
help_menu = gtk.Menu()
281
232
help_menuitem = gtk.MenuItem("_Help")
282
233
help_menuitem.set_submenu(help_menu)
304
255
self.treeview = TreeView(self.branch, self.start_revs, self.maxnum, self.compact_view)
257
self.treeview.connect('revision-selected',
258
self._treeselection_changed_cb)
259
self.treeview.connect('revision-activated',
260
self._tree_revision_activated)
262
self.treeview.connect('tag-added', lambda w, t, r: self._update_tags())
306
264
for col in ["revno", "date"]:
307
265
option = self.config.get_user_option(col + '-column-visible')
308
266
if option is not None:
315
273
align = gtk.Alignment(0.0, 0.0, 1.0, 1.0)
316
274
align.set_padding(5, 0, 0, 0)
317
275
align.add(self.treeview)
318
# user-configured size
319
size = self._load_size('viz-graph-size')
322
align.set_size_request(width, height)
324
(width, height) = self.get_size()
325
align.set_size_request(width, int(height / 2.5))
326
self._save_size_on_destroy(align, 'viz-graph-size')
352
301
def construct_bottom(self):
353
302
"""Construct the bottom half of the window."""
354
if self.config.get_user_option('viz-wide-diffs') == 'True':
355
self.diff_paned = gtk.VPaned()
357
self.diff_paned = gtk.HPaned()
358
(width, height) = self.get_size()
359
self.diff_paned.set_size_request(20, 20) # shrinkable
361
303
from bzrlib.plugins.gtk.revisionview import RevisionView
362
304
self.revisionview = RevisionView(branch=self.branch)
363
self.revisionview.set_size_request(width/3, int(height / 2.5))
364
# user-configured size
365
size = self._load_size('viz-revisionview-size')
368
self.revisionview.set_size_request(width, height)
369
self._save_size_on_destroy(self.revisionview, 'viz-revisionview-size')
305
(width, height) = self.get_size()
306
self.revisionview.set_size_request(width, int(height / 2.5))
370
307
self.revisionview.show()
371
308
self.revisionview.set_show_callback(self._show_clicked_cb)
372
309
self.revisionview.connect('notify::revision', self._go_clicked_cb)
373
310
self.treeview.connect('tag-added', lambda w, t, r: self.revisionview.update_tags())
374
self.diff_paned.pack1(self.revisionview)
376
from bzrlib.plugins.gtk.diff import DiffWidget
377
self.diff = DiffWidget()
378
self.diff_paned.pack2(self.diff)
380
self.diff_paned.show_all()
381
if self.config.get_user_option('viz-show-diffs') != 'True':
384
return self.diff_paned
311
return self.revisionview
386
313
def _tag_selected_cb(self, menuitem, revid):
387
314
self.treeview.set_revision_id(revid)
414
339
self.prev_rev_action.set_sensitive(False)
417
if getattr(self.prev_button, 'set_menu', None) is not None:
418
self.prev_button.set_menu(prev_menu)
342
self.prev_button.set_menu(prev_menu)
420
344
next_menu = gtk.Menu()
421
345
if len(children) > 0:
435
359
self.next_rev_action.set_sensitive(False)
438
if getattr(self.next_button, 'set_menu', None) is not None:
439
self.next_button.set_menu(next_menu)
362
self.next_button.set_menu(next_menu)
441
364
self.revisionview.set_revision(revision)
442
365
self.revisionview.set_children(children)
443
self.update_diff_panel(revision, parents)
445
367
def _tree_revision_activated(self, widget, path, col):
446
368
# TODO: more than one parent
447
369
"""Callback for when a treeview row gets activated."""
449
371
parents = self.treeview.get_parents()
451
373
if len(parents) == 0:
452
parent_id = NULL_REVISION
454
376
parent_id = parents[0]
456
378
self.show_diff(revision.revision_id, parent_id)
457
379
self.treeview.grab_focus()
381
def _menu_diff_cb(self,w):
382
(path, focus) = self.treeview.treeview.get_cursor()
383
revid = self.treeview.model[path][treemodel.REVID]
385
parentids = self.branch.repository.revision_parents(revid)
387
if len(parentids) == 0:
388
parentid = NULL_REVISION
390
parentid = parentids[0]
392
self.show_diff(revid,parentid)
459
394
def _back_clicked_cb(self, *args):
460
395
"""Callback for when the back button is clicked."""
461
396
self.treeview.back()
474
409
self.show_diff(revid, parentid)
475
410
self.treeview.grab_focus()
412
def _compare_with_cb(self,w):
413
"""Callback for revision 'compare with' menu. Will show a small
414
dialog with branch revisions to compare with selected revision in TreeView"""
416
from bzrlib.plugins.gtk.revbrowser import RevisionBrowser
418
rb = RevisionBrowser(self.branch,self)
421
if ret == gtk.RESPONSE_OK:
422
(path, focus) = self.treeview.treeview.get_cursor()
423
revid = self.treeview.model[path][treemodel.REVID]
424
self.show_diff(revid, rb.selected_revid)
477
428
def _set_revision_cb(self, w, revision_id):
478
429
self.treeview.set_revision_id(revision_id)
489
440
self.treeview.set_property('compact', self.compact_view)
490
441
self.treeview.refresh()
492
def _branch_index_cb(self, w):
493
from bzrlib.plugins.search import index as _mod_index
494
_mod_index.index_url(self.branch.base)
496
def _branch_search_cb(self, w):
497
from bzrlib.plugins.search import index as _mod_index
498
from bzrlib.plugins.gtk.search import SearchDialog
499
from bzrlib.plugins.search import errors as search_errors
443
def _tag_revision_cb(self, w):
502
index = _mod_index.open_index_url(self.branch.base)
503
except search_errors.NoSearchIndex:
504
dialog = gtk.MessageDialog(self, type=gtk.MESSAGE_QUESTION,
505
buttons=gtk.BUTTONS_OK_CANCEL,
506
message_format="This branch has not been indexed yet. "
508
if dialog.run() == gtk.RESPONSE_OK:
510
index = _mod_index.index_url(self.branch.base)
515
dialog = SearchDialog(index)
517
if dialog.run() == gtk.RESPONSE_OK:
518
self.set_revision(dialog.get_revision())
445
self.treeview.set_sensitive(False)
446
dialog = AddTagDialog(self.branch.repository, self.treeview.get_revision().revision_id, self.branch)
447
response = dialog.run()
448
if response != gtk.RESPONSE_NONE:
451
if response == gtk.RESPONSE_OK:
452
self.treeview.add_tag(dialog.tagname, dialog._revid)
457
self.treeview.set_sensitive(True)
522
459
def _about_dialog_cb(self, w):
523
460
from bzrlib.plugins.gtk.about import AboutDialog
531
468
def _toolbar_visibility_changed(self, col):
532
469
if col.get_active():
535
472
self.toolbar.hide()
536
self.config.set_user_option('viz-toolbar-visible', col.get_active())
538
def _make_diff_nonzero_size(self):
539
"""make sure the diff isn't zero-width or zero-height"""
540
alloc = self.diff.get_allocation()
541
if (alloc.width < 10) or (alloc.height < 10):
542
width, height = self.get_size()
543
self.revisionview.set_size_request(width/3, int(height / 2.5))
545
def _diff_visibility_changed(self, col):
546
"""Hide or show the diff panel."""
549
self._make_diff_nonzero_size()
552
self.config.set_user_option('viz-show-diffs', str(col.get_active()))
553
self.update_diff_panel()
555
def _diff_placement_changed(self, col):
556
"""Toggle the diff panel's position."""
557
self.config.set_user_option('viz-wide-diffs', str(col.get_active()))
559
old = self.paned.get_child2()
560
self.paned.remove(old)
561
self.paned.pack2(self.construct_bottom(), resize=True, shrink=False)
562
self._make_diff_nonzero_size()
564
self.treeview.emit('revision-selected')
566
def _diff_wrap_changed(self, widget):
567
"""Toggle word wrap in the diff widget."""
568
self.config.set_user_option('viz-wrap-diffs', widget.get_active())
569
self.diff._on_wraplines_toggled(widget)
571
474
def _show_about_cb(self, w):
572
475
dialog = AboutDialog()
573
476
dialog.connect('response', lambda d,r: d.destroy())
582
485
if self.branch.supports_tags():
583
486
tags = self.branch.tags.get_tag_dict().items()
584
tags.sort(reverse=True)
585
489
for tag, revid in tags:
586
490
tag_image = gtk.Image()
587
491
tag_image.set_from_file(icon_path('tag-16.png'))
588
492
tag_item = gtk.ImageMenuItem(tag.replace('_', '__'))
589
493
tag_item.set_image(tag_image)
590
494
tag_item.connect('activate', self._tag_selected_cb, revid)
591
tag_item.set_sensitive(self.treeview.has_revision_id(revid))
592
495
menu.add(tag_item)
593
496
self.go_menu_tags.set_submenu(menu)
599
502
self.go_menu_tags.show_all()
601
def _load_size(self, name):
602
"""Read and parse 'name' from self.config.
603
The value is a string, formatted as WIDTHxHEIGHT
604
Returns None, or (width, height)
606
size = self.config.get_user_option(name)
608
width, height = [int(num) for num in size.split('x')]
609
# avoid writing config every time we start
613
def show_diff(self, revid=None, parentid=NULL_REVISION):
504
def show_diff(self, revid=None, parentid=None):
614
505
"""Open a new window to show a diff between the given revisions."""
615
506
from bzrlib.plugins.gtk.diff import DiffWindow
616
507
window = DiffWindow(parent=self)
510
parentid = NULL_REVISION
618
512
rev_tree = self.branch.repository.revision_tree(revid)
619
513
parent_tree = self.branch.repository.revision_tree(parentid)
621
description = revid + " - " + self.branch._get_nick(local=True)
515
description = revid + " - " + self.branch.nick
622
516
window.set_diff(description, rev_tree, parent_tree)
625
def update_diff_panel(self, revision=None, parents=None):
626
"""Show the current revision in the diff panel."""
627
if self.config.get_user_option('viz-show-diffs') != 'True':
630
if not revision: # default to selected row
631
revision = self.treeview.get_revision()
632
if revision == NULL_REVISION:
635
if not parents: # default to selected row's parents
636
parents = self.treeview.get_parents()
637
if len(parents) == 0:
638
parent_id = NULL_REVISION
640
parent_id = parents[0]
642
rev_tree = self.branch.repository.revision_tree(revision.revision_id)
643
parent_tree = self.branch.repository.revision_tree(parent_id)
645
self.diff.set_diff(rev_tree, parent_tree)
646
if self.config.get_user_option('viz-wrap-diffs') == 'True':
647
self.diff._on_wraplines_toggled(wrap=True)