2
# -*- coding: UTF-8 -*-
3
5
This module contains the code to manage the branch information window,
4
6
which contains both the revision graph and details panes.
7
__copyright__ = "Copyright (c) 2005 Canonical Ltd."
9
__copyright__ = "Copyright © 2005 Canonical Ltd."
8
10
__author__ = "Scott James Remnant <scott@ubuntu.com>"
11
from gi.repository import Gtk
13
from bzrlib.plugins.gtk import icon_path
14
from bzrlib.plugins.gtk.branchview import TreeView
15
from bzrlib.plugins.gtk.preferences import PreferencesWindow
16
from bzrlib.plugins.gtk.revisionmenu import RevisionMenu
17
from bzrlib.plugins.gtk.window import Window
19
from bzrlib.config import GlobalConfig
20
from bzrlib.revision import NULL_REVISION
21
from bzrlib.trace import mutter
24
class BranchWindow(Window):
17
from bzrlib.osutils import format_date
19
from graph import distances, graph, same_branch
20
from graphcell import CellRendererGraph
23
class BranchWindow(gtk.Window):
27
26
This object represents and manages a single window containing information
28
27
for a particular branch.
31
def __init__(self, branch, start_revs, maxnum, parent=None):
32
"""Create a new BranchWindow.
34
:param branch: Branch object for branch to show.
35
:param start_revs: Revision ids of top revisions.
36
:param maxnum: Maximum number of revisions to display,
40
Window.__init__(self, parent=parent)
30
def __init__(self, app=None):
31
gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
41
32
self.set_border_width(0)
44
self.start_revs = start_revs
46
self.config = GlobalConfig()
48
if self.config.get_user_option('viz-compact-view') == 'yes':
49
self.compact_view = True
51
self.compact_view = False
53
self.set_title(branch._get_nick(local=True) + " - 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)
33
self.set_title("bzrk")
37
# Use three-quarters of the screen by default
38
screen = self.get_screen()
39
monitor = screen.get_monitor_geometry(0)
40
width = int(monitor.width * 0.75)
41
height = int(monitor.height * 0.75)
65
42
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
icon = self.render_icon(Gtk.STOCK_INDEX, Gtk.IconSize.BUTTON)
45
icon = self.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)
71
46
self.set_icon(icon)
73
Gtk.AccelMap.add_entry("<viz>/Go/Next Revision", Gdk.KEY_Up, Gdk.ModifierType.MOD1_MASK)
74
Gtk.AccelMap.add_entry("<viz>/Go/Previous Revision", Gdk.KEY_Down, Gdk.ModifierType.MOD1_MASK)
75
Gtk.AccelMap.add_entry("<viz>/View/Refresh", Gdk.KEY_F5, 0)
77
self.accel_group = Gtk.AccelGroup()
48
self.accel_group = gtk.AccelGroup()
78
49
self.add_accel_group(self.accel_group)
80
if getattr(Gtk.Action, 'set_tool_item_type', None) is not None:
81
Gtk.Action.set_tool_item_type(Gtk.MenuToolButton)
83
self.prev_rev_action = Gtk.Action("prev-rev", "_Previous Revision", "Go to the previous revision", Gtk.STOCK_GO_DOWN)
84
self.prev_rev_action.set_accel_path("<viz>/Go/Previous Revision")
85
self.prev_rev_action.set_accel_group(self.accel_group)
86
self.prev_rev_action.connect("activate", self._back_clicked_cb)
87
self.prev_rev_action.connect_accelerator()
89
self.next_rev_action = Gtk.Action("next-rev", "_Next Revision", "Go to the next revision", Gtk.STOCK_GO_UP)
90
self.next_rev_action.set_accel_path("<viz>/Go/Next Revision")
91
self.next_rev_action.set_accel_group(self.accel_group)
92
self.next_rev_action.connect("activate", self._fwd_clicked_cb)
93
self.next_rev_action.connect_accelerator()
95
self.refresh_action = Gtk.Action("refresh", "_Refresh", "Refresh view", Gtk.STOCK_REFRESH)
96
self.refresh_action.set_accel_path("<viz>/View/Refresh")
97
self.refresh_action.set_accel_group(self.accel_group)
98
self.refresh_action.connect("activate", self._refresh_clicked)
99
self.refresh_action.connect_accelerator()
101
self.vbox = self.construct()
103
def _save_size_on_destroy(self, widget, config_name):
104
"""Creates a hook that saves the size of widget to config option
105
config_name when the window is destroyed/closed."""
107
width, height = widget.allocation.width, widget.allocation.height
108
value = '%sx%s' % (width, height)
109
self.config.set_user_option(config_name, value)
110
self.connect("destroy", save_size)
112
def set_revision(self, revid):
113
self.treeview.set_revision_id(revid)
115
53
def construct(self):
116
54
"""Construct the window contents."""
117
vbox = Gtk.VBox(spacing=0)
55
vbox = gtk.VBox(spacing=0)
120
# order is important here
121
paned = self.construct_paned()
122
nav = self.construct_navigation()
123
menubar = self.construct_menubar()
58
vbox.pack_start(self.construct_navigation(), expand=False, fill=True)
125
vbox.pack_start(menubar, expand=False, fill=True)
126
vbox.pack_start(nav, expand=False, fill=True)
61
paned.pack1(self.construct_top(), resize=True, shrink=False)
62
paned.pack2(self.construct_bottom(), resize=False, shrink=True)
127
64
vbox.pack_start(paned, expand=True, fill=True)
128
65
vbox.set_focus_child(paned)
135
def construct_paned(self):
136
"""Construct the main HPaned/VPaned contents."""
137
if self.config.get_user_option('viz-vertical') == 'True':
138
self.paned = Gtk.HPaned()
140
self.paned = Gtk.VPaned()
142
self.paned.pack1(self.construct_top(), resize=False, shrink=True)
143
self.paned.pack2(self.construct_bottom(), resize=True, shrink=False)
148
def construct_menubar(self):
149
menubar = Gtk.MenuBar()
151
file_menu = Gtk.Menu()
152
file_menuitem = Gtk.MenuItem("_File")
153
file_menuitem.set_submenu(file_menu)
155
file_menu_close = Gtk.ImageMenuItem(Gtk.STOCK_CLOSE, self.accel_group)
156
file_menu_close.connect('activate', lambda x: self.destroy())
158
file_menu_quit = Gtk.ImageMenuItem(Gtk.STOCK_QUIT, self.accel_group)
159
file_menu_quit.connect('activate', lambda x: Gtk.main_quit())
161
if self._parent is not None:
162
file_menu.add(file_menu_close)
163
file_menu.add(file_menu_quit)
165
edit_menu = Gtk.Menu()
166
edit_menuitem = Gtk.MenuItem("_Edit")
167
edit_menuitem.set_submenu(edit_menu)
169
edit_menu_branchopts = Gtk.MenuItem("Branch Settings")
170
edit_menu_branchopts.connect('activate', lambda x: PreferencesWindow(self.branch.get_config()).show())
172
edit_menu_globopts = Gtk.MenuItem("Global Settings")
173
edit_menu_globopts.connect('activate', lambda x: PreferencesWindow().show())
175
edit_menu.add(edit_menu_branchopts)
176
edit_menu.add(edit_menu_globopts)
178
view_menu = Gtk.Menu()
179
view_menuitem = Gtk.MenuItem("_View")
180
view_menuitem.set_submenu(view_menu)
182
view_menu_refresh = self.refresh_action.create_menu_item()
183
view_menu_refresh.connect('activate', self._refresh_clicked)
185
view_menu.add(view_menu_refresh)
186
view_menu.add(Gtk.SeparatorMenuItem())
188
view_menu_toolbar = Gtk.CheckMenuItem("Show Toolbar")
189
view_menu_toolbar.set_active(True)
190
if self.config.get_user_option('viz-toolbar-visible') == 'False':
191
view_menu_toolbar.set_active(False)
193
view_menu_toolbar.connect('toggled', self._toolbar_visibility_changed)
195
view_menu_compact = Gtk.CheckMenuItem("Show Compact Graph")
196
view_menu_compact.set_active(self.compact_view)
197
view_menu_compact.connect('activate', self._brokenlines_toggled_cb)
199
view_menu_vertical = Gtk.CheckMenuItem("Side-by-side Layout")
200
view_menu_vertical.set_active(False)
201
if self.config.get_user_option('viz-vertical') == 'True':
202
view_menu_vertical.set_active(True)
203
view_menu_vertical.connect('toggled', self._vertical_layout)
205
view_menu_diffs = Gtk.CheckMenuItem("Show Diffs")
206
view_menu_diffs.set_active(False)
207
if self.config.get_user_option('viz-show-diffs') == 'True':
208
view_menu_diffs.set_active(True)
209
view_menu_diffs.connect('toggled', self._diff_visibility_changed)
211
view_menu_wide_diffs = Gtk.CheckMenuItem("Wide Diffs")
212
view_menu_wide_diffs.set_active(False)
213
if self.config.get_user_option('viz-wide-diffs') == 'True':
214
view_menu_wide_diffs.set_active(True)
215
view_menu_wide_diffs.connect('toggled', self._diff_placement_changed)
217
view_menu_wrap_diffs = Gtk.CheckMenuItem("Wrap _Long Lines in Diffs")
218
view_menu_wrap_diffs.set_active(False)
219
if self.config.get_user_option('viz-wrap-diffs') == 'True':
220
view_menu_wrap_diffs.set_active(True)
221
view_menu_wrap_diffs.connect('toggled', self._diff_wrap_changed)
223
view_menu.add(view_menu_toolbar)
224
view_menu.add(view_menu_compact)
225
view_menu.add(view_menu_vertical)
226
view_menu.add(Gtk.SeparatorMenuItem())
227
view_menu.add(view_menu_diffs)
228
view_menu.add(view_menu_wide_diffs)
229
view_menu.add(view_menu_wrap_diffs)
230
view_menu.add(Gtk.SeparatorMenuItem())
232
self.mnu_show_revno_column = Gtk.CheckMenuItem("Show Revision _Number Column")
233
self.mnu_show_date_column = Gtk.CheckMenuItem("Show _Date Column")
235
# Revision numbers are pointless if there are multiple branches
236
if len(self.start_revs) > 1:
237
self.mnu_show_revno_column.set_sensitive(False)
238
self.treeview.set_property('revno-column-visible', False)
240
for (col, name) in [(self.mnu_show_revno_column, "revno"),
241
(self.mnu_show_date_column, "date")]:
242
col.set_active(self.treeview.get_property(name + "-column-visible"))
243
col.connect('toggled', self._col_visibility_changed, name)
247
go_menu.set_accel_group(self.accel_group)
248
go_menuitem = Gtk.MenuItem("_Go")
249
go_menuitem.set_submenu(go_menu)
251
go_menu_next = self.next_rev_action.create_menu_item()
252
go_menu_prev = self.prev_rev_action.create_menu_item()
254
tag_image = Gtk.Image()
255
tag_image.set_from_file(icon_path("tag-16.png"))
256
self.go_menu_tags = Gtk.ImageMenuItem("_Tags")
257
self.go_menu_tags.set_image(tag_image)
258
self.treeview.connect('refreshed', lambda w: self._update_tags())
260
go_menu.add(go_menu_next)
261
go_menu.add(go_menu_prev)
262
go_menu.add(Gtk.SeparatorMenuItem())
263
go_menu.add(self.go_menu_tags)
265
self.revision_menu = RevisionMenu(self.branch.repository, [],
266
self.branch, parent=self)
267
revision_menuitem = Gtk.MenuItem("_Revision")
268
revision_menuitem.set_submenu(self.revision_menu)
270
branch_menu = Gtk.Menu()
271
branch_menuitem = Gtk.MenuItem("_Branch")
272
branch_menuitem.set_submenu(branch_menu)
274
branch_menu.add(Gtk.MenuItem("Pu_ll Revisions"))
275
branch_menu.add(Gtk.MenuItem("Pu_sh Revisions"))
278
from bzrlib.plugins import search
280
mutter("Didn't find search plugin")
282
branch_menu.add(Gtk.SeparatorMenuItem())
284
branch_index_menuitem = Gtk.MenuItem("_Index")
285
branch_index_menuitem.connect('activate', self._branch_index_cb)
286
branch_menu.add(branch_index_menuitem)
288
branch_search_menuitem = Gtk.MenuItem("_Search")
289
branch_search_menuitem.connect('activate', self._branch_search_cb)
290
branch_menu.add(branch_search_menuitem)
292
help_menu = Gtk.Menu()
293
help_menuitem = Gtk.MenuItem("_Help")
294
help_menuitem.set_submenu(help_menu)
296
help_about_menuitem = Gtk.ImageMenuItem(Gtk.STOCK_ABOUT,
298
help_about_menuitem.connect('activate', self._about_dialog_cb)
300
help_menu.add(help_about_menuitem)
302
menubar.add(file_menuitem)
303
menubar.add(edit_menuitem)
304
menubar.add(view_menuitem)
305
menubar.add(go_menuitem)
306
menubar.add(revision_menuitem)
307
menubar.add(branch_menuitem)
308
menubar.add(help_menuitem)
313
69
def construct_top(self):
314
70
"""Construct the top-half of the window."""
315
# FIXME: Make broken_line_length configurable
317
self.treeview = TreeView(self.branch, self.start_revs, self.maxnum,
320
for col in ["revno", "date"]:
321
option = self.config.get_user_option(col + '-column-visible')
322
if option is not None:
323
self.treeview.set_property(col + '-column-visible',
326
self.treeview.set_property(col + '-column-visible', False)
71
scrollwin = gtk.ScrolledWindow()
72
scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
73
scrollwin.set_shadow_type(gtk.SHADOW_IN)
76
self.treeview = gtk.TreeView()
77
self.treeview.set_rules_hint(True)
78
self.treeview.set_search_column(4)
79
self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
80
self.treeview.connect("row-activated", self._treeview_row_activated_cb)
81
scrollwin.add(self.treeview)
328
82
self.treeview.show()
330
align = Gtk.Alignment.new(0.0, 0.0, 1.0, 1.0)
331
align.set_padding(5, 0, 0, 0)
332
align.add(self.treeview)
333
# user-configured size
334
size = self._load_size('viz-graph-size')
337
align.set_size_request(width, height)
339
(width, height) = self.get_size()
340
align.set_size_request(width, int(height / 2.5))
341
self._save_size_on_destroy(align, 'viz-graph-size')
84
cell = CellRendererGraph()
85
column = gtk.TreeViewColumn()
86
column.set_resizable(True)
87
column.pack_start(cell, expand=False)
88
column.add_attribute(cell, "node", 1)
89
column.add_attribute(cell, "in-lines", 2)
90
column.add_attribute(cell, "out-lines", 3)
91
self.treeview.append_column(column)
93
cell = gtk.CellRendererText()
94
cell.set_property("width-chars", 40)
95
cell.set_property("ellipsize", pango.ELLIPSIZE_END)
96
column = gtk.TreeViewColumn("Message")
97
column.set_resizable(True)
98
column.pack_start(cell, expand=True)
99
column.add_attribute(cell, "text", 4)
100
self.treeview.append_column(column)
102
cell = gtk.CellRendererText()
103
cell.set_property("width-chars", 40)
104
cell.set_property("ellipsize", pango.ELLIPSIZE_END)
105
column = gtk.TreeViewColumn("Committer")
106
column.set_resizable(True)
107
column.pack_start(cell, expand=True)
108
column.add_attribute(cell, "text", 5)
109
self.treeview.append_column(column)
111
cell = gtk.CellRendererText()
112
cell.set_property("ellipsize", pango.ELLIPSIZE_END)
113
column = gtk.TreeViewColumn("Date")
114
column.set_resizable(True)
115
column.pack_start(cell, expand=True)
116
column.add_attribute(cell, "text", 6)
117
self.treeview.append_column(column)
346
121
def construct_navigation(self):
347
122
"""Construct the navigation buttons."""
348
self.toolbar = Gtk.Toolbar()
349
self.toolbar.set_style(Gtk.TOOLBAR_BOTH_HORIZ)
351
self.prev_button = self.prev_rev_action.create_tool_item()
352
self.toolbar.insert(self.prev_button, -1)
354
self.next_button = self.next_rev_action.create_tool_item()
355
self.toolbar.insert(self.next_button, -1)
357
self.toolbar.insert(Gtk.SeparatorToolItem(), -1)
359
refresh_button = Gtk.ToolButton(Gtk.STOCK_REFRESH)
360
refresh_button.connect('clicked', self._refresh_clicked)
361
self.toolbar.insert(refresh_button, -1)
363
self.toolbar.show_all()
124
frame.set_shadow_type(gtk.SHADOW_OUT)
127
hbox = gtk.HBox(spacing=12)
131
self.back_button = gtk.Button(stock=gtk.STOCK_GO_BACK)
132
self.back_button.set_relief(gtk.RELIEF_NONE)
133
self.back_button.add_accelerator("clicked", self.accel_group, ord('['),
135
self.back_button.connect("clicked", self._back_clicked_cb)
136
hbox.pack_start(self.back_button, expand=False, fill=True)
137
self.back_button.show()
139
self.fwd_button = gtk.Button(stock=gtk.STOCK_GO_FORWARD)
140
self.fwd_button.set_relief(gtk.RELIEF_NONE)
141
self.fwd_button.add_accelerator("clicked", self.accel_group, ord(']'),
143
self.fwd_button.connect("clicked", self._fwd_clicked_cb)
144
hbox.pack_start(self.fwd_button, expand=False, fill=True)
145
self.fwd_button.show()
367
149
def construct_bottom(self):
368
150
"""Construct the bottom half of the window."""
369
if self.config.get_user_option('viz-wide-diffs') == 'True':
370
self.diff_paned = Gtk.VPaned()
372
self.diff_paned = Gtk.HPaned()
151
scrollwin = gtk.ScrolledWindow()
152
scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
153
scrollwin.set_shadow_type(gtk.SHADOW_NONE)
373
154
(width, height) = self.get_size()
374
self.diff_paned.set_size_request(20, 20) # shrinkable
376
from bzrlib.plugins.gtk.revisionview import RevisionView
377
self.revisionview = RevisionView(branch=self.branch)
378
self.revisionview.set_size_request(width/3, int(height / 2.5))
379
# user-configured size
380
size = self._load_size('viz-revisionview-size')
383
self.revisionview.set_size_request(width, height)
384
self._save_size_on_destroy(self.revisionview, 'viz-revisionview-size')
385
self.revisionview.show()
386
self.revisionview.set_show_callback(self._show_clicked_cb)
387
self.revisionview.connect('notify::revision', self._go_clicked_cb)
388
self.treeview.connect('tag-added',
389
lambda w, t, r: self.revisionview.update_tags())
390
self.treeview.connect('revision-selected',
391
self._treeselection_changed_cb)
392
self.treeview.connect('revision-activated',
393
self._tree_revision_activated)
394
self.diff_paned.pack1(self.revisionview)
396
from bzrlib.plugins.gtk.diff import DiffWidget
397
self.diff = DiffWidget()
398
self.diff_paned.pack2(self.diff)
400
self.diff_paned.show_all()
401
if self.config.get_user_option('viz-show-diffs') != 'True':
404
return self.diff_paned
406
def _tag_selected_cb(self, menuitem, revid):
407
self.treeview.set_revision_id(revid)
409
def _treeselection_changed_cb(self, selection, *args):
410
"""callback for when the treeview changes."""
411
revision = self.treeview.get_revision()
412
parents = self.treeview.get_parents()
413
children = self.treeview.get_children()
415
if revision and revision.revision_id != NULL_REVISION:
416
self.revision_menu.set_revision_ids([revision.revision_id])
417
prev_menu = Gtk.Menu()
419
self.prev_rev_action.set_sensitive(True)
420
for parent_id in parents:
421
if parent_id and parent_id != NULL_REVISION:
422
parent = self.branch.repository.get_revision(parent_id)
424
str = ' (%s)' % parent.properties['branch-nick']
428
item = Gtk.MenuItem(parent.message.split("\n")[0] + str)
429
item.connect('activate', self._set_revision_cb, parent_id)
433
self.prev_rev_action.set_sensitive(False)
436
if getattr(self.prev_button, 'set_menu', None) is not None:
437
self.prev_button.set_menu(prev_menu)
439
next_menu = Gtk.Menu()
440
if len(children) > 0:
441
self.next_rev_action.set_sensitive(True)
442
for child_id in children:
443
child = self.branch.repository.get_revision(child_id)
445
str = ' (%s)' % child.properties['branch-nick']
449
item = Gtk.MenuItem(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
155
scrollwin.set_size_request(width, int(height / 2.5))
158
vbox = gtk.VBox(False, spacing=6)
159
vbox.set_border_width(6)
160
scrollwin.add_with_viewport(vbox)
163
table = gtk.Table(rows=4, columns=2)
164
table.set_row_spacings(6)
165
table.set_col_spacings(6)
166
vbox.pack_start(table, expand=False, fill=True)
169
align = gtk.Alignment(0.0, 0.5)
171
label.set_markup("<b>Revision:</b>")
173
table.attach(align, 0, 1, 0, 1, gtk.FILL, gtk.FILL)
177
align = gtk.Alignment(0.0, 0.5)
178
self.revid_label = gtk.Label()
179
self.revid_label.set_selectable(True)
180
align.add(self.revid_label)
181
table.attach(align, 1, 2, 0, 1, gtk.EXPAND | gtk.FILL, gtk.FILL)
182
self.revid_label.show()
185
align = gtk.Alignment(0.0, 0.5)
187
label.set_markup("<b>Committer:</b>")
189
table.attach(align, 0, 1, 1, 2, gtk.FILL, gtk.FILL)
193
align = gtk.Alignment(0.0, 0.5)
194
self.committer_label = gtk.Label()
195
self.committer_label.set_selectable(True)
196
align.add(self.committer_label)
197
table.attach(align, 1, 2, 1, 2, gtk.EXPAND | gtk.FILL, gtk.FILL)
198
self.committer_label.show()
201
align = gtk.Alignment(0.0, 0.5)
203
label.set_markup("<b>Branch nick:</b>")
205
table.attach(align, 0, 1, 2, 3, gtk.FILL, gtk.FILL)
209
align = gtk.Alignment(0.0, 0.5)
210
self.branchnick_label = gtk.Label()
211
self.branchnick_label.set_selectable(True)
212
align.add(self.branchnick_label)
213
table.attach(align, 1, 2, 2, 3, gtk.EXPAND | gtk.FILL, gtk.FILL)
214
self.branchnick_label.show()
217
align = gtk.Alignment(0.0, 0.5)
219
label.set_markup("<b>Timestamp:</b>")
221
table.attach(align, 0, 1, 3, 4, gtk.FILL, gtk.FILL)
225
align = gtk.Alignment(0.0, 0.5)
226
self.timestamp_label = gtk.Label()
227
self.timestamp_label.set_selectable(True)
228
align.add(self.timestamp_label)
229
table.attach(align, 1, 2, 3, 4, gtk.EXPAND | gtk.FILL, gtk.FILL)
230
self.timestamp_label.show()
233
self.parents_table = gtk.Table(rows=1, columns=2)
234
self.parents_table.set_row_spacings(3)
235
self.parents_table.set_col_spacings(6)
236
self.parents_table.show()
237
vbox.pack_start(self.parents_table, expand=False, fill=True)
238
self.parents_widgets = []
241
label.set_markup("<b>Parents:</b>")
242
align = gtk.Alignment(0.0, 0.5)
244
self.parents_table.attach(align, 0, 1, 0, 1, gtk.FILL, gtk.FILL)
248
self.message_buffer = gtk.TextBuffer()
249
textview = gtk.TextView(self.message_buffer)
250
textview.set_editable(False)
251
textview.set_wrap_mode(gtk.WRAP_WORD)
252
textview.modify_font(pango.FontDescription("Monospace"))
253
vbox.pack_start(textview, expand=True, fill=True)
258
def set_branch(self, branch, start, maxnum):
259
"""Set the branch and start position for this window.
261
Creates a new TreeModel and populates it with information about
262
the new branch before updating the window title and model of the
267
# [ revision, node, last_lines, lines, message, committer, timestamp ]
268
self.model = gtk.ListStore(gobject.TYPE_PYOBJECT,
269
gobject.TYPE_PYOBJECT,
270
gobject.TYPE_PYOBJECT,
271
gobject.TYPE_PYOBJECT,
277
(self.revisions, colours, self.children, self.parent_ids,
278
merge_sorted) = distances(branch, start)
279
for (index, (revision, node, lines)) in enumerate(graph(
280
self.revisions, colours, merge_sorted)):
281
# FIXME: at this point we should be able to show the graph order
282
# and lines with no message or commit data - and then incrementally
283
# fill the timestamp, committer etc data as desired.
284
message = revision.message.split("\n")[0]
285
if revision.committer is not None:
286
timestamp = format_date(revision.timestamp, revision.timezone)
289
self.model.append([revision, node, last_lines, lines,
290
message, revision.committer, timestamp])
291
self.index[revision] = index
293
if maxnum is not None and index > maxnum:
296
self.set_title(branch.nick + " - bzrk")
297
self.treeview.set_model(self.model)
299
def _treeview_cursor_cb(self, *args):
300
"""Callback for when the treeview cursor changes."""
301
(path, col) = self.treeview.get_cursor()
302
revision = self.model[path][0]
304
self.back_button.set_sensitive(len(self.parent_ids[revision]) > 0)
305
self.fwd_button.set_sensitive(len(self.children[revision]) > 0)
307
if revision.committer is not None:
309
committer = revision.committer
310
timestamp = format_date(revision.timestamp, revision.timezone)
311
message = revision.message
313
branchnick = revision.properties['branch-nick']
473
parent_id = parents[0]
475
self.show_diff(revision.revision_id, parent_id)
476
self.treeview.grab_focus()
323
self.revid_label.set_text(revision.revision_id)
324
self.branchnick_label.set_text(branchnick)
326
self.committer_label.set_text(committer)
327
self.timestamp_label.set_text(timestamp)
328
self.message_buffer.set_text(message)
330
for widget in self.parents_widgets:
331
self.parents_table.remove(widget)
333
self.parents_widgets = []
334
self.parents_table.resize(max(len(self.parent_ids[revision]), 1), 2)
336
for idx, parent_id in enumerate(self.parent_ids[revision]):
337
align = gtk.Alignment(0.0, 0.0)
338
self.parents_widgets.append(align)
339
self.parents_table.attach(align, 1, 2, idx, idx + 1,
340
gtk.EXPAND | gtk.FILL, gtk.FILL)
343
hbox = gtk.HBox(False, spacing=6)
348
image.set_from_stock(
349
gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_SMALL_TOOLBAR)
352
button = gtk.Button()
354
button.connect("clicked", self._go_clicked_cb, parent_id)
355
hbox.pack_start(button, expand=False, fill=True)
359
image.set_from_stock(
360
gtk.STOCK_FIND, gtk.ICON_SIZE_SMALL_TOOLBAR)
363
button = gtk.Button()
365
button.set_sensitive(self.app is not None)
366
button.connect("clicked", self._show_clicked_cb,
367
revision.revision_id, parent_id)
368
hbox.pack_start(button, expand=False, fill=True)
371
label = gtk.Label(parent_id)
372
label.set_selectable(True)
373
hbox.pack_start(label, expand=False, fill=True)
478
377
def _back_clicked_cb(self, *args):
479
378
"""Callback for when the back button is clicked."""
379
(path, col) = self.treeview.get_cursor()
380
revision = self.model[path][0]
381
if not len(self.parent_ids[revision]):
384
for parent_id in self.parent_ids[revision]:
385
parent = self.revisions[parent_id]
386
if same_branch(revision, parent):
387
self.treeview.set_cursor(self.index[parent])
390
next = self.revisions[self.parent_ids[revision][0]]
391
self.treeview.set_cursor(self.index[next])
392
self.treeview.grab_focus()
482
394
def _fwd_clicked_cb(self, *args):
483
395
"""Callback for when the forward button is clicked."""
484
self.treeview.forward()
486
def _go_clicked_cb(self, w, p):
396
(path, col) = self.treeview.get_cursor()
397
revision = self.model[path][0]
398
if not len(self.children[revision]):
401
for child in self.children[revision]:
402
if same_branch(child, revision):
403
self.treeview.set_cursor(self.index[child])
406
prev = list(self.children[revision])[0]
407
self.treeview.set_cursor(self.index[prev])
408
self.treeview.grab_focus()
410
def _go_clicked_cb(self, widget, revid):
487
411
"""Callback for when the go button for a parent is clicked."""
488
if self.revisionview.get_revision() is not None:
489
self.treeview.set_revision(self.revisionview.get_revision())
412
self.treeview.set_cursor(self.index[self.revisions[revid]])
413
self.treeview.grab_focus()
491
def _show_clicked_cb(self, revid, parentid):
415
def _show_clicked_cb(self, widget, revid, parentid):
492
416
"""Callback for when the show button for a parent is clicked."""
493
self.show_diff(revid, parentid)
494
self.treeview.grab_focus()
496
def _set_revision_cb(self, w, revision_id):
497
self.treeview.set_revision_id(revision_id)
499
def _brokenlines_toggled_cb(self, button):
500
self.compact_view = button.get_active()
502
if self.compact_view:
507
self.config.set_user_option('viz-compact-view', option)
508
self.treeview.set_property('compact', self.compact_view)
509
self.treeview.refresh()
511
def _branch_index_cb(self, w):
512
from bzrlib.plugins.search import index as _mod_index
513
_mod_index.index_url(self.branch.base)
515
def _branch_search_cb(self, w):
516
from bzrlib.plugins.search import (
518
errors as search_errors,
520
from bzrlib.plugins.gtk.search import SearchDialog
523
index = _mod_index.open_index_url(self.branch.base)
524
except search_errors.NoSearchIndex:
525
dialog = Gtk.MessageDialog(self, type=Gtk.MessageType.QUESTION,
526
buttons=Gtk.ButtonsType.OK_CANCEL,
527
message_format="This branch has not been indexed yet. "
529
if dialog.run() == Gtk.ResponseType.OK:
531
index = _mod_index.index_url(self.branch.base)
536
dialog = SearchDialog(index)
538
if dialog.run() == Gtk.ResponseType.OK:
539
self.set_revision(dialog.get_revision())
543
def _about_dialog_cb(self, w):
544
from bzrlib.plugins.gtk.about import AboutDialog
547
def _col_visibility_changed(self, col, property):
548
self.config.set_user_option(property + '-column-visible', col.get_active())
549
self.treeview.set_property(property + '-column-visible', col.get_active())
551
def _toolbar_visibility_changed(self, col):
556
self.config.set_user_option('viz-toolbar-visible', col.get_active())
558
def _vertical_layout(self, col):
559
"""Toggle the layout vertical/horizontal"""
560
self.config.set_user_option('viz-vertical', str(col.get_active()))
563
self.vbox.remove(old)
564
self.vbox.pack_start(self.construct_paned(, True, True, 0), expand=True, fill=True)
565
self._make_diff_paned_nonzero_size()
566
self._make_diff_nonzero_size()
568
self.treeview.emit('revision-selected')
570
def _make_diff_paned_nonzero_size(self):
571
"""make sure the diff/revision pane isn't zero-width or zero-height"""
572
alloc = self.diff_paned.get_allocation()
573
if (alloc.width < 10) or (alloc.height < 10):
574
width, height = self.get_size()
575
self.diff_paned.set_size_request(width/3, int(height / 2.5))
577
def _make_diff_nonzero_size(self):
578
"""make sure the diff isn't zero-width or zero-height"""
579
alloc = self.diff.get_allocation()
580
if (alloc.width < 10) or (alloc.height < 10):
581
width, height = self.get_size()
582
self.revisionview.set_size_request(width/3, int(height / 2.5))
584
def _diff_visibility_changed(self, col):
585
"""Hide or show the diff panel."""
588
self._make_diff_nonzero_size()
591
self.config.set_user_option('viz-show-diffs', str(col.get_active()))
592
self.update_diff_panel()
594
def _diff_placement_changed(self, col):
595
"""Toggle the diff panel's position."""
596
self.config.set_user_option('viz-wide-diffs', str(col.get_active()))
598
old = self.paned.get_child2()
599
self.paned.remove(old)
600
self.paned.pack2(self.construct_bottom(), resize=True, shrink=False)
601
self._make_diff_nonzero_size()
603
self.treeview.emit('revision-selected')
605
def _diff_wrap_changed(self, widget):
606
"""Toggle word wrap in the diff widget."""
607
self.config.set_user_option('viz-wrap-diffs', widget.get_active())
608
self.diff._on_wraplines_toggled(widget)
610
def _refresh_clicked(self, w):
611
self.treeview.refresh()
613
def _update_tags(self):
616
if self.branch.supports_tags():
617
tags = self.branch.tags.get_tag_dict().items()
618
tags.sort(reverse=True)
619
for tag, revid in tags:
620
tag_image = Gtk.Image()
621
tag_image.set_from_file(icon_path('tag-16.png'))
622
tag_item = Gtk.ImageMenuItem(tag.replace('_', '__'))
623
tag_item.set_image(tag_image)
624
tag_item.connect('activate', self._tag_selected_cb, revid)
625
tag_item.set_sensitive(self.treeview.has_revision_id(revid))
627
self.go_menu_tags.set_submenu(menu)
629
self.go_menu_tags.set_sensitive(len(tags) != 0)
631
self.go_menu_tags.set_sensitive(False)
633
self.go_menu_tags.show_all()
635
def _load_size(self, name):
636
"""Read and parse 'name' from self.config.
637
The value is a string, formatted as WIDTHxHEIGHT
638
Returns None, or (width, height)
640
size = self.config.get_user_option(name)
642
width, height = [int(num) for num in size.split('x')]
643
# avoid writing config every time we start
647
def show_diff(self, revid=None, parentid=NULL_REVISION):
648
"""Open a new window to show a diff between the given revisions."""
649
from bzrlib.plugins.gtk.diff import DiffWindow
650
window = DiffWindow(parent=self)
652
rev_tree = self.branch.repository.revision_tree(revid)
653
parent_tree = self.branch.repository.revision_tree(parentid)
655
description = revid + " - " + self.branch._get_nick(local=True)
656
window.set_diff(description, rev_tree, parent_tree)
659
def update_diff_panel(self, revision=None, parents=None):
660
"""Show the current revision in the diff panel."""
661
if self.config.get_user_option('viz-show-diffs') != 'True':
664
if not revision: # default to selected row
665
revision = self.treeview.get_revision()
666
if revision == NULL_REVISION:
669
if not parents: # default to selected row's parents
670
parents = self.treeview.get_parents()
671
if len(parents) == 0:
672
parent_id = NULL_REVISION
674
parent_id = parents[0]
676
rev_tree = self.branch.repository.revision_tree(revision.revision_id)
677
parent_tree = self.branch.repository.revision_tree(parent_id)
679
self.diff.set_diff(rev_tree, parent_tree)
680
if self.config.get_user_option('viz-wrap-diffs') == 'True':
681
self.diff._on_wraplines_toggled(wrap=True)
417
if self.app is not None:
418
self.app.show_diff(self.branch, revid, parentid)
419
self.treeview.grab_focus()
421
def _treeview_row_activated_cb(self, widget, path, col):
422
# TODO: more than one parent
423
"""Callback for when a treeview row gets activated."""
424
revision = self.model[path][0]
425
parent_id = self.parent_ids[revision][0]
426
if self.app is not None:
427
self.app.show_diff(self.branch, revision.revision_id, parent_id)
428
self.treeview.grab_focus()