16
from bzrlib.plugins.gtk import icon_path
17
from bzrlib.plugins.gtk.branchview import TreeView, treemodel
16
from bzrlib.plugins.gtk.window import Window
18
17
from bzrlib.plugins.gtk.tags import AddTagDialog
19
18
from bzrlib.plugins.gtk.preferences import PreferencesWindow
20
from bzrlib.plugins.gtk.revisionmenu import RevisionMenu
21
from bzrlib.plugins.gtk.window import Window
23
from bzrlib.config import BranchConfig, GlobalConfig
24
from bzrlib.revision import Revision, NULL_REVISION
25
from bzrlib.trace import mutter
19
from bzrlib.revision import Revision
20
from bzrlib.config import BranchConfig
21
from bzrlib.config import GlobalConfig
22
from treeview import TreeView
23
from about import AboutDialog
27
25
class BranchWindow(Window):
58
54
self.set_title(branch.nick + " - revision history")
60
# user-configured window size
61
size = self._load_size('viz-window-size')
65
# Use three-quarters of the screen by default
66
screen = self.get_screen()
67
monitor = screen.get_monitor_geometry(0)
68
width = int(monitor.width * 0.75)
69
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)
70
61
self.set_default_size(width, height)
71
self.set_size_request(width/3, height/3)
72
self.connect("size-allocate", self._on_size_allocate, 'viz-window-size')
75
64
icon = self.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)
115
97
self.paned = gtk.VPaned()
116
self.paned.pack1(self.construct_top(), resize=False, shrink=True)
117
self.paned.pack2(self.construct_bottom(), resize=True, shrink=False)
98
self.paned.pack1(self.construct_top(), resize=True, shrink=False)
99
self.paned.pack2(self.construct_bottom(), resize=False, shrink=True)
118
100
self.paned.show()
120
nav = self.construct_navigation()
121
menubar = self.construct_menubar()
122
vbox.pack_start(menubar, expand=False, fill=True)
123
vbox.pack_start(nav, expand=False, fill=True)
102
vbox.pack_start(self.construct_menubar(), expand=False, fill=True)
103
vbox.pack_start(self.construct_navigation(), expand=False, fill=True)
104
vbox.pack_start(self.construct_loading_msg(), expand=False, fill=True)
125
106
vbox.pack_start(self.paned, expand=True, fill=True)
126
107
vbox.set_focus_child(self.paned)
130
111
def construct_menubar(self):
131
112
menubar = gtk.MenuBar()
140
121
file_menu_quit = gtk.ImageMenuItem(gtk.STOCK_QUIT, self.accel_group)
141
122
file_menu_quit.connect('activate', lambda x: gtk.main_quit())
143
if self._parent is not None:
144
file_menu.add(file_menu_close)
124
file_menu.add(file_menu_close)
145
125
file_menu.add(file_menu_quit)
147
127
edit_menu = gtk.Menu()
148
128
edit_menuitem = gtk.MenuItem("_Edit")
149
129
edit_menuitem.set_submenu(edit_menu)
131
edit_menu_find = gtk.ImageMenuItem(gtk.STOCK_FIND)
151
133
edit_menu_branchopts = gtk.MenuItem("Branch Settings")
152
134
edit_menu_branchopts.connect('activate', lambda x: PreferencesWindow(self.branch.get_config()).show())
154
edit_menu_globopts = gtk.MenuItem("Global Settings")
155
edit_menu_globopts.connect('activate', lambda x: PreferencesWindow().show())
136
edit_menu_prefs = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES)
137
edit_menu_prefs.connect('activate', lambda x: PreferencesWindow().show())
139
edit_menu.add(edit_menu_find)
157
140
edit_menu.add(edit_menu_branchopts)
158
edit_menu.add(edit_menu_globopts)
141
edit_menu.add(edit_menu_prefs)
160
143
view_menu = gtk.Menu()
161
144
view_menuitem = gtk.MenuItem("_View")
162
145
view_menuitem.set_submenu(view_menu)
164
view_menu_refresh = self.refresh_action.create_menu_item()
165
view_menu_refresh.connect('activate', self._refresh_clicked)
167
view_menu.add(view_menu_refresh)
168
view_menu.add(gtk.SeparatorMenuItem())
170
147
view_menu_toolbar = gtk.CheckMenuItem("Show Toolbar")
171
148
view_menu_toolbar.set_active(True)
172
if self.config.get_user_option('viz-toolbar-visible') == 'False':
173
view_menu_toolbar.set_active(False)
175
149
view_menu_toolbar.connect('toggled', self._toolbar_visibility_changed)
177
151
view_menu_compact = gtk.CheckMenuItem("Show Compact Graph")
178
152
view_menu_compact.set_active(self.compact_view)
179
153
view_menu_compact.connect('activate', self._brokenlines_toggled_cb)
181
view_menu_diffs = gtk.CheckMenuItem("Show Diffs")
182
view_menu_diffs.set_active(False)
183
if self.config.get_user_option('viz-show-diffs') == 'True':
184
view_menu_diffs.set_active(True)
185
view_menu_diffs.connect('toggled', self._diff_visibility_changed)
187
view_menu_wide_diffs = gtk.CheckMenuItem("Wide Diffs")
188
view_menu_wide_diffs.set_active(False)
189
if self.config.get_user_option('viz-wide-diffs') == 'True':
190
view_menu_wide_diffs.set_active(True)
191
view_menu_wide_diffs.connect('toggled', self._diff_placement_changed)
193
view_menu_wrap_diffs = gtk.CheckMenuItem("Wrap _Long Lines in Diffs")
194
view_menu_wrap_diffs.set_active(False)
195
if self.config.get_user_option('viz-wrap-diffs') == 'True':
196
view_menu_wrap_diffs.set_active(True)
197
view_menu_wrap_diffs.connect('toggled', self._diff_wrap_changed)
199
155
view_menu.add(view_menu_toolbar)
200
156
view_menu.add(view_menu_compact)
201
157
view_menu.add(gtk.SeparatorMenuItem())
202
view_menu.add(view_menu_diffs)
203
view_menu.add(view_menu_wide_diffs)
204
view_menu.add(view_menu_wrap_diffs)
205
view_menu.add(gtk.SeparatorMenuItem())
207
self.mnu_show_revno_column = gtk.CheckMenuItem("Show Revision _Number Column")
208
self.mnu_show_date_column = gtk.CheckMenuItem("Show _Date Column")
210
# Revision numbers are pointless if there are multiple branches
211
if len(self.start_revs) > 1:
212
self.mnu_show_revno_column.set_sensitive(False)
213
self.treeview.set_property('revno-column-visible', False)
215
for (col, name) in [(self.mnu_show_revno_column, "revno"),
216
(self.mnu_show_date_column, "date")]:
159
for (label, name) in [("Revision _Number", "revno"), ("_Date", "date")]:
160
col = gtk.CheckMenuItem("Show " + label + " Column")
217
161
col.set_active(self.treeview.get_property(name + "-column-visible"))
218
162
col.connect('toggled', self._col_visibility_changed, name)
219
163
view_menu.add(col)
226
170
go_menu_next = self.next_rev_action.create_menu_item()
227
171
go_menu_prev = self.prev_rev_action.create_menu_item()
229
tag_image = gtk.Image()
230
tag_image.set_from_file(icon_path("tag-16.png"))
231
self.go_menu_tags = gtk.ImageMenuItem("_Tags")
232
self.go_menu_tags.set_image(tag_image)
173
tags_menu = gtk.Menu()
174
go_menu_tags = gtk.MenuItem("_Tags")
175
go_menu_tags.set_submenu(tags_menu)
177
if self.branch.supports_tags():
178
tags = self.branch.tags.get_tag_dict().items()
181
for tag, revid in tags:
182
tag_item = gtk.MenuItem(tag)
183
tag_item.connect('activate', self._tag_selected_cb, revid)
184
tags_menu.add(tag_item)
186
go_menu_tags.set_sensitive(len(tags) != 0)
188
go_menu_tags.set_sensitive(False)
235
190
go_menu.add(go_menu_next)
236
191
go_menu.add(go_menu_prev)
237
192
go_menu.add(gtk.SeparatorMenuItem())
238
go_menu.add(self.go_menu_tags)
193
go_menu.add(go_menu_tags)
240
self.revision_menu = RevisionMenu(self.branch.repository, [], self.branch, parent=self)
195
revision_menu = gtk.Menu()
241
196
revision_menuitem = gtk.MenuItem("_Revision")
242
revision_menuitem.set_submenu(self.revision_menu)
197
revision_menuitem.set_submenu(revision_menu)
199
revision_menu_diff = gtk.MenuItem("View Changes")
200
revision_menu_diff.connect('activate',
201
lambda w: self.treeview.show_diff())
203
revision_menu_tag = gtk.MenuItem("Tag Revision")
204
revision_menu_tag.connect('activate', self._tag_revision_cb)
206
revision_menu.add(revision_menu_tag)
207
revision_menu.add(revision_menu_diff)
244
209
branch_menu = gtk.Menu()
245
210
branch_menuitem = gtk.MenuItem("_Branch")
248
213
branch_menu.add(gtk.MenuItem("Pu_ll Revisions"))
249
214
branch_menu.add(gtk.MenuItem("Pu_sh Revisions"))
252
from bzrlib.plugins import search
254
mutter("Didn't find search plugin")
256
branch_menu.add(gtk.SeparatorMenuItem())
258
branch_index_menuitem = gtk.MenuItem("_Index")
259
branch_index_menuitem.connect('activate', self._branch_index_cb)
260
branch_menu.add(branch_index_menuitem)
262
branch_search_menuitem = gtk.MenuItem("_Search")
263
branch_search_menuitem.connect('activate', self._branch_search_cb)
264
branch_menu.add(branch_search_menuitem)
266
216
help_menu = gtk.Menu()
267
217
help_menuitem = gtk.MenuItem("_Help")
268
218
help_menuitem.set_submenu(help_menu)
270
help_about_menuitem = gtk.ImageMenuItem(gtk.STOCK_ABOUT, self.accel_group)
271
help_about_menuitem.connect('activate', self._about_dialog_cb)
273
help_menu.add(help_about_menuitem)
220
help_menu_about = gtk.ImageMenuItem(gtk.STOCK_ABOUT)
221
help_menu_about.connect('activate', self._show_about_cb)
223
help_menu.add(help_menu_about)
275
225
menubar.add(file_menuitem)
276
226
menubar.add(edit_menuitem)
277
227
menubar.add(view_menuitem)
282
232
menubar.show_all()
236
def construct_loading_msg(self):
237
image_loading = gtk.image_new_from_stock(gtk.STOCK_REFRESH,
238
gtk.ICON_SIZE_BUTTON)
241
label_loading = gtk.Label(_("Please wait, loading ancestral graph..."))
242
label_loading.set_alignment(0.0, 0.5)
245
self.loading_msg_box = gtk.HBox()
246
self.loading_msg_box.set_spacing(5)
247
self.loading_msg_box.set_border_width(5)
248
self.loading_msg_box.pack_start(image_loading, False, False)
249
self.loading_msg_box.pack_start(label_loading, True, True)
250
self.loading_msg_box.show()
252
return self.loading_msg_box
286
254
def construct_top(self):
287
255
"""Construct the top-half of the window."""
288
256
# FIXME: Make broken_line_length configurable
257
if self.compact_view:
290
self.treeview = TreeView(self.branch, self.start_revs, self.maxnum, self.compact_view)
262
self.treeview = TreeView(self.branch, self.start, self.maxnum, brokenlines)
292
264
self.treeview.connect('revision-selected',
293
265
self._treeselection_changed_cb)
294
self.treeview.connect('revision-activated',
295
self._tree_revision_activated)
297
self.treeview.connect('tag-added', lambda w, t, r: self._update_tags())
267
self.treeview.connect('revisions-loaded',
268
lambda x: self.loading_msg_box.hide())
299
270
for col in ["revno", "date"]:
300
271
option = self.config.get_user_option(col + '-column-visible')
301
272
if option is not None:
302
273
self.treeview.set_property(col + '-column-visible', option == 'True')
304
self.treeview.set_property(col + '-column-visible', False)
306
275
self.treeview.show()
308
277
align = gtk.Alignment(0.0, 0.0, 1.0, 1.0)
309
278
align.set_padding(5, 0, 0, 0)
310
279
align.add(self.treeview)
311
# user-configured size
312
size = self._load_size('viz-graph-size')
315
align.set_size_request(width, height)
317
(width, height) = self.get_size()
318
align.set_size_request(width, int(height / 2.5))
319
align.connect('size-allocate', self._on_size_allocate, 'viz-graph-size')
332
292
self.next_button = self.next_rev_action.create_tool_item()
333
293
self.toolbar.insert(self.next_button, -1)
335
self.toolbar.insert(gtk.SeparatorToolItem(), -1)
337
refresh_button = gtk.ToolButton(gtk.STOCK_REFRESH)
338
refresh_button.connect('clicked', self._refresh_clicked)
339
self.toolbar.insert(refresh_button, -1)
341
295
self.toolbar.show_all()
343
297
return self.toolbar
345
299
def construct_bottom(self):
346
300
"""Construct the bottom half of the window."""
347
if self.config.get_user_option('viz-wide-diffs') == 'True':
348
self.diff_paned = gtk.VPaned()
350
self.diff_paned = gtk.HPaned()
301
from bzrlib.plugins.gtk.revisionview import RevisionView
302
self.revisionview = RevisionView(None, tags=[], show_children=True, branch=self.branch)
351
303
(width, height) = self.get_size()
352
self.diff_paned.set_size_request(20, 20) # shrinkable
354
from bzrlib.plugins.gtk.revisionview import RevisionView
355
self.revisionview = RevisionView(branch=self.branch)
356
self.revisionview.set_size_request(width/3, int(height / 2.5))
357
# user-configured size
358
size = self._load_size('viz-revisionview-size')
361
self.revisionview.set_size_request(width, height)
362
self.revisionview.connect('size-allocate', self._on_size_allocate, 'viz-revisionview-size')
304
self.revisionview.set_size_request(width, int(height / 2.5))
363
305
self.revisionview.show()
364
306
self.revisionview.set_show_callback(self._show_clicked_cb)
365
self.revisionview.connect('notify::revision', self._go_clicked_cb)
366
self.treeview.connect('tag-added', lambda w, t, r: self.revisionview.update_tags())
367
self.diff_paned.pack1(self.revisionview)
369
from bzrlib.plugins.gtk.diff import DiffWidget
370
self.diff = DiffWidget()
371
self.diff_paned.pack2(self.diff)
373
self.diff_paned.show_all()
374
if self.config.get_user_option('viz-show-diffs') != 'True':
377
return self.diff_paned
307
self.revisionview.set_go_callback(self._go_clicked_cb)
308
return self.revisionview
379
310
def _tag_selected_cb(self, menuitem, revid):
380
311
self.treeview.set_revision_id(revid)
385
316
parents = self.treeview.get_parents()
386
317
children = self.treeview.get_children()
388
self.revision_menu.set_revision_ids([revision.revision_id])
390
if revision and revision != NULL_REVISION:
319
if revision is not None:
391
320
prev_menu = gtk.Menu()
392
321
if len(parents) > 0:
393
322
self.prev_rev_action.set_sensitive(True)
394
323
for parent_id in parents:
395
if parent_id and parent_id != NULL_REVISION:
396
parent = self.branch.repository.get_revision(parent_id)
398
str = ' (' + parent.properties['branch-nick'] + ')'
324
parent = self.branch.repository.get_revision(parent_id)
326
str = ' (' + parent.properties['branch-nick'] + ')'
402
item = gtk.MenuItem(parent.message.split("\n")[0] + str)
403
item.connect('activate', self._set_revision_cb, parent_id)
330
item = gtk.MenuItem(parent.message.split("\n")[0] + str)
331
item.connect('activate', self._set_revision_cb, parent_id)
405
333
prev_menu.show_all()
407
335
self.prev_rev_action.set_sensitive(False)
430
358
self.next_button.set_menu(next_menu)
432
self.revisionview.set_revision(revision)
433
self.revisionview.set_children(children)
434
self.update_diff_panel(revision, parents)
436
def _tree_revision_activated(self, widget, path, col):
437
# TODO: more than one parent
438
"""Callback for when a treeview row gets activated."""
439
revision = self.treeview.get_revision()
440
parents = self.treeview.get_parents()
442
if len(parents) == 0:
445
parent_id = parents[0]
447
self.show_diff(revision.revision_id, parent_id)
448
self.treeview.grab_focus()
361
if self.branch.supports_tags():
362
tagdict = self.branch.tags.get_reverse_tag_dict()
363
if tagdict.has_key(revision.revision_id):
364
tags = tagdict[revision.revision_id]
365
self.revisionview.set_revision(revision, tags, children)
450
367
def _back_clicked_cb(self, *args):
451
368
"""Callback for when the back button is clicked."""
452
369
self.treeview.back()
455
372
"""Callback for when the forward button is clicked."""
456
373
self.treeview.forward()
458
def _go_clicked_cb(self, w, p):
375
def _go_clicked_cb(self, revid):
459
376
"""Callback for when the go button for a parent is clicked."""
460
if self.revisionview.get_revision() is not None:
461
self.treeview.set_revision(self.revisionview.get_revision())
377
self.treeview.set_revision_id(revid)
463
379
def _show_clicked_cb(self, revid, parentid):
464
380
"""Callback for when the show button for a parent is clicked."""
465
self.show_diff(revid, parentid)
381
self.treeview.show_diff(revid, parentid)
466
382
self.treeview.grab_focus()
468
384
def _set_revision_cb(self, w, revision_id):
479
395
self.config.set_user_option('viz-compact-view', option)
480
self.treeview.set_property('compact', self.compact_view)
481
self.treeview.refresh()
483
def _branch_index_cb(self, w):
484
from bzrlib.plugins.search import index as _mod_index
485
_mod_index.index_url(self.branch.base)
487
def _branch_search_cb(self, w):
488
from bzrlib.plugins.search import index as _mod_index
489
from bzrlib.plugins.gtk.search import SearchDialog
490
from bzrlib.plugins.search import errors as search_errors
397
revision = self.treeview.get_revision()
399
self.paned.get_child1().destroy()
400
self.paned.pack1(self.construct_top(), resize=True, shrink=False)
402
gobject.idle_add(self.set_revision, revision.revision_id)
404
def _tag_revision_cb(self, w):
493
index = _mod_index.open_index_url(self.branch.base)
494
except search_errors.NoSearchIndex:
495
dialog = gtk.MessageDialog(self, type=gtk.MESSAGE_QUESTION,
496
buttons=gtk.BUTTONS_OK_CANCEL,
497
message_format="This branch has not been indexed yet. "
499
if dialog.run() == gtk.RESPONSE_OK:
501
index = _mod_index.index_url(self.branch.base)
506
dialog = SearchDialog(index)
508
if dialog.run() == gtk.RESPONSE_OK:
509
self.set_revision(dialog.get_revision())
513
def _about_dialog_cb(self, w):
514
from bzrlib.plugins.gtk.about import AboutDialog
406
self.treeview.set_sensitive(False)
408
dialog = AddTagDialog(self.branch.repository, self.treeview.get_revision().revision_id, self.branch)
409
response = dialog.run()
410
if response != gtk.RESPONSE_NONE:
413
if response == gtk.RESPONSE_OK:
415
self.branch.lock_write()
416
self.branch.tags.set_tag(dialog.tagname, dialog._revid)
423
self.branch.lock_read()
424
self.treeview.emit("revision-selected")
425
self.treeview.set_sensitive(True)
518
427
def _col_visibility_changed(self, col, property):
519
428
self.config.set_user_option(property + '-column-visible', col.get_active())
522
431
def _toolbar_visibility_changed(self, col):
523
432
if col.get_active():
526
435
self.toolbar.hide()
527
self.config.set_user_option('viz-toolbar-visible', col.get_active())
529
def _make_diff_nonzero_size(self):
530
"""make sure the diff isn't zero-width or zero-height"""
531
alloc = self.diff.get_allocation()
532
if (alloc.width < 10) or (alloc.height < 10):
533
width, height = self.get_size()
534
self.revisionview.set_size_request(width/3, int(height / 2.5))
536
def _diff_visibility_changed(self, col):
537
"""Hide or show the diff panel."""
540
self._make_diff_nonzero_size()
543
self.config.set_user_option('viz-show-diffs', str(col.get_active()))
544
self.update_diff_panel()
546
def _diff_placement_changed(self, col):
547
"""Toggle the diff panel's position."""
548
self.config.set_user_option('viz-wide-diffs', str(col.get_active()))
550
old = self.paned.get_child2()
551
self.paned.remove(old)
552
self.paned.pack2(self.construct_bottom(), resize=True, shrink=False)
553
self._make_diff_nonzero_size()
555
self.treeview.emit('revision-selected')
557
def _diff_wrap_changed(self, widget):
558
"""Toggle word wrap in the diff widget."""
559
self.config.set_user_option('viz-wrap-diffs', widget.get_active())
560
self.diff._on_wraplines_toggled(widget)
562
437
def _show_about_cb(self, w):
563
438
dialog = AboutDialog()
564
439
dialog.connect('response', lambda d,r: d.destroy())
567
def _refresh_clicked(self, w):
568
self.treeview.refresh()
570
def _update_tags(self):
573
if self.branch.supports_tags():
574
tags = self.branch.tags.get_tag_dict().items()
577
for tag, revid in tags:
578
tag_image = gtk.Image()
579
tag_image.set_from_file(icon_path('tag-16.png'))
580
tag_item = gtk.ImageMenuItem(tag.replace('_', '__'))
581
tag_item.set_image(tag_image)
582
tag_item.connect('activate', self._tag_selected_cb, revid)
584
self.go_menu_tags.set_submenu(menu)
586
self.go_menu_tags.set_sensitive(len(tags) != 0)
588
self.go_menu_tags.set_sensitive(False)
590
self.go_menu_tags.show_all()
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
601
self._sizes[name] = (width, height)
605
def _on_size_allocate(self, widget, allocation, name):
606
"""When window has been resized, save the new size."""
608
if name in self._sizes:
609
width, height = self._sizes[name]
611
size_changed = (width != allocation.width) or \
612
(height != allocation.height)
615
width, height = allocation.width, allocation.height
616
self._sizes[name] = (width, height)
617
value = '%sx%s' % (width, height)
618
self.config.set_user_option(name, value)
620
def show_diff(self, revid=None, parentid=None):
621
"""Open a new window to show a diff between the given revisions."""
622
from bzrlib.plugins.gtk.diff import DiffWindow
623
window = DiffWindow(parent=self)
626
parentid = NULL_REVISION
628
rev_tree = self.branch.repository.revision_tree(revid)
629
parent_tree = self.branch.repository.revision_tree(parentid)
631
description = revid + " - " + self.branch.nick
632
window.set_diff(description, rev_tree, parent_tree)
635
def update_diff_panel(self, revision=None, parents=None):
636
"""Show the current revision in the diff panel."""
637
if self.config.get_user_option('viz-show-diffs') != 'True':
640
if not revision: # default to selected row
641
revision = self.treeview.get_revision()
642
if (not revision) or (revision == NULL_REVISION):
645
if not parents: # default to selected row's parents
646
parents = self.treeview.get_parents()
647
if len(parents) == 0:
650
parent_id = parents[0]
652
rev_tree = self.branch.repository.revision_tree(revision.revision_id)
653
parent_tree = self.branch.repository.revision_tree(parent_id)
655
self.diff.set_diff(rev_tree, parent_tree)
656
if self.config.get_user_option('viz-wrap-diffs') == 'True':
657
self.diff._on_wraplines_toggled(wrap=True)