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")
99
91
self.refresh_action.connect("activate", self._refresh_clicked)
100
92
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)
113
96
def set_revision(self, revid):
114
97
self.treeview.set_revision_id(revid)
118
101
vbox = gtk.VBox(spacing=0)
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)
104
self.paned = gtk.VPaned()
105
self.paned.pack1(self.construct_top(), resize=True, shrink=False)
106
self.paned.pack2(self.construct_bottom(), resize=False, shrink=True)
109
vbox.pack_start(self.construct_menubar(), expand=False, fill=True)
110
vbox.pack_start(self.construct_navigation(), expand=False, fill=True)
112
vbox.pack_start(self.paned, expand=True, fill=True)
113
vbox.set_focus_child(self.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)
149
117
def construct_menubar(self):
150
118
menubar = gtk.MenuBar()
156
124
file_menu_close = gtk.ImageMenuItem(gtk.STOCK_CLOSE, self.accel_group)
157
125
file_menu_close.connect('activate', lambda x: self.destroy())
159
127
file_menu_quit = gtk.ImageMenuItem(gtk.STOCK_QUIT, self.accel_group)
160
128
file_menu_quit.connect('activate', lambda x: gtk.main_quit())
162
130
if self._parent is not None:
163
131
file_menu.add(file_menu_close)
164
132
file_menu.add(file_menu_quit)
167
135
edit_menuitem = gtk.MenuItem("_Edit")
168
136
edit_menuitem.set_submenu(edit_menu)
138
edit_menu_find = gtk.ImageMenuItem(gtk.STOCK_FIND)
170
140
edit_menu_branchopts = gtk.MenuItem("Branch Settings")
171
141
edit_menu_branchopts.connect('activate', lambda x: PreferencesWindow(self.branch.get_config()).show())
173
143
edit_menu_globopts = gtk.MenuItem("Global Settings")
174
144
edit_menu_globopts.connect('activate', lambda x: PreferencesWindow().show())
146
edit_menu.add(edit_menu_find)
176
147
edit_menu.add(edit_menu_branchopts)
177
148
edit_menu.add(edit_menu_globopts)
189
160
view_menu_toolbar = gtk.CheckMenuItem("Show Toolbar")
190
161
view_menu_toolbar.set_active(True)
191
if self.config.get_user_option('viz-toolbar-visible') == 'False':
192
view_menu_toolbar.set_active(False)
194
162
view_menu_toolbar.connect('toggled', self._toolbar_visibility_changed)
196
164
view_menu_compact = gtk.CheckMenuItem("Show Compact Graph")
197
165
view_menu_compact.set_active(self.compact_view)
198
166
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)
224
168
view_menu.add(view_menu_toolbar)
225
169
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)
231
170
view_menu.add(gtk.SeparatorMenuItem())
233
172
self.mnu_show_revno_column = gtk.CheckMenuItem("Show Revision _Number Column")
256
195
tag_image.set_from_file(icon_path("tag-16.png"))
257
196
self.go_menu_tags = gtk.ImageMenuItem("_Tags")
258
197
self.go_menu_tags.set_image(tag_image)
259
self.treeview.connect('refreshed', lambda w: self._update_tags())
261
200
go_menu.add(go_menu_next)
262
201
go_menu.add(go_menu_prev)
263
202
go_menu.add(gtk.SeparatorMenuItem())
264
203
go_menu.add(self.go_menu_tags)
266
self.revision_menu = RevisionMenu(self.branch.repository, [],
267
self.branch, parent=self)
205
revision_menu = gtk.Menu()
268
206
revision_menuitem = gtk.MenuItem("_Revision")
269
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)
271
224
branch_menu = gtk.Menu()
272
225
branch_menuitem = gtk.MenuItem("_Branch")
275
228
branch_menu.add(gtk.MenuItem("Pu_ll Revisions"))
276
229
branch_menu.add(gtk.MenuItem("Pu_sh Revisions"))
279
from bzrlib.plugins import search
281
mutter("Didn't find search plugin")
283
branch_menu.add(gtk.SeparatorMenuItem())
285
branch_index_menuitem = gtk.MenuItem("_Index")
286
branch_index_menuitem.connect('activate', self._branch_index_cb)
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)
293
231
help_menu = gtk.Menu()
294
232
help_menuitem = gtk.MenuItem("_Help")
295
233
help_menuitem.set_submenu(help_menu)
297
help_about_menuitem = gtk.ImageMenuItem(gtk.STOCK_ABOUT,
235
help_about_menuitem = gtk.ImageMenuItem(gtk.STOCK_ABOUT, self.accel_group)
299
236
help_about_menuitem.connect('activate', self._about_dialog_cb)
301
238
help_menu.add(help_about_menuitem)
315
252
"""Construct the top-half of the window."""
316
253
# FIXME: Make broken_line_length configurable
318
self.treeview = TreeView(self.branch, self.start_revs, self.maxnum,
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())
321
264
for col in ["revno", "date"]:
322
265
option = self.config.get_user_option(col + '-column-visible')
323
266
if option is not None:
324
self.treeview.set_property(col + '-column-visible',
267
self.treeview.set_property(col + '-column-visible', option == 'True')
327
269
self.treeview.set_property(col + '-column-visible', False)
331
273
align = gtk.Alignment(0.0, 0.0, 1.0, 1.0)
332
274
align.set_padding(5, 0, 0, 0)
333
275
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')
368
301
def construct_bottom(self):
369
302
"""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
377
303
from bzrlib.plugins.gtk.revisionview import RevisionView
378
304
self.revisionview = RevisionView(branch=self.branch)
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')
305
(width, height) = self.get_size()
306
self.revisionview.set_size_request(width, int(height / 2.5))
386
307
self.revisionview.show()
387
308
self.revisionview.set_show_callback(self._show_clicked_cb)
388
309
self.revisionview.connect('notify::revision', self._go_clicked_cb)
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
310
self.treeview.connect('tag-added', lambda w, t, r: self.revisionview.update_tags())
311
return self.revisionview
407
313
def _tag_selected_cb(self, menuitem, revid):
408
314
self.treeview.set_revision_id(revid)
455
359
self.next_rev_action.set_sensitive(False)
458
if getattr(self.next_button, 'set_menu', None) is not None:
459
self.next_button.set_menu(next_menu)
362
self.next_button.set_menu(next_menu)
461
364
self.revisionview.set_revision(revision)
462
365
self.revisionview.set_children(children)
463
self.update_diff_panel(revision, parents)
465
367
def _tree_revision_activated(self, widget, path, col):
466
368
# TODO: more than one parent
467
369
"""Callback for when a treeview row gets activated."""
469
371
parents = self.treeview.get_parents()
471
373
if len(parents) == 0:
472
parent_id = NULL_REVISION
474
376
parent_id = parents[0]
476
378
self.show_diff(revision.revision_id, parent_id)
477
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)
479
394
def _back_clicked_cb(self, *args):
480
395
"""Callback for when the back button is clicked."""
481
396
self.treeview.back()
483
398
def _fwd_clicked_cb(self, *args):
484
399
"""Callback for when the forward button is clicked."""
485
400
self.treeview.forward()
494
409
self.show_diff(revid, parentid)
495
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)
497
428
def _set_revision_cb(self, w, revision_id):
498
429
self.treeview.set_revision_id(revision_id)
509
440
self.treeview.set_property('compact', self.compact_view)
510
441
self.treeview.refresh()
512
def _branch_index_cb(self, w):
513
from bzrlib.plugins.search import index as _mod_index
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
443
def _tag_revision_cb(self, w):
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())
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)
544
459
def _about_dialog_cb(self, w):
545
460
from bzrlib.plugins.gtk.about import AboutDialog
546
462
AboutDialog().run()
548
464
def _col_visibility_changed(self, col, property):
552
468
def _toolbar_visibility_changed(self, col):
553
469
if col.get_active():
556
472
self.toolbar.hide()
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)
474
def _show_about_cb(self, w):
475
dialog = AboutDialog()
476
dialog.connect('response', lambda d,r: d.destroy())
611
479
def _refresh_clicked(self, w):
612
480
self.treeview.refresh()
617
485
if self.branch.supports_tags():
618
486
tags = self.branch.tags.get_tag_dict().items()
619
tags.sort(reverse=True)
620
489
for tag, revid in tags:
621
490
tag_image = gtk.Image()
622
491
tag_image.set_from_file(icon_path('tag-16.png'))
623
492
tag_item = gtk.ImageMenuItem(tag.replace('_', '__'))
624
493
tag_item.set_image(tag_image)
625
494
tag_item.connect('activate', self._tag_selected_cb, revid)
626
tag_item.set_sensitive(self.treeview.has_revision_id(revid))
627
495
menu.add(tag_item)
628
496
self.go_menu_tags.set_submenu(menu)
634
502
self.go_menu_tags.show_all()
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):
504
def show_diff(self, revid=None, parentid=None):
649
505
"""Open a new window to show a diff between the given revisions."""
650
506
from bzrlib.plugins.gtk.diff import DiffWindow
651
507
window = DiffWindow(parent=self)
510
parentid = NULL_REVISION
653
512
rev_tree = self.branch.repository.revision_tree(revid)
654
513
parent_tree = self.branch.repository.revision_tree(parentid)
656
description = revid + " - " + self.branch._get_nick(local=True)
515
description = revid + " - " + self.branch.nick
657
516
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)