/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz

« back to all changes in this revision

Viewing changes to viz/branchwin.py

  • Committer: Jelmer Vernooij
  • Date: 2007-03-09 17:47:28 UTC
  • Revision ID: jelmer@samba.org-20070309174728-gljlmt9b7fu0rrn9
Add simple test for tortoise_bzr

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: UTF-8 -*-
1
2
"""Branch window.
2
3
 
3
4
This module contains the code to manage the branch information window,
4
5
which contains both the revision graph and details panes.
5
6
"""
6
7
 
7
 
__copyright__ = "Copyright (c) 2005 Canonical Ltd."
 
8
__copyright__ = "Copyright © 2005 Canonical Ltd."
8
9
__author__    = "Scott James Remnant <scott@ubuntu.com>"
9
10
 
10
11
 
11
12
import gtk
12
 
 
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
18
 
 
19
 
from bzrlib.config import GlobalConfig
20
 
from bzrlib.revision import NULL_REVISION
21
 
from bzrlib.trace import mutter
22
 
 
23
 
class BranchWindow(Window):
 
13
import gobject
 
14
import pango
 
15
 
 
16
from bzrlib.osutils import format_date
 
17
 
 
18
from graph import distances, graph, same_branch
 
19
from graphcell import CellRendererGraph
 
20
 
 
21
 
 
22
class BranchWindow(gtk.Window):
24
23
    """Branch window.
25
24
 
26
25
    This object represents and manages a single window containing information
27
26
    for a particular branch.
28
27
    """
29
28
 
30
 
    def __init__(self, branch, start_revs, maxnum, parent=None):
31
 
        """Create a new BranchWindow.
32
 
 
33
 
        :param branch: Branch object for branch to show.
34
 
        :param start_revs: Revision ids of top revisions.
35
 
        :param maxnum: Maximum number of revisions to display, 
36
 
                       None for no limit.
37
 
        """
38
 
 
39
 
        Window.__init__(self, parent=parent)
 
29
    def __init__(self):
 
30
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
40
31
        self.set_border_width(0)
41
 
 
42
 
        self.branch      = branch
43
 
        self.start_revs  = start_revs
44
 
        self.maxnum      = maxnum
45
 
        self.config      = GlobalConfig()
46
 
 
47
 
        if self.config.get_user_option('viz-compact-view') == 'yes':
48
 
            self.compact_view = True
49
 
        else:
50
 
            self.compact_view = False
51
 
 
52
 
        self.set_title(branch._get_nick(local=True) + " - revision history")
53
 
 
54
 
        # user-configured window size
55
 
        size = self._load_size('viz-window-size')
56
 
        if size:
57
 
            width, height = size
58
 
        else:
59
 
            # Use three-quarters of the screen by default
60
 
            screen = self.get_screen()
61
 
            monitor = screen.get_monitor_geometry(0)
62
 
            width = int(monitor.width * 0.75)
63
 
            height = int(monitor.height * 0.75)
 
32
        self.set_title("bzrk")
 
33
 
 
34
        # Use three-quarters of the screen by default
 
35
        screen = self.get_screen()
 
36
        monitor = screen.get_monitor_geometry(0)
 
37
        width = int(monitor.width * 0.75)
 
38
        height = int(monitor.height * 0.75)
64
39
        self.set_default_size(width, height)
65
 
        self.set_size_request(width/3, height/3)
66
 
        self._save_size_on_destroy(self, 'viz-window-size')
67
40
 
68
41
        # FIXME AndyFitz!
69
42
        icon = self.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)
70
43
        self.set_icon(icon)
71
44
 
72
 
        gtk.accel_map_add_entry("<viz>/Go/Next Revision", gtk.keysyms.Up, gtk.gdk.MOD1_MASK)
73
 
        gtk.accel_map_add_entry("<viz>/Go/Previous Revision", gtk.keysyms.Down, gtk.gdk.MOD1_MASK)
74
 
        gtk.accel_map_add_entry("<viz>/View/Refresh", gtk.keysyms.F5, 0)
75
 
 
76
45
        self.accel_group = gtk.AccelGroup()
77
46
        self.add_accel_group(self.accel_group)
78
47
 
79
 
        if getattr(gtk.Action, 'set_tool_item_type', None) is not None:
80
 
            # Not available before PyGtk-2.10
81
 
            gtk.Action.set_tool_item_type(gtk.MenuToolButton)
82
 
 
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()
88
 
 
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()
94
 
 
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()
100
 
 
101
48
        self.construct()
102
49
 
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."""
106
 
        def save_size(src):
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)
111
 
 
112
 
    def set_revision(self, revid):
113
 
        self.treeview.set_revision_id(revid)
114
 
 
115
50
    def construct(self):
116
51
        """Construct the window contents."""
117
52
        vbox = gtk.VBox(spacing=0)
118
53
        self.add(vbox)
119
54
 
120
 
        self.paned = gtk.VPaned()
121
 
        self.paned.pack1(self.construct_top(), resize=False, shrink=True)
122
 
        self.paned.pack2(self.construct_bottom(), resize=True, shrink=False)
123
 
        self.paned.show()
124
 
 
125
 
        nav = self.construct_navigation()
126
 
        menubar = self.construct_menubar()
127
 
        vbox.pack_start(menubar, expand=False, fill=True)
128
 
        vbox.pack_start(nav, expand=False, fill=True)
129
 
 
130
 
        vbox.pack_start(self.paned, expand=True, fill=True)
131
 
        vbox.set_focus_child(self.paned)
132
 
 
133
 
        self.treeview.connect('revision-selected',
134
 
                self._treeselection_changed_cb)
135
 
        self.treeview.connect('revision-activated',
136
 
                self._tree_revision_activated)
137
 
 
138
 
        self.treeview.connect('tag-added', lambda w, t, r: self._update_tags())
 
55
        vbox.pack_start(self.construct_navigation(), expand=False, fill=True)
 
56
 
 
57
        paned = gtk.VPaned()
 
58
        paned.pack1(self.construct_top(), resize=True, shrink=False)
 
59
        paned.pack2(self.construct_bottom(), resize=False, shrink=True)
 
60
        paned.show()
 
61
        vbox.pack_start(paned, expand=True, fill=True)
 
62
        vbox.set_focus_child(paned)
 
63
 
139
64
        vbox.show()
140
65
 
141
 
    def construct_menubar(self):
142
 
        menubar = gtk.MenuBar()
143
 
 
144
 
        file_menu = gtk.Menu()
145
 
        file_menuitem = gtk.MenuItem("_File")
146
 
        file_menuitem.set_submenu(file_menu)
147
 
 
148
 
        file_menu_close = gtk.ImageMenuItem(gtk.STOCK_CLOSE, self.accel_group)
149
 
        file_menu_close.connect('activate', lambda x: self.destroy())
150
 
 
151
 
        file_menu_quit = gtk.ImageMenuItem(gtk.STOCK_QUIT, self.accel_group)
152
 
        file_menu_quit.connect('activate', lambda x: gtk.main_quit())
153
 
 
154
 
        if self._parent is not None:
155
 
            file_menu.add(file_menu_close)
156
 
        file_menu.add(file_menu_quit)
157
 
 
158
 
        edit_menu = gtk.Menu()
159
 
        edit_menuitem = gtk.MenuItem("_Edit")
160
 
        edit_menuitem.set_submenu(edit_menu)
161
 
 
162
 
        edit_menu_branchopts = gtk.MenuItem("Branch Settings")
163
 
        edit_menu_branchopts.connect('activate', lambda x: PreferencesWindow(self.branch.get_config()).show())
164
 
 
165
 
        edit_menu_globopts = gtk.MenuItem("Global Settings")
166
 
        edit_menu_globopts.connect('activate', lambda x: PreferencesWindow().show())
167
 
 
168
 
        edit_menu.add(edit_menu_branchopts)
169
 
        edit_menu.add(edit_menu_globopts)
170
 
 
171
 
        view_menu = gtk.Menu()
172
 
        view_menuitem = gtk.MenuItem("_View")
173
 
        view_menuitem.set_submenu(view_menu)
174
 
 
175
 
        view_menu_refresh = self.refresh_action.create_menu_item()
176
 
        view_menu_refresh.connect('activate', self._refresh_clicked)
177
 
 
178
 
        view_menu.add(view_menu_refresh)
179
 
        view_menu.add(gtk.SeparatorMenuItem())
180
 
 
181
 
        view_menu_toolbar = gtk.CheckMenuItem("Show Toolbar")
182
 
        view_menu_toolbar.set_active(True)
183
 
        if self.config.get_user_option('viz-toolbar-visible') == 'False':
184
 
            view_menu_toolbar.set_active(False)
185
 
            self.toolbar.hide()
186
 
        view_menu_toolbar.connect('toggled', self._toolbar_visibility_changed)
187
 
 
188
 
        view_menu_compact = gtk.CheckMenuItem("Show Compact Graph")
189
 
        view_menu_compact.set_active(self.compact_view)
190
 
        view_menu_compact.connect('activate', self._brokenlines_toggled_cb)
191
 
 
192
 
        view_menu_diffs = gtk.CheckMenuItem("Show Diffs")
193
 
        view_menu_diffs.set_active(False)
194
 
        if self.config.get_user_option('viz-show-diffs') == 'True':
195
 
            view_menu_diffs.set_active(True)
196
 
        view_menu_diffs.connect('toggled', self._diff_visibility_changed)
197
 
 
198
 
        view_menu_wide_diffs = gtk.CheckMenuItem("Wide Diffs")
199
 
        view_menu_wide_diffs.set_active(False)
200
 
        if self.config.get_user_option('viz-wide-diffs') == 'True':
201
 
            view_menu_wide_diffs.set_active(True)
202
 
        view_menu_wide_diffs.connect('toggled', self._diff_placement_changed)
203
 
 
204
 
        view_menu_wrap_diffs = gtk.CheckMenuItem("Wrap _Long Lines in Diffs")
205
 
        view_menu_wrap_diffs.set_active(False)
206
 
        if self.config.get_user_option('viz-wrap-diffs') == 'True':
207
 
            view_menu_wrap_diffs.set_active(True)
208
 
        view_menu_wrap_diffs.connect('toggled', self._diff_wrap_changed)
209
 
 
210
 
        view_menu.add(view_menu_toolbar)
211
 
        view_menu.add(view_menu_compact)
212
 
        view_menu.add(gtk.SeparatorMenuItem())
213
 
        view_menu.add(view_menu_diffs)
214
 
        view_menu.add(view_menu_wide_diffs)
215
 
        view_menu.add(view_menu_wrap_diffs)
216
 
        view_menu.add(gtk.SeparatorMenuItem())
217
 
 
218
 
        self.mnu_show_revno_column = gtk.CheckMenuItem("Show Revision _Number Column")
219
 
        self.mnu_show_date_column = gtk.CheckMenuItem("Show _Date Column")
220
 
 
221
 
        # Revision numbers are pointless if there are multiple branches
222
 
        if len(self.start_revs) > 1:
223
 
            self.mnu_show_revno_column.set_sensitive(False)
224
 
            self.treeview.set_property('revno-column-visible', False)
225
 
 
226
 
        for (col, name) in [(self.mnu_show_revno_column, "revno"), 
227
 
                            (self.mnu_show_date_column, "date")]:
228
 
            col.set_active(self.treeview.get_property(name + "-column-visible"))
229
 
            col.connect('toggled', self._col_visibility_changed, name)
230
 
            view_menu.add(col)
231
 
 
232
 
        go_menu = gtk.Menu()
233
 
        go_menu.set_accel_group(self.accel_group)
234
 
        go_menuitem = gtk.MenuItem("_Go")
235
 
        go_menuitem.set_submenu(go_menu)
236
 
 
237
 
        go_menu_next = self.next_rev_action.create_menu_item()
238
 
        go_menu_prev = self.prev_rev_action.create_menu_item()
239
 
 
240
 
        tag_image = gtk.Image()
241
 
        tag_image.set_from_file(icon_path("tag-16.png"))
242
 
        self.go_menu_tags = gtk.ImageMenuItem("_Tags")
243
 
        self.go_menu_tags.set_image(tag_image)
244
 
        self.treeview.connect('refreshed', lambda w: self._update_tags())
245
 
 
246
 
        go_menu.add(go_menu_next)
247
 
        go_menu.add(go_menu_prev)
248
 
        go_menu.add(gtk.SeparatorMenuItem())
249
 
        go_menu.add(self.go_menu_tags)
250
 
 
251
 
        self.revision_menu = RevisionMenu(self.branch.repository, [], self.branch, parent=self)
252
 
        revision_menuitem = gtk.MenuItem("_Revision")
253
 
        revision_menuitem.set_submenu(self.revision_menu)
254
 
 
255
 
        branch_menu = gtk.Menu()
256
 
        branch_menuitem = gtk.MenuItem("_Branch")
257
 
        branch_menuitem.set_submenu(branch_menu)
258
 
 
259
 
        branch_menu.add(gtk.MenuItem("Pu_ll Revisions"))
260
 
        branch_menu.add(gtk.MenuItem("Pu_sh Revisions"))
261
 
 
262
 
        try:
263
 
            from bzrlib.plugins import search
264
 
        except ImportError:
265
 
            mutter("Didn't find search plugin")
266
 
        else:
267
 
            branch_menu.add(gtk.SeparatorMenuItem())
268
 
 
269
 
            branch_index_menuitem = gtk.MenuItem("_Index")
270
 
            branch_index_menuitem.connect('activate', self._branch_index_cb)
271
 
            branch_menu.add(branch_index_menuitem)
272
 
 
273
 
            branch_search_menuitem = gtk.MenuItem("_Search")
274
 
            branch_search_menuitem.connect('activate', self._branch_search_cb)
275
 
            branch_menu.add(branch_search_menuitem)
276
 
 
277
 
        help_menu = gtk.Menu()
278
 
        help_menuitem = gtk.MenuItem("_Help")
279
 
        help_menuitem.set_submenu(help_menu)
280
 
 
281
 
        help_about_menuitem = gtk.ImageMenuItem(gtk.STOCK_ABOUT, self.accel_group)
282
 
        help_about_menuitem.connect('activate', self._about_dialog_cb)
283
 
 
284
 
        help_menu.add(help_about_menuitem)
285
 
 
286
 
        menubar.add(file_menuitem)
287
 
        menubar.add(edit_menuitem)
288
 
        menubar.add(view_menuitem)
289
 
        menubar.add(go_menuitem)
290
 
        menubar.add(revision_menuitem)
291
 
        menubar.add(branch_menuitem)
292
 
        menubar.add(help_menuitem)
293
 
        menubar.show_all()
294
 
 
295
 
        return menubar
296
 
 
297
66
    def construct_top(self):
298
67
        """Construct the top-half of the window."""
299
 
        # FIXME: Make broken_line_length configurable
300
 
 
301
 
        self.treeview = TreeView(self.branch, self.start_revs, self.maxnum, self.compact_view)
302
 
 
303
 
        for col in ["revno", "date"]:
304
 
            option = self.config.get_user_option(col + '-column-visible')
305
 
            if option is not None:
306
 
                self.treeview.set_property(col + '-column-visible', option == 'True')
307
 
            else:
308
 
                self.treeview.set_property(col + '-column-visible', False)
309
 
 
 
68
        scrollwin = gtk.ScrolledWindow()
 
69
        scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
 
70
        scrollwin.set_shadow_type(gtk.SHADOW_IN)
 
71
        scrollwin.show()
 
72
 
 
73
        self.treeview = gtk.TreeView()
 
74
        self.treeview.set_rules_hint(True)
 
75
        self.treeview.set_search_column(4)
 
76
        self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
 
77
        self.treeview.connect("row-activated", self._treeview_row_activated_cb)
 
78
        scrollwin.add(self.treeview)
310
79
        self.treeview.show()
311
80
 
312
 
        align = gtk.Alignment(0.0, 0.0, 1.0, 1.0)
313
 
        align.set_padding(5, 0, 0, 0)
314
 
        align.add(self.treeview)
315
 
        # user-configured size
316
 
        size = self._load_size('viz-graph-size')
317
 
        if size:
318
 
            width, height = size
319
 
            align.set_size_request(width, height)
320
 
        else:
321
 
            (width, height) = self.get_size()
322
 
            align.set_size_request(width, int(height / 2.5))
323
 
        self._save_size_on_destroy(align, 'viz-graph-size')
324
 
        align.show()
325
 
 
326
 
        return align
 
81
        cell = CellRendererGraph()
 
82
        column = gtk.TreeViewColumn()
 
83
        column.set_resizable(True)
 
84
        column.pack_start(cell, expand=False)
 
85
        column.add_attribute(cell, "node", 1)
 
86
        column.add_attribute(cell, "in-lines", 2)
 
87
        column.add_attribute(cell, "out-lines", 3)
 
88
        self.treeview.append_column(column)
 
89
 
 
90
        cell = gtk.CellRendererText()
 
91
        cell.set_property("width-chars", 40)
 
92
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
 
93
        column = gtk.TreeViewColumn("Message")
 
94
        column.set_resizable(True)
 
95
        column.pack_start(cell, expand=True)
 
96
        column.add_attribute(cell, "text", 4)
 
97
        self.treeview.append_column(column)
 
98
 
 
99
        cell = gtk.CellRendererText()
 
100
        cell.set_property("width-chars", 40)
 
101
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
 
102
        column = gtk.TreeViewColumn("Committer")
 
103
        column.set_resizable(True)
 
104
        column.pack_start(cell, expand=True)
 
105
        column.add_attribute(cell, "text", 5)
 
106
        self.treeview.append_column(column)
 
107
 
 
108
        cell = gtk.CellRendererText()
 
109
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
 
110
        column = gtk.TreeViewColumn("Date")
 
111
        column.set_resizable(True)
 
112
        column.pack_start(cell, expand=True)
 
113
        column.add_attribute(cell, "text", 6)
 
114
        self.treeview.append_column(column)
 
115
 
 
116
        return scrollwin
327
117
 
328
118
    def construct_navigation(self):
329
119
        """Construct the navigation buttons."""
330
 
        self.toolbar = gtk.Toolbar()
331
 
        self.toolbar.set_style(gtk.TOOLBAR_BOTH_HORIZ)
332
 
 
333
 
        self.prev_button = self.prev_rev_action.create_tool_item()
334
 
        self.toolbar.insert(self.prev_button, -1)
335
 
 
336
 
        self.next_button = self.next_rev_action.create_tool_item()
337
 
        self.toolbar.insert(self.next_button, -1)
338
 
 
339
 
        self.toolbar.insert(gtk.SeparatorToolItem(), -1)
340
 
 
341
 
        refresh_button = gtk.ToolButton(gtk.STOCK_REFRESH)
342
 
        refresh_button.connect('clicked', self._refresh_clicked)
343
 
        self.toolbar.insert(refresh_button, -1)
344
 
 
345
 
        self.toolbar.show_all()
346
 
 
347
 
        return self.toolbar
 
120
        frame = gtk.Frame()
 
121
        frame.set_shadow_type(gtk.SHADOW_OUT)
 
122
        frame.show()
 
123
        
 
124
        hbox = gtk.HBox(spacing=12)
 
125
        frame.add(hbox)
 
126
        hbox.show()
 
127
 
 
128
        self.back_button = gtk.Button(stock=gtk.STOCK_GO_BACK)
 
129
        self.back_button.set_relief(gtk.RELIEF_NONE)
 
130
        self.back_button.add_accelerator("clicked", self.accel_group, ord('['),
 
131
                                         0, 0)
 
132
        self.back_button.connect("clicked", self._back_clicked_cb)
 
133
        hbox.pack_start(self.back_button, expand=False, fill=True)
 
134
        self.back_button.show()
 
135
 
 
136
        self.fwd_button = gtk.Button(stock=gtk.STOCK_GO_FORWARD)
 
137
        self.fwd_button.set_relief(gtk.RELIEF_NONE)
 
138
        self.fwd_button.add_accelerator("clicked", self.accel_group, ord(']'),
 
139
                                        0, 0)
 
140
        self.fwd_button.connect("clicked", self._fwd_clicked_cb)
 
141
        hbox.pack_start(self.fwd_button, expand=False, fill=True)
 
142
        self.fwd_button.show()
 
143
 
 
144
        return frame
348
145
 
349
146
    def construct_bottom(self):
350
147
        """Construct the bottom half of the window."""
351
 
        if self.config.get_user_option('viz-wide-diffs') == 'True':
352
 
            self.diff_paned = gtk.VPaned()
353
 
        else:
354
 
            self.diff_paned = gtk.HPaned()
 
148
        from bzrlib.plugins.gtk.logview import LogView
 
149
        self.logview = LogView()
 
150
        self.logview.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
 
151
        self.logview.set_shadow_type(gtk.SHADOW_NONE)
355
152
        (width, height) = self.get_size()
356
 
        self.diff_paned.set_size_request(20, 20) # shrinkable
357
 
 
358
 
        from bzrlib.plugins.gtk.revisionview import RevisionView
359
 
        self.revisionview = RevisionView(branch=self.branch)
360
 
        self.revisionview.set_size_request(width/3, int(height / 2.5))
361
 
        # user-configured size
362
 
        size = self._load_size('viz-revisionview-size')
363
 
        if size:
364
 
            width, height = size
365
 
            self.revisionview.set_size_request(width, height)
366
 
        self._save_size_on_destroy(self.revisionview, 'viz-revisionview-size')
367
 
        self.revisionview.show()
368
 
        self.revisionview.set_show_callback(self._show_clicked_cb)
369
 
        self.revisionview.connect('notify::revision', self._go_clicked_cb)
370
 
        self.treeview.connect('tag-added', lambda w, t, r: self.revisionview.update_tags())
371
 
        self.diff_paned.pack1(self.revisionview)
372
 
 
373
 
        from bzrlib.plugins.gtk.diff import DiffWidget
374
 
        self.diff = DiffWidget()
375
 
        self.diff_paned.pack2(self.diff)
376
 
 
377
 
        self.diff_paned.show_all()
378
 
        if self.config.get_user_option('viz-show-diffs') != 'True':
379
 
            self.diff.hide()
380
 
 
381
 
        return self.diff_paned
382
 
 
383
 
    def _tag_selected_cb(self, menuitem, revid):
384
 
        self.treeview.set_revision_id(revid)
385
 
 
386
 
    def _treeselection_changed_cb(self, selection, *args):
387
 
        """callback for when the treeview changes."""
388
 
        revision = self.treeview.get_revision()
389
 
        parents  = self.treeview.get_parents()
390
 
        children = self.treeview.get_children()
391
 
 
392
 
        self.revision_menu.set_revision_ids([revision.revision_id])
393
 
 
394
 
        if revision and revision != NULL_REVISION:
395
 
            prev_menu = gtk.Menu()
396
 
            if len(parents) > 0:
397
 
                self.prev_rev_action.set_sensitive(True)
398
 
                for parent_id in parents:
399
 
                    if parent_id and parent_id != NULL_REVISION:
400
 
                        parent = self.branch.repository.get_revision(parent_id)
401
 
                        try:
402
 
                            str = ' (' + parent.properties['branch-nick'] + ')'
403
 
                        except KeyError:
404
 
                            str = ""
405
 
 
406
 
                        item = gtk.MenuItem(parent.message.split("\n")[0] + str)
407
 
                        item.connect('activate', self._set_revision_cb, parent_id)
408
 
                        prev_menu.add(item)
409
 
                prev_menu.show_all()
410
 
            else:
411
 
                self.prev_rev_action.set_sensitive(False)
412
 
                prev_menu.hide()
413
 
 
414
 
            if getattr(self.prev_button, 'set_menu', None) is not None:
415
 
                self.prev_button.set_menu(prev_menu)
416
 
 
417
 
            next_menu = gtk.Menu()
418
 
            if len(children) > 0:
419
 
                self.next_rev_action.set_sensitive(True)
420
 
                for child_id in children:
421
 
                    child = self.branch.repository.get_revision(child_id)
422
 
                    try:
423
 
                        str = ' (' + child.properties['branch-nick'] + ')'
424
 
                    except KeyError:
425
 
                        str = ""
426
 
 
427
 
                    item = gtk.MenuItem(child.message.split("\n")[0] + str)
428
 
                    item.connect('activate', self._set_revision_cb, child_id)
429
 
                    next_menu.add(item)
430
 
                next_menu.show_all()
431
 
            else:
432
 
                self.next_rev_action.set_sensitive(False)
433
 
                next_menu.hide()
434
 
 
435
 
            if getattr(self.next_button, 'set_menu', None) is not None:
436
 
                self.next_button.set_menu(next_menu)
437
 
 
438
 
            self.revisionview.set_revision(revision)
439
 
            self.revisionview.set_children(children)
440
 
            self.update_diff_panel(revision, parents)
441
 
 
442
 
    def _tree_revision_activated(self, widget, path, col):
443
 
        # TODO: more than one parent
444
 
        """Callback for when a treeview row gets activated."""
445
 
        revision = self.treeview.get_revision()
446
 
        parents  = self.treeview.get_parents()
447
 
 
448
 
        if len(parents) == 0:
449
 
            parent_id = NULL_REVISION
450
 
        else:
451
 
            parent_id = parents[0]
452
 
 
453
 
        self.show_diff(revision.revision_id, parent_id)
454
 
        self.treeview.grab_focus()
 
153
        self.logview.set_size_request(width, int(height / 2.5))
 
154
        self.logview.show()
 
155
        self.logview.set_show_callback(self._show_clicked_cb)
 
156
        self.logview.set_go_callback(self._go_clicked_cb)
 
157
        return self.logview
 
158
 
 
159
    def set_branch(self, branch, start, maxnum):
 
160
        """Set the branch and start position for this window.
 
161
 
 
162
        Creates a new TreeModel and populates it with information about
 
163
        the new branch before updating the window title and model of the
 
164
        treeview itself.
 
165
        """
 
166
        self.branch = branch
 
167
 
 
168
        # [ revision, node, last_lines, lines, message, committer, timestamp ]
 
169
        self.model = gtk.ListStore(gobject.TYPE_PYOBJECT,
 
170
                                   gobject.TYPE_PYOBJECT,
 
171
                                   gobject.TYPE_PYOBJECT,
 
172
                                   gobject.TYPE_PYOBJECT,
 
173
                                   str, str, str)
 
174
        self.index = {}
 
175
        index = 0
 
176
 
 
177
        last_lines = []
 
178
        (self.revisions, colours, self.children, self.parent_ids,
 
179
         merge_sorted) = distances(branch, start)
 
180
        for (index, (revision, node, lines)) in enumerate(graph(
 
181
                self.revisions, colours, merge_sorted)):
 
182
            # FIXME: at this point we should be able to show the graph order
 
183
            # and lines with no message or commit data - and then incrementally
 
184
            # fill the timestamp, committer etc data as desired.
 
185
            message = revision.message.split("\n")[0]
 
186
            if revision.committer is not None:
 
187
                timestamp = format_date(revision.timestamp, revision.timezone)
 
188
            else:
 
189
                timestamp = None
 
190
            self.model.append([revision, node, last_lines, lines,
 
191
                               message, revision.committer, timestamp])
 
192
            self.index[revision] = index
 
193
            last_lines = lines
 
194
            if maxnum is not None and index > maxnum:
 
195
                break
 
196
 
 
197
        self.set_title(branch.nick + " - bzrk")
 
198
        self.treeview.set_model(self.model)
 
199
 
 
200
    def _treeview_cursor_cb(self, *args):
 
201
        """Callback for when the treeview cursor changes."""
 
202
        (path, col) = self.treeview.get_cursor()
 
203
        revision = self.model[path][0]
 
204
 
 
205
        self.back_button.set_sensitive(len(self.parent_ids[revision]) > 0)
 
206
        self.fwd_button.set_sensitive(len(self.children[revision]) > 0)
 
207
        self.logview.set_revision(revision)
455
208
 
456
209
    def _back_clicked_cb(self, *args):
457
210
        """Callback for when the back button is clicked."""
458
 
        self.treeview.back()
 
211
        (path, col) = self.treeview.get_cursor()
 
212
        revision = self.model[path][0]
 
213
        if not len(self.parent_ids[revision]):
 
214
            return
 
215
 
 
216
        for parent_id in self.parent_ids[revision]:
 
217
            parent = self.revisions[parent_id]
 
218
            if same_branch(revision, parent):
 
219
                self.treeview.set_cursor(self.index[parent])
 
220
                break
 
221
        else:
 
222
            next = self.revisions[self.parent_ids[revision][0]]
 
223
            self.treeview.set_cursor(self.index[next])
 
224
        self.treeview.grab_focus()
459
225
 
460
226
    def _fwd_clicked_cb(self, *args):
461
227
        """Callback for when the forward button is clicked."""
462
 
        self.treeview.forward()
463
 
 
464
 
    def _go_clicked_cb(self, w, p):
 
228
        (path, col) = self.treeview.get_cursor()
 
229
        revision = self.model[path][0]
 
230
        if not len(self.children[revision]):
 
231
            return
 
232
 
 
233
        for child in self.children[revision]:
 
234
            if same_branch(child, revision):
 
235
                self.treeview.set_cursor(self.index[child])
 
236
                break
 
237
        else:
 
238
            prev = list(self.children[revision])[0]
 
239
            self.treeview.set_cursor(self.index[prev])
 
240
        self.treeview.grab_focus()
 
241
 
 
242
    def _go_clicked_cb(self, revid):
465
243
        """Callback for when the go button for a parent is clicked."""
466
 
        if self.revisionview.get_revision() is not None:
467
 
            self.treeview.set_revision(self.revisionview.get_revision())
468
 
 
469
 
    def _show_clicked_cb(self, revid, parentid):
470
 
        """Callback for when the show button for a parent is clicked."""
471
 
        self.show_diff(revid, parentid)
 
244
        self.treeview.set_cursor(self.index[self.revisions[revid]])
472
245
        self.treeview.grab_focus()
473
246
 
474
 
    def _set_revision_cb(self, w, revision_id):
475
 
        self.treeview.set_revision_id(revision_id)
476
 
 
477
 
    def _brokenlines_toggled_cb(self, button):
478
 
        self.compact_view = button.get_active()
479
 
 
480
 
        if self.compact_view:
481
 
            option = 'yes'
482
 
        else:
483
 
            option = 'no'
484
 
 
485
 
        self.config.set_user_option('viz-compact-view', option)
486
 
        self.treeview.set_property('compact', self.compact_view)
487
 
        self.treeview.refresh()
488
 
 
489
 
    def _branch_index_cb(self, w):
490
 
        from bzrlib.plugins.search import index as _mod_index
491
 
        _mod_index.index_url(self.branch.base)
492
 
 
493
 
    def _branch_search_cb(self, w):
494
 
        from bzrlib.plugins.search import index as _mod_index
495
 
        from bzrlib.plugins.gtk.search import SearchDialog
496
 
        from bzrlib.plugins.search import errors as search_errors
497
 
 
498
 
        try:
499
 
            index = _mod_index.open_index_url(self.branch.base)
500
 
        except search_errors.NoSearchIndex:
501
 
            dialog = gtk.MessageDialog(self, type=gtk.MESSAGE_QUESTION, 
502
 
                buttons=gtk.BUTTONS_OK_CANCEL, 
503
 
                message_format="This branch has not been indexed yet. "
504
 
                               "Index now?")
505
 
            if dialog.run() == gtk.RESPONSE_OK:
506
 
                dialog.destroy()
507
 
                index = _mod_index.index_url(self.branch.base)
508
 
            else:
509
 
                dialog.destroy()
510
 
                return
511
 
 
512
 
        dialog = SearchDialog(index)
513
 
 
514
 
        if dialog.run() == gtk.RESPONSE_OK:
515
 
            self.set_revision(dialog.get_revision())
516
 
 
517
 
        dialog.destroy()
518
 
 
519
 
    def _about_dialog_cb(self, w):
520
 
        from bzrlib.plugins.gtk.about import AboutDialog
521
 
        AboutDialog().run()
522
 
 
523
 
    def _col_visibility_changed(self, col, property):
524
 
        self.config.set_user_option(property + '-column-visible', col.get_active())
525
 
        self.treeview.set_property(property + '-column-visible', col.get_active())
526
 
 
527
 
    def _toolbar_visibility_changed(self, col):
528
 
        if col.get_active():
529
 
            self.toolbar.show()
530
 
        else:
531
 
            self.toolbar.hide()
532
 
        self.config.set_user_option('viz-toolbar-visible', col.get_active())
533
 
 
534
 
    def _make_diff_nonzero_size(self):
535
 
        """make sure the diff isn't zero-width or zero-height"""
536
 
        alloc = self.diff.get_allocation()
537
 
        if (alloc.width < 10) or (alloc.height < 10):
538
 
            width, height = self.get_size()
539
 
            self.revisionview.set_size_request(width/3, int(height / 2.5))
540
 
 
541
 
    def _diff_visibility_changed(self, col):
542
 
        """Hide or show the diff panel."""
543
 
        if col.get_active():
544
 
            self.diff.show()
545
 
            self._make_diff_nonzero_size()
546
 
        else:
547
 
            self.diff.hide()
548
 
        self.config.set_user_option('viz-show-diffs', str(col.get_active()))
549
 
        self.update_diff_panel()
550
 
 
551
 
    def _diff_placement_changed(self, col):
552
 
        """Toggle the diff panel's position."""
553
 
        self.config.set_user_option('viz-wide-diffs', str(col.get_active()))
554
 
 
555
 
        old = self.paned.get_child2()
556
 
        self.paned.remove(old)
557
 
        self.paned.pack2(self.construct_bottom(), resize=True, shrink=False)
558
 
        self._make_diff_nonzero_size()
559
 
 
560
 
        self.treeview.emit('revision-selected')
561
 
 
562
 
    def _diff_wrap_changed(self, widget):
563
 
        """Toggle word wrap in the diff widget."""
564
 
        self.config.set_user_option('viz-wrap-diffs', widget.get_active())
565
 
        self.diff._on_wraplines_toggled(widget)
566
 
 
567
 
    def _refresh_clicked(self, w):
568
 
        self.treeview.refresh()
569
 
 
570
 
    def _update_tags(self):
571
 
        menu = gtk.Menu()
572
 
 
573
 
        if self.branch.supports_tags():
574
 
            tags = self.branch.tags.get_tag_dict().items()
575
 
            tags.sort(reverse=True)
576
 
            for tag, revid in tags:
577
 
                tag_image = gtk.Image()
578
 
                tag_image.set_from_file(icon_path('tag-16.png'))
579
 
                tag_item = gtk.ImageMenuItem(tag.replace('_', '__'))
580
 
                tag_item.set_image(tag_image)
581
 
                tag_item.connect('activate', self._tag_selected_cb, revid)
582
 
                tag_item.set_sensitive(self.treeview.has_revision_id(revid))
583
 
                menu.add(tag_item)
584
 
            self.go_menu_tags.set_submenu(menu)
585
 
 
586
 
            self.go_menu_tags.set_sensitive(len(tags) != 0)
587
 
        else:
588
 
            self.go_menu_tags.set_sensitive(False)
589
 
 
590
 
        self.go_menu_tags.show_all()
591
 
 
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)
596
 
        """
597
 
        size = self.config.get_user_option(name)
598
 
        if size:
599
 
            width, height = [int(num) for num in size.split('x')]
600
 
            # avoid writing config every time we start
601
 
            return width, height
602
 
        return None
603
 
 
604
 
    def show_diff(self, revid=None, parentid=NULL_REVISION):
 
247
    def show_diff(self, branch, revid, parentid):
605
248
        """Open a new window to show a diff between the given revisions."""
606
249
        from bzrlib.plugins.gtk.diff import DiffWindow
607
 
        window = DiffWindow(parent=self)
608
 
 
609
 
        rev_tree    = self.branch.repository.revision_tree(revid)
610
 
        parent_tree = self.branch.repository.revision_tree(parentid)
611
 
 
612
 
        description = revid + " - " + self.branch._get_nick(local=True)
 
250
        window = DiffWindow()
 
251
        rev_tree = branch.repository.revision_tree(revid)
 
252
        parent_tree = branch.repository.revision_tree(parentid)
 
253
        description = revid + " - " + branch.nick
613
254
        window.set_diff(description, rev_tree, parent_tree)
614
255
        window.show()
615
256
 
616
 
    def update_diff_panel(self, revision=None, parents=None):
617
 
        """Show the current revision in the diff panel."""
618
 
        if self.config.get_user_option('viz-show-diffs') != 'True':
619
 
            return
620
 
 
621
 
        if not revision: # default to selected row
622
 
            revision = self.treeview.get_revision()
623
 
        if revision == NULL_REVISION:
624
 
            return
625
 
 
626
 
        if not parents: # default to selected row's parents
627
 
            parents  = self.treeview.get_parents()
628
 
        if len(parents) == 0:
629
 
            parent_id = NULL_REVISION
630
 
        else:
631
 
            parent_id = parents[0]
632
 
 
633
 
        rev_tree    = self.branch.repository.revision_tree(revision.revision_id)
634
 
        parent_tree = self.branch.repository.revision_tree(parent_id)
635
 
 
636
 
        self.diff.set_diff(rev_tree, parent_tree)
637
 
        if self.config.get_user_option('viz-wrap-diffs') == 'True':
638
 
            self.diff._on_wraplines_toggled(wrap=True)
639
 
        self.diff.show_all()
 
257
    def _show_clicked_cb(self, revid, parentid):
 
258
        """Callback for when the show button for a parent is clicked."""
 
259
        self.show_diff(self.branch, revid, parentid)
 
260
        self.treeview.grab_focus()
 
261
 
 
262
    def _treeview_row_activated_cb(self, widget, path, col):
 
263
        # TODO: more than one parent
 
264
        """Callback for when a treeview row gets activated."""
 
265
        revision = self.model[path][0]
 
266
        if len(self.parent_ids[revision]) == 0:
 
267
            # Ignore revisions without parent
 
268
            return
 
269
        parent_id = self.parent_ids[revision][0]
 
270
        self.show_diff(self.branch, revision.revision_id, parent_id)
 
271
        self.treeview.grab_focus()