diff --git a/.gitmodules b/.gitmodules index 3d867c35cc..03f667088e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -30,3 +30,6 @@ [submodule "deps/pugixml"] path = deps/pugixml url = https://github.com/zeux/pugixml.git +[submodule "deps/dtl"] + path = deps/dtl + url = https://github.com/cubicdaiya/dtl diff --git a/configure.ac b/configure.ac index 5b1c16c434..cd9ef7d5fa 100644 --- a/configure.ac +++ b/configure.ac @@ -170,7 +170,6 @@ PKG_CHECK_MODULES([ICU], [icu-uc icu-i18n], AC_MSG_ERROR([missing ICU library]) ]) - dnl we need GtkSpell and GTK+ >= 2 for this, check if we're compatible AC_MSG_CHECKING([if wxWidgets toolkit uses GTK+ 3]) AC_TRY_COMPILE([#include ], @@ -231,6 +230,12 @@ PKG_CHECK_MODULES([PUGIXML], [pugixml >= 1.9], dnl use bundled copy ]) +AC_CHECK_HEADERS([dtl/dtl.hpp], + [ ], + [ + AC_MSG_ERROR([missing dtl c++ header-only library]) + ]) + dnl Check for Compact Language Detector 2 dnl (used for better language detection and for non-English source languages) diff --git a/deps/dtl b/deps/dtl new file mode 160000 index 0000000000..831aeecf1f --- /dev/null +++ b/deps/dtl @@ -0,0 +1 @@ +Subproject commit 831aeecf1f8afa08cd9c24677f389183f6834876 diff --git a/src/sidebar.cpp b/src/sidebar.cpp index e3c1fd0237..6aa0c401bc 100644 --- a/src/sidebar.cpp +++ b/src/sidebar.cpp @@ -130,13 +130,14 @@ class OldMsgidSidebarBlock : public SidebarBlock public: OldMsgidSidebarBlock(Sidebar *parent) /// TRANSLATORS: "Previous" as in used in the past, now replaced with newer. - : SidebarBlock(parent, _("Previous source text:")) + : SidebarBlock(parent, _("Diff to previous source:")) { m_innerSizer->AddSpacer(PX(2)); - m_innerSizer->Add(new ExplanationLabel(parent, _("The old source text (before it changed during an update) that the now-inaccurate translation corresponds to.")), + m_innerSizer->Add(new ExplanationLabel(parent, _("The difference to old source text (before it changed during an update) that the now-inaccurate translation corresponds to.")), wxSizerFlags().Expand()); m_innerSizer->AddSpacer(PX(5)); - m_text = new SelectableAutoWrappingText(parent, ""); + //m_text = new SelectableAutoWrappingText(parent, ""); + m_text = new wxStaticText(parent, wxID_ANY, ""); m_innerSizer->Add(m_text, wxSizerFlags().Expand()); } @@ -147,11 +148,15 @@ class OldMsgidSidebarBlock : public SidebarBlock void Update(const CatalogItemPtr& item) override { - m_text->SetAndWrapLabel(item->GetOldMsgid()); + //m_text->SetAndWrapLabel(item->GetOldMsgid()); + m_text->SetLabelMarkup(Diff(item->GetOldMsgid(), item->GetString()).getMarkup()); + m_text->SetMinSize(wxSize(-1, 50)); + } private: - SelectableAutoWrappingText *m_text; + //SelectableAutoWrappingText *m_text; + wxStaticText *m_text; }; diff --git a/src/utility.cpp b/src/utility.cpp index 7f07f1dd89..0272ca0aa9 100644 --- a/src/utility.cpp +++ b/src/utility.cpp @@ -44,6 +44,8 @@ #include #endif +#include + #include "str_helpers.h" wxString EscapeMarkup(const wxString& str) @@ -117,6 +119,67 @@ wxFileName MakeFileName(const wxString& path) return fn; } +/// A convertor from dtl's to +Diff::Action dtl2DiffAction(dtl::edit_t lastType) { + switch (lastType) { + case dtl::SES_DELETE: return Diff::Action::Delete; + case dtl::SES_COMMON: return Diff::Action::Common; + case dtl::SES_ADD: return Diff::Action::Add; + } + // Shouldn't happend + throw std::invalid_argument("Unexpected dtl::edit_t value"); +} + +Diff::Diff(const wxString& from, const wxString& to) { + // Do the diff + dtl::Diff d(from, to); + d.compose(); + auto dtlSeq = d.getSes().getSequence(); + + // collapes per-symbol diff into string-size chunks + if (!dtlSeq.empty()) { + dtl::edit_t lastType = dtlSeq.front().second.type; + wxString curStr = dtlSeq.front().first; + + for ( auto it = std::next(dtlSeq.cbegin()); it != dtlSeq.cend(); ++it ) { + + if ( it->second.type != lastType ) { + ses.emplace_back( dtl2DiffAction(lastType), std::move(curStr) ); + curStr.Empty(); + lastType = it->second.type; + } + curStr.Append(it->first); + } + + ses.emplace_back( dtl2DiffAction(lastType), std::move(curStr) ); + curStr.Empty(); + } +} + +wxString Diff::getMarkup(const wxString &addColor, const wxString &deleteColor) { + wxString rv; + + for (const auto& el: ses) { + switch (el.first) { + case Diff::Action::Common: + rv.Append(EscapeMarkup(el.second)); + break; + case Diff::Action::Add: + rv.Append(""); + rv.Append(EscapeMarkup(el.second)); + rv.Append(""); + break; + case Diff::Action::Delete: + rv.Append(""); + rv.Append(EscapeMarkup(el.second)); + rv.Append(""); + break; + } + } + + return rv; +} + // ---------------------------------------------------------------------- // TempDirectory // ---------------------------------------------------------------------- diff --git a/src/utility.h b/src/utility.h index 48e8e74bec..f028077d8b 100644 --- a/src/utility.h +++ b/src/utility.h @@ -33,6 +33,7 @@ #endif #include +#include #include #include @@ -202,6 +203,39 @@ inline wxString MaskForType(const char *extensions, const wxString& description, return wxString::Format("%s|%s", description, extensions); } +/// A helper class to calculate a display diff of strings +class Diff +{ +public: + /// Constructs a Diff object with a edit sequence from a string + /// @arg from to the string @arg to + Diff(const wxString& from, const wxString& to); + + /// A type of element in the shortest edit sequence + enum class Action { + Common, ///< symbols are the same + Add, ///< symbols were added + Delete ///< symbols were removed + }; + + /// An element from the shortest edit sequence + typedef std::pair sequenceElement; + /// A type to represent a shortest edit sequence i.e. the sequence of + /// substrings with an action (add remove, don't change) attached to them + typedef std::vector sequence; + + /// @returns the shortest edit sequence in a form suitable for interpretation + const sequence& getSes() { return ses; }; + + /// @returns the diff ready to be displayed as a markup + /// @arg addColor a background color for a string being added + /// @arg deleteColor a background color for a string being removed + wxString getMarkup(const wxString& addColor=wxString("lightgreen"), + const wxString& deleteColor=wxString("pink")); + +private: + sequence ses; +}; // ---------------------------------------------------------------------- // TempDirectory @@ -262,7 +296,6 @@ class TempOutputFileFor wxString m_filenameFinal; }; - #ifdef __WXMSW__ /// Return filename safe for passing to CLI tools (gettext). /// Uses 8.3 short names to avoid Unicode and codepage issues.