Commit 80afe268 authored by Nicolas Fella's avatar Nicolas Fella Committed by Albert Astals Cid
Browse files

Implement event.change

This represents the newly entered data for each keystroke. This is often a single added character, but for cases like pasting text it can be more complex.

The PDF API reference doesn't specify any algorithm to use.

The algorithm used here works by iterating through both strings from the start until the first different character is encountered. Then the rest of the new text is considered the difference.

This doesn't produce the theoretically optimal/minimal diff, but seems to work well enough for practical application.

When text is removed the diff is empty
parent 69a2cc14
Pipeline #231550 passed with stage
in 6 minutes and 26 seconds
......@@ -19,7 +19,7 @@ build_ubuntu_20_04:
- apt-get update
- apt-get install --yes eatmydata
- eatmydata apt-get build-dep --yes --no-install-recommends okular
- eatmydata apt-get install --yes --no-install-recommends ninja-build
- eatmydata apt-get install --yes --no-install-recommends ninja-build qtbase5-private-dev
script:
- mkdir -p build && cd build
- cmake -DOKULAR_UI=desktop -G Ninja ..
......@@ -37,7 +37,7 @@ build_clazy_clang_tidy:
- apt-get update
- apt-get install --yes eatmydata
- eatmydata apt-get build-dep --yes --no-install-recommends okular
- eatmydata apt-get install --yes --no-install-recommends ninja-build clazy clang clang-tidy libkf5crash-dev libkf5purpose-dev kirigami2-dev libegl-dev jq
- eatmydata apt-get install --yes --no-install-recommends ninja-build clazy clang clang-tidy qtbase5-private-dev libkf5crash-dev libkf5purpose-dev kirigami2-dev libegl-dev jq
script:
- srcdir=`pwd` && mkdir -p /tmp/okular_build && cd /tmp/okular_build && CC=clang CXX=clazy CXXFLAGS="-Werror -Wno-deprecated-declarations" cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -G Ninja $srcdir && cat compile_commands.json | jq '[.[] | select(.file | contains("'"$srcdir"'"))]' > compile_commands.aux.json && cat compile_commands.aux.json | jq '[.[] | select(.file | contains("/synctex/")| not)]' > compile_commands.json && cp "$srcdir/.clang-tidy" .
......
......@@ -451,6 +451,7 @@ PUBLIC # these are included from the installed headers
KF5::ConfigGui
Qt5::PrintSupport
Qt5::Widgets
Qt5::CorePrivate
)
......
......@@ -29,6 +29,8 @@ class DocumentTest : public QObject
private Q_SLOTS:
void testCloseDuringRotationJob();
void testDocdataMigration();
void testDiff_data();
void testDiff();
};
// Test that we don't crash if the document is closed while a RotationJob
......@@ -126,5 +128,52 @@ void DocumentTest::testDocdataMigration()
delete m_document;
}
void DocumentTest::testDiff_data()
{
QTest::addColumn<QString>("oldVal");
QTest::addColumn<QString>("newVal");
QTest::addColumn<QString>("expectedDiff");
QTest::addRow("empty") << ""
<< ""
<< "";
QTest::addRow("a") << ""
<< "a"
<< "a";
QTest::addRow("ab") << "a"
<< "b"
<< "b";
QTest::addRow("ab2") << "a"
<< "ab"
<< "b";
QTest::addRow("kaesekuchen") << "Käse"
<< "Käsekuchen"
<< "kuchen";
QTest::addRow("replace") << "kuchen"
<< "wurst"
<< "wurst";
QTest::addRow("okular") << "Oku"
<< "Okular"
<< "lar";
QTest::addRow("removal1") << "a"
<< ""
<< "";
QTest::addRow("removal2") << "ab"
<< "a"
<< "";
QTest::addRow("unicode") << "☮🤌"
<< "☮🤌❤️"
<< "❤️";
}
void DocumentTest::testDiff()
{
QFETCH(QString, oldVal);
QFETCH(QString, newVal);
QFETCH(QString, expectedDiff);
QCOMPARE(Okular::DocumentPrivate::diff(oldVal, newVal), expectedDiff);
}
QTEST_MAIN(DocumentTest)
#include "documenttest.moc"
......@@ -47,6 +47,7 @@
#include <QUndoCommand>
#include <QWindow>
#include <QtAlgorithms>
#include <private/qstringiterator_p.h>
#include <KApplicationTrader>
#include <KAuthorized>
......@@ -4342,6 +4343,29 @@ void Document::processFormatAction(const Action *action, Okular::FormFieldText *
}
}
QString DocumentPrivate::diff(const QString &oldVal, const QString &newVal)
{
QString diff;
QStringIterator oldIt(oldVal);
QStringIterator newIt(newVal);
while (oldIt.hasNext() && newIt.hasNext()) {
QChar oldToken = oldIt.next();
QChar newToken = newIt.next();
if (oldToken != newToken) {
diff += newToken;
break;
}
}
while (newIt.hasNext()) {
diff += newIt.next();
}
return diff;
}
void Document::processKeystrokeAction(const Action *action, Okular::FormFieldText *fft, const QVariant &newValue)
{
if (action->actionType() != Action::Script) {
......@@ -4357,6 +4381,7 @@ void Document::processKeystrokeAction(const Action *action, Okular::FormFieldTex
}
std::shared_ptr<Event> event = Event::createKeystrokeEvent(fft, d->m_pagesVector[foundPage]);
event->setChange(DocumentPrivate::diff(fft->text(), newValue.toString()));
const ScriptAction *linkscript = static_cast<const ScriptAction *>(action);
......
......@@ -230,6 +230,8 @@ public:
void clearAndWaitForRequests();
OKULARCORE_EXPORT static QString diff(const QString &oldVal, const QString &newVal);
/*
* Executes a ScriptAction with the event passed as parameter.
*/
......
......@@ -36,6 +36,7 @@ public:
bool m_returnCode;
bool m_shiftModifier;
bool m_willCommit;
QString m_change;
};
Event::Event()
......@@ -181,6 +182,16 @@ void Event::setWillCommit(bool willCommit)
d->m_willCommit = willCommit;
}
QString Event::change() const
{
return d->m_change;
}
void Event::setChange(const QString &change)
{
d->m_change = change;
}
// static
std::shared_ptr<Event> Event::createFormCalculateEvent(FormField *target, Page *targetPage, FormField *source, Page *sourcePage, const QString &targetName)
{
......
......@@ -108,6 +108,9 @@ public:
bool willCommit() const;
void setWillCommit(bool willCommit);
QString change() const;
void setChange(const QString &change);
static std::shared_ptr<Event> createFormCalculateEvent(FormField *target, Page *targetPage, FormField *source = nullptr, Page *sourcePage = nullptr, const QString &targetName = QString());
static std::shared_ptr<Event> createFormatEvent(FormField *target, Page *targetPage, const QString &targetName = QString());
static std::shared_ptr<Event> createKeystrokeEvent(FormField *target, Page *targetPage);
......
......@@ -123,6 +123,13 @@ static KJSObject eventGetWillCommit(KJSContext *, void *object)
return KJSBoolean(event->willCommit());
}
// Event.change (getter)
static KJSObject eventGetChange(KJSContext *, void *object)
{
const Event *event = reinterpret_cast<Event *>(object);
return KJSString(event->change());
}
void JSEvent::initType(KJSContext *ctx)
{
static bool initialized = false;
......@@ -144,6 +151,7 @@ void JSEvent::initType(KJSContext *ctx)
g_eventProto->defineProperty(ctx, QStringLiteral("willCommit"), eventGetWillCommit);
g_eventProto->defineProperty(ctx, QStringLiteral("value"), eventGetValue, eventSetValue);
g_eventProto->defineProperty(ctx, QStringLiteral("rc"), eventGetReturnCode, eventSetReturnCode);
g_eventProto->defineProperty(ctx, QStringLiteral("change"), eventGetChange);
}
KJSObject JSEvent::wrapEvent(KJSContext *ctx, Event *event)
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment