7
7
__copyright__ = "Copyright 2005 Canonical Ltd."
8
__author__ = "Scott James Remnant <scott@ubuntu.com>"
8
__author__ = "Scott James Remnant <scott@ubuntu.com>"
11
11
from cStringIO import StringIO
21
from xml.etree.ElementTree import Element, SubElement, tostring
23
from elementtree.ElementTree import Element, SubElement, tostring
16
from gi.repository import Gtk
17
from gi.repository import Pango
19
from gi.repository import GtkSource
27
20
have_gtksourceview = True
28
21
except ImportError:
29
22
have_gtksourceview = False
36
24
from bzrlib import (
37
26
merge as _mod_merge,
43
from bzrlib.diff import show_diff_trees, internal_diff
44
from bzrlib.errors import NoSuchFile
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 (
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
52
42
def fallback_guess_language(slm, content_type):
65
class DiffFileView(gtk.ScrolledWindow):
55
class DiffFileView(Gtk.ScrolledWindow):
66
56
"""Window for displaying diffs from a diff file"""
68
60
def __init__(self):
69
gtk.ScrolledWindow.__init__(self)
61
super(DiffFileView, self).__init__()
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)
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")
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)
89
self.sourceview = gtksourceview2.View(self.buffer)
75
self.sourceview = GtkSource.View(buffer=self.buffer)
91
self.buffer = gtk.TextBuffer()
92
self.sourceview = gtk.TextView(self.buffer)
77
self.buffer = Gtk.TextBuffer()
78
self.sourceview = Gtk.TextView(self.buffer)
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()
100
def apply_gedit_colors(buf):
101
"""Set style to that specified in gedit configuration.
103
This method needs the gconf module.
105
:param buf: a gtksourceview2.Buffer object.
107
GEDIT_SCHEME_PATH = '/apps/gedit-2/preferences/editor/colors/scheme'
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)
114
buf.set_style_scheme(style_scheme)
117
def apply_colordiff_colors(klass, buf):
118
"""Set style colors for lang using the colordiff configuration file.
120
Both ~/.colordiffrc and ~/.colordiffrc.bzr-gtk are read.
122
:param buf: a "Diff" gtksourceview2.Buffer object.
124
scheme_manager = gtksourceview2.StyleSchemeManager()
125
style_scheme = scheme_manager.get_scheme('colordiff')
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:
132
for f in ('~/.colordiffrc', '~/.colordiffrc.bzr-gtk'):
133
f = os.path.expanduser(f)
134
if os.path.exists(f):
138
warning('could not open file %s: %s' % (f, str(e)))
140
colors.update(klass.parse_colordiffrc(f))
144
# ~/.colordiffrc does not exist
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'],
158
converted_colors = {}
159
for name, values in mapping.items():
162
color = colors.get(value, None)
163
if color is not None:
167
converted_colors[name] = color
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)
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)
184
scheme_manager.force_rescan()
185
style_scheme = scheme_manager.get_scheme('colordiff')
187
buf.set_style_scheme(style_scheme)
190
def parse_colordiffrc(fileobj):
191
"""Parse fileobj as a colordiff configuration file.
193
:return: A dict with the key -> value pairs.
197
if re.match(r'^\s*#', line):
201
key, val = line.split('=', 1)
202
colors[key.strip()] = val.strip()
84
self.sourceview.show()
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()
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]
275
158
self.buffer.set_text(decoded.encode('UTF-8'))
278
class DiffWidget(gtk.HPaned):
161
class DiffWidget(Gtk.HPaned):
282
168
def __init__(self):
283
169
super(DiffWidget, self).__init__()
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)
292
self.model = gtk.TreeStore(str, str)
293
self.treeview = gtk.TreeView(self.model)
176
if self.SHOW_WIDGETS:
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)
185
if self.SHOW_WIDGETS:
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)
341
231
self.model.clear()
342
232
delta = self.rev_tree.changes_from(self.parent_tree)
344
self.model.append(None, [ "Complete Diff", "" ])
234
self.model.append(None, ["Complete Diff", ""])
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])
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])
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])
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])
367
257
self.treeview.expand_all()
368
258
self.diff_view.show_diff(None)
376
266
tv_path = child.path
378
268
if tv_path is None:
379
raise NoSuchFile(file_path)
380
self.treeview.set_cursor(tv_path)
269
raise errors.NoSuchFile(file_path)
270
self.treeview.set_cursor(tv_path, None, False)
381
271
self.treeview.scroll_to_cell(tv_path)
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 ]:
389
elif specific_files == [ "" ]:
278
specific_files = [self.model[path][1]]
279
if specific_files == [None]:
281
elif specific_files == [""]:
390
282
specific_files = None
392
284
self.diff_view.show_diff(specific_files)
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)
399
self.diff_view.sourceview.set_wrap_mode(gtk.WRAP_NONE)
291
self.diff_view.sourceview.set_wrap_mode(Gtk.WrapMode.NONE)
401
294
class DiffWindow(Window):
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)
320
if self.SHOW_WIDGETS:
426
322
self.diff = DiffWidget()
427
323
self.vbox.pack_end(self.diff, True, True, 0)
324
if self.SHOW_WIDGETS:
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)
437
333
def _get_menu_bar(self):
438
menubar = gtk.MenuBar()
334
menubar = Gtk.MenuBar()
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)
447
342
mb_view.set_submenu(mb_view_menu)
449
343
menubar.append(mb_view)
344
if self.SHOW_WIDGETS:
453
348
def _get_button_bar(self, operations):
454
349
"""Return a button bar to use.
458
353
if operations is 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)
465
merge_button.set_relief(gtk.RELIEF_NONE)
358
merge_button = Gtk.Button(title)
359
if self.SHOW_WIDGETS:
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)
363
hbox.pack_start(merge_button, False, True, 0)
364
if self.SHOW_WIDGETS:
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,
476
gtk.RESPONSE_CANCEL,))
369
d = Gtk.FileChooserDialog('Merge branch', self,
370
Gtk.FileChooserAction.SELECT_FOLDER,
371
buttons=(Gtk.STOCK_OK, Gtk.ResponseType.OK,
373
Gtk.ResponseType.CANCEL,))
479
if result != gtk.RESPONSE_OK:
376
if result != Gtk.ResponseType.OK:
480
377
raise SelectCancelled()
481
378
return d.get_current_folder_uri()
496
393
error_dialog('Error', str(e))
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,
503
gtk.RESPONSE_CANCEL,))
396
d = Gtk.FileChooserDialog('Save As', self,
397
Gtk.FileChooserAction.SAVE,
398
buttons=(Gtk.STOCK_OK, Gtk.ResponseType.OK,
400
Gtk.ResponseType.CANCEL,))
504
401
d.set_current_name(basename)
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())