/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 viz/diffwin.py

  • Committer: Jelmer Vernooij
  • Date: 2006-05-19 16:37:13 UTC
  • Revision ID: jelmer@samba.org-20060519163713-be77b31c72cbc7e8
Move visualisation code to a separate directory, preparing for bundling 
the GTK+ plugins for bzr.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
1
2
# -*- coding: UTF-8 -*-
2
3
"""Difference window.
3
4
 
11
12
 
12
13
from cStringIO import StringIO
13
14
 
14
 
import pygtk
15
 
pygtk.require("2.0")
16
15
import gtk
17
16
import pango
18
 
import os
19
 
import re
20
 
import sys
21
17
 
22
18
try:
23
19
    import gtksourceview
24
20
    have_gtksourceview = True
25
21
except ImportError:
26
22
    have_gtksourceview = False
27
 
try:
28
 
    import gconf
29
 
    have_gconf = True
30
 
except ImportError:
31
 
    have_gconf = False
32
 
 
33
 
import bzrlib
34
 
 
 
23
 
 
24
from bzrlib.delta import compare_trees
35
25
from bzrlib.diff import show_diff_trees
36
 
from bzrlib.errors import NoSuchFile
37
 
from bzrlib.trace import warning
38
26
 
39
27
 
40
28
class DiffWindow(gtk.Window):
44
32
    differences between two revisions on a branch.
45
33
    """
46
34
 
47
 
    def __init__(self):
 
35
    def __init__(self, app=None):
48
36
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
49
37
        self.set_border_width(0)
50
38
        self.set_title("bzrk diff")
51
39
 
 
40
        self.app = app
 
41
 
52
42
        # Use two thirds of the screen by default
53
43
        screen = self.get_screen()
54
44
        monitor = screen.get_monitor_geometry(0)
60
50
 
61
51
    def construct(self):
62
52
        """Construct the window contents."""
63
 
        # The   window  consists  of   a  pane   containing:  the
64
 
        # hierarchical list  of files on  the left, and  the diff
65
 
        # for the currently selected file on the right.
66
 
        pane = gtk.HPaned()
67
 
        self.add(pane)
68
 
        pane.show()
 
53
        hbox = gtk.HBox(spacing=6)
 
54
        hbox.set_border_width(12)
 
55
        self.add(hbox)
 
56
        hbox.show()
69
57
 
70
 
        # The file hierarchy: a scrollable treeview
71
58
        scrollwin = gtk.ScrolledWindow()
72
59
        scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
73
60
        scrollwin.set_shadow_type(gtk.SHADOW_IN)
74
 
        pane.pack1(scrollwin)
 
61
        hbox.pack_start(scrollwin, expand=False, fill=True)
75
62
        scrollwin.show()
76
63
 
77
64
        self.model = gtk.TreeStore(str, str)
89
76
        column.add_attribute(cell, "text", 0)
90
77
        self.treeview.append_column(column)
91
78
 
92
 
        # The diffs of the  selected file: a scrollable source or
93
 
        # text view
 
79
 
94
80
        scrollwin = gtk.ScrolledWindow()
95
81
        scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
96
82
        scrollwin.set_shadow_type(gtk.SHADOW_IN)
97
 
        pane.pack2(scrollwin)
 
83
        hbox.pack_start(scrollwin, expand=True, fill=True)
98
84
        scrollwin.show()
99
85
 
100
86
        if have_gtksourceview:
101
87
            self.buffer = gtksourceview.SourceBuffer()
102
88
            slm = gtksourceview.SourceLanguagesManager()
103
89
            gsl = slm.get_language_from_mime_type("text/x-patch")
104
 
            if have_gconf:
105
 
                self.apply_gedit_colors(gsl)
106
 
            self.apply_colordiff_colors(gsl)
107
90
            self.buffer.set_language(gsl)
108
91
            self.buffer.set_highlight(True)
109
92
 
117
100
        scrollwin.add(sourceview)
118
101
        sourceview.show()
119
102
 
120
 
    def set_diff(self, description, rev_tree, parent_tree):
 
103
    def set_diff(self, branch, revid, parentid):
121
104
        """Set the differences showed by this window.
122
105
 
123
106
        Compares the two trees and populates the window with the
124
107
        differences.
125
108
        """
126
 
        self.rev_tree = rev_tree
127
 
        self.parent_tree = parent_tree
 
109
        self.rev_tree = branch.repository.revision_tree(revid)
 
110
        self.parent_tree = branch.repository.revision_tree(parentid)
128
111
 
129
112
        self.model.clear()
130
 
        delta = self.rev_tree.changes_from(self.parent_tree)
 
113
        delta = compare_trees(self.parent_tree, self.rev_tree)
131
114
 
132
115
        self.model.append(None, [ "Complete Diff", "" ])
133
116
 
145
128
            titer = self.model.append(None, [ "Renamed", None ])
146
129
            for oldpath, newpath, id, kind, text_modified, meta_modified \
147
130
                    in delta.renamed:
148
 
                self.model.append(titer, [ oldpath, newpath ])
 
131
                self.model.append(titer, [ oldpath, oldpath ])
149
132
 
150
133
        if len(delta.modified):
151
134
            titer = self.model.append(None, [ "Modified", None ])
153
136
                self.model.append(titer, [ path, path ])
154
137
 
155
138
        self.treeview.expand_all()
156
 
        self.set_title(description + " - bzrk diff")
157
 
 
158
 
    def set_file(self, file_path):
159
 
        tv_path = None
160
 
        for data in self.model:
161
 
            for child in data.iterchildren():
162
 
                if child[0] == file_path or child[1] == file_path:
163
 
                    tv_path = child.path
164
 
                    break
165
 
        if tv_path is None:
166
 
            raise NoSuchFile(file_path)
167
 
        self.treeview.set_cursor(tv_path)
168
 
        self.treeview.scroll_to_cell(tv_path)
 
139
        self.set_title(revid + " - " + branch.nick + " - bzrk diff")
169
140
 
170
141
    def _treeview_cursor_cb(self, *args):
171
142
        """Callback for when the treeview cursor changes."""
178
149
 
179
150
        s = StringIO()
180
151
        show_diff_trees(self.parent_tree, self.rev_tree, s, specific_files)
181
 
        self.buffer.set_text(s.getvalue().decode(sys.getdefaultencoding(), 'replace'))
182
 
 
183
 
    @staticmethod
184
 
    def apply_gedit_colors(lang):
185
 
        """Set style for lang to that specified in gedit configuration.
186
 
 
187
 
        This method needs the gconf module.
188
 
        
189
 
        :param lang: a gtksourceview.SourceLanguage object.
190
 
        """
191
 
        GEDIT_SYNTAX_PATH = '/apps/gedit-2/preferences/syntax_highlighting'
192
 
        GEDIT_LANG_PATH = GEDIT_SYNTAX_PATH + '/' + lang.get_id()
193
 
 
194
 
        client = gconf.client_get_default()
195
 
        client.add_dir(GEDIT_LANG_PATH, gconf.CLIENT_PRELOAD_NONE)
196
 
 
197
 
        for tag in lang.get_tags():
198
 
            tag_id = tag.get_id()
199
 
            gconf_key = GEDIT_LANG_PATH + '/' + tag_id
200
 
            style_string = client.get_string(gconf_key)
201
 
 
202
 
            if style_string is None:
203
 
                continue
204
 
 
205
 
            # function to get a bool from a string that's either '0' or '1'
206
 
            string_bool = lambda x: bool(int(x))
207
 
 
208
 
            # style_string is a string like "2/#FFCCAA/#000000/0/1/0/0"
209
 
            # values are: mask, fg, bg, italic, bold, underline, strike
210
 
            # this packs them into (str_value, attr_name, conv_func) tuples
211
 
            items = zip(style_string.split('/'), ['mask', 'foreground',
212
 
                'background', 'italic', 'bold', 'underline', 'strikethrough' ],
213
 
                [ int, gtk.gdk.color_parse, gtk.gdk.color_parse, string_bool,
214
 
                    string_bool, string_bool, string_bool ]
215
 
            )
216
 
 
217
 
            style = gtksourceview.SourceTagStyle()
218
 
 
219
 
            # XXX The mask attribute controls whether the present values of
220
 
            # foreground and background color should in fact be used. Ideally
221
 
            # (and that's what gedit does), one could set all three attributes,
222
 
            # and let the TagStyle object figure out which colors to use.
223
 
            # However, in the GtkSourceview python bindings, the mask attribute
224
 
            # is read-only, and it's derived instead from the colors being
225
 
            # set or not. This means that we have to sometimes refrain from
226
 
            # setting fg or bg colors, depending on the value of the mask.
227
 
            # This code could go away if mask were writable.
228
 
            mask = int(items[0][0])
229
 
            if not (mask & 1): # GTK_SOURCE_TAG_STYLE_USE_BACKGROUND
230
 
                items[2:3] = []
231
 
            if not (mask & 2): # GTK_SOURCE_TAG_STYLE_USE_FOREGROUND
232
 
                items[1:2] = []
233
 
            items[0:1] = [] # skip the mask unconditionally
234
 
 
235
 
            for value, attr, func in items:
236
 
                try:
237
 
                    value = func(value)
238
 
                except ValueError:
239
 
                    warning('gconf key %s contains an invalid value: %s'
240
 
                            % gconf_key, value)
241
 
                else:
242
 
                    setattr(style, attr, value)
243
 
 
244
 
            lang.set_tag_style(tag_id, style)
245
 
 
246
 
    @staticmethod
247
 
    def apply_colordiff_colors(lang):
248
 
        """Set style colors for lang using the colordiff configuration file.
249
 
 
250
 
        Both ~/.colordiffrc and ~/.colordiffrc.bzr-gtk are read.
251
 
 
252
 
        :param lang: a "Diff" gtksourceview.SourceLanguage object.
253
 
        """
254
 
        colors = {}
255
 
 
256
 
        for f in ('~/.colordiffrc', '~/.colordiffrc.bzr-gtk'):
257
 
            f = os.path.expanduser(f)
258
 
            if os.path.exists(f):
259
 
                try:
260
 
                    f = file(f)
261
 
                except IOError, e:
262
 
                    warning('could not open file %s: %s' % (f, str(e)))
263
 
                else:
264
 
                    colors.update(DiffWindow.parse_colordiffrc(f))
265
 
                    f.close()
266
 
 
267
 
        if not colors:
268
 
            # ~/.colordiffrc does not exist
269
 
            return
270
 
 
271
 
        mapping = {
272
 
                # map GtkSourceView tags to colordiff names
273
 
                # since GSV is richer, accept new names for extra bits,
274
 
                # defaulting to old names if they're not present
275
 
                'Added@32@line': ['newtext'],
276
 
                'Removed@32@line': ['oldtext'],
277
 
                'Location': ['location', 'diffstuff'],
278
 
                'Diff@32@file': ['file', 'diffstuff'],
279
 
                'Special@32@case': ['specialcase', 'diffstuff'],
280
 
        }
281
 
 
282
 
        for tag in lang.get_tags():
283
 
            tag_id = tag.get_id()
284
 
            keys = mapping.get(tag_id, [])
285
 
            color = None
286
 
 
287
 
            for key in keys:
288
 
                color = colors.get(key, None)
289
 
                if color is not None:
290
 
                    break
291
 
 
292
 
            if color is None:
293
 
                continue
294
 
 
295
 
            style = gtksourceview.SourceTagStyle()
296
 
            try:
297
 
                style.foreground = gtk.gdk.color_parse(color)
298
 
            except ValueError:
299
 
                warning('not a valid color: %s' % color)
300
 
            else:
301
 
                lang.set_tag_style(tag_id, style)
302
 
 
303
 
    @staticmethod
304
 
    def parse_colordiffrc(fileobj):
305
 
        """Parse fileobj as a colordiff configuration file.
306
 
        
307
 
        :return: A dict with the key -> value pairs.
308
 
        """
309
 
        colors = {}
310
 
        for line in fileobj:
311
 
            if re.match(r'^\s*#', line):
312
 
                continue
313
 
            if '=' not in line:
314
 
                continue
315
 
            key, val = line.split('=', 1)
316
 
            colors[key.strip()] = val.strip()
317
 
        return colors
318
 
 
 
152
        self.buffer.set_text(s.getvalue())