/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 diff.py

  • Committer: Curtis Hovey
  • Date: 2011-07-31 16:50:29 UTC
  • mto: This revision was merged to the branch mainline in revision 741.
  • Revision ID: sinzui.is@verizon.net-20110731165029-9gixuqypi3lwapzm
Removed import_pygtk because gi does not impicitly call Main(). Inlined checks for gtk availablility.

Show diffs side-by-side

added added

removed removed

Lines of Context:
5
5
"""
6
6
 
7
7
__copyright__ = "Copyright 2005 Canonical Ltd."
8
 
__author__ = "Scott James Remnant <scott@ubuntu.com>"
 
8
__author__    = "Scott James Remnant <scott@ubuntu.com>"
9
9
 
10
10
 
11
11
from cStringIO import StringIO
12
12
 
 
13
from gi.repository import Gtk
 
14
from gi.repository import Pango
 
15
import os
 
16
import re
13
17
import sys
14
18
import inspect
 
19
try:
 
20
    from xml.etree.ElementTree import Element, SubElement, tostring
 
21
except ImportError:
 
22
    from elementtree.ElementTree import Element, SubElement, tostring
15
23
 
16
 
from gi.repository import Gtk
17
 
from gi.repository import Pango
18
24
try:
19
25
    from gi.repository import GtkSource
20
26
    have_gtksourceview = True
21
27
except ImportError:
22
28
    have_gtksourceview = False
 
29
try:
 
30
    from gi.repository import GConf
 
31
    have_gconf = True
 
32
except ImportError:
 
33
    have_gconf = False
23
34
 
24
35
from bzrlib import (
25
36
    errors,
27
38
    osutils,
28
39
    urlutils,
29
40
    workingtree,
30
 
    )
 
41
)
31
42
from bzrlib.diff import show_diff_trees
32
43
from bzrlib.patches import parse_patches
 
44
from bzrlib.trace import warning
33
45
from bzrlib.plugins.gtk.dialog import (
34
46
    error_dialog,
35
47
    info_dialog,
55
67
class DiffFileView(Gtk.ScrolledWindow):
56
68
    """Window for displaying diffs from a diff file"""
57
69
 
58
 
    SHOW_WIDGETS = True
59
 
 
60
70
    def __init__(self):
61
 
        super(DiffFileView, self).__init__()
 
71
        GObject.GObject.__init__(self)
62
72
        self.construct()
63
73
        self._diffs = {}
64
74
 
68
78
 
69
79
        if have_gtksourceview:
70
80
            self.buffer = GtkSource.Buffer()
71
 
            lang_manager = GtkSource.LanguageManager.get_default()
72
 
            language = lang_manager.guess_language(None, "text/x-patch")
73
 
            self.buffer.set_language(language)
 
81
            slm = GtkSource.LanguageManager()
 
82
            guess_language = getattr(GtkSource.LanguageManager, 
 
83
                "guess_language", fallback_guess_language)
 
84
            gsl = guess_language(slm, content_type="text/x-patch")
 
85
            if have_gconf:
 
86
                self.apply_gedit_colors(self.buffer)
 
87
            self.apply_colordiff_colors(self.buffer)
 
88
            self.buffer.set_language(gsl)
74
89
            self.buffer.set_highlight_syntax(True)
75
 
            self.sourceview = GtkSource.View(buffer=self.buffer)
 
90
 
 
91
            self.sourceview = GtkSource.View(self.buffer)
76
92
        else:
77
93
            self.buffer = Gtk.TextBuffer()
78
94
            self.sourceview = Gtk.TextView(self.buffer)
79
95
 
80
96
        self.sourceview.set_editable(False)
81
 
        self.sourceview.override_font(Pango.FontDescription("Monospace"))
 
97
        self.sourceview.modify_font(Pango.FontDescription("Monospace"))
82
98
        self.add(self.sourceview)
83
 
        if self.SHOW_WIDGETS:
84
 
            self.sourceview.show()
 
99
        self.sourceview.show()
 
100
 
 
101
    @staticmethod
 
102
    def apply_gedit_colors(buf):
 
103
        """Set style to that specified in gedit configuration.
 
104
 
 
105
        This method needs the gconf module.
 
106
 
 
107
        :param buf: a GtkSource.Buffer object.
 
108
        """
 
109
        GEDIT_SCHEME_PATH = '/apps/gedit-2/preferences/editor/colors/scheme'
 
110
        GEDIT_USER_STYLES_PATH = os.path.expanduser('~/.gnome2/gedit/styles')
 
111
 
 
112
        client = GConf.Client.get_default()
 
113
        style_scheme_name = client.get_string(GEDIT_SCHEME_PATH)
 
114
        if style_scheme_name is not None:
 
115
            style_scheme_mgr = GtkSource.StyleSchemeManager()
 
116
            style_scheme_mgr.append_search_path(GEDIT_USER_STYLES_PATH)
 
117
            
 
118
            style_scheme = style_scheme_mgr.get_scheme(style_scheme_name)
 
119
            
 
120
            if style_scheme is not None:
 
121
                buf.set_style_scheme(style_scheme)
 
122
 
 
123
    @classmethod
 
124
    def apply_colordiff_colors(klass, buf):
 
125
        """Set style colors for lang using the colordiff configuration file.
 
126
 
 
127
        Both ~/.colordiffrc and ~/.colordiffrc.bzr-gtk are read.
 
128
 
 
129
        :param buf: a "Diff" GtkSource.Buffer object.
 
130
        """
 
131
        scheme_manager = GtkSource.StyleSchemeManager()
 
132
        style_scheme = scheme_manager.get_scheme('colordiff')
 
133
        
 
134
        # if style scheme not found, we'll generate it from colordiffrc
 
135
        # TODO: reload if colordiffrc has changed.
 
136
        if style_scheme is None:
 
137
            colors = {}
 
138
 
 
139
            for f in ('~/.colordiffrc', '~/.colordiffrc.bzr-gtk'):
 
140
                f = os.path.expanduser(f)
 
141
                if os.path.exists(f):
 
142
                    try:
 
143
                        f = file(f)
 
144
                    except IOError, e:
 
145
                        warning('could not open file %s: %s' % (f, str(e)))
 
146
                    else:
 
147
                        colors.update(klass.parse_colordiffrc(f))
 
148
                        f.close()
 
149
 
 
150
            if not colors:
 
151
                # ~/.colordiffrc does not exist
 
152
                return
 
153
            
 
154
            mapping = {
 
155
                # map GtkSourceView2 scheme styles to colordiff names
 
156
                # since GSV is richer, accept new names for extra bits,
 
157
                # defaulting to old names if they're not present
 
158
                'diff:added-line': ['newtext'],
 
159
                'diff:removed-line': ['oldtext'],
 
160
                'diff:location': ['location', 'diffstuff'],
 
161
                'diff:file': ['file', 'diffstuff'],
 
162
                'diff:special-case': ['specialcase', 'diffstuff'],
 
163
            }
 
164
            
 
165
            converted_colors = {}
 
166
            for name, values in mapping.items():
 
167
                color = None
 
168
                for value in values:
 
169
                    color = colors.get(value, None)
 
170
                    if color is not None:
 
171
                        break
 
172
                if color is None:
 
173
                    continue
 
174
                converted_colors[name] = color
 
175
            
 
176
            # some xml magic to produce needed style scheme description
 
177
            e_style_scheme = Element('style-scheme')
 
178
            e_style_scheme.set('id', 'colordiff')
 
179
            e_style_scheme.set('_name', 'ColorDiff')
 
180
            e_style_scheme.set('version', '1.0')
 
181
            for name, color in converted_colors.items():
 
182
                style = SubElement(e_style_scheme, 'style')
 
183
                style.set('name', name)
 
184
                style.set('foreground', '#%s' % color)
 
185
            
 
186
            scheme_xml = tostring(e_style_scheme, 'UTF-8')
 
187
            if not os.path.exists(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles')):
 
188
                os.makedirs(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles'))
 
189
            file(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles/colordiff.xml'), 'w').write(scheme_xml)
 
190
            
 
191
            scheme_manager.force_rescan()
 
192
            style_scheme = scheme_manager.get_scheme('colordiff')
 
193
        
 
194
        buf.set_style_scheme(style_scheme)
 
195
 
 
196
    @staticmethod
 
197
    def parse_colordiffrc(fileobj):
 
198
        """Parse fileobj as a colordiff configuration file.
 
199
 
 
200
        :return: A dict with the key -> value pairs.
 
201
        """
 
202
        colors = {}
 
203
        for line in fileobj:
 
204
            if re.match(r'^\s*#', line):
 
205
                continue
 
206
            if '=' not in line:
 
207
                continue
 
208
            key, val = line.split('=', 1)
 
209
            colors[key.strip()] = val.strip()
 
210
        return colors
85
211
 
86
212
    def set_trees(self, rev_tree, parent_tree):
87
213
        self.rev_tree = rev_tree
92
218
#        self.parent_tree.lock_read()
93
219
#        self.rev_tree.lock_read()
94
220
#        try:
95
 
#            self.delta = iter_changes_to_status(
96
 
#               self.parent_tree, self.rev_tree)
 
221
#            self.delta = iter_changes_to_status(self.parent_tree, self.rev_tree)
97
222
#            self.path_to_status = {}
98
223
#            self.path_to_diff = {}
99
224
#            source_inv = self.parent_tree.inventory
100
225
#            target_inv = self.rev_tree.inventory
101
226
#            for (file_id, real_path, change_type, display_path) in self.delta:
102
 
#                self.path_to_status[real_path] = u'=== %s %s' % (
103
 
#                    change_type, display_path)
 
227
#                self.path_to_status[real_path] = u'=== %s %s' % (change_type, display_path)
104
228
#                if change_type in ('modified', 'renamed and modified'):
105
229
#                    source_ie = source_inv[file_id]
106
230
#                    target_ie = target_inv[file_id]
108
232
#                    source_ie.diff(internal_diff, *old path, *old_tree,
109
233
#                                   *new_path, target_ie, self.rev_tree,
110
234
#                                   sio)
111
 
#                    self.path_to_diff[real_path] =
 
235
#                    self.path_to_diff[real_path] = 
112
236
#
113
237
#        finally:
114
238
#            self.rev_tree.unlock()
128
252
    """This is the soft and chewy filling for a DiffWindow."""
129
253
 
130
254
    def __init__(self):
131
 
        super(DiffView, self).__init__()
 
255
        DiffFileView.__init__(self)
132
256
        self.rev_tree = None
133
257
        self.parent_tree = None
134
258
 
162
286
    """Diff widget
163
287
 
164
288
    """
165
 
 
166
 
    SHOW_WIDGETS = True
167
 
 
168
289
    def __init__(self):
169
290
        super(DiffWidget, self).__init__()
170
291
 
173
294
        scrollwin.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
174
295
        scrollwin.set_shadow_type(Gtk.ShadowType.IN)
175
296
        self.pack1(scrollwin)
176
 
        if self.SHOW_WIDGETS:
177
 
            scrollwin.show()
178
 
 
 
297
        scrollwin.show()
 
298
        
179
299
        self.model = Gtk.TreeStore(str, str)
180
 
        self.treeview = Gtk.TreeView(model=self.model)
 
300
        self.treeview = Gtk.TreeView(self.model)
181
301
        self.treeview.set_headers_visible(False)
182
302
        self.treeview.set_search_column(1)
183
303
        self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
184
304
        scrollwin.add(self.treeview)
185
 
        if self.SHOW_WIDGETS:
186
 
            self.treeview.show()
 
305
        self.treeview.show()
187
306
 
188
307
        cell = Gtk.CellRendererText()
189
308
        cell.set_property("width-chars", 20)
190
309
        column = Gtk.TreeViewColumn()
191
 
        column.pack_start(cell, True)
 
310
        column.pack_start(cell, True, True, 0)
192
311
        column.add_attribute(cell, "text", 0)
193
312
        self.treeview.append_column(column)
194
313
 
204
323
        if getattr(self, 'diff_view', None) is None:
205
324
            self.diff_view = DiffFileView()
206
325
            self.pack2(self.diff_view)
207
 
        if self.SHOW_WIDGETS:
208
 
            self.diff_view.show()
 
326
        self.diff_view.show()
209
327
        for oldname, newname, patch in sections:
210
328
            self.diff_view._diffs[newname] = str(patch)
211
329
            if newname is None:
222
340
        if getattr(self, 'diff_view', None) is None:
223
341
            self.diff_view = DiffView()
224
342
            self.pack2(self.diff_view)
225
 
        if self.SHOW_WIDGETS:
226
 
            self.diff_view.show()
 
343
        self.diff_view.show()
227
344
        self.diff_view.set_trees(rev_tree, parent_tree)
228
345
        self.rev_tree = rev_tree
229
346
        self.parent_tree = parent_tree
231
348
        self.model.clear()
232
349
        delta = self.rev_tree.changes_from(self.parent_tree)
233
350
 
234
 
        self.model.append(None, ["Complete Diff", ""])
 
351
        self.model.append(None, [ "Complete Diff", "" ])
235
352
 
236
353
        if len(delta.added):
237
 
            titer = self.model.append(None, ["Added", None])
 
354
            titer = self.model.append(None, [ "Added", None ])
238
355
            for path, id, kind in delta.added:
239
 
                self.model.append(titer, [path, path])
 
356
                self.model.append(titer, [ path, path ])
240
357
 
241
358
        if len(delta.removed):
242
 
            titer = self.model.append(None, ["Removed", None])
 
359
            titer = self.model.append(None, [ "Removed", None ])
243
360
            for path, id, kind in delta.removed:
244
 
                self.model.append(titer, [path, path])
 
361
                self.model.append(titer, [ path, path ])
245
362
 
246
363
        if len(delta.renamed):
247
 
            titer = self.model.append(None, ["Renamed", None])
 
364
            titer = self.model.append(None, [ "Renamed", None ])
248
365
            for oldpath, newpath, id, kind, text_modified, meta_modified \
249
366
                    in delta.renamed:
250
 
                self.model.append(titer, [oldpath, newpath])
 
367
                self.model.append(titer, [ oldpath, newpath ])
251
368
 
252
369
        if len(delta.modified):
253
 
            titer = self.model.append(None, ["Modified", None])
 
370
            titer = self.model.append(None, [ "Modified", None ])
254
371
            for path, id, kind, text_modified, meta_modified in delta.modified:
255
 
                self.model.append(titer, [path, path])
 
372
                self.model.append(titer, [ path, path ])
256
373
 
257
374
        self.treeview.expand_all()
258
375
        self.diff_view.show_diff(None)
267
384
                    break
268
385
        if tv_path is None:
269
386
            raise errors.NoSuchFile(file_path)
270
 
        self.treeview.set_cursor(tv_path, None, False)
 
387
        self.treeview.set_cursor(tv_path)
271
388
        self.treeview.scroll_to_cell(tv_path)
272
389
 
273
390
    def _treeview_cursor_cb(self, *args):
274
391
        """Callback for when the treeview cursor changes."""
275
392
        (path, col) = self.treeview.get_cursor()
276
 
        if path is None:
277
 
            return
278
 
        specific_files = [self.model[path][1]]
279
 
        if specific_files == [None]:
280
 
            return
281
 
        elif specific_files == [""]:
 
393
        specific_files = [ self.model[path][1] ]
 
394
        if specific_files == [ None ]:
 
395
            return
 
396
        elif specific_files == [ "" ]:
282
397
            specific_files = None
283
 
 
 
398
        
284
399
        self.diff_view.show_diff(specific_files)
285
 
 
 
400
    
286
401
    def _on_wraplines_toggled(self, widget=None, wrap=False):
287
402
        """Callback for when the wrap lines checkbutton is toggled"""
288
403
        if wrap or widget.get_active():
290
405
        else:
291
406
            self.diff_view.sourceview.set_wrap_mode(Gtk.WrapMode.NONE)
292
407
 
293
 
 
294
408
class DiffWindow(Window):
295
409
    """Diff window.
296
410
 
298
412
    differences between two revisions on a branch.
299
413
    """
300
414
 
301
 
    SHOW_WIDGETS = True
302
 
 
303
415
    def __init__(self, parent=None, operations=None):
304
 
        super(DiffWindow, self).__init__(parent=parent)
 
416
        Window.__init__(self, parent)
305
417
        self.set_border_width(0)
306
 
        self.set_title("bzr diff")
 
418
        self.set_title("bzrk diff")
307
419
 
308
420
        # Use two thirds of the screen by default
309
421
        screen = self.get_screen()
317
429
        """Construct the window contents."""
318
430
        self.vbox = Gtk.VBox()
319
431
        self.add(self.vbox)
320
 
        if self.SHOW_WIDGETS:
321
 
            self.vbox.show()
 
432
        self.vbox.show()
322
433
        self.diff = DiffWidget()
323
434
        self.vbox.pack_end(self.diff, True, True, 0)
324
 
        if self.SHOW_WIDGETS:
325
 
            self.diff.show_all()
 
435
        self.diff.show_all()
326
436
        # Build after DiffWidget to connect signals
327
437
        menubar = self._get_menu_bar()
328
438
        self.vbox.pack_start(menubar, False, False, 0)
329
439
        hbox = self._get_button_bar(operations)
330
440
        if hbox is not None:
331
441
            self.vbox.pack_start(hbox, False, True, 0)
332
 
 
 
442
        
 
443
    
333
444
    def _get_menu_bar(self):
334
445
        menubar = Gtk.MenuBar()
335
446
        # View menu
336
 
        mb_view = Gtk.MenuItem.new_with_mnemonic(_i18n("_View"))
 
447
        mb_view = Gtk.MenuItem(_i18n("_View"))
337
448
        mb_view_menu = Gtk.Menu()
338
 
        mb_view_wrapsource = Gtk.CheckMenuItem.new_with_mnemonic(
339
 
            _i18n("Wrap _Long Lines"))
 
449
        mb_view_wrapsource = Gtk.CheckMenuItem(_i18n("Wrap _Long Lines"))
340
450
        mb_view_wrapsource.connect('activate', self.diff._on_wraplines_toggled)
 
451
        mb_view_wrapsource.show()
341
452
        mb_view_menu.append(mb_view_wrapsource)
 
453
        mb_view.show()
342
454
        mb_view.set_submenu(mb_view_menu)
 
455
        mb_view.show()
343
456
        menubar.append(mb_view)
344
 
        if self.SHOW_WIDGETS:
345
 
            menubar.show_all()
 
457
        menubar.show()
346
458
        return menubar
347
 
 
 
459
    
348
460
    def _get_button_bar(self, operations):
349
461
        """Return a button bar to use.
350
462
 
356
468
        hbox.set_layout(Gtk.ButtonBoxStyle.START)
357
469
        for title, method in operations:
358
470
            merge_button = Gtk.Button(title)
359
 
            if self.SHOW_WIDGETS:
360
 
                merge_button.show()
 
471
            merge_button.show()
361
472
            merge_button.set_relief(Gtk.ReliefStyle.NONE)
362
473
            merge_button.connect("clicked", method)
363
 
            hbox.pack_start(merge_button, False, True, 0)
364
 
        if self.SHOW_WIDGETS:
365
 
            hbox.show()
 
474
            hbox.pack_start(merge_button, expand=False, fill=True)
 
475
        hbox.show()
366
476
        return hbox
367
477
 
368
478
    def _get_merge_target(self):
469
579
class MergeDirectiveController(DiffController):
470
580
 
471
581
    def __init__(self, path, directive, window=None):
472
 
        super(MergeDirectiveController, self).__init__(
473
 
            path, directive.patch.splitlines(True), window)
 
582
        DiffController.__init__(self, path, directive.patch.splitlines(True),
 
583
                                window)
474
584
        self.directive = directive
475
585
        self.merge_target = None
476
586