16
from bzrlib.plugins.gtk.window import Window
 
16
17
from bzrlib.plugins.gtk import icon_path
 
17
 
from bzrlib.plugins.gtk.branchview import TreeView, treemodel
 
18
18
from bzrlib.plugins.gtk.tags import AddTagDialog
 
19
19
from bzrlib.plugins.gtk.preferences import PreferencesWindow
 
20
 
from bzrlib.plugins.gtk.revisionmenu import RevisionMenu
 
21
 
from bzrlib.plugins.gtk.window import Window
 
23
 
from bzrlib.config import BranchConfig, GlobalConfig
 
 
20
from bzrlib.plugins.gtk.branchview import TreeView, treemodel
 
24
21
from bzrlib.revision import Revision, NULL_REVISION
 
25
 
from bzrlib.trace import mutter
 
 
22
from bzrlib.config import BranchConfig
 
 
23
from bzrlib.config import GlobalConfig
 
27
25
class BranchWindow(Window):
 
 
54
52
            self.compact_view = False
 
56
 
        self.set_title(branch._get_nick(local=True) + " - revision history")
 
 
54
        self.set_title(branch.nick + " - revision history")
 
58
 
        # user-configured window size
 
59
 
        size = self._load_size('viz-window-size')
 
63
 
            # Use three-quarters of the screen by default
 
64
 
            screen = self.get_screen()
 
65
 
            monitor = screen.get_monitor_geometry(0)
 
66
 
            width = int(monitor.width * 0.75)
 
67
 
            height = int(monitor.height * 0.75)
 
 
56
        # Use three-quarters of the screen by default
 
 
57
        screen = self.get_screen()
 
 
58
        monitor = screen.get_monitor_geometry(0)
 
 
59
        width = int(monitor.width * 0.75)
 
 
60
        height = int(monitor.height * 0.75)
 
68
61
        self.set_default_size(width, height)
 
69
 
        self.set_size_request(width/3, height/3)
 
70
 
        self._save_size_on_destroy(self, 'viz-window-size')
 
73
64
        icon = self.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)
 
 
105
 
    def _save_size_on_destroy(self, widget, config_name):
 
106
 
        """Creates a hook that saves the size of widget to config option 
 
107
 
           config_name when the window is destroyed/closed."""
 
109
 
            width, height = widget.allocation.width, widget.allocation.height
 
110
 
            value = '%sx%s' % (width, height)
 
111
 
            self.config.set_user_option(config_name, value)
 
112
 
        self.connect("destroy", save_size)
 
114
96
    def set_revision(self, revid):
 
115
97
        self.treeview.set_revision_id(revid)
 
 
122
104
        self.paned = gtk.VPaned()
 
123
 
        self.paned.pack1(self.construct_top(), resize=False, shrink=True)
 
124
 
        self.paned.pack2(self.construct_bottom(), resize=True, shrink=False)
 
 
105
        self.paned.pack1(self.construct_top(), resize=True, shrink=False)
 
 
106
        self.paned.pack2(self.construct_bottom(), resize=False, shrink=True)
 
125
107
        self.paned.show()
 
127
 
        nav = self.construct_navigation()
 
128
 
        menubar = self.construct_menubar()
 
129
 
        vbox.pack_start(menubar, expand=False, fill=True)
 
130
 
        vbox.pack_start(nav, expand=False, fill=True)
 
 
109
        vbox.pack_start(self.construct_menubar(), expand=False, fill=True)
 
 
110
        vbox.pack_start(self.construct_navigation(), expand=False, fill=True)
 
132
112
        vbox.pack_start(self.paned, expand=True, fill=True)
 
133
113
        vbox.set_focus_child(self.paned)
 
135
 
        self.treeview.connect('revision-selected',
 
136
 
                self._treeselection_changed_cb)
 
137
 
        self.treeview.connect('revision-activated',
 
138
 
                self._tree_revision_activated)
 
140
 
        self.treeview.connect('tag-added', lambda w, t, r: self._update_tags())
 
143
117
    def construct_menubar(self):
 
144
118
        menubar = gtk.MenuBar()
 
 
161
135
        edit_menuitem = gtk.MenuItem("_Edit")
 
162
136
        edit_menuitem.set_submenu(edit_menu)
 
 
138
        edit_menu_find = gtk.ImageMenuItem(gtk.STOCK_FIND)
 
164
140
        edit_menu_branchopts = gtk.MenuItem("Branch Settings")
 
165
141
        edit_menu_branchopts.connect('activate', lambda x: PreferencesWindow(self.branch.get_config()).show())
 
167
143
        edit_menu_globopts = gtk.MenuItem("Global Settings")
 
168
144
        edit_menu_globopts.connect('activate', lambda x: PreferencesWindow().show())
 
 
146
        edit_menu.add(edit_menu_find)
 
170
147
        edit_menu.add(edit_menu_branchopts)
 
171
148
        edit_menu.add(edit_menu_globopts)
 
 
183
160
        view_menu_toolbar = gtk.CheckMenuItem("Show Toolbar")
 
184
161
        view_menu_toolbar.set_active(True)
 
185
 
        if self.config.get_user_option('viz-toolbar-visible') == 'False':
 
186
 
            view_menu_toolbar.set_active(False)
 
188
162
        view_menu_toolbar.connect('toggled', self._toolbar_visibility_changed)
 
190
164
        view_menu_compact = gtk.CheckMenuItem("Show Compact Graph")
 
191
165
        view_menu_compact.set_active(self.compact_view)
 
192
166
        view_menu_compact.connect('activate', self._brokenlines_toggled_cb)
 
194
 
        view_menu_diffs = gtk.CheckMenuItem("Show Diffs")
 
195
 
        view_menu_diffs.set_active(False)
 
196
 
        if self.config.get_user_option('viz-show-diffs') == 'True':
 
197
 
            view_menu_diffs.set_active(True)
 
198
 
        view_menu_diffs.connect('toggled', self._diff_visibility_changed)
 
200
 
        view_menu_wide_diffs = gtk.CheckMenuItem("Wide Diffs")
 
201
 
        view_menu_wide_diffs.set_active(False)
 
202
 
        if self.config.get_user_option('viz-wide-diffs') == 'True':
 
203
 
            view_menu_wide_diffs.set_active(True)
 
204
 
        view_menu_wide_diffs.connect('toggled', self._diff_placement_changed)
 
206
 
        view_menu_wrap_diffs = gtk.CheckMenuItem("Wrap _Long Lines in Diffs")
 
207
 
        view_menu_wrap_diffs.set_active(False)
 
208
 
        if self.config.get_user_option('viz-wrap-diffs') == 'True':
 
209
 
            view_menu_wrap_diffs.set_active(True)
 
210
 
        view_menu_wrap_diffs.connect('toggled', self._diff_wrap_changed)
 
212
168
        view_menu.add(view_menu_toolbar)
 
213
169
        view_menu.add(view_menu_compact)
 
214
170
        view_menu.add(gtk.SeparatorMenuItem())
 
215
 
        view_menu.add(view_menu_diffs)
 
216
 
        view_menu.add(view_menu_wide_diffs)
 
217
 
        view_menu.add(view_menu_wrap_diffs)
 
218
 
        view_menu.add(gtk.SeparatorMenuItem())
 
220
172
        self.mnu_show_revno_column = gtk.CheckMenuItem("Show Revision _Number Column")
 
221
173
        self.mnu_show_date_column = gtk.CheckMenuItem("Show _Date Column")
 
 
243
195
        tag_image.set_from_file(icon_path("tag-16.png"))
 
244
196
        self.go_menu_tags = gtk.ImageMenuItem("_Tags")
 
245
197
        self.go_menu_tags.set_image(tag_image)
 
246
 
        self.treeview.connect('refreshed', lambda w: self._update_tags())
 
248
200
        go_menu.add(go_menu_next)
 
249
201
        go_menu.add(go_menu_prev)
 
250
202
        go_menu.add(gtk.SeparatorMenuItem())
 
251
203
        go_menu.add(self.go_menu_tags)
 
253
 
        self.revision_menu = RevisionMenu(self.branch.repository, [], self.branch, parent=self)
 
 
205
        revision_menu = gtk.Menu()
 
254
206
        revision_menuitem = gtk.MenuItem("_Revision")
 
255
 
        revision_menuitem.set_submenu(self.revision_menu)
 
 
207
        revision_menuitem.set_submenu(revision_menu)
 
 
209
        revision_menu_diff = gtk.MenuItem("View Changes")
 
 
210
        revision_menu_diff.connect('activate', 
 
 
213
        revision_menu_compare = gtk.MenuItem("Compare with...")
 
 
214
        revision_menu_compare.connect('activate',
 
 
215
                self._compare_with_cb)
 
 
217
        revision_menu_tag = gtk.MenuItem("Tag Revision")
 
 
218
        revision_menu_tag.connect('activate', self._tag_revision_cb)
 
 
220
        revision_menu.add(revision_menu_tag)
 
 
221
        revision_menu.add(revision_menu_diff)
 
 
222
        revision_menu.add(revision_menu_compare)
 
257
224
        branch_menu = gtk.Menu()
 
258
225
        branch_menuitem = gtk.MenuItem("_Branch")
 
 
261
228
        branch_menu.add(gtk.MenuItem("Pu_ll Revisions"))
 
262
229
        branch_menu.add(gtk.MenuItem("Pu_sh Revisions"))
 
265
 
            from bzrlib.plugins import search
 
267
 
            mutter("Didn't find search plugin")
 
269
 
            branch_menu.add(gtk.SeparatorMenuItem())
 
271
 
            branch_index_menuitem = gtk.MenuItem("_Index")
 
272
 
            branch_index_menuitem.connect('activate', self._branch_index_cb)
 
273
 
            branch_menu.add(branch_index_menuitem)
 
275
 
            branch_search_menuitem = gtk.MenuItem("_Search")
 
276
 
            branch_search_menuitem.connect('activate', self._branch_search_cb)
 
277
 
            branch_menu.add(branch_search_menuitem)
 
279
231
        help_menu = gtk.Menu()
 
280
232
        help_menuitem = gtk.MenuItem("_Help")
 
281
233
        help_menuitem.set_submenu(help_menu)
 
 
303
255
        self.treeview = TreeView(self.branch, self.start_revs, self.maxnum, self.compact_view)
 
 
257
        self.treeview.connect('revision-selected',
 
 
258
                self._treeselection_changed_cb)
 
 
259
        self.treeview.connect('revision-activated',
 
 
260
                self._tree_revision_activated)
 
 
262
        self.treeview.connect('tag-added', lambda w, t, r: self._update_tags())
 
305
264
        for col in ["revno", "date"]:
 
306
265
            option = self.config.get_user_option(col + '-column-visible')
 
307
266
            if option is not None:
 
 
314
273
        align = gtk.Alignment(0.0, 0.0, 1.0, 1.0)
 
315
274
        align.set_padding(5, 0, 0, 0)
 
316
275
        align.add(self.treeview)
 
317
 
        # user-configured size
 
318
 
        size = self._load_size('viz-graph-size')
 
321
 
            align.set_size_request(width, height)
 
323
 
            (width, height) = self.get_size()
 
324
 
            align.set_size_request(width, int(height / 2.5))
 
325
 
        self._save_size_on_destroy(align, 'viz-graph-size')
 
 
351
301
    def construct_bottom(self):
 
352
302
        """Construct the bottom half of the window."""
 
353
 
        if self.config.get_user_option('viz-wide-diffs') == 'True':
 
354
 
            self.diff_paned = gtk.VPaned()
 
356
 
            self.diff_paned = gtk.HPaned()
 
357
 
        (width, height) = self.get_size()
 
358
 
        self.diff_paned.set_size_request(20, 20) # shrinkable
 
360
303
        from bzrlib.plugins.gtk.revisionview import RevisionView
 
361
304
        self.revisionview = RevisionView(branch=self.branch)
 
362
 
        self.revisionview.set_size_request(width/3, int(height / 2.5))
 
363
 
        # user-configured size
 
364
 
        size = self._load_size('viz-revisionview-size')
 
367
 
            self.revisionview.set_size_request(width, height)
 
368
 
        self._save_size_on_destroy(self.revisionview, 'viz-revisionview-size')
 
 
305
        (width, height) = self.get_size()
 
 
306
        self.revisionview.set_size_request(width, int(height / 2.5))
 
369
307
        self.revisionview.show()
 
370
308
        self.revisionview.set_show_callback(self._show_clicked_cb)
 
371
309
        self.revisionview.connect('notify::revision', self._go_clicked_cb)
 
372
310
        self.treeview.connect('tag-added', lambda w, t, r: self.revisionview.update_tags())
 
373
 
        self.diff_paned.pack1(self.revisionview)
 
375
 
        from bzrlib.plugins.gtk.diff import DiffWidget
 
376
 
        self.diff = DiffWidget()
 
377
 
        self.diff_paned.pack2(self.diff)
 
379
 
        self.diff_paned.show_all()
 
380
 
        if self.config.get_user_option('viz-show-diffs') != 'True':
 
383
 
        return self.diff_paned
 
 
311
        return self.revisionview
 
385
313
    def _tag_selected_cb(self, menuitem, revid):
 
386
314
        self.treeview.set_revision_id(revid)
 
 
438
364
            self.revisionview.set_revision(revision)
 
439
365
            self.revisionview.set_children(children)
 
440
 
            self.update_diff_panel(revision, parents)
 
442
367
    def _tree_revision_activated(self, widget, path, col):
 
443
368
        # TODO: more than one parent
 
444
369
        """Callback for when a treeview row gets activated."""
 
 
446
371
        parents  = self.treeview.get_parents()
 
448
373
        if len(parents) == 0:
 
449
 
            parent_id = NULL_REVISION
 
451
376
            parent_id = parents[0]
 
453
378
        self.show_diff(revision.revision_id, parent_id)
 
454
379
        self.treeview.grab_focus()
 
 
381
    def _menu_diff_cb(self,w):
 
 
382
        (path, focus) = self.treeview.treeview.get_cursor()
 
 
383
        revid = self.treeview.model[path][treemodel.REVID]
 
 
385
        parentids = self.branch.repository.revision_parents(revid)
 
 
387
        if len(parentids) == 0:
 
 
388
            parentid = NULL_REVISION
 
 
390
            parentid = parentids[0]
 
 
392
        self.show_diff(revid,parentid)    
 
456
394
    def _back_clicked_cb(self, *args):
 
457
395
        """Callback for when the back button is clicked."""
 
458
396
        self.treeview.back()
 
 
471
409
        self.show_diff(revid, parentid)
 
472
410
        self.treeview.grab_focus()
 
 
412
    def _compare_with_cb(self,w):
 
 
413
        """Callback for revision 'compare with' menu. Will show a small
 
 
414
            dialog with branch revisions to compare with selected revision in TreeView"""
 
 
416
        from bzrlib.plugins.gtk.revbrowser import RevisionBrowser
 
 
418
        rb = RevisionBrowser(self.branch,self)
 
 
421
        if ret == gtk.RESPONSE_OK:          
 
 
422
            (path, focus) = self.treeview.treeview.get_cursor()
 
 
423
            revid = self.treeview.model[path][treemodel.REVID]
 
 
424
            self.show_diff(revid, rb.selected_revid)
 
474
428
    def _set_revision_cb(self, w, revision_id):
 
475
429
        self.treeview.set_revision_id(revision_id)
 
 
486
440
        self.treeview.set_property('compact', self.compact_view)
 
487
441
        self.treeview.refresh()
 
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)
 
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
 
 
443
    def _tag_revision_cb(self, w):
 
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. "
 
505
 
            if dialog.run() == gtk.RESPONSE_OK:
 
507
 
                index = _mod_index.index_url(self.branch.base)
 
512
 
        dialog = SearchDialog(index)
 
514
 
        if dialog.run() == gtk.RESPONSE_OK:
 
515
 
            self.set_revision(dialog.get_revision())
 
 
445
            self.treeview.set_sensitive(False)
 
 
446
            dialog = AddTagDialog(self.branch.repository, self.treeview.get_revision().revision_id, self.branch)
 
 
447
            response = dialog.run()
 
 
448
            if response != gtk.RESPONSE_NONE:
 
 
451
                if response == gtk.RESPONSE_OK:
 
 
452
                    self.treeview.add_tag(dialog.tagname, dialog._revid)
 
 
457
            self.treeview.set_sensitive(True)
 
519
459
    def _about_dialog_cb(self, w):
 
520
460
        from bzrlib.plugins.gtk.about import AboutDialog
 
 
528
468
    def _toolbar_visibility_changed(self, col):
 
529
469
        if col.get_active():
 
532
472
            self.toolbar.hide()
 
533
 
        self.config.set_user_option('viz-toolbar-visible', col.get_active())
 
535
 
    def _make_diff_nonzero_size(self):
 
536
 
        """make sure the diff isn't zero-width or zero-height"""
 
537
 
        alloc = self.diff.get_allocation()
 
538
 
        if (alloc.width < 10) or (alloc.height < 10):
 
539
 
            width, height = self.get_size()
 
540
 
            self.revisionview.set_size_request(width/3, int(height / 2.5))
 
542
 
    def _diff_visibility_changed(self, col):
 
543
 
        """Hide or show the diff panel."""
 
546
 
            self._make_diff_nonzero_size()
 
549
 
        self.config.set_user_option('viz-show-diffs', str(col.get_active()))
 
550
 
        self.update_diff_panel()
 
552
 
    def _diff_placement_changed(self, col):
 
553
 
        """Toggle the diff panel's position."""
 
554
 
        self.config.set_user_option('viz-wide-diffs', str(col.get_active()))
 
556
 
        old = self.paned.get_child2()
 
557
 
        self.paned.remove(old)
 
558
 
        self.paned.pack2(self.construct_bottom(), resize=True, shrink=False)
 
559
 
        self._make_diff_nonzero_size()
 
561
 
        self.treeview.emit('revision-selected')
 
563
 
    def _diff_wrap_changed(self, widget):
 
564
 
        """Toggle word wrap in the diff widget."""
 
565
 
        self.config.set_user_option('viz-wrap-diffs', widget.get_active())
 
566
 
        self.diff._on_wraplines_toggled(widget)
 
568
474
    def _show_about_cb(self, w):
 
569
475
        dialog = AboutDialog()
 
570
476
        dialog.connect('response', lambda d,r: d.destroy())
 
 
579
485
        if self.branch.supports_tags():
 
580
486
            tags = self.branch.tags.get_tag_dict().items()
 
581
 
            tags.sort(reverse=True)
 
582
489
            for tag, revid in tags:
 
583
490
                tag_image = gtk.Image()
 
584
491
                tag_image.set_from_file(icon_path('tag-16.png'))
 
585
492
                tag_item = gtk.ImageMenuItem(tag.replace('_', '__'))
 
586
493
                tag_item.set_image(tag_image)
 
587
494
                tag_item.connect('activate', self._tag_selected_cb, revid)
 
588
 
                tag_item.set_sensitive(self.treeview.has_revision_id(revid))
 
589
495
                menu.add(tag_item)
 
590
496
            self.go_menu_tags.set_submenu(menu)
 
 
596
502
        self.go_menu_tags.show_all()
 
598
 
    def _load_size(self, name):
 
599
 
        """Read and parse 'name' from self.config.
 
600
 
        The value is a string, formatted as WIDTHxHEIGHT
 
601
 
        Returns None, or (width, height)
 
603
 
        size = self.config.get_user_option(name)
 
605
 
            width, height = [int(num) for num in size.split('x')]
 
606
 
            # avoid writing config every time we start
 
610
 
    def show_diff(self, revid=None, parentid=NULL_REVISION):
 
 
504
    def show_diff(self, revid=None, parentid=None):
 
611
505
        """Open a new window to show a diff between the given revisions."""
 
612
506
        from bzrlib.plugins.gtk.diff import DiffWindow
 
613
507
        window = DiffWindow(parent=self)
 
 
510
            parentid = NULL_REVISION
 
615
512
        rev_tree    = self.branch.repository.revision_tree(revid)
 
616
513
        parent_tree = self.branch.repository.revision_tree(parentid)
 
618
 
        description = revid + " - " + self.branch._get_nick(local=True)
 
 
515
        description = revid + " - " + self.branch.nick
 
619
516
        window.set_diff(description, rev_tree, parent_tree)
 
622
 
    def update_diff_panel(self, revision=None, parents=None):
 
623
 
        """Show the current revision in the diff panel."""
 
624
 
        if self.config.get_user_option('viz-show-diffs') != 'True':
 
627
 
        if not revision: # default to selected row
 
628
 
            revision = self.treeview.get_revision()
 
629
 
        if revision == NULL_REVISION:
 
632
 
        if not parents: # default to selected row's parents
 
633
 
            parents  = self.treeview.get_parents()
 
634
 
        if len(parents) == 0:
 
635
 
            parent_id = NULL_REVISION
 
637
 
            parent_id = parents[0]
 
639
 
        rev_tree    = self.branch.repository.revision_tree(revision.revision_id)
 
640
 
        parent_tree = self.branch.repository.revision_tree(parent_id)
 
642
 
        self.diff.set_diff(rev_tree, parent_tree)
 
643
 
        if self.config.get_user_option('viz-wrap-diffs') == 'True':
 
644
 
            self.diff._on_wraplines_toggled(wrap=True)