Skip to content

Commit

Permalink
Highlight changed parts in "Correct Texts"
Browse files Browse the repository at this point in the history
Closes #34
  • Loading branch information
otsaloma committed Jan 15, 2019
1 parent 314e5de commit e3ba93d
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 8 deletions.
1 change: 1 addition & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ Gaupol 1.5
==========

* [ ] Add support for building a Flatpak
* [x] Highlight changed parts in "Correct Texts" (#34)
* [x] Add keybinding Ctrl+I for toggling italic (#118)
* [x] Add keybinding Ctrl+I for toggling italic while editing (#118)
* [x] Change keybinding for Invert Selection to Ctrl+J
Expand Down
12 changes: 7 additions & 5 deletions gaupol/assistants.py
Original file line number Diff line number Diff line change
Expand Up @@ -833,17 +833,19 @@ def __init__(self, assistant):
self._init_tree_view()
self._init_values()

def _add_text_column(self, index, title):
def _add_text_column(self, index, ref_index, title):
"""Add a multiline text column to the tree view."""
renderer = gaupol.MultilineCellRenderer()
renderer = gaupol.MultilineDiffCellRenderer()
renderer.set_show_lengths(True)
renderer.props.editable = (index == 4)
renderer.props.ellipsize = Pango.EllipsizeMode.END
renderer.props.font = gaupol.util.get_font()
renderer.props.ref_type = ref_index - index
renderer.props.yalign = 0
renderer.props.xpad = 4
renderer.props.ypad = 4
column = Gtk.TreeViewColumn(title, renderer, text=index)
column = Gtk.TreeViewColumn(
title, renderer, text=index, ref_text=ref_index)
column.set_resizable(True)
column.set_expand(True)
self._tree_view.append_column(column)
Expand Down Expand Up @@ -892,8 +894,8 @@ def _init_tree_view(self):
if gaupol.conf.editor.use_zebra_stripes:
callback = self._on_renderer_set_background
column.set_cell_data_func(renderer, callback, None)
self._add_text_column(3, _("Original Text"))
self._add_text_column(4, _("Corrected Text"))
self._add_text_column(3, 4, _("Original Text"))
self._add_text_column(4, 3, _("Corrected Text"))
column = self._tree_view.get_column(2)
renderer = column.get_cells()[0]
renderer.connect("edited", self._on_tree_view_cell_edited)
Expand Down
3 changes: 3 additions & 0 deletions gaupol/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@
},
"general": {
"dark_theme": False,
"diff_color_change": "#ffff0033",
"diff_color_delete": "#ff555533",
"diff_color_insert": "#00ff0033",
"version": None,
},
"editor": {
Expand Down
2 changes: 2 additions & 0 deletions gaupol/renderers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@
from .float import FloatCellRenderer
from .integer import IntegerCellRenderer
from .multiline import MultilineCellRenderer
from .multiline import MultilineDiffCellRenderer
from .time import TimeCellRenderer

__all__ = (
"FloatCellRenderer",
"IntegerCellRenderer",
"MultilineCellRenderer",
"MultilineDiffCellRenderer",
"TimeCellRenderer",
)
76 changes: 73 additions & 3 deletions gaupol/renderers/multiline.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"""Cell renderer for multiline text data."""

import aeidon
import difflib
import gaupol
import math
import re
Expand All @@ -28,7 +29,7 @@
from gi.repository import GObject
from gi.repository import Gtk

__all__ = ("MultilineCellRenderer",)
__all__ = ("MultilineCellRenderer", "MultilineDiffCellRenderer")


class CellTextView(Gtk.TextView, Gtk.CellEditable):
Expand Down Expand Up @@ -221,11 +222,80 @@ def _text_to_markup(self, text, show_lengths, length_unit):
# We don't actually use the length_unit argument,
# but do need it accounted for in memoized values.
if not text: return ""
if not show_lengths:
return GLib.markup_escape_text(text)
text = GLib.markup_escape_text(text)
if not show_lengths: return text
lines = text.split("\n")
lengths = gaupol.ruler.get_lengths(text)
return "\n".join(("{} <small>[{:d}]</small>"
.format(lines[i], lengths[i])
if lines[i] else lines[i]
for i in range(len(lines))))


class MultilineDiffCellRenderer(MultilineCellRenderer):

"""Cell renderer for multiline diffed text data."""

__gtype_name__ = "MultilineDiffCellRenderer"

ref_text = GObject.Property(type=str, default="")
ref_type = GObject.Property(type=int, default=-1)

COLOR_CHANGE = gaupol.conf.general.diff_color_change
COLOR_DELETE = gaupol.conf.general.diff_color_delete
COLOR_INSERT = gaupol.conf.general.diff_color_insert

def __init__(self):
"""Initialize a :class:`MultilineDiffCellRenderer` instance."""
MultilineCellRenderer.__init__(self)
self._ref_text = ""

def _add_diff_markup(self, a, b):
"""Return `a` with markup for parts different from `b`."""
matcher = difflib.SequenceMatcher(a=a, b=b)
ops = matcher.get_opcodes()
return "".join(self._highlight(a[i1:i2], tag)
for tag, i1, i2, j1, j2 in ops)

def _get_diff_color(self, tag):
"""Return color to use to highlight changes of type `tag`."""
if tag == "insert" and self.props.ref_type > 0: return self.COLOR_INSERT
if tag == "insert" and self.props.ref_type < 0: return self.COLOR_DELETE
if tag == "delete" and self.props.ref_type > 0: return self.COLOR_DELETE
if tag == "delete" and self.props.ref_type < 0: return self.COLOR_INSERT
return self.COLOR_CHANGE

def _highlight(self, text, tag):
"""Return `text` as markup highlighting changes of type `tag`."""
if tag == "equal": return text
color = self._get_diff_color(tag)
return '<span background="{}">{}</span>'.format(color, text)

def _on_notify_text(self, *args):
"""Set markup by adding line lengths to text."""
# Since GTK+ 3.6, the notify::text signal seems to get
# emitted insanely often even if text hasn't changed at
# all. Let's try to keep this callback as fast as possible.
self._text = self.props.text
self._ref_text = self.props.ref_text
self.props.markup = self._text_to_markup(self.props.text,
self.props.ref_text,
self._show_lengths,
gaupol.conf.editor.length_unit)

@aeidon.deco.memoize(1000)
def _text_to_markup(self, text, ref_text, show_lengths, length_unit):
"""Return `text` rendered as markup for display."""
# We don't actually use the length_unit argument,
# but do need it accounted for in memoized values.
if not text: return ""
text = GLib.markup_escape_text(text)
ref_text = GLib.markup_escape_text(ref_text)
with aeidon.util.silent(Exception, tb=True):
text = self._add_diff_markup(text, ref_text)
if not show_lengths: return text
lines = text.split("\n")
lengths = gaupol.ruler.get_lengths(text)
return "\n".join(("{} <small>[{:d}]</small>"
.format(lines[i], lengths[i])
if lines[i] else lines[i]
Expand Down
23 changes: 23 additions & 0 deletions gaupol/renderers/test/test_multiline.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,26 @@ def run_renderer(self):

def setup_method(self, method):
self.renderer = gaupol.MultilineCellRenderer()


class TestMultilineDiffCellRenderer(gaupol.TestCase):

def run_renderer(self):
tree_view = Gtk.TreeView()
tree_view.set_headers_visible(False)
store = Gtk.ListStore(str, str)
store.append(("test", "test\ntest"))
tree_view.set_model(store)
self.renderer.props.editable = True
column = Gtk.TreeViewColumn("", self.renderer, text=1, ref_text=0)
tree_view.append_column(column)
window = Gtk.Window()
window.connect("delete-event", Gtk.main_quit)
window.set_position(Gtk.WindowPosition.CENTER)
window.set_default_size(240, 70)
window.add(tree_view)
window.show_all()
Gtk.main()

def setup_method(self, method):
self.renderer = gaupol.MultilineDiffCellRenderer()

0 comments on commit e3ba93d

Please sign in to comment.