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>"
11
from gi.repository import Gdk
12
from gi.repository import Gtk
14
from bzrlib.plugins.gtk import icon_path
15
from bzrlib.plugins.gtk.branchview import TreeView
16
from bzrlib.plugins.gtk.preferences import PreferencesWindow
17
from bzrlib.plugins.gtk.revisionmenu import RevisionMenu
18
from bzrlib.plugins.gtk.window import Window
20
from bzrlib.config import GlobalConfig
21
from bzrlib.revision import NULL_REVISION
22
from bzrlib.trace import mutter
25
class BranchWindow(Window):
16
from bzrlib.osutils import format_date
18
from graph import distances, graph, same_branch
19
from graphcell import CellRendererGraph
22
class BranchWindow(gtk.Window):
28
25
This object represents and manages a single window containing information
29
26
for a particular branch.
32
def __init__(self, branch, start_revs, maxnum, parent=None):
33
"""Create a new BranchWindow.
35
:param branch: Branch object for branch to show.
36
:param start_revs: Revision ids of top revisions.
37
:param maxnum: Maximum number of revisions to display,
41
super(BranchWindow, self).__init__(parent=parent)
29
def __init__(self, app=None):
30
gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
42
31
self.set_border_width(0)
45
self.start_revs = start_revs
47
self.config = GlobalConfig()
49
if self.config.get_user_option('viz-compact-view') == 'yes':
50
self.compact_view = True
52
self.compact_view = False
54
self.set_title(branch._get_nick(local=True) + " - revision history")
56
# user-configured window size
57
size = self._load_size('viz-window-size')
61
# Use three-quarters of the screen by default
62
screen = self.get_screen()
63
monitor = screen.get_monitor_geometry(0)
64
width = int(monitor.width * 0.75)
65
height = int(monitor.height * 0.75)
32
self.set_title("bzrk")
36
# Use three-quarters of the screen by default
37
screen = self.get_screen()
38
monitor = screen.get_monitor_geometry(0)
39
width = int(monitor.width * 0.75)
40
height = int(monitor.height * 0.75)
66
41
self.set_default_size(width, height)
67
self.set_size_request(width/3, height/3)
68
self._save_size_on_destroy(self, 'viz-window-size')
71
icon = self.render_icon_pixbuf(Gtk.STOCK_INDEX, Gtk.IconSize.BUTTON)
44
icon = self.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)
72
45
self.set_icon(icon)
74
Gtk.AccelMap.add_entry("<viz>/Go/Next Revision", Gdk.KEY_Up, Gdk.ModifierType.MOD1_MASK)
75
Gtk.AccelMap.add_entry("<viz>/Go/Previous Revision", Gdk.KEY_Down, Gdk.ModifierType.MOD1_MASK)
76
Gtk.AccelMap.add_entry("<viz>/View/Refresh", Gdk.KEY_F5, 0)
78
self.accel_group = Gtk.AccelGroup()
47
self.accel_group = gtk.AccelGroup()
79
48
self.add_accel_group(self.accel_group)
81
self.prev_rev_action = Gtk.Action("prev-rev", "_Previous Revision", "Go to the previous revision", Gtk.STOCK_GO_DOWN)
82
self.prev_rev_action.set_accel_path("<viz>/Go/Previous Revision")
83
self.prev_rev_action.set_accel_group(self.accel_group)
84
self.prev_rev_action.connect("activate", self._back_clicked_cb)
85
self.prev_rev_action.connect_accelerator()
87
self.next_rev_action = Gtk.Action("next-rev", "_Next Revision", "Go to the next revision", Gtk.STOCK_GO_UP)
88
self.next_rev_action.set_accel_path("<viz>/Go/Next Revision")
89
self.next_rev_action.set_accel_group(self.accel_group)
90
self.next_rev_action.connect("activate", self._fwd_clicked_cb)
91
self.next_rev_action.connect_accelerator()
93
self.refresh_action = Gtk.Action("refresh", "_Refresh", "Refresh view", Gtk.STOCK_REFRESH)
94
self.refresh_action.set_accel_path("<viz>/View/Refresh")
95
self.refresh_action.set_accel_group(self.accel_group)
96
self.refresh_action.connect("activate", self._refresh_clicked)
97
self.refresh_action.connect_accelerator()
99
self.vbox = self.construct()
101
def _save_size_on_destroy(self, widget, config_name):
102
"""Creates a hook that saves the size of widget to config option
103
config_name when the window is destroyed/closed."""
105
allocation = widget.get_allocation()
106
width, height = allocation.width, allocation.height
107
value = '%sx%s' % (width, height)
108
self.config.set_user_option(config_name, value)
109
self.connect("destroy", save_size)
111
def set_revision(self, revid):
112
self.treeview.set_revision_id(revid)
114
52
def construct(self):
115
53
"""Construct the window contents."""
116
vbox = Gtk.VBox(spacing=0)
54
vbox = gtk.VBox(spacing=0)
119
# order is important here
120
paned = self.construct_paned()
121
nav = self.construct_navigation()
122
menubar = self.construct_menubar()
57
vbox.pack_start(self.construct_navigation(), expand=False, fill=True)
124
vbox.pack_start(menubar, False, True, 0)
125
vbox.pack_start(nav, False, True, 0)
126
vbox.pack_start(paned, True, True, 0)
60
paned.pack1(self.construct_top(), resize=True, shrink=False)
61
paned.pack2(self.construct_bottom(), resize=False, shrink=True)
63
vbox.pack_start(paned, expand=True, fill=True)
127
64
vbox.set_focus_child(paned)
134
def construct_paned(self):
135
"""Construct the main HPaned/VPaned contents."""
136
if self.config.get_user_option('viz-vertical') == 'True':
137
self.paned = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
139
self.paned = Gtk.Paned.new(Gtk.Orientation.VERTICAL)
141
self.paned.pack1(self.construct_top(), resize=False, shrink=True)
142
self.paned.pack2(self.construct_bottom(), resize=True, shrink=False)
147
def construct_menubar(self):
148
menubar = Gtk.MenuBar()
150
file_menu = Gtk.Menu()
151
file_menuitem = Gtk.MenuItem.new_with_mnemonic("_File")
152
file_menuitem.set_submenu(file_menu)
154
file_menu_close = Gtk.ImageMenuItem.new_from_stock(
155
Gtk.STOCK_CLOSE, self.accel_group)
156
file_menu_close.connect('activate', lambda x: self.destroy())
158
file_menu_quit = Gtk.ImageMenuItem.new_from_stock(
159
Gtk.STOCK_QUIT, self.accel_group)
160
file_menu_quit.connect('activate', lambda x: Gtk.main_quit())
162
if self._parent is not None:
163
file_menu.add(file_menu_close)
164
file_menu.add(file_menu_quit)
166
edit_menu = Gtk.Menu()
167
edit_menuitem = Gtk.MenuItem.new_with_mnemonic("_Edit")
168
edit_menuitem.set_submenu(edit_menu)
170
edit_menu_branchopts = Gtk.MenuItem(label="Branch Settings")
171
edit_menu_branchopts.connect('activate', lambda x: PreferencesWindow(self.branch.get_config()).show())
173
edit_menu_globopts = Gtk.MenuItem(label="Global Settings")
174
edit_menu_globopts.connect('activate', lambda x: PreferencesWindow().show())
176
edit_menu.add(edit_menu_branchopts)
177
edit_menu.add(edit_menu_globopts)
179
view_menu = Gtk.Menu()
180
view_menuitem = Gtk.MenuItem.new_with_mnemonic("_View")
181
view_menuitem.set_submenu(view_menu)
183
view_menu_refresh = self.refresh_action.create_menu_item()
184
view_menu_refresh.connect('activate', self._refresh_clicked)
186
view_menu.add(view_menu_refresh)
187
view_menu.add(Gtk.SeparatorMenuItem())
189
view_menu_toolbar = Gtk.CheckMenuItem(label="Show Toolbar")
190
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
view_menu_toolbar.connect('toggled', self._toolbar_visibility_changed)
196
view_menu_compact = Gtk.CheckMenuItem(label="Show Compact Graph")
197
view_menu_compact.set_active(self.compact_view)
198
view_menu_compact.connect('activate', self._brokenlines_toggled_cb)
200
view_menu_vertical = Gtk.CheckMenuItem(label="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(label="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(label="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.new_with_mnemonic(
219
"Wrap _Long Lines in Diffs")
220
view_menu_wrap_diffs.set_active(False)
221
if self.config.get_user_option('viz-wrap-diffs') == 'True':
222
view_menu_wrap_diffs.set_active(True)
223
view_menu_wrap_diffs.connect('toggled', self._diff_wrap_changed)
225
view_menu.add(view_menu_toolbar)
226
view_menu.add(view_menu_compact)
227
view_menu.add(view_menu_vertical)
228
view_menu.add(Gtk.SeparatorMenuItem())
229
view_menu.add(view_menu_diffs)
230
view_menu.add(view_menu_wide_diffs)
231
view_menu.add(view_menu_wrap_diffs)
232
view_menu.add(Gtk.SeparatorMenuItem())
234
self.mnu_show_revno_column = Gtk.CheckMenuItem.new_with_mnemonic(
235
"Show Revision _Number Column")
236
self.mnu_show_date_column = Gtk.CheckMenuItem.new_with_mnemonic(
239
# Revision numbers are pointless if there are multiple branches
240
if len(self.start_revs) > 1:
241
self.mnu_show_revno_column.set_sensitive(False)
242
self.treeview.set_property('revno-column-visible', False)
244
for (col, name) in [(self.mnu_show_revno_column, "revno"),
245
(self.mnu_show_date_column, "date")]:
246
col.set_active(self.treeview.get_property(name + "-column-visible"))
247
col.connect('toggled', self._col_visibility_changed, name)
251
go_menu.set_accel_group(self.accel_group)
252
go_menuitem = Gtk.MenuItem.new_with_mnemonic("_Go")
253
go_menuitem.set_submenu(go_menu)
255
go_menu_next = self.next_rev_action.create_menu_item()
256
go_menu_prev = self.prev_rev_action.create_menu_item()
258
tag_image = Gtk.Image()
259
tag_image.set_from_file(icon_path("tag-16.png"))
260
self.go_menu_tags = Gtk.ImageMenuItem.new_with_mnemonic("_Tags")
261
self.go_menu_tags.set_image(tag_image)
262
self.treeview.connect('refreshed', lambda w: self._update_tags())
264
go_menu.add(go_menu_next)
265
go_menu.add(go_menu_prev)
266
go_menu.add(Gtk.SeparatorMenuItem())
267
go_menu.add(self.go_menu_tags)
269
self.revision_menu = RevisionMenu(self.branch.repository, [],
270
self.branch, parent=self)
271
revision_menuitem = Gtk.MenuItem.new_with_mnemonic("_Revision")
272
revision_menuitem.set_submenu(self.revision_menu)
274
branch_menu = Gtk.Menu()
275
branch_menuitem = Gtk.MenuItem.new_with_mnemonic("_Branch")
276
branch_menuitem.set_submenu(branch_menu)
278
branch_menu.add(Gtk.MenuItem.new_with_mnemonic("Pu_ll Revisions"))
279
branch_menu.add(Gtk.MenuItem.new_with_mnemonic("Pu_sh Revisions"))
282
from bzrlib.plugins import search
284
mutter("Didn't find search plugin")
286
branch_menu.add(Gtk.SeparatorMenuItem())
288
branch_index_menuitem = Gtk.MenuItem.new_with_mnemonic("_Index")
289
branch_index_menuitem.connect('activate', self._branch_index_cb)
290
branch_menu.add(branch_index_menuitem)
292
branch_search_menuitem = Gtk.MenuItem.new_with_mnemonic("_Search")
293
branch_search_menuitem.connect('activate', self._branch_search_cb)
294
branch_menu.add(branch_search_menuitem)
296
help_menu = Gtk.Menu()
297
help_menuitem = Gtk.MenuItem.new_with_mnemonic("_Help")
298
help_menuitem.set_submenu(help_menu)
300
help_about_menuitem = Gtk.ImageMenuItem.new_from_stock(
301
Gtk.STOCK_ABOUT, self.accel_group)
302
help_about_menuitem.connect('activate', self._about_dialog_cb)
304
help_menu.add(help_about_menuitem)
306
menubar.add(file_menuitem)
307
menubar.add(edit_menuitem)
308
menubar.add(view_menuitem)
309
menubar.add(go_menuitem)
310
menubar.add(revision_menuitem)
311
menubar.add(branch_menuitem)
312
menubar.add(help_menuitem)
317
68
def construct_top(self):
318
69
"""Construct the top-half of the window."""
319
# FIXME: Make broken_line_length configurable
321
self.treeview = TreeView(self.branch, self.start_revs, self.maxnum,
324
for col in ["revno", "date"]:
325
option = self.config.get_user_option(col + '-column-visible')
326
if option is not None:
327
self.treeview.set_property(col + '-column-visible',
330
self.treeview.set_property(col + '-column-visible', False)
70
scrollwin = gtk.ScrolledWindow()
71
scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
72
scrollwin.set_shadow_type(gtk.SHADOW_IN)
75
self.treeview = gtk.TreeView()
76
self.treeview.set_rules_hint(True)
77
self.treeview.set_search_column(4)
78
self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
79
self.treeview.connect("row-activated", self._treeview_row_activated_cb)
80
scrollwin.add(self.treeview)
332
81
self.treeview.show()
334
align = Gtk.Alignment.new(0.0, 0.0, 1.0, 1.0)
335
align.set_padding(5, 0, 0, 0)
336
align.add(self.treeview)
337
# user-configured size
338
size = self._load_size('viz-graph-size')
339
self._save_size_on_destroy(align, 'viz-graph-size')
83
cell = CellRendererGraph()
84
column = gtk.TreeViewColumn()
85
column.set_resizable(True)
86
column.pack_start(cell, expand=False)
87
column.add_attribute(cell, "node", 1)
88
column.add_attribute(cell, "in-lines", 2)
89
column.add_attribute(cell, "out-lines", 3)
90
self.treeview.append_column(column)
92
cell = gtk.CellRendererText()
93
cell.set_property("width-chars", 40)
94
cell.set_property("ellipsize", pango.ELLIPSIZE_END)
95
column = gtk.TreeViewColumn("Message")
96
column.set_resizable(True)
97
column.pack_start(cell, expand=True)
98
column.add_attribute(cell, "text", 4)
99
self.treeview.append_column(column)
101
cell = gtk.CellRendererText()
102
cell.set_property("width-chars", 40)
103
cell.set_property("ellipsize", pango.ELLIPSIZE_END)
104
column = gtk.TreeViewColumn("Committer")
105
column.set_resizable(True)
106
column.pack_start(cell, expand=True)
107
column.add_attribute(cell, "text", 5)
108
self.treeview.append_column(column)
110
cell = gtk.CellRendererText()
111
cell.set_property("ellipsize", pango.ELLIPSIZE_END)
112
column = gtk.TreeViewColumn("Date")
113
column.set_resizable(True)
114
column.pack_start(cell, expand=True)
115
column.add_attribute(cell, "text", 6)
116
self.treeview.append_column(column)
344
120
def construct_navigation(self):
345
121
"""Construct the navigation buttons."""
346
self.toolbar = Gtk.Toolbar()
347
self.toolbar.set_style(Gtk.ToolbarStyle.BOTH_HORIZ)
349
self.prev_button = self.prev_rev_action.create_tool_item()
350
self.toolbar.insert(self.prev_button, -1)
352
self.next_button = self.next_rev_action.create_tool_item()
353
self.toolbar.insert(self.next_button, -1)
355
self.toolbar.insert(Gtk.SeparatorToolItem(), -1)
357
refresh_button = Gtk.ToolButton.new_from_stock(Gtk.STOCK_REFRESH)
358
refresh_button.connect('clicked', self._refresh_clicked)
359
self.toolbar.insert(refresh_button, -1)
361
self.toolbar.show_all()
123
frame.set_shadow_type(gtk.SHADOW_OUT)
126
hbox = gtk.HBox(spacing=12)
130
self.back_button = gtk.Button(stock=gtk.STOCK_GO_BACK)
131
self.back_button.set_relief(gtk.RELIEF_NONE)
132
self.back_button.add_accelerator("clicked", self.accel_group, ord('['),
134
self.back_button.connect("clicked", self._back_clicked_cb)
135
hbox.pack_start(self.back_button, expand=False, fill=True)
136
self.back_button.show()
138
self.fwd_button = gtk.Button(stock=gtk.STOCK_GO_FORWARD)
139
self.fwd_button.set_relief(gtk.RELIEF_NONE)
140
self.fwd_button.add_accelerator("clicked", self.accel_group, ord(']'),
142
self.fwd_button.connect("clicked", self._fwd_clicked_cb)
143
hbox.pack_start(self.fwd_button, expand=False, fill=True)
144
self.fwd_button.show()
365
148
def construct_bottom(self):
366
149
"""Construct the bottom half of the window."""
367
if self.config.get_user_option('viz-wide-diffs') == 'True':
368
self.diff_paned = Gtk.Paned.new(Gtk.Orientation.VERTICAL)
370
self.diff_paned = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
150
scrollwin = gtk.ScrolledWindow()
151
scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
152
scrollwin.set_shadow_type(gtk.SHADOW_NONE)
371
153
(width, height) = self.get_size()
372
self.diff_paned.set_size_request(20, 20) # shrinkable
374
from bzrlib.plugins.gtk.revisionview import RevisionView
375
self.revisionview = RevisionView(branch=self.branch)
376
self.revisionview.set_size_request(width/3, int(height / 2.5))
377
# user-configured size
378
size = self._load_size('viz-revisionview-size')
381
self.revisionview.set_size_request(width, height)
382
self._save_size_on_destroy(self.revisionview, 'viz-revisionview-size')
383
self.revisionview.show()
384
self.revisionview.set_show_callback(self._show_clicked_cb)
385
self.revisionview.connect('notify::revision', self._go_clicked_cb)
386
self.treeview.connect('tag-added',
387
lambda w, t, r: self.revisionview.update_tags())
388
self.treeview.connect('revision-selected',
389
self._treeselection_changed_cb)
390
self.treeview.connect('revision-activated',
391
self._tree_revision_activated)
392
self.diff_paned.pack1(self.revisionview)
394
from bzrlib.plugins.gtk.diff import DiffWidget
395
self.diff = DiffWidget()
396
self.diff_paned.pack2(self.diff)
398
self.diff_paned.show_all()
399
if self.config.get_user_option('viz-show-diffs') != 'True':
402
return self.diff_paned
404
def _tag_selected_cb(self, menuitem, revid):
405
self.treeview.set_revision_id(revid)
407
def _treeselection_changed_cb(self, selection, *args):
408
"""callback for when the treeview changes."""
409
revision = self.treeview.get_revision()
410
parents = self.treeview.get_parents()
411
children = self.treeview.get_children()
413
if revision and revision.revision_id != NULL_REVISION:
414
self.revision_menu.set_revision_ids([revision.revision_id])
415
prev_menu = Gtk.Menu()
417
self.prev_rev_action.set_sensitive(True)
418
for parent_id in parents:
419
if parent_id and parent_id != NULL_REVISION:
420
parent = self.branch.repository.get_revision(parent_id)
422
str = ' (%s)' % parent.properties['branch-nick']
427
label=parent.message.split("\n")[0] + str)
428
item.connect('activate', self._set_revision_cb, parent_id)
432
self.prev_rev_action.set_sensitive(False)
435
if getattr(self.prev_button, 'set_menu', None) is not None:
436
self.prev_button.set_menu(prev_menu)
438
next_menu = Gtk.Menu()
439
if len(children) > 0:
440
self.next_rev_action.set_sensitive(True)
441
for child_id in children:
442
child = self.branch.repository.get_revision(child_id)
444
str = ' (%s)' % child.properties['branch-nick']
449
label=child.message.split("\n")[0] + str)
450
item.connect('activate', self._set_revision_cb, child_id)
454
self.next_rev_action.set_sensitive(False)
457
if getattr(self.next_button, 'set_menu', None) is not None:
458
self.next_button.set_menu(next_menu)
460
self.revisionview.set_revision(revision)
461
self.revisionview.set_children(children)
462
self.update_diff_panel(revision, parents)
464
def _tree_revision_activated(self, widget, path, col):
465
# TODO: more than one parent
466
"""Callback for when a treeview row gets activated."""
467
revision = self.treeview.get_revision()
468
parents = self.treeview.get_parents()
470
if len(parents) == 0:
471
parent_id = NULL_REVISION
473
parent_id = parents[0]
475
if revision is not None:
476
self.show_diff(revision.revision_id, parent_id)
478
self.show_diff(NULL_REVISION)
479
self.treeview.grab_focus()
154
scrollwin.set_size_request(width, int(height / 2.5))
157
vbox = gtk.VBox(False, spacing=6)
158
vbox.set_border_width(6)
159
scrollwin.add_with_viewport(vbox)
162
table = gtk.Table(rows=4, columns=2)
163
table.set_row_spacings(6)
164
table.set_col_spacings(6)
165
vbox.pack_start(table, expand=False, fill=True)
168
align = gtk.Alignment(0.0, 0.5)
170
label.set_markup("<b>Revision:</b>")
172
table.attach(align, 0, 1, 0, 1, gtk.FILL, gtk.FILL)
176
align = gtk.Alignment(0.0, 0.5)
177
self.revid_label = gtk.Label()
178
self.revid_label.set_selectable(True)
179
align.add(self.revid_label)
180
table.attach(align, 1, 2, 0, 1, gtk.EXPAND | gtk.FILL, gtk.FILL)
181
self.revid_label.show()
184
align = gtk.Alignment(0.0, 0.5)
186
label.set_markup("<b>Committer:</b>")
188
table.attach(align, 0, 1, 1, 2, gtk.FILL, gtk.FILL)
192
align = gtk.Alignment(0.0, 0.5)
193
self.committer_label = gtk.Label()
194
self.committer_label.set_selectable(True)
195
align.add(self.committer_label)
196
table.attach(align, 1, 2, 1, 2, gtk.EXPAND | gtk.FILL, gtk.FILL)
197
self.committer_label.show()
200
align = gtk.Alignment(0.0, 0.5)
202
label.set_markup("<b>Branch nick:</b>")
204
table.attach(align, 0, 1, 2, 3, gtk.FILL, gtk.FILL)
208
align = gtk.Alignment(0.0, 0.5)
209
self.branchnick_label = gtk.Label()
210
self.branchnick_label.set_selectable(True)
211
align.add(self.branchnick_label)
212
table.attach(align, 1, 2, 2, 3, gtk.EXPAND | gtk.FILL, gtk.FILL)
213
self.branchnick_label.show()
216
align = gtk.Alignment(0.0, 0.5)
218
label.set_markup("<b>Timestamp:</b>")
220
table.attach(align, 0, 1, 3, 4, gtk.FILL, gtk.FILL)
224
align = gtk.Alignment(0.0, 0.5)
225
self.timestamp_label = gtk.Label()
226
self.timestamp_label.set_selectable(True)
227
align.add(self.timestamp_label)
228
table.attach(align, 1, 2, 3, 4, gtk.EXPAND | gtk.FILL, gtk.FILL)
229
self.timestamp_label.show()
232
self.parents_table = gtk.Table(rows=1, columns=2)
233
self.parents_table.set_row_spacings(3)
234
self.parents_table.set_col_spacings(6)
235
self.parents_table.show()
236
vbox.pack_start(self.parents_table, expand=False, fill=True)
237
self.parents_widgets = []
240
label.set_markup("<b>Parents:</b>")
241
align = gtk.Alignment(0.0, 0.5)
243
self.parents_table.attach(align, 0, 1, 0, 1, gtk.FILL, gtk.FILL)
247
self.message_buffer = gtk.TextBuffer()
248
textview = gtk.TextView(self.message_buffer)
249
textview.set_editable(False)
250
textview.set_wrap_mode(gtk.WRAP_WORD)
251
textview.modify_font(pango.FontDescription("Monospace"))
252
vbox.pack_start(textview, expand=True, fill=True)
257
def set_branch(self, branch, start, maxnum):
258
"""Set the branch and start position for this window.
260
Creates a new TreeModel and populates it with information about
261
the new branch before updating the window title and model of the
266
# [ revision, node, last_lines, lines, message, committer, timestamp ]
267
self.model = gtk.ListStore(gobject.TYPE_PYOBJECT,
268
gobject.TYPE_PYOBJECT,
269
gobject.TYPE_PYOBJECT,
270
gobject.TYPE_PYOBJECT,
276
(self.revisions, colours, self.children, self.parent_ids,
277
merge_sorted) = distances(branch, start)
278
for (index, (revision, node, lines)) in enumerate(graph(
279
self.revisions, colours, merge_sorted)):
280
# FIXME: at this point we should be able to show the graph order
281
# and lines with no message or commit data - and then incrementally
282
# fill the timestamp, committer etc data as desired.
283
message = revision.message.split("\n")[0]
284
if revision.committer is not None:
285
timestamp = format_date(revision.timestamp, revision.timezone)
288
self.model.append([revision, node, last_lines, lines,
289
message, revision.committer, timestamp])
290
self.index[revision] = index
292
if maxnum is not None and index > maxnum:
295
self.set_title(branch.nick + " - bzrk")
296
self.treeview.set_model(self.model)
298
def _treeview_cursor_cb(self, *args):
299
"""Callback for when the treeview cursor changes."""
300
(path, col) = self.treeview.get_cursor()
301
revision = self.model[path][0]
303
self.back_button.set_sensitive(len(self.parent_ids[revision]) > 0)
304
self.fwd_button.set_sensitive(len(self.children[revision]) > 0)
306
if revision.committer is not None:
308
committer = revision.committer
309
timestamp = format_date(revision.timestamp, revision.timezone)
310
message = revision.message
312
branchnick = revision.properties['branch-nick']
322
self.revid_label.set_text(revision.revision_id)
323
self.branchnick_label.set_text(branchnick)
325
self.committer_label.set_text(committer)
326
self.timestamp_label.set_text(timestamp)
327
self.message_buffer.set_text(message)
329
for widget in self.parents_widgets:
330
self.parents_table.remove(widget)
332
self.parents_widgets = []
333
self.parents_table.resize(max(len(self.parent_ids[revision]), 1), 2)
335
for idx, parent_id in enumerate(self.parent_ids[revision]):
336
align = gtk.Alignment(0.0, 0.0)
337
self.parents_widgets.append(align)
338
self.parents_table.attach(align, 1, 2, idx, idx + 1,
339
gtk.EXPAND | gtk.FILL, gtk.FILL)
342
hbox = gtk.HBox(False, spacing=6)
347
image.set_from_stock(
348
gtk.STOCK_FIND, gtk.ICON_SIZE_SMALL_TOOLBAR)
351
button = gtk.Button()
353
button.set_sensitive(self.app is not None)
354
button.connect("clicked", self._show_clicked_cb,
355
revision.revision_id, parent_id)
356
hbox.pack_start(button, expand=False, fill=True)
359
button = gtk.Button(parent_id)
360
button.set_use_underline(False)
361
button.connect("clicked", self._go_clicked_cb, parent_id)
362
hbox.pack_start(button, expand=False, fill=True)
481
366
def _back_clicked_cb(self, *args):
482
367
"""Callback for when the back button is clicked."""
368
(path, col) = self.treeview.get_cursor()
369
revision = self.model[path][0]
370
if not len(self.parent_ids[revision]):
373
for parent_id in self.parent_ids[revision]:
374
parent = self.revisions[parent_id]
375
if same_branch(revision, parent):
376
self.treeview.set_cursor(self.index[parent])
379
next = self.revisions[self.parent_ids[revision][0]]
380
self.treeview.set_cursor(self.index[next])
381
self.treeview.grab_focus()
485
383
def _fwd_clicked_cb(self, *args):
486
384
"""Callback for when the forward button is clicked."""
487
self.treeview.forward()
489
def _go_clicked_cb(self, w, p):
385
(path, col) = self.treeview.get_cursor()
386
revision = self.model[path][0]
387
if not len(self.children[revision]):
390
for child in self.children[revision]:
391
if same_branch(child, revision):
392
self.treeview.set_cursor(self.index[child])
395
prev = list(self.children[revision])[0]
396
self.treeview.set_cursor(self.index[prev])
397
self.treeview.grab_focus()
399
def _go_clicked_cb(self, widget, revid):
490
400
"""Callback for when the go button for a parent is clicked."""
491
if self.revisionview.get_revision() is not None:
492
self.treeview.set_revision(self.revisionview.get_revision())
401
self.treeview.set_cursor(self.index[self.revisions[revid]])
402
self.treeview.grab_focus()
494
def _show_clicked_cb(self, revid, parentid):
404
def _show_clicked_cb(self, widget, revid, parentid):
495
405
"""Callback for when the show button for a parent is clicked."""
496
self.show_diff(revid, parentid)
497
self.treeview.grab_focus()
499
def _set_revision_cb(self, w, revision_id):
500
self.treeview.set_revision_id(revision_id)
502
def _brokenlines_toggled_cb(self, button):
503
self.compact_view = button.get_active()
505
if self.compact_view:
510
self.config.set_user_option('viz-compact-view', option)
511
self.treeview.set_property('compact', self.compact_view)
512
self.treeview.refresh()
514
def _branch_index_cb(self, w):
515
from bzrlib.plugins.search import index as _mod_index
516
_mod_index.index_url(self.branch.base)
518
def _branch_search_cb(self, w):
519
from bzrlib.plugins.search import (
521
errors as search_errors,
523
from bzrlib.plugins.gtk.search import SearchDialog
526
index = _mod_index.open_index_url(self.branch.base)
527
except search_errors.NoSearchIndex:
528
dialog = Gtk.MessageDialog(self, type=Gtk.MessageType.QUESTION,
529
buttons=Gtk.ButtonsType.OK_CANCEL,
530
message_format="This branch has not been indexed yet. "
532
if dialog.run() == Gtk.ResponseType.OK:
534
index = _mod_index.index_url(self.branch.base)
539
dialog = SearchDialog(index)
541
if dialog.run() == Gtk.ResponseType.OK:
542
revid = dialog.get_revision()
543
if revid is not None:
544
self.set_revision(revid)
548
def _about_dialog_cb(self, w):
549
from bzrlib.plugins.gtk.about import AboutDialog
552
def _col_visibility_changed(self, col, property):
553
self.config.set_user_option(property + '-column-visible', col.get_active())
554
self.treeview.set_property(property + '-column-visible', col.get_active())
556
def _toolbar_visibility_changed(self, col):
561
self.config.set_user_option('viz-toolbar-visible', col.get_active())
563
def _vertical_layout(self, col):
564
"""Toggle the layout vertical/horizontal"""
565
self.config.set_user_option('viz-vertical', str(col.get_active()))
568
self.vbox.remove(old)
569
self.vbox.pack_start(
570
self.construct_paned(), True, True, 0)
571
self._make_diff_paned_nonzero_size()
572
self._make_diff_nonzero_size()
574
self.treeview.emit('revision-selected')
576
def _make_diff_paned_nonzero_size(self):
577
"""make sure the diff/revision pane isn't zero-width or zero-height"""
578
alloc = self.diff_paned.get_allocation()
579
if (alloc.width < 10) or (alloc.height < 10):
580
width, height = self.get_size()
581
self.diff_paned.set_size_request(width/3, int(height / 2.5))
583
def _make_diff_nonzero_size(self):
584
"""make sure the diff isn't zero-width or zero-height"""
585
alloc = self.diff.get_allocation()
586
if (alloc.width < 10) or (alloc.height < 10):
587
width, height = self.get_size()
588
self.revisionview.set_size_request(width/3, int(height / 2.5))
590
def _diff_visibility_changed(self, col):
591
"""Hide or show the diff panel."""
594
self._make_diff_nonzero_size()
597
self.config.set_user_option('viz-show-diffs', str(col.get_active()))
598
self.update_diff_panel()
600
def _diff_placement_changed(self, col):
601
"""Toggle the diff panel's position."""
602
self.config.set_user_option('viz-wide-diffs', str(col.get_active()))
604
old = self.paned.get_child2()
605
self.paned.remove(old)
606
self.paned.pack2(self.construct_bottom(), resize=True, shrink=False)
607
self._make_diff_nonzero_size()
609
self.treeview.emit('revision-selected')
611
def _diff_wrap_changed(self, widget):
612
"""Toggle word wrap in the diff widget."""
613
self.config.set_user_option('viz-wrap-diffs', widget.get_active())
614
self.diff._on_wraplines_toggled(widget)
616
def _refresh_clicked(self, w):
617
self.treeview.refresh()
619
def _update_tags(self):
622
if self.branch.supports_tags():
623
tags = self.branch.tags.get_tag_dict().items()
624
tags.sort(reverse=True)
625
for tag, revid in tags:
626
tag_image = Gtk.Image()
627
tag_image.set_from_file(icon_path('tag-16.png'))
628
tag_item = Gtk.ImageMenuItem.new_with_mnemonic(
629
tag.replace('_', '__'))
630
tag_item.set_image(tag_image)
631
tag_item.connect('activate', self._tag_selected_cb, revid)
632
tag_item.set_sensitive(self.treeview.has_revision_id(revid))
634
self.go_menu_tags.set_submenu(menu)
636
self.go_menu_tags.set_sensitive(len(tags) != 0)
638
self.go_menu_tags.set_sensitive(False)
640
self.go_menu_tags.show_all()
642
def _load_size(self, name):
643
"""Read and parse 'name' from self.config.
644
The value is a string, formatted as WIDTHxHEIGHT
645
Returns None, or (width, height)
647
size = self.config.get_user_option(name)
649
width, height = [int(num) for num in size.split('x')]
650
# avoid writing config every time we start
654
def show_diff(self, revid, parentid=NULL_REVISION):
655
"""Open a new window to show a diff between the given revisions."""
656
from bzrlib.plugins.gtk.diff import DiffWindow
657
window = DiffWindow(parent=self)
659
rev_tree = self.branch.repository.revision_tree(revid)
660
parent_tree = self.branch.repository.revision_tree(parentid)
662
description = revid + " - " + self.branch._get_nick(local=True)
663
window.set_diff(description, rev_tree, parent_tree)
666
def update_diff_panel(self, revision=None, parents=None):
667
"""Show the current revision in the diff panel."""
668
if self.config.get_user_option('viz-show-diffs') != 'True':
671
if not revision: # default to selected row
672
revision = self.treeview.get_revision()
673
if revision == NULL_REVISION:
676
if not parents: # default to selected row's parents
677
parents = self.treeview.get_parents()
678
if len(parents) == 0:
679
parent_id = NULL_REVISION
681
parent_id = parents[0]
683
rev_tree = self.branch.repository.revision_tree(revision.revision_id)
684
parent_tree = self.branch.repository.revision_tree(parent_id)
686
self.diff.set_diff(rev_tree, parent_tree)
687
if self.config.get_user_option('viz-wrap-diffs') == 'True':
688
self.diff._on_wraplines_toggled(wrap=True)
406
if self.app is not None:
407
self.app.show_diff(self.branch, revid, parentid)
408
self.treeview.grab_focus()
410
def _treeview_row_activated_cb(self, widget, path, col):
411
# TODO: more than one parent
412
"""Callback for when a treeview row gets activated."""
413
revision = self.model[path][0]
414
if len(self.parent_ids[revision]) == 0:
415
# Ignore revisions without parent
417
parent_id = self.parent_ids[revision][0]
418
if self.app is not None:
419
self.app.show_diff(self.branch, revision.revision_id, parent_id)
420
self.treeview.grab_focus()