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