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
13
17
from bzrlib.plugins.gtk import icon_path
14
from bzrlib.plugins.gtk.branchview import TreeView
18
from bzrlib.plugins.gtk.tags import AddTagDialog
15
19
from bzrlib.plugins.gtk.preferences import PreferencesWindow
16
from bzrlib.plugins.gtk.revisionmenu import RevisionMenu
17
from bzrlib.plugins.gtk.window import Window
20
from bzrlib.plugins.gtk.branchview import TreeView, treemodel
21
from bzrlib.revision import Revision, NULL_REVISION
22
from bzrlib.config import BranchConfig
19
23
from bzrlib.config import GlobalConfig
20
from bzrlib.revision import NULL_REVISION
21
from bzrlib.trace import mutter
24
25
class BranchWindow(Window):
51
52
self.compact_view = False
53
self.set_title(branch._get_nick(local=True) + " - revision history")
54
self.set_title(branch.nick + " - revision history")
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)
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)
65
61
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')
70
64
icon = self.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)
77
71
self.accel_group = gtk.AccelGroup()
78
72
self.add_accel_group(self.accel_group)
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)
74
gtk.Action.set_tool_item_type(gtk.MenuToolButton)
84
76
self.prev_rev_action = gtk.Action("prev-rev", "_Previous Revision", "Go to the previous revision", gtk.STOCK_GO_DOWN)
85
77
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)
113
96
def set_revision(self, revid):
114
97
self.treeview.set_revision_id(revid)
121
104
self.paned = gtk.VPaned()
122
self.paned.pack1(self.construct_top(), resize=False, shrink=True)
123
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)
124
107
self.paned.show()
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)
109
vbox.pack_start(self.construct_menubar(), expand=False, fill=True)
110
vbox.pack_start(self.construct_navigation(), expand=False, fill=True)
131
112
vbox.pack_start(self.paned, expand=True, fill=True)
132
113
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())
142
117
def construct_menubar(self):
149
124
file_menu_close = gtk.ImageMenuItem(gtk.STOCK_CLOSE, self.accel_group)
150
125
file_menu_close.connect('activate', lambda x: self.destroy())
152
127
file_menu_quit = gtk.ImageMenuItem(gtk.STOCK_QUIT, self.accel_group)
153
128
file_menu_quit.connect('activate', lambda x: gtk.main_quit())
155
130
if self._parent is not None:
156
131
file_menu.add(file_menu_close)
157
132
file_menu.add(file_menu_quit)
160
135
edit_menuitem = gtk.MenuItem("_Edit")
161
136
edit_menuitem.set_submenu(edit_menu)
138
edit_menu_find = gtk.ImageMenuItem(gtk.STOCK_FIND)
163
140
edit_menu_branchopts = gtk.MenuItem("Branch Settings")
164
141
edit_menu_branchopts.connect('activate', lambda x: PreferencesWindow(self.branch.get_config()).show())
166
143
edit_menu_globopts = gtk.MenuItem("Global Settings")
167
144
edit_menu_globopts.connect('activate', lambda x: PreferencesWindow().show())
146
edit_menu.add(edit_menu_find)
169
147
edit_menu.add(edit_menu_branchopts)
170
148
edit_menu.add(edit_menu_globopts)
182
160
view_menu_toolbar = gtk.CheckMenuItem("Show Toolbar")
183
161
view_menu_toolbar.set_active(True)
184
if self.config.get_user_option('viz-toolbar-visible') == 'False':
185
view_menu_toolbar.set_active(False)
187
162
view_menu_toolbar.connect('toggled', self._toolbar_visibility_changed)
189
164
view_menu_compact = gtk.CheckMenuItem("Show Compact Graph")
190
165
view_menu_compact.set_active(self.compact_view)
191
166
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)
211
168
view_menu.add(view_menu_toolbar)
212
169
view_menu.add(view_menu_compact)
213
170
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())
219
172
self.mnu_show_revno_column = gtk.CheckMenuItem("Show Revision _Number Column")
220
173
self.mnu_show_date_column = gtk.CheckMenuItem("Show _Date Column")
234
187
go_menu.set_accel_group(self.accel_group)
235
188
go_menuitem = gtk.MenuItem("_Go")
236
189
go_menuitem.set_submenu(go_menu)
238
191
go_menu_next = self.next_rev_action.create_menu_item()
239
192
go_menu_prev = self.prev_rev_action.create_menu_item()
242
195
tag_image.set_from_file(icon_path("tag-16.png"))
243
196
self.go_menu_tags = gtk.ImageMenuItem("_Tags")
244
197
self.go_menu_tags.set_image(tag_image)
245
self.treeview.connect('refreshed', lambda w: self._update_tags())
247
200
go_menu.add(go_menu_next)
248
201
go_menu.add(go_menu_prev)
249
202
go_menu.add(gtk.SeparatorMenuItem())
250
203
go_menu.add(self.go_menu_tags)
252
self.revision_menu = RevisionMenu(self.branch.repository, [], self.branch, parent=self)
205
revision_menu = gtk.Menu()
253
206
revision_menuitem = gtk.MenuItem("_Revision")
254
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)
256
224
branch_menu = gtk.Menu()
257
225
branch_menuitem = gtk.MenuItem("_Branch")
260
228
branch_menu.add(gtk.MenuItem("Pu_ll Revisions"))
261
229
branch_menu.add(gtk.MenuItem("Pu_sh Revisions"))
264
from bzrlib.plugins import search
266
mutter("Didn't find search plugin")
268
branch_menu.add(gtk.SeparatorMenuItem())
270
branch_index_menuitem = gtk.MenuItem("_Index")
271
branch_index_menuitem.connect('activate', self._branch_index_cb)
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)
278
231
help_menu = gtk.Menu()
279
232
help_menuitem = gtk.MenuItem("_Help")
280
233
help_menuitem.set_submenu(help_menu)
302
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())
304
264
for col in ["revno", "date"]:
305
265
option = self.config.get_user_option(col + '-column-visible')
306
266
if option is not None:
313
273
align = gtk.Alignment(0.0, 0.0, 1.0, 1.0)
314
274
align.set_padding(5, 0, 0, 0)
315
275
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')
350
301
def construct_bottom(self):
351
302
"""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
359
303
from bzrlib.plugins.gtk.revisionview import RevisionView
360
304
self.revisionview = RevisionView(branch=self.branch)
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')
305
(width, height) = self.get_size()
306
self.revisionview.set_size_request(width, int(height / 2.5))
368
307
self.revisionview.show()
369
308
self.revisionview.set_show_callback(self._show_clicked_cb)
370
309
self.revisionview.connect('notify::revision', self._go_clicked_cb)
371
310
self.treeview.connect('tag-added', lambda w, t, r: self.revisionview.update_tags())
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
311
return self.revisionview
384
313
def _tag_selected_cb(self, menuitem, revid):
385
314
self.treeview.set_revision_id(revid)
412
339
self.prev_rev_action.set_sensitive(False)
415
if getattr(self.prev_button, 'set_menu', None) is not None:
416
self.prev_button.set_menu(prev_menu)
342
self.prev_button.set_menu(prev_menu)
418
344
next_menu = gtk.Menu()
419
345
if len(children) > 0:
433
359
self.next_rev_action.set_sensitive(False)
436
if getattr(self.next_button, 'set_menu', None) is not None:
437
self.next_button.set_menu(next_menu)
362
self.next_button.set_menu(next_menu)
439
364
self.revisionview.set_revision(revision)
440
365
self.revisionview.set_children(children)
441
self.update_diff_panel(revision, parents)
443
367
def _tree_revision_activated(self, widget, path, col):
444
368
# TODO: more than one parent
445
369
"""Callback for when a treeview row gets activated."""
447
371
parents = self.treeview.get_parents()
449
373
if len(parents) == 0:
450
parent_id = NULL_REVISION
452
376
parent_id = parents[0]
454
378
self.show_diff(revision.revision_id, parent_id)
455
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)
457
394
def _back_clicked_cb(self, *args):
458
395
"""Callback for when the back button is clicked."""
459
396
self.treeview.back()
461
398
def _fwd_clicked_cb(self, *args):
462
399
"""Callback for when the forward button is clicked."""
463
400
self.treeview.forward()
472
409
self.show_diff(revid, parentid)
473
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)
475
428
def _set_revision_cb(self, w, revision_id):
476
429
self.treeview.set_revision_id(revision_id)
487
440
self.treeview.set_property('compact', self.compact_view)
488
441
self.treeview.refresh()
490
def _branch_index_cb(self, w):
491
from bzrlib.plugins.search import index as _mod_index
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
443
def _tag_revision_cb(self, w):
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())
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)
520
459
def _about_dialog_cb(self, w):
521
460
from bzrlib.plugins.gtk.about import AboutDialog
522
462
AboutDialog().run()
524
464
def _col_visibility_changed(self, col, property):
528
468
def _toolbar_visibility_changed(self, col):
529
469
if col.get_active():
532
472
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)
474
def _show_about_cb(self, w):
475
dialog = AboutDialog()
476
dialog.connect('response', lambda d,r: d.destroy())
568
479
def _refresh_clicked(self, w):
569
480
self.treeview.refresh()
574
485
if self.branch.supports_tags():
575
486
tags = self.branch.tags.get_tag_dict().items()
576
tags.sort(reverse=True)
577
489
for tag, revid in tags:
578
490
tag_image = gtk.Image()
579
491
tag_image.set_from_file(icon_path('tag-16.png'))
580
492
tag_item = gtk.ImageMenuItem(tag.replace('_', '__'))
581
493
tag_item.set_image(tag_image)
582
494
tag_item.connect('activate', self._tag_selected_cb, revid)
583
tag_item.set_sensitive(self.treeview.has_revision_id(revid))
584
495
menu.add(tag_item)
585
496
self.go_menu_tags.set_submenu(menu)
591
502
self.go_menu_tags.show_all()
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):
504
def show_diff(self, revid=None, parentid=None):
606
505
"""Open a new window to show a diff between the given revisions."""
607
506
from bzrlib.plugins.gtk.diff import DiffWindow
608
507
window = DiffWindow(parent=self)
510
parentid = NULL_REVISION
610
512
rev_tree = self.branch.repository.revision_tree(revid)
611
513
parent_tree = self.branch.repository.revision_tree(parentid)
613
description = revid + " - " + self.branch._get_nick(local=True)
515
description = revid + " - " + self.branch.nick
614
516
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)