/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
1
"""Difference window.
2
3
This module contains the code to manage the diff window which shows
4
the changes made between two revisions on a branch.
5
"""
6
613.1.1 by Vincent Ladeuil
Fix bug #286834 bu handling 'missing' files corner case.
7
__copyright__ = "Copyright 2005 Canonical Ltd."
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
8
__author__    = "Scott James Remnant <scott@ubuntu.com>"
9
10
11
from cStringIO import StringIO
12
252 by Aaron Bentley
Fix test suite
13
import pygtk
14
pygtk.require("2.0")
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
15
import gtk
16
import pango
232.1.1 by Adeodato Simó
Read ~/.colordiffrc to set colors in the diff window.
17
import os
18
import re
76 by Jelmer Vernooij
Replace non-UTF8 characters rather than generating an exception (fixes #44677).
19
import sys
635.3.3 by Szilveszter Farkas
Made apply_colordiff_colors() work with gtksourceview2.
20
from xml.etree.ElementTree import Element, SubElement, tostring
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
21
22
try:
635.3.1 by Szilveszter Farkas
Changed all occurences of gtksourceview to version 2.
23
    import gtksourceview2
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
24
    have_gtksourceview = True
25
except ImportError:
26
    have_gtksourceview = False
232.1.3 by Adeodato Simó
Support setting diff colors from gedit's syntax highlighting config too.
27
try:
28
    import gconf
29
    have_gconf = True
30
except ImportError:
31
    have_gconf = False
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
32
434 by Aaron Bentley
Better errors, merge directive saving
33
from bzrlib import (
34
    merge as _mod_merge,
35
    osutils,
36
    progress,
37
    urlutils,
38
    workingtree,
39
)
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
40
from bzrlib.diff import show_diff_trees, internal_diff
59.2.4 by Aaron Bentley
Teach gdiff to accept a single file argument
41
from bzrlib.errors import NoSuchFile
426 by Aaron Bentley
Start support for Merge Directives
42
from bzrlib.patches import parse_patches
232.1.1 by Adeodato Simó
Read ~/.colordiffrc to set colors in the diff window.
43
from bzrlib.trace import warning
475.1.2 by Vincent Ladeuil
Fix bug #187283 fix replacing _() by _i18n().
44
from bzrlib.plugins.gtk import _i18n
298.2.1 by Daniel Schierbeck
Refactored the GTK window code, creating a single base window class that handles keyboard events.
45
from bzrlib.plugins.gtk.window import Window
430 by Aaron Bentley
Handle conflicts appropriately
46
from dialog import error_dialog, info_dialog, warning_dialog
428 by Aaron Bentley
Get merging working
47
48
49
class SelectCancelled(Exception):
50
51
    pass
298.2.1 by Daniel Schierbeck
Refactored the GTK window code, creating a single base window class that handles keyboard events.
52
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
53
424 by Aaron Bentley
Add ghandle-patch
54
class DiffFileView(gtk.ScrolledWindow):
432 by Aaron Bentley
Misc updates
55
    """Window for displaying diffs from a diff file"""
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
56
51 by Jelmer Vernooij
Rework some of the parameters to DiffWindow.set_diff() to be
57
    def __init__(self):
278.1.4 by John Arbash Meinel
Just playing around.
58
        gtk.ScrolledWindow.__init__(self)
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
59
        self.construct()
424 by Aaron Bentley
Add ghandle-patch
60
        self._diffs = {}
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
61
62
    def construct(self):
278.1.4 by John Arbash Meinel
Just playing around.
63
        self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
64
        self.set_shadow_type(gtk.SHADOW_IN)
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
65
66
        if have_gtksourceview:
635.3.1 by Szilveszter Farkas
Changed all occurences of gtksourceview to version 2.
67
            self.buffer = gtksourceview2.Buffer()
68
            slm = gtksourceview2.LanguageManager()
69
            gsl = slm.guess_language(content_type="text/x-patch")
232.1.3 by Adeodato Simó
Support setting diff colors from gedit's syntax highlighting config too.
70
            if have_gconf:
635.3.2 by Szilveszter Farkas
Support gedit color schemes for gtksourceview2.
71
                self.apply_gedit_colors(self.buffer)
635.3.3 by Szilveszter Farkas
Made apply_colordiff_colors() work with gtksourceview2.
72
            self.apply_colordiff_colors(self.buffer)
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
73
            self.buffer.set_language(gsl)
635.3.2 by Szilveszter Farkas
Support gedit color schemes for gtksourceview2.
74
            self.buffer.set_highlight_syntax(True)
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
75
635.3.1 by Szilveszter Farkas
Changed all occurences of gtksourceview to version 2.
76
            self.sourceview = gtksourceview2.View(self.buffer)
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
77
        else:
78
            self.buffer = gtk.TextBuffer()
555.2.1 by Jasper Groenewegen
Modify DiffWidget to include a checkbutton to wrap long lines in the diff view
79
            self.sourceview = gtk.TextView(self.buffer)
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
80
555.2.1 by Jasper Groenewegen
Modify DiffWidget to include a checkbutton to wrap long lines in the diff view
81
        self.sourceview.set_editable(False)
82
        self.sourceview.modify_font(pango.FontDescription("Monospace"))
83
        self.add(self.sourceview)
84
        self.sourceview.show()
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
85
232.1.1 by Adeodato Simó
Read ~/.colordiffrc to set colors in the diff window.
86
    @staticmethod
635.3.2 by Szilveszter Farkas
Support gedit color schemes for gtksourceview2.
87
    def apply_gedit_colors(buf):
88
        """Set style to that specified in gedit configuration.
232.1.3 by Adeodato Simó
Support setting diff colors from gedit's syntax highlighting config too.
89
90
        This method needs the gconf module.
278.1.4 by John Arbash Meinel
Just playing around.
91
635.3.2 by Szilveszter Farkas
Support gedit color schemes for gtksourceview2.
92
        :param buf: a gtksourceview2.Buffer object.
232.1.3 by Adeodato Simó
Support setting diff colors from gedit's syntax highlighting config too.
93
        """
635.3.2 by Szilveszter Farkas
Support gedit color schemes for gtksourceview2.
94
        GEDIT_SCHEME_PATH = '/apps/gedit-2/preferences/editor/colors/scheme'
232.1.3 by Adeodato Simó
Support setting diff colors from gedit's syntax highlighting config too.
95
96
        client = gconf.client_get_default()
635.3.2 by Szilveszter Farkas
Support gedit color schemes for gtksourceview2.
97
        style_scheme_name = client.get_string(GEDIT_SCHEME_PATH)
98
        style_scheme = gtksourceview2.StyleSchemeManager().get_scheme(style_scheme_name)
99
        
100
        buf.set_style_scheme(style_scheme)
232.1.3 by Adeodato Simó
Support setting diff colors from gedit's syntax highlighting config too.
101
424 by Aaron Bentley
Add ghandle-patch
102
    @classmethod
635.3.3 by Szilveszter Farkas
Made apply_colordiff_colors() work with gtksourceview2.
103
    def apply_colordiff_colors(klass, buf):
232.1.2 by Adeodato Simó
Rename apply_colordiffrc to apply_colordiff_colors, improve docstring.
104
        """Set style colors for lang using the colordiff configuration file.
232.1.1 by Adeodato Simó
Read ~/.colordiffrc to set colors in the diff window.
105
106
        Both ~/.colordiffrc and ~/.colordiffrc.bzr-gtk are read.
107
635.3.3 by Szilveszter Farkas
Made apply_colordiff_colors() work with gtksourceview2.
108
        :param buf: a "Diff" gtksourceview2.Buffer object.
232.1.1 by Adeodato Simó
Read ~/.colordiffrc to set colors in the diff window.
109
        """
635.3.3 by Szilveszter Farkas
Made apply_colordiff_colors() work with gtksourceview2.
110
        scheme_manager = gtksourceview2.StyleSchemeManager()
111
        style_scheme = scheme_manager.get_scheme('colordiff')
112
        
113
        # if style scheme not found, we'll generate it from colordiffrc
114
        # TODO: reload if colordiffrc has changed.
115
        if style_scheme is None:
116
            colors = {}
117
118
            for f in ('~/.colordiffrc', '~/.colordiffrc.bzr-gtk'):
119
                f = os.path.expanduser(f)
120
                if os.path.exists(f):
121
                    try:
122
                        f = file(f)
123
                    except IOError, e:
124
                        warning('could not open file %s: %s' % (f, str(e)))
125
                    else:
126
                        colors.update(klass.parse_colordiffrc(f))
127
                        f.close()
128
129
            if not colors:
130
                # ~/.colordiffrc does not exist
131
                return
132
            
133
            mapping = {
134
                # map GtkSourceView2 scheme styles to colordiff names
232.1.1 by Adeodato Simó
Read ~/.colordiffrc to set colors in the diff window.
135
                # since GSV is richer, accept new names for extra bits,
136
                # defaulting to old names if they're not present
635.3.3 by Szilveszter Farkas
Made apply_colordiff_colors() work with gtksourceview2.
137
                'diff:added-line': ['newtext'],
138
                'diff:removed-line': ['oldtext'],
139
                'diff:location': ['location', 'diffstuff'],
140
                'diff:file': ['file', 'diffstuff'],
141
                'diff:special-case': ['specialcase', 'diffstuff'],
142
            }
143
            
144
            converted_colors = {}
145
            for name, values in mapping.items():
146
                color = None
147
                for value in values:
148
                    color = colors.get(value, None)
149
                    if color is not None:
150
                        break
151
                if color is None:
152
                    continue
153
                converted_colors[name] = color
154
            
155
            # some xml magic to produce needed style scheme description
156
            e_style_scheme = Element('style-scheme')
157
            e_style_scheme.set('id', 'colordiff')
158
            e_style_scheme.set('_name', 'ColorDiff')
159
            e_style_scheme.set('version', '1.0')
160
            for name, color in converted_colors.items():
161
                style = SubElement(e_style_scheme, 'style')
162
                style.set('name', name)
163
                style.set('foreground', '#%s' % color)
164
            
165
            scheme_xml = tostring(e_style_scheme, 'UTF-8')
166
            if not os.path.exists(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles')):
167
                os.makedirs(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles'))
168
            file(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles/colordiff.xml'), 'w').write(scheme_xml)
169
            
170
            scheme_manager.force_rescan()
171
            style_scheme = scheme_manager.get_scheme('colordiff')
172
        
173
        buf.set_style_scheme(style_scheme)
232.1.4 by Adeodato Simó
Add a test for parse_colordiffrc, and fix the function to handle empty lines.
174
175
    @staticmethod
176
    def parse_colordiffrc(fileobj):
177
        """Parse fileobj as a colordiff configuration file.
278.1.4 by John Arbash Meinel
Just playing around.
178
232.1.4 by Adeodato Simó
Add a test for parse_colordiffrc, and fix the function to handle empty lines.
179
        :return: A dict with the key -> value pairs.
180
        """
181
        colors = {}
182
        for line in fileobj:
183
            if re.match(r'^\s*#', line):
184
                continue
185
            if '=' not in line:
186
                continue
187
            key, val = line.split('=', 1)
188
            colors[key.strip()] = val.strip()
189
        return colors
190
278.1.4 by John Arbash Meinel
Just playing around.
191
    def set_trees(self, rev_tree, parent_tree):
192
        self.rev_tree = rev_tree
193
        self.parent_tree = parent_tree
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
194
#        self._build_delta()
195
196
#    def _build_delta(self):
197
#        self.parent_tree.lock_read()
198
#        self.rev_tree.lock_read()
199
#        try:
450 by Aaron Bentley
Update to use new Tree.iter_changes
200
#            self.delta = iter_changes_to_status(self.parent_tree, self.rev_tree)
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
201
#            self.path_to_status = {}
202
#            self.path_to_diff = {}
203
#            source_inv = self.parent_tree.inventory
204
#            target_inv = self.rev_tree.inventory
205
#            for (file_id, real_path, change_type, display_path) in self.delta:
206
#                self.path_to_status[real_path] = u'=== %s %s' % (change_type, display_path)
207
#                if change_type in ('modified', 'renamed and modified'):
208
#                    source_ie = source_inv[file_id]
209
#                    target_ie = target_inv[file_id]
210
#                    sio = StringIO()
211
#                    source_ie.diff(internal_diff, *old path, *old_tree,
212
#                                   *new_path, target_ie, self.rev_tree,
213
#                                   sio)
214
#                    self.path_to_diff[real_path] = 
215
#
216
#        finally:
217
#            self.rev_tree.unlock()
218
#            self.parent_tree.unlock()
278.1.4 by John Arbash Meinel
Just playing around.
219
220
    def show_diff(self, specific_files):
426 by Aaron Bentley
Start support for Merge Directives
221
        sections = []
222
        if specific_files is None:
223
            self.buffer.set_text(self._diffs[None])
224
        else:
225
            for specific_file in specific_files:
226
                sections.append(self._diffs[specific_file])
227
            self.buffer.set_text(''.join(sections))
424 by Aaron Bentley
Add ghandle-patch
228
229
230
class DiffView(DiffFileView):
231
    """This is the soft and chewy filling for a DiffWindow."""
232
233
    def __init__(self):
234
        DiffFileView.__init__(self)
235
        self.rev_tree = None
236
        self.parent_tree = None
237
238
    def show_diff(self, specific_files):
432 by Aaron Bentley
Misc updates
239
        """Show the diff for the specified files"""
278.1.4 by John Arbash Meinel
Just playing around.
240
        s = StringIO()
278.1.18 by John Arbash Meinel
Start checking the diff view is correct.
241
        show_diff_trees(self.parent_tree, self.rev_tree, s, specific_files,
242
                        old_label='', new_label='',
243
                        # path_encoding=sys.getdefaultencoding()
244
                        # The default is utf-8, but we interpret the file
245
                        # contents as getdefaultencoding(), so we should
246
                        # probably try to make the paths in the same encoding.
247
                        )
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
248
        # str.decode(encoding, 'replace') doesn't do anything. Because if a
249
        # character is not valid in 'encoding' there is nothing to replace, the
250
        # 'replace' is for 'str.encode()'
251
        try:
252
            decoded = s.getvalue().decode(sys.getdefaultencoding())
253
        except UnicodeDecodeError:
254
            try:
255
                decoded = s.getvalue().decode('UTF-8')
256
            except UnicodeDecodeError:
257
                decoded = s.getvalue().decode('iso-8859-1')
258
                # This always works, because every byte has a valid
259
                # mapping from iso-8859-1 to Unicode
260
        # TextBuffer must contain pure UTF-8 data
261
        self.buffer.set_text(decoded.encode('UTF-8'))
278.1.4 by John Arbash Meinel
Just playing around.
262
263
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
264
class DiffWidget(gtk.HPaned):
265
    """Diff widget
278.1.4 by John Arbash Meinel
Just playing around.
266
267
    """
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
268
    def __init__(self):
269
        super(DiffWidget, self).__init__()
278.1.4 by John Arbash Meinel
Just playing around.
270
271
        # The file hierarchy: a scrollable treeview
272
        scrollwin = gtk.ScrolledWindow()
273
        scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
274
        scrollwin.set_shadow_type(gtk.SHADOW_IN)
555.2.2 by Jasper Groenewegen
Change from checkbox to menu item in bzr vis, add menu bar + item in gdiff
275
        self.pack1(scrollwin)
278.1.4 by John Arbash Meinel
Just playing around.
276
        scrollwin.show()
555.2.1 by Jasper Groenewegen
Modify DiffWidget to include a checkbutton to wrap long lines in the diff view
277
        
278.1.4 by John Arbash Meinel
Just playing around.
278
        self.model = gtk.TreeStore(str, str)
279
        self.treeview = gtk.TreeView(self.model)
280
        self.treeview.set_headers_visible(False)
281
        self.treeview.set_search_column(1)
282
        self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
283
        scrollwin.add(self.treeview)
284
        self.treeview.show()
285
286
        cell = gtk.CellRendererText()
287
        cell.set_property("width-chars", 20)
288
        column = gtk.TreeViewColumn()
289
        column.pack_start(cell, expand=True)
290
        column.add_attribute(cell, "text", 0)
291
        self.treeview.append_column(column)
292
429 by Aaron Bentley
Merge from mainline
293
    def set_diff_text(self, lines):
432 by Aaron Bentley
Misc updates
294
        """Set the current diff from a list of lines
295
296
        :param lines: The diff to show, in unified diff format
297
        """
278.1.4 by John Arbash Meinel
Just playing around.
298
        # The diffs of the  selected file: a scrollable source or
299
        # text view
487.2.3 by Aaron Bentley
Much refactoring
300
301
    def set_diff_text_sections(self, sections):
531.7.13 by Scott Scriven
Removed exception-swallowing hasattr()s.
302
        if getattr(self, 'diff_view', None) is None:
531.7.5 by Scott Scriven
Fixed DiffWidget so more than one call to set_diff() will work.
303
            self.diff_view = DiffFileView()
304
            self.pack2(self.diff_view)
278.1.12 by John Arbash Meinel
Delay computing the delta, and clean up some of the diff view names.
305
        self.diff_view.show()
487.2.3 by Aaron Bentley
Much refactoring
306
        for oldname, newname, patch in sections:
307
            self.diff_view._diffs[newname] = str(patch)
308
            if newname is None:
309
                newname = ''
426 by Aaron Bentley
Start support for Merge Directives
310
            self.model.append(None, [oldname, newname])
427 by Aaron Bentley
Add merge button when displaying merge directives
311
        self.diff_view.show_diff(None)
278.1.4 by John Arbash Meinel
Just playing around.
312
429 by Aaron Bentley
Merge from mainline
313
    def set_diff(self, rev_tree, parent_tree):
278.1.4 by John Arbash Meinel
Just playing around.
314
        """Set the differences showed by this window.
315
316
        Compares the two trees and populates the window with the
317
        differences.
318
        """
531.7.13 by Scott Scriven
Removed exception-swallowing hasattr()s.
319
        if getattr(self, 'diff_view', None) is None:
531.7.5 by Scott Scriven
Fixed DiffWidget so more than one call to set_diff() will work.
320
            self.diff_view = DiffView()
321
            self.pack2(self.diff_view)
424 by Aaron Bentley
Add ghandle-patch
322
        self.diff_view.show()
278.1.12 by John Arbash Meinel
Delay computing the delta, and clean up some of the diff view names.
323
        self.diff_view.set_trees(rev_tree, parent_tree)
278.1.4 by John Arbash Meinel
Just playing around.
324
        self.rev_tree = rev_tree
325
        self.parent_tree = parent_tree
326
327
        self.model.clear()
328
        delta = self.rev_tree.changes_from(self.parent_tree)
329
330
        self.model.append(None, [ "Complete Diff", "" ])
331
332
        if len(delta.added):
333
            titer = self.model.append(None, [ "Added", None ])
334
            for path, id, kind in delta.added:
335
                self.model.append(titer, [ path, path ])
336
337
        if len(delta.removed):
338
            titer = self.model.append(None, [ "Removed", None ])
339
            for path, id, kind in delta.removed:
340
                self.model.append(titer, [ path, path ])
341
342
        if len(delta.renamed):
343
            titer = self.model.append(None, [ "Renamed", None ])
344
            for oldpath, newpath, id, kind, text_modified, meta_modified \
345
                    in delta.renamed:
346
                self.model.append(titer, [ oldpath, newpath ])
347
348
        if len(delta.modified):
349
            titer = self.model.append(None, [ "Modified", None ])
350
            for path, id, kind, text_modified, meta_modified in delta.modified:
351
                self.model.append(titer, [ path, path ])
352
353
        self.treeview.expand_all()
531.7.5 by Scott Scriven
Fixed DiffWidget so more than one call to set_diff() will work.
354
        self.diff_view.show_diff(None)
278.1.4 by John Arbash Meinel
Just playing around.
355
356
    def set_file(self, file_path):
432 by Aaron Bentley
Misc updates
357
        """Select the current file to display"""
278.1.4 by John Arbash Meinel
Just playing around.
358
        tv_path = None
359
        for data in self.model:
360
            for child in data.iterchildren():
361
                if child[0] == file_path or child[1] == file_path:
362
                    tv_path = child.path
363
                    break
364
        if tv_path is None:
365
            raise NoSuchFile(file_path)
366
        self.treeview.set_cursor(tv_path)
367
        self.treeview.scroll_to_cell(tv_path)
368
369
    def _treeview_cursor_cb(self, *args):
370
        """Callback for when the treeview cursor changes."""
371
        (path, col) = self.treeview.get_cursor()
372
        specific_files = [ self.model[path][1] ]
373
        if specific_files == [ None ]:
374
            return
375
        elif specific_files == [ "" ]:
376
            specific_files = None
555.2.1 by Jasper Groenewegen
Modify DiffWidget to include a checkbutton to wrap long lines in the diff view
377
        
278.1.12 by John Arbash Meinel
Delay computing the delta, and clean up some of the diff view names.
378
        self.diff_view.show_diff(specific_files)
555.2.1 by Jasper Groenewegen
Modify DiffWidget to include a checkbutton to wrap long lines in the diff view
379
    
555.2.2 by Jasper Groenewegen
Change from checkbox to menu item in bzr vis, add menu bar + item in gdiff
380
    def _on_wraplines_toggled(self, widget=None, wrap=False):
555.2.1 by Jasper Groenewegen
Modify DiffWidget to include a checkbutton to wrap long lines in the diff view
381
        """Callback for when the wrap lines checkbutton is toggled"""
555.2.2 by Jasper Groenewegen
Change from checkbox to menu item in bzr vis, add menu bar + item in gdiff
382
        if wrap or widget.get_active():
555.2.1 by Jasper Groenewegen
Modify DiffWidget to include a checkbutton to wrap long lines in the diff view
383
            self.diff_view.sourceview.set_wrap_mode(gtk.WRAP_WORD)
384
        else:
385
            self.diff_view.sourceview.set_wrap_mode(gtk.WRAP_NONE)
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
386
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
387
class DiffWindow(Window):
388
    """Diff window.
389
390
    This object represents and manages a single window containing the
391
    differences between two revisions on a branch.
392
    """
393
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
394
    def __init__(self, parent=None, operations=None):
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
395
        Window.__init__(self, parent)
396
        self.set_border_width(0)
397
        self.set_title("bzrk diff")
398
399
        # Use two thirds of the screen by default
400
        screen = self.get_screen()
401
        monitor = screen.get_monitor_geometry(0)
402
        width = int(monitor.width * 0.66)
403
        height = int(monitor.height * 0.66)
404
        self.set_default_size(width, height)
487.2.3 by Aaron Bentley
Much refactoring
405
        self.construct(operations)
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
406
487.2.3 by Aaron Bentley
Much refactoring
407
    def construct(self, operations):
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
408
        """Construct the window contents."""
429 by Aaron Bentley
Merge from mainline
409
        self.vbox = gtk.VBox()
410
        self.add(self.vbox)
411
        self.vbox.show()
555.2.2 by Jasper Groenewegen
Change from checkbox to menu item in bzr vis, add menu bar + item in gdiff
412
        self.diff = DiffWidget()
413
        self.vbox.pack_end(self.diff, True, True, 0)
414
        self.diff.show_all()
415
        # Build after DiffWidget to connect signals
416
        menubar = self._get_menu_bar()
417
        self.vbox.pack_start(menubar, False, False, 0)
487.2.3 by Aaron Bentley
Much refactoring
418
        hbox = self._get_button_bar(operations)
429 by Aaron Bentley
Merge from mainline
419
        if hbox is not None:
555.2.2 by Jasper Groenewegen
Change from checkbox to menu item in bzr vis, add menu bar + item in gdiff
420
            self.vbox.pack_start(hbox, False, True, 0)
421
        
422
    
423
    def _get_menu_bar(self):
424
        menubar = gtk.MenuBar()
425
        # View menu
426
        mb_view = gtk.MenuItem(_i18n("_View"))
427
        mb_view_menu = gtk.Menu()
571 by Jasper Groenewegen
Merge addition of word wrap control to gdiff and visualise windows
428
        mb_view_wrapsource = gtk.CheckMenuItem(_i18n("Wrap _Long Lines"))
555.2.2 by Jasper Groenewegen
Change from checkbox to menu item in bzr vis, add menu bar + item in gdiff
429
        mb_view_wrapsource.connect('activate', self.diff._on_wraplines_toggled)
430
        mb_view_wrapsource.show()
431
        mb_view_menu.append(mb_view_wrapsource)
432
        mb_view.show()
433
        mb_view.set_submenu(mb_view_menu)
434
        mb_view.show()
435
        menubar.append(mb_view)
436
        menubar.show()
437
        return menubar
438
    
487.2.3 by Aaron Bentley
Much refactoring
439
    def _get_button_bar(self, operations):
432 by Aaron Bentley
Misc updates
440
        """Return a button bar to use.
441
442
        :return: None, meaning that no button bar will be used.
443
        """
487.2.3 by Aaron Bentley
Much refactoring
444
        if operations is None:
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
445
            return None
446
        hbox = gtk.HButtonBox()
447
        hbox.set_layout(gtk.BUTTONBOX_START)
487.2.3 by Aaron Bentley
Much refactoring
448
        for title, method in operations:
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
449
            merge_button = gtk.Button(title)
450
            merge_button.show()
451
            merge_button.set_relief(gtk.RELIEF_NONE)
452
            merge_button.connect("clicked", method)
453
            hbox.pack_start(merge_button, expand=False, fill=True)
454
        hbox.show()
455
        return hbox
456
457
    def _get_merge_target(self):
458
        d = gtk.FileChooserDialog('Merge branch', self,
459
                                  gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
460
                                  buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
461
                                           gtk.STOCK_CANCEL,
462
                                           gtk.RESPONSE_CANCEL,))
463
        try:
464
            result = d.run()
465
            if result != gtk.RESPONSE_OK:
466
                raise SelectCancelled()
467
            return d.get_current_folder_uri()
468
        finally:
469
            d.destroy()
470
487.2.5 by Aaron Bentley
Test successful merge
471
    def _merge_successful(self):
472
        # No conflicts found.
492 by Aaron Bentley
Allow saving from patch window, refactoring, testing.
473
        info_dialog(_i18n('Merge successful'),
474
                    _i18n('All changes applied successfully.'))
487.2.5 by Aaron Bentley
Test successful merge
475
487.2.7 by Aaron Bentley
Add more merge tests
476
    def _conflicts(self):
492 by Aaron Bentley
Allow saving from patch window, refactoring, testing.
477
        warning_dialog(_i18n('Conflicts encountered'),
478
                       _i18n('Please resolve the conflicts manually'
479
                             ' before committing.'))
487.2.7 by Aaron Bentley
Add more merge tests
480
487.2.8 by Aaron Bentley
Update error handling to use window
481
    def _handle_error(self, e):
482
        error_dialog('Error', str(e))
487.2.7 by Aaron Bentley
Add more merge tests
483
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
484
    def _get_save_path(self, basename):
485
        d = gtk.FileChooserDialog('Save As', self,
486
                                  gtk.FILE_CHOOSER_ACTION_SAVE,
487
                                  buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
488
                                           gtk.STOCK_CANCEL,
489
                                           gtk.RESPONSE_CANCEL,))
490
        d.set_current_name(basename)
491
        try:
492
            result = d.run()
493
            if result != gtk.RESPONSE_OK:
494
                raise SelectCancelled()
495
            return urlutils.local_path_from_url(d.get_uri())
496
        finally:
497
            d.destroy()
429 by Aaron Bentley
Merge from mainline
498
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
499
    def set_diff(self, description, rev_tree, parent_tree):
500
        """Set the differences showed by this window.
501
502
        Compares the two trees and populates the window with the
503
        differences.
504
        """
505
        self.diff.set_diff(rev_tree, parent_tree)
506
        self.set_title(description + " - bzrk diff")
507
508
    def set_file(self, file_path):
509
        self.diff.set_file(file_path)
510
511
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
512
class DiffController(object):
513
514
    def __init__(self, path, patch, window=None):
515
        self.path = path
516
        self.patch = patch
517
        if window is None:
518
            window = DiffWindow(operations=self._provide_operations())
519
            self.initialize_window(window)
487.2.3 by Aaron Bentley
Much refactoring
520
        self.window = window
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
521
522
    def initialize_window(self, window):
487.2.3 by Aaron Bentley
Much refactoring
523
        window.diff.set_diff_text_sections(self.get_diff_sections())
524
        window.set_title(self.path + " - diff")
525
526
    def get_diff_sections(self):
527
        yield "Complete Diff", None, ''.join(self.patch)
528
        for patch in parse_patches(self.patch):
529
            oldname = patch.oldname.split('\t')[0]
530
            newname = patch.newname.split('\t')[0]
531
            yield oldname, newname, str(patch)
487.2.2 by Aaron Bentley
Unify MergeDirectiveWindow and DiffWindow
532
533
    def perform_save(self, window):
534
        try:
535
            save_path = self.window._get_save_path(osutils.basename(self.path))
536
        except SelectCancelled:
537
            return
538
        source = open(self.path, 'rb')
539
        try:
540
            target = open(save_path, 'wb')
541
            try:
542
                osutils.pumpfile(source, target)
543
            finally:
544
                target.close()
545
        finally:
546
            source.close()
547
548
    def _provide_operations(self):
549
        return [('Save', self.perform_save)]
550
551
552
class MergeDirectiveController(DiffController):
553
487.2.8 by Aaron Bentley
Update error handling to use window
554
    def __init__(self, path, directive, window=None):
487.2.5 by Aaron Bentley
Test successful merge
555
        DiffController.__init__(self, path, directive.patch.splitlines(True),
556
                                window)
428 by Aaron Bentley
Get merging working
557
        self.directive = directive
487.2.1 by Aaron Bentley
Refactor merge directive window into MergeController
558
        self.merge_target = None
559
560
    def _provide_operations(self):
561
        return [('Merge', self.perform_merge), ('Save', self.perform_save)]
427 by Aaron Bentley
Add merge button when displaying merge directives
562
428 by Aaron Bentley
Get merging working
563
    def perform_merge(self, window):
487.2.1 by Aaron Bentley
Refactor merge directive window into MergeController
564
        if self.merge_target is None:
565
            try:
566
                self.merge_target = self.window._get_merge_target()
567
            except SelectCancelled:
568
                return
569
        tree = workingtree.WorkingTree.open(self.merge_target)
428 by Aaron Bentley
Get merging working
570
        tree.lock_write()
571
        try:
572
            try:
573
                merger, verified = _mod_merge.Merger.from_mergeable(tree,
574
                    self.directive, progress.DummyProgress())
575
                merger.check_basis(True)
576
                merger.merge_type = _mod_merge.Merge3Merger
430 by Aaron Bentley
Handle conflicts appropriately
577
                conflict_count = merger.do_merge()
428 by Aaron Bentley
Get merging working
578
                merger.set_pending()
430 by Aaron Bentley
Handle conflicts appropriately
579
                if conflict_count == 0:
487.2.5 by Aaron Bentley
Test successful merge
580
                    self.window._merge_successful()
430 by Aaron Bentley
Handle conflicts appropriately
581
                else:
487.2.7 by Aaron Bentley
Add more merge tests
582
                    self.window._conflicts()
430 by Aaron Bentley
Handle conflicts appropriately
583
                    # There are conflicts to be resolved.
487.2.1 by Aaron Bentley
Refactor merge directive window into MergeController
584
                self.window.destroy()
428 by Aaron Bentley
Get merging working
585
            except Exception, e:
487.2.8 by Aaron Bentley
Update error handling to use window
586
                self.window._handle_error(e)
428 by Aaron Bentley
Get merging working
587
        finally:
588
            tree.unlock()
589
427 by Aaron Bentley
Add merge button when displaying merge directives
590
450 by Aaron Bentley
Update to use new Tree.iter_changes
591
def iter_changes_to_status(source, target):
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
592
    """Determine the differences between trees.
593
450 by Aaron Bentley
Update to use new Tree.iter_changes
594
    This is a wrapper around iter_changes which just yields more
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
595
    understandable results.
596
597
    :param source: The source tree (basis tree)
598
    :param target: The target tree
599
    :return: A list of (file_id, real_path, change_type, display_path)
600
    """
601
    added = 'added'
602
    removed = 'removed'
603
    renamed = 'renamed'
604
    renamed_and_modified = 'renamed and modified'
605
    modified = 'modified'
606
    kind_changed = 'kind changed'
613.1.1 by Vincent Ladeuil
Fix bug #286834 bu handling 'missing' files corner case.
607
    missing = 'missing'
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
608
609
    # TODO: Handle metadata changes
610
611
    status = []
612
    target.lock_read()
613
    try:
614
        source.lock_read()
615
        try:
616
            for (file_id, paths, changed_content, versioned, parent_ids, names,
450 by Aaron Bentley
Update to use new Tree.iter_changes
617
                 kinds, executables) in target.iter_changes(source):
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
618
619
                # Skip the root entry if it isn't very interesting
620
                if parent_ids == (None, None):
621
                    continue
622
623
                change_type = None
624
                if kinds[0] is None:
625
                    source_marker = ''
626
                else:
627
                    source_marker = osutils.kind_marker(kinds[0])
613.1.1 by Vincent Ladeuil
Fix bug #286834 bu handling 'missing' files corner case.
628
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
629
                if kinds[1] is None:
613.1.1 by Vincent Ladeuil
Fix bug #286834 bu handling 'missing' files corner case.
630
                    if kinds[0] is None:
631
                        # We assume bzr will flag only files in that case,
632
                        # there may be a bzr bug there as only files seems to
633
                        # not receive any kind.
634
                        marker = osutils.kind_marker('file')
635
                    else:
636
                        marker = osutils.kind_marker(kinds[0])
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
637
                else:
638
                    marker = osutils.kind_marker(kinds[1])
639
640
                real_path = paths[1]
641
                if real_path is None:
642
                    real_path = paths[0]
643
                assert real_path is not None
644
645
                present_source = versioned[0] and kinds[0] is not None
646
                present_target = versioned[1] and kinds[1] is not None
647
613.1.1 by Vincent Ladeuil
Fix bug #286834 bu handling 'missing' files corner case.
648
                if kinds[0] is None and kinds[1] is None:
649
                    change_type = missing
650
                    display_path = real_path + marker
651
                elif present_source != present_target:
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
652
                    if present_target:
653
                        change_type = added
654
                    else:
655
                        assert present_source
656
                        change_type = removed
613.1.1 by Vincent Ladeuil
Fix bug #286834 bu handling 'missing' files corner case.
657
                    display_path = real_path + marker
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
658
                elif names[0] != names[1] or parent_ids[0] != parent_ids[1]:
659
                    # Renamed
660
                    if changed_content or executables[0] != executables[1]:
661
                        # and modified
662
                        change_type = renamed_and_modified
663
                    else:
664
                        change_type = renamed
665
                    display_path = (paths[0] + source_marker
666
                                    + ' => ' + paths[1] + marker)
667
                elif kinds[0] != kinds[1]:
668
                    change_type = kind_changed
669
                    display_path = (paths[0] + source_marker
670
                                    + ' => ' + paths[1] + marker)
605.1.1 by John Arbash Meinel
just use changed_content as a truth value, rather than checking 'is True'
671
                elif changed_content or executables[0] != executables[1]:
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
672
                    change_type = modified
613.1.1 by Vincent Ladeuil
Fix bug #286834 bu handling 'missing' files corner case.
673
                    display_path = real_path + marker
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
674
                else:
675
                    assert False, "How did we get here?"
676
677
                status.append((file_id, real_path, change_type, display_path))
678
        finally:
679
            source.unlock()
680
    finally:
681
        target.unlock()
682
683
    return status