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
15
from bzrlib.plugins.gtk import icon_path
16
from bzrlib.plugins.gtk.branchview import TreeView, treemodel
18
17
from bzrlib.plugins.gtk.tags import AddTagDialog
19
18
from bzrlib.plugins.gtk.preferences import PreferencesWindow
20
from bzrlib.plugins.gtk.branchview import TreeView, treemodel
19
from bzrlib.plugins.gtk.revisionmenu import RevisionMenu
20
from bzrlib.plugins.gtk.window import Window
22
22
from bzrlib.config import BranchConfig, GlobalConfig
23
23
from bzrlib.revision import Revision, NULL_REVISION
53
53
self.compact_view = False
55
self.set_title(branch.nick + " - revision history")
55
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)
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)
62
67
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')
65
72
icon = self.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)
72
79
self.accel_group = gtk.AccelGroup()
73
80
self.add_accel_group(self.accel_group)
75
gtk.Action.set_tool_item_type(gtk.MenuToolButton)
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)
77
86
self.prev_rev_action = gtk.Action("prev-rev", "_Previous Revision", "Go to the previous revision", gtk.STOCK_GO_DOWN)
78
87
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)
97
115
def set_revision(self, revid):
98
116
self.treeview.set_revision_id(revid)
105
123
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)
124
self.paned.pack1(self.construct_top(), resize=False, shrink=True)
125
self.paned.pack2(self.construct_bottom(), resize=True, shrink=False)
108
126
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)
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)
113
133
vbox.pack_start(self.paned, expand=True, fill=True)
114
134
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())
118
144
def construct_menubar(self):
119
145
menubar = gtk.MenuBar()
136
162
edit_menuitem = gtk.MenuItem("_Edit")
137
163
edit_menuitem.set_submenu(edit_menu)
139
edit_menu_find = gtk.ImageMenuItem(gtk.STOCK_FIND)
141
165
edit_menu_branchopts = gtk.MenuItem("Branch Settings")
142
166
edit_menu_branchopts.connect('activate', lambda x: PreferencesWindow(self.branch.get_config()).show())
144
168
edit_menu_globopts = gtk.MenuItem("Global Settings")
145
169
edit_menu_globopts.connect('activate', lambda x: PreferencesWindow().show())
147
edit_menu.add(edit_menu_find)
148
171
edit_menu.add(edit_menu_branchopts)
149
172
edit_menu.add(edit_menu_globopts)
161
184
view_menu_toolbar = gtk.CheckMenuItem("Show Toolbar")
162
185
view_menu_toolbar.set_active(True)
186
if self.config.get_user_option('viz-toolbar-visible') == 'False':
187
view_menu_toolbar.set_active(False)
163
189
view_menu_toolbar.connect('toggled', self._toolbar_visibility_changed)
165
191
view_menu_compact = gtk.CheckMenuItem("Show Compact Graph")
166
192
view_menu_compact.set_active(self.compact_view)
167
193
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)
169
213
view_menu.add(view_menu_toolbar)
170
214
view_menu.add(view_menu_compact)
171
215
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())
173
221
self.mnu_show_revno_column = gtk.CheckMenuItem("Show Revision _Number Column")
174
222
self.mnu_show_date_column = gtk.CheckMenuItem("Show _Date Column")
196
244
tag_image.set_from_file(icon_path("tag-16.png"))
197
245
self.go_menu_tags = gtk.ImageMenuItem("_Tags")
198
246
self.go_menu_tags.set_image(tag_image)
247
self.treeview.connect('refreshed', lambda w: self._update_tags())
201
249
go_menu.add(go_menu_next)
202
250
go_menu.add(go_menu_prev)
203
251
go_menu.add(gtk.SeparatorMenuItem())
204
252
go_menu.add(self.go_menu_tags)
206
revision_menu = gtk.Menu()
254
self.revision_menu = RevisionMenu(self.branch.repository, [], self.branch, parent=self)
207
255
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)
256
revision_menuitem.set_submenu(self.revision_menu)
225
258
branch_menu = gtk.Menu()
226
259
branch_menuitem = gtk.MenuItem("_Branch")
234
267
except ImportError:
235
268
mutter("Didn't find search plugin")
270
branch_menu.add(gtk.SeparatorMenuItem())
237
272
branch_index_menuitem = gtk.MenuItem("_Index")
238
273
branch_index_menuitem.connect('activate', self._branch_index_cb)
239
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)
241
280
help_menu = gtk.Menu()
242
281
help_menuitem = gtk.MenuItem("_Help")
243
282
help_menuitem.set_submenu(help_menu)
265
304
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
306
for col in ["revno", "date"]:
275
307
option = self.config.get_user_option(col + '-column-visible')
276
308
if option is not None:
283
315
align = gtk.Alignment(0.0, 0.0, 1.0, 1.0)
284
316
align.set_padding(5, 0, 0, 0)
285
317
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')
311
352
def construct_bottom(self):
312
353
"""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
313
361
from bzrlib.plugins.gtk.revisionview import RevisionView
314
362
self.revisionview = RevisionView(branch=self.branch)
315
(width, height) = self.get_size()
316
self.revisionview.set_size_request(width, int(height / 2.5))
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')
317
370
self.revisionview.show()
318
371
self.revisionview.set_show_callback(self._show_clicked_cb)
319
372
self.revisionview.connect('notify::revision', self._go_clicked_cb)
320
373
self.treeview.connect('tag-added', lambda w, t, r: self.revisionview.update_tags())
321
return self.revisionview
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
323
386
def _tag_selected_cb(self, menuitem, revid):
324
387
self.treeview.set_revision_id(revid)
349
414
self.prev_rev_action.set_sensitive(False)
352
self.prev_button.set_menu(prev_menu)
417
if getattr(self.prev_button, 'set_menu', None) is not None:
418
self.prev_button.set_menu(prev_menu)
354
420
next_menu = gtk.Menu()
355
421
if len(children) > 0:
369
435
self.next_rev_action.set_sensitive(False)
372
self.next_button.set_menu(next_menu)
438
if getattr(self.next_button, 'set_menu', None) is not None:
439
self.next_button.set_menu(next_menu)
374
441
self.revisionview.set_revision(revision)
375
442
self.revisionview.set_children(children)
443
self.update_diff_panel(revision, parents)
377
445
def _tree_revision_activated(self, widget, path, col):
378
446
# TODO: more than one parent
379
447
"""Callback for when a treeview row gets activated."""
381
449
parents = self.treeview.get_parents()
383
451
if len(parents) == 0:
452
parent_id = NULL_REVISION
386
454
parent_id = parents[0]
388
456
self.show_diff(revision.revision_id, parent_id)
389
457
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
459
def _back_clicked_cb(self, *args):
405
460
"""Callback for when the back button is clicked."""
406
461
self.treeview.back()
419
474
self.show_diff(revid, parentid)
420
475
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
477
def _set_revision_cb(self, w, revision_id):
439
478
self.treeview.set_revision_id(revision_id)
450
489
self.treeview.set_property('compact', self.compact_view)
451
490
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
492
def _branch_index_cb(self, w):
470
493
from bzrlib.plugins.search import index as _mod_index
471
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
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())
473
522
def _about_dialog_cb(self, w):
474
523
from bzrlib.plugins.gtk.about import AboutDialog
482
531
def _toolbar_visibility_changed(self, col):
483
532
if col.get_active():
486
535
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)
488
571
def _show_about_cb(self, w):
489
572
dialog = AboutDialog()
490
573
dialog.connect('response', lambda d,r: d.destroy())
499
582
if self.branch.supports_tags():
500
583
tags = self.branch.tags.get_tag_dict().items()
584
tags.sort(reverse=True)
503
585
for tag, revid in tags:
504
586
tag_image = gtk.Image()
505
587
tag_image.set_from_file(icon_path('tag-16.png'))
506
588
tag_item = gtk.ImageMenuItem(tag.replace('_', '__'))
507
589
tag_item.set_image(tag_image)
508
590
tag_item.connect('activate', self._tag_selected_cb, revid)
591
tag_item.set_sensitive(self.treeview.has_revision_id(revid))
509
592
menu.add(tag_item)
510
593
self.go_menu_tags.set_submenu(menu)
516
599
self.go_menu_tags.show_all()
518
def show_diff(self, revid=None, parentid=None):
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):
519
614
"""Open a new window to show a diff between the given revisions."""
520
615
from bzrlib.plugins.gtk.diff import DiffWindow
521
616
window = DiffWindow(parent=self)
524
parentid = NULL_REVISION
526
618
rev_tree = self.branch.repository.revision_tree(revid)
527
619
parent_tree = self.branch.repository.revision_tree(parentid)
529
description = revid + " - " + self.branch.nick
621
description = revid + " - " + self.branch._get_nick(local=True)
530
622
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)