/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: Jelmer Vernooij
  • Date: 2012-07-09 15:23:26 UTC
  • mto: This revision was merged to the branch mainline in revision 794.
  • Revision ID: jelmer@samba.org-20120709152326-dzxb8zoz0btull7n
Remove bzr-notify.

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