/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
# -*- coding: UTF-8 -*-
2
"""Difference window.
3
4
This module contains the code to manage the diff window which shows
5
the changes made between two revisions on a branch.
6
"""
7
8
__copyright__ = "Copyright © 2005 Canonical Ltd."
9
__author__    = "Scott James Remnant <scott@ubuntu.com>"
10
11
12
from cStringIO import StringIO
13
252 by Aaron Bentley
Fix test suite
14
import pygtk
15
pygtk.require("2.0")
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
16
import gtk
17
import pango
232.1.1 by Adeodato Simó
Read ~/.colordiffrc to set colors in the diff window.
18
import os
19
import re
76 by Jelmer Vernooij
Replace non-UTF8 characters rather than generating an exception (fixes #44677).
20
import sys
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
21
22
try:
23
    import gtksourceview
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
71 by Szilveszter Farkas (Phanatic)
(phanatic) compare_trees deprecated in 0.9
33
import bzrlib
34
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
35
from bzrlib.diff import show_diff_trees
59.2.4 by Aaron Bentley
Teach gdiff to accept a single file argument
36
from bzrlib.errors import NoSuchFile
232.1.1 by Adeodato Simó
Read ~/.colordiffrc to set colors in the diff window.
37
from bzrlib.trace import warning
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
38
39
278.1.4 by John Arbash Meinel
Just playing around.
40
class DiffDisplay(gtk.ScrolledWindow):
41
    """This is the soft and chewy filling for a DiffWindow."""
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
42
51 by Jelmer Vernooij
Rework some of the parameters to DiffWindow.set_diff() to be
43
    def __init__(self):
278.1.4 by John Arbash Meinel
Just playing around.
44
        gtk.ScrolledWindow.__init__(self)
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
45
46
        self.construct()
278.1.4 by John Arbash Meinel
Just playing around.
47
        self.rev_tree = None
48
        self.parent_tree = None
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
49
50
    def construct(self):
278.1.4 by John Arbash Meinel
Just playing around.
51
        self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
52
        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
53
54
        if have_gtksourceview:
55
            self.buffer = gtksourceview.SourceBuffer()
56
            slm = gtksourceview.SourceLanguagesManager()
57
            gsl = slm.get_language_from_mime_type("text/x-patch")
232.1.3 by Adeodato Simó
Support setting diff colors from gedit's syntax highlighting config too.
58
            if have_gconf:
59
                self.apply_gedit_colors(gsl)
232.1.2 by Adeodato Simó
Rename apply_colordiffrc to apply_colordiff_colors, improve docstring.
60
            self.apply_colordiff_colors(gsl)
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
61
            self.buffer.set_language(gsl)
62
            self.buffer.set_highlight(True)
63
64
            sourceview = gtksourceview.SourceView(self.buffer)
65
        else:
66
            self.buffer = gtk.TextBuffer()
67
            sourceview = gtk.TextView(self.buffer)
68
69
        sourceview.set_editable(False)
70
        sourceview.modify_font(pango.FontDescription("Monospace"))
278.1.4 by John Arbash Meinel
Just playing around.
71
        self.add(sourceview)
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
72
        sourceview.show()
73
232.1.1 by Adeodato Simó
Read ~/.colordiffrc to set colors in the diff window.
74
    @staticmethod
232.1.3 by Adeodato Simó
Support setting diff colors from gedit's syntax highlighting config too.
75
    def apply_gedit_colors(lang):
76
        """Set style for lang to that specified in gedit configuration.
77
78
        This method needs the gconf module.
278.1.4 by John Arbash Meinel
Just playing around.
79
232.1.3 by Adeodato Simó
Support setting diff colors from gedit's syntax highlighting config too.
80
        :param lang: a gtksourceview.SourceLanguage object.
81
        """
82
        GEDIT_SYNTAX_PATH = '/apps/gedit-2/preferences/syntax_highlighting'
83
        GEDIT_LANG_PATH = GEDIT_SYNTAX_PATH + '/' + lang.get_id()
84
85
        client = gconf.client_get_default()
86
        client.add_dir(GEDIT_LANG_PATH, gconf.CLIENT_PRELOAD_NONE)
87
88
        for tag in lang.get_tags():
89
            tag_id = tag.get_id()
90
            gconf_key = GEDIT_LANG_PATH + '/' + tag_id
91
            style_string = client.get_string(gconf_key)
92
93
            if style_string is None:
94
                continue
95
96
            # function to get a bool from a string that's either '0' or '1'
97
            string_bool = lambda x: bool(int(x))
98
99
            # style_string is a string like "2/#FFCCAA/#000000/0/1/0/0"
100
            # values are: mask, fg, bg, italic, bold, underline, strike
101
            # this packs them into (str_value, attr_name, conv_func) tuples
102
            items = zip(style_string.split('/'), ['mask', 'foreground',
103
                'background', 'italic', 'bold', 'underline', 'strikethrough' ],
104
                [ int, gtk.gdk.color_parse, gtk.gdk.color_parse, string_bool,
105
                    string_bool, string_bool, string_bool ]
106
            )
107
108
            style = gtksourceview.SourceTagStyle()
109
110
            # XXX The mask attribute controls whether the present values of
111
            # foreground and background color should in fact be used. Ideally
112
            # (and that's what gedit does), one could set all three attributes,
113
            # and let the TagStyle object figure out which colors to use.
114
            # However, in the GtkSourceview python bindings, the mask attribute
115
            # is read-only, and it's derived instead from the colors being
116
            # set or not. This means that we have to sometimes refrain from
117
            # setting fg or bg colors, depending on the value of the mask.
118
            # This code could go away if mask were writable.
119
            mask = int(items[0][0])
120
            if not (mask & 1): # GTK_SOURCE_TAG_STYLE_USE_BACKGROUND
121
                items[2:3] = []
122
            if not (mask & 2): # GTK_SOURCE_TAG_STYLE_USE_FOREGROUND
123
                items[1:2] = []
124
            items[0:1] = [] # skip the mask unconditionally
125
126
            for value, attr, func in items:
127
                try:
128
                    value = func(value)
129
                except ValueError:
130
                    warning('gconf key %s contains an invalid value: %s'
131
                            % gconf_key, value)
132
                else:
133
                    setattr(style, attr, value)
134
135
            lang.set_tag_style(tag_id, style)
136
137
    @staticmethod
232.1.2 by Adeodato Simó
Rename apply_colordiffrc to apply_colordiff_colors, improve docstring.
138
    def apply_colordiff_colors(lang):
139
        """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.
140
141
        Both ~/.colordiffrc and ~/.colordiffrc.bzr-gtk are read.
142
232.1.2 by Adeodato Simó
Rename apply_colordiffrc to apply_colordiff_colors, improve docstring.
143
        :param lang: a "Diff" gtksourceview.SourceLanguage object.
232.1.1 by Adeodato Simó
Read ~/.colordiffrc to set colors in the diff window.
144
        """
145
        colors = {}
146
147
        for f in ('~/.colordiffrc', '~/.colordiffrc.bzr-gtk'):
148
            f = os.path.expanduser(f)
149
            if os.path.exists(f):
150
                try:
151
                    f = file(f)
152
                except IOError, e:
153
                    warning('could not open file %s: %s' % (f, str(e)))
154
                else:
278.1.4 by John Arbash Meinel
Just playing around.
155
                    colors.update(DiffDisplay.parse_colordiffrc(f))
232.1.1 by Adeodato Simó
Read ~/.colordiffrc to set colors in the diff window.
156
                    f.close()
157
158
        if not colors:
159
            # ~/.colordiffrc does not exist
160
            return
161
162
        mapping = {
163
                # map GtkSourceView tags to colordiff names
164
                # since GSV is richer, accept new names for extra bits,
165
                # defaulting to old names if they're not present
166
                'Added@32@line': ['newtext'],
167
                'Removed@32@line': ['oldtext'],
168
                'Location': ['location', 'diffstuff'],
169
                'Diff@32@file': ['file', 'diffstuff'],
170
                'Special@32@case': ['specialcase', 'diffstuff'],
171
        }
172
173
        for tag in lang.get_tags():
174
            tag_id = tag.get_id()
175
            keys = mapping.get(tag_id, [])
176
            color = None
177
178
            for key in keys:
179
                color = colors.get(key, None)
180
                if color is not None:
181
                    break
182
183
            if color is None:
184
                continue
185
186
            style = gtksourceview.SourceTagStyle()
187
            try:
188
                style.foreground = gtk.gdk.color_parse(color)
189
            except ValueError:
190
                warning('not a valid color: %s' % color)
191
            else:
192
                lang.set_tag_style(tag_id, style)
232.1.4 by Adeodato Simó
Add a test for parse_colordiffrc, and fix the function to handle empty lines.
193
194
    @staticmethod
195
    def parse_colordiffrc(fileobj):
196
        """Parse fileobj as a colordiff configuration file.
278.1.4 by John Arbash Meinel
Just playing around.
197
232.1.4 by Adeodato Simó
Add a test for parse_colordiffrc, and fix the function to handle empty lines.
198
        :return: A dict with the key -> value pairs.
199
        """
200
        colors = {}
201
        for line in fileobj:
202
            if re.match(r'^\s*#', line):
203
                continue
204
            if '=' not in line:
205
                continue
206
            key, val = line.split('=', 1)
207
            colors[key.strip()] = val.strip()
208
        return colors
209
278.1.4 by John Arbash Meinel
Just playing around.
210
    def set_trees(self, rev_tree, parent_tree):
211
        self.rev_tree = rev_tree
212
        self.parent_tree = parent_tree
213
214
    def show_diff(self, specific_files):
215
        s = StringIO()
216
        show_diff_trees(self.parent_tree, self.rev_tree, s, specific_files)
217
        self.buffer.set_text(s.getvalue().decode(sys.getdefaultencoding(), 'replace'))
218
219
220
class DiffWindow(gtk.Window):
221
    """Diff window.
222
223
    This object represents and manages a single window containing the
224
    differences between two revisions on a branch.
225
    """
226
227
    def __init__(self):
228
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
229
        self.set_border_width(0)
230
        self.set_title("bzrk diff")
231
232
        # Use two thirds of the screen by default
233
        screen = self.get_screen()
234
        monitor = screen.get_monitor_geometry(0)
235
        width = int(monitor.width * 0.66)
236
        height = int(monitor.height * 0.66)
237
        self.set_default_size(width, height)
238
239
        self.construct()
240
241
    def construct(self):
242
        """Construct the window contents."""
243
        # The   window  consists  of   a  pane   containing:  the
244
        # hierarchical list  of files on  the left, and  the diff
245
        # for the currently selected file on the right.
246
        pane = gtk.HPaned()
247
        self.add(pane)
248
        pane.show()
249
250
        # The file hierarchy: a scrollable treeview
251
        scrollwin = gtk.ScrolledWindow()
252
        scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
253
        scrollwin.set_shadow_type(gtk.SHADOW_IN)
254
        pane.pack1(scrollwin)
255
        scrollwin.show()
256
257
        self.model = gtk.TreeStore(str, str)
258
        self.treeview = gtk.TreeView(self.model)
259
        self.treeview.set_headers_visible(False)
260
        self.treeview.set_search_column(1)
261
        self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
262
        scrollwin.add(self.treeview)
263
        self.treeview.show()
264
265
        cell = gtk.CellRendererText()
266
        cell.set_property("width-chars", 20)
267
        column = gtk.TreeViewColumn()
268
        column.pack_start(cell, expand=True)
269
        column.add_attribute(cell, "text", 0)
270
        self.treeview.append_column(column)
271
272
        # The diffs of the  selected file: a scrollable source or
273
        # text view
274
        self.diff_win = DiffDisplay()
275
        pane.pack2(self.diff_win)
276
        self.diff_win.show()
277
278
    def set_diff(self, description, rev_tree, parent_tree):
279
        """Set the differences showed by this window.
280
281
        Compares the two trees and populates the window with the
282
        differences.
283
        """
284
        self.diff_win.set_trees(rev_tree, parent_tree)
285
        self.rev_tree = rev_tree
286
        self.parent_tree = parent_tree
287
288
        self.model.clear()
289
        delta = self.rev_tree.changes_from(self.parent_tree)
290
291
        self.model.append(None, [ "Complete Diff", "" ])
292
293
        if len(delta.added):
294
            titer = self.model.append(None, [ "Added", None ])
295
            for path, id, kind in delta.added:
296
                self.model.append(titer, [ path, path ])
297
298
        if len(delta.removed):
299
            titer = self.model.append(None, [ "Removed", None ])
300
            for path, id, kind in delta.removed:
301
                self.model.append(titer, [ path, path ])
302
303
        if len(delta.renamed):
304
            titer = self.model.append(None, [ "Renamed", None ])
305
            for oldpath, newpath, id, kind, text_modified, meta_modified \
306
                    in delta.renamed:
307
                self.model.append(titer, [ oldpath, newpath ])
308
309
        if len(delta.modified):
310
            titer = self.model.append(None, [ "Modified", None ])
311
            for path, id, kind, text_modified, meta_modified in delta.modified:
312
                self.model.append(titer, [ path, path ])
313
314
        self.treeview.expand_all()
315
        self.set_title(description + " - bzrk diff")
316
317
    def set_file(self, file_path):
318
        tv_path = None
319
        for data in self.model:
320
            for child in data.iterchildren():
321
                if child[0] == file_path or child[1] == file_path:
322
                    tv_path = child.path
323
                    break
324
        if tv_path is None:
325
            raise NoSuchFile(file_path)
326
        self.treeview.set_cursor(tv_path)
327
        self.treeview.scroll_to_cell(tv_path)
328
329
    def _treeview_cursor_cb(self, *args):
330
        """Callback for when the treeview cursor changes."""
331
        (path, col) = self.treeview.get_cursor()
332
        specific_files = [ self.model[path][1] ]
333
        if specific_files == [ None ]:
334
            return
335
        elif specific_files == [ "" ]:
336
            specific_files = None
337
338
        self.diff_win.show_diff(specific_files)