okular/autotests/parttest.cpp
Sune Vuorela 717751a1f1 Fix multiline selection
In qt6, QRect::normalized changed it's meaning and it now has a 'span'
that preserves the old meaning.

Use the old span to get the old version of normalized so we can match on
the corners once again.

see https://codereview.qt-project.org/c/qt/qtbase/+/309932 for details.

BUG: 482249
2024-03-05 21:07:10 +00:00

2385 lines
97 KiB
C++

/*
SPDX-FileCopyrightText: 2013 Albert Astals Cid <aacid@kde.org>
Work sponsored by the LiMux project of the city of Munich:
SPDX-FileCopyrightText: 2017 Klarälvdalens Datakonsult AB a KDAB Group company <info@kdab.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// clazy:excludeall=qstring-allocations
#include <QSignalSpy>
#include <QTest>
#include "../core/annotations.h"
#include "../core/document_p.h"
#include "../core/form.h"
#include "../core/page.h"
#include "../part/pageview.h"
#include "../part/part.h"
#include "../part/presentationwidget.h"
#include "../part/sidebar.h"
#include "../part/toc.h"
#include "../part/toggleactionmenu.h"
#include "../settings.h"
#include "closedialoghelper.h"
#include <KActionCollection>
#include <KConfigDialog>
#include <KParts/OpenUrlArguments>
#include <QApplication>
#include <QClipboard>
#include <QDesktopServices>
#include <QLineEdit>
#include <QMenu>
#include <QMessageBox>
#include <QPushButton>
#include <QScrollBar>
#include <QTabletEvent>
#include <QTemporaryDir>
#include <QTemporaryFile>
#include <QTextEdit>
#include <QTimer>
#include <QToolBar>
#include <QTreeView>
#include <QUrl>
namespace Okular
{
class PartTest : public QObject
{
Q_OBJECT
static bool openDocument(Okular::Part *part, const QString &filePath);
Q_SIGNALS:
void urlHandler(const QUrl &url); // NOLINT(readability-inconsistent-declaration-parameter-name)
private Q_SLOTS:
void init();
void testZoomWithCrop();
void testReload();
void testCanceledReload();
void testTOCReload();
void testForwardPDF();
void testForwardPDF_data();
void testGeneratorPreferences();
void testSelectText();
void testSelectTextMultiline();
void testClickInternalLink();
void testScrollBarAndMouseWheel();
void testOpenUrlArguments();
void test388288();
void testSaveAs();
void testSaveAs_data();
void testSaveAsToNonExistingPath();
void testSaveAsToSymlink();
void testSaveIsSymlink();
void testSidebarItemAfterSaving();
void testViewModeSavingPerFile();
void testSaveAsUndoStackAnnotations();
void testSaveAsUndoStackAnnotations_data();
void testSaveAsUndoStackForms();
void testSaveAsUndoStackForms_data();
void testMouseMoveOverLinkWhileInSelectionMode();
void testClickUrlLinkWhileInSelectionMode();
void testeTextSelectionOverAndAcrossLinks_data();
void testeTextSelectionOverAndAcrossLinks();
void testClickUrlLinkWhileLinkTextIsSelected();
void testRClickWhileLinkTextIsSelected();
void testRClickOverLinkWhileLinkTextIsSelected();
void testRClickOnSelectionModeShoulShowFollowTheLinkMenu();
void testClickAnywhereAfterSelectionShouldUnselect();
void testeRectSelectionStartingOnLinks();
void testCheckBoxReadOnly();
void testCrashTextEditDestroy();
void testAnnotWindow();
void testAdditionalActionTriggers();
void testTypewriterAnnotTool();
void testJumpToPage();
void testOpenAtPage();
void testForwardBackwardNavigation();
void testTabletProximityBehavior();
void testOpenPrintPreview();
void testMouseModeMenu();
void testFullScreenRequest();
void testZoomInFacingPages();
void testLinkWithCrop();
void testFieldFormatting();
private:
void simulateMouseSelection(double startX, double startY, double endX, double endY, QWidget *target);
};
class PartThatHijacksQueryClose : public Okular::Part
{
Q_OBJECT
public:
PartThatHijacksQueryClose(QObject *parent, const QVariantList &args)
: Okular::Part(parent, args)
, behavior(PassThru)
{
}
enum Behavior { PassThru, ReturnTrue, ReturnFalse };
void setQueryCloseBehavior(Behavior new_behavior)
{
behavior = new_behavior;
}
bool queryClose() override
{
if (behavior == PassThru) {
return Okular::Part::queryClose();
} else { // ReturnTrue or ReturnFalse
return (behavior == ReturnTrue);
}
}
private:
Behavior behavior;
};
bool PartTest::openDocument(Okular::Part *part, const QString &filePath)
{
part->openDocument(filePath);
return part->m_document->isOpened();
}
void PartTest::init()
{
// Default settings for every test
Okular::Settings::self()->setDefaults();
// Clean docdatas
const QList<QUrl> urls = {QUrl::fromUserInput(QStringLiteral("file://" KDESRCDIR "data/file1.pdf")),
QUrl::fromUserInput(QStringLiteral("file://" KDESRCDIR "data/file2.pdf")),
QUrl::fromUserInput(QStringLiteral("file://" KDESRCDIR "data/simple-multipage.pdf")),
QUrl::fromUserInput(QStringLiteral("file://" KDESRCDIR "data/tocreload.pdf")),
QUrl::fromUserInput(QStringLiteral("file://" KDESRCDIR "data/pdf_with_links.pdf")),
QUrl::fromUserInput(QStringLiteral("file://" KDESRCDIR "data/RequestFullScreen.pdf"))};
for (const QUrl &url : urls) {
QFileInfo fileReadTest(url.toLocalFile());
const QString docDataPath = Okular::DocumentPrivate::docDataFileName(url, fileReadTest.size());
QFile::remove(docDataPath);
}
}
// Test that Okular doesn't crash after a successful reload
void PartTest::testReload()
{
QVariantList dummyArgs;
Okular::Part part(nullptr, dummyArgs);
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file1.pdf")));
part.reload();
qApp->processEvents();
}
// Test that Okular doesn't crash after a canceled reload
void PartTest::testCanceledReload()
{
QVariantList dummyArgs;
PartThatHijacksQueryClose part(nullptr, dummyArgs);
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file1.pdf")));
// When queryClose() returns false, the reload operation is canceled (as if
// the user had chosen Cancel in the "Save changes?" message box)
part.setQueryCloseBehavior(PartThatHijacksQueryClose::ReturnFalse);
part.reload();
qApp->processEvents();
}
void PartTest::testTOCReload()
{
QVariantList dummyArgs;
Okular::Part part(nullptr, dummyArgs);
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/tocreload.pdf")));
QCOMPARE(part.m_toc->expandedNodes().count(), 0);
part.m_toc->m_treeView->expandAll();
QCOMPARE(part.m_toc->expandedNodes().count(), 3);
part.reload();
qApp->processEvents();
QCOMPARE(part.m_toc->expandedNodes().count(), 3);
}
void PartTest::testForwardPDF()
{
QFETCH(QString, dir);
QVariantList dummyArgs;
Okular::Part part(nullptr, dummyArgs);
// Create temp dir named like this: ${system temp dir}/${random string}/${dir}
const QTemporaryDir tempDir;
const QDir workDir(QDir(tempDir.path()).filePath(dir));
workDir.mkpath(QStringLiteral("."));
const QString pdfResult = workDir.path() + QStringLiteral("/synctextest.pdf");
QVERIFY(QFile::copy(QStringLiteral(KDESRCDIR "data/synctextest.pdf"), pdfResult));
const QString gzDestination = workDir.path() + QStringLiteral("/synctextest.synctex.gz");
QVERIFY(QFile::copy(QStringLiteral(KDESRCDIR "data/synctextest.synctex.gz"), gzDestination));
QVERIFY(openDocument(&part, pdfResult));
part.m_document->setViewportPage(0);
QCOMPARE(part.m_document->currentPage(), 0u);
part.closeUrl();
QUrl u(QUrl::fromLocalFile(pdfResult));
// Update this if you regenerate the synctextest.pdf somewhere else
u.setFragment(QStringLiteral("src:100/home/tsdgeos/devel/kde/okular/autotests/data/synctextest.tex"));
part.openUrl(u);
QCOMPARE(part.m_document->currentPage(), 1u);
}
void PartTest::testForwardPDF_data()
{
QTest::addColumn<QString>("dir");
QTest::newRow("non-utf8") << QStringLiteral("synctextest");
// QStringliteral is broken on windows with non ascii chars so using QString::fromUtf8
QTest::newRow("utf8") << QString::fromUtf8("ßðđđŋßðđŋ");
}
void PartTest::testGeneratorPreferences()
{
KConfigDialog *dialog;
QVariantList dummyArgs;
Okular::Part part(nullptr, dummyArgs);
// Test that we don't crash while opening the dialog
dialog = part.slotGeneratorPreferences();
qApp->processEvents();
delete dialog; // closes the dialog and recursively destroys all widgets
// Test that we don't crash while opening a new instance of the dialog
// This catches attempts to reuse widgets that have been destroyed
dialog = part.slotGeneratorPreferences();
qApp->processEvents();
delete dialog;
}
void PartTest::testSelectText()
{
QVariantList dummyArgs;
Okular::Part part(nullptr, dummyArgs);
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file2.pdf")));
part.widget()->show();
QVERIFY(QTest::qWaitForWindowExposed(part.widget()));
part.m_document->setViewportPage(0);
// wait for pixmap
QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView));
const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width();
const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height();
QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseTextSelect"));
const int mouseY = height * 0.052;
const int mouseStartX = width * 0.12;
const int mouseEndX = width * 0.7;
simulateMouseSelection(mouseStartX, mouseY, mouseEndX, mouseY, part.m_pageView->viewport());
QApplication::clipboard()->clear();
QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "copyTextSelection"));
QCOMPARE(QApplication::clipboard()->text(), QStringLiteral("Hola que tal"));
}
void PartTest::testSelectTextMultiline()
{
// This test tests a specific variation of multiline selection
// Select from middle to end of line, then continue to select next line
// then move selection back on next line past the point of the first line
// https://bugs.kde.org/show_bug.cgi?id=482249 has a nice animation.
QVariantList dummyArgs;
Okular::Part part(nullptr, dummyArgs);
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file2.pdf")));
part.widget()->show();
QVERIFY(QTest::qWaitForWindowExposed(part.widget()));
part.m_document->setViewportPage(1);
// wait for pixmap
QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView));
const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width();
const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height();
QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseTextSelect"));
const int startY = height * 0.052;
const int startX = width * 0.22;
const int endY = height * 0.072;
const int endX = width * 0.6;
const int steps = 5;
const double diffX = endX - startX;
const double diffXStep = diffX / steps;
QTestEventList events;
events.addMouseMove(QPoint(startX, startY));
events.addMousePress(Qt::LeftButton, Qt::NoModifier, QPoint(startX, startY));
for (int i = 0; i < steps - 1; ++i) {
events.addMouseMove(QPoint(startX + i * diffXStep, startY));
events.addDelay(100);
}
events.addMouseMove(QPoint(endX, startY));
events.addDelay(100);
events.addMouseMove(QPoint(endX, endY));
events.addDelay(100);
for (int i = 0; i < (steps); i++) {
events.addMouseMove(QPoint(endX - (i * diffXStep), endY));
events.addDelay(100);
}
events.addMouseMove(QPoint(endX - (diffXStep * (steps)), endY));
events.addDelay(100);
events.addMouseMove(QPoint(endX - (diffXStep * (steps + 0.5)), endY));
events.addDelay(100);
events.addMouseRelease(Qt::LeftButton, Qt::NoModifier, QPoint(endX - (diffXStep * (steps + 0.5)), endY));
events.simulate(part.m_pageView->viewport());
QApplication::clipboard()->clear();
QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "copyTextSelection"));
QCOMPARE(QApplication::clipboard()->text(), QStringLiteral("cks!\nOf c"));
}
void PartTest::testClickInternalLink()
{
QVariantList dummyArgs;
Okular::Part part(nullptr, dummyArgs);
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file2.pdf")));
part.widget()->show();
QVERIFY(QTest::qWaitForWindowExposed(part.widget()));
part.m_document->setViewportPage(0);
// wait for pixmap
QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView));
const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width();
const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height();
QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseNormal"));
QCOMPARE(part.m_document->currentPage(), 0u);
QTest::mouseMove(part.m_pageView->viewport(), QPoint(width * 0.17, height * 0.05));
QTest::mouseClick(part.m_pageView->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(width * 0.17, height * 0.05));
QTRY_COMPARE(part.m_document->currentPage(), 1u);
// make sure cursor goes back to being an open hand again. Bug 421437
QTRY_COMPARE_WITH_TIMEOUT(part.m_pageView->cursor().shape(), Qt::OpenHandCursor, 1000);
}
// Test for bug 421159, which is: When scrolling down with the scroll bar
// followed by scrolling down with the mouse wheel, the mouse wheel scrolling
// will make the viewport jump back to the first page.
void PartTest::testScrollBarAndMouseWheel()
{
QVariantList dummyArgs;
Okular::Part part(nullptr, dummyArgs);
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/simple-multipage.pdf")));
part.widget()->show();
QVERIFY(QTest::qWaitForWindowExposed(part.widget()));
part.m_document->setViewportPage(0);
// wait for pixmap
QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView));
// Make sure we are on the first page
QCOMPARE(part.m_document->currentPage(), 0u);
// Two clicks on the vertical scrollbar
auto scrollBar = part.m_pageView->verticalScrollBar();
QTest::mouseClick(scrollBar, Qt::LeftButton);
QTest::qWait(QApplication::doubleClickInterval() * 2); // Wait a tiny bit
QTest::mouseClick(scrollBar, Qt::LeftButton);
// We have scrolled enough to be on the second page now
QCOMPARE(part.m_document->currentPage(), 1u);
// Scroll further down using the mouse wheel
auto wheelDown = new QWheelEvent({}, {}, {}, {0, -150}, Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, false);
QCoreApplication::postEvent(part.m_pageView->viewport(), wheelDown);
// Wait a little for the scrolling to actually happen.
// We should still be on the second page after that.
QTest::qWait(1000);
QCOMPARE(part.m_document->currentPage(), 1u);
}
// cursor switches to Hand when hovering over link in TextSelect mode.
void PartTest::testMouseMoveOverLinkWhileInSelectionMode()
{
QVariantList dummyArgs;
Okular::Part part(nullptr, dummyArgs);
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/pdf_with_links.pdf")));
// resize window to avoid problem with selection areas
part.widget()->resize(800, 600);
part.widget()->show();
QVERIFY(QTest::qWaitForWindowExposed(part.widget()));
part.m_document->setViewportPage(0);
// wait for pixmap
QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView));
const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width();
const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height();
// enter text-selection mode
QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseTextSelect"));
// move mouse over link
QTest::mouseMove(part.m_pageView->viewport(), QPoint(width * 0.250, height * 0.127));
// check if mouse icon changed to proper icon
QTRY_COMPARE(part.m_pageView->cursor().shape(), Qt::PointingHandCursor);
}
// clicking on hyperlink jumps to destination in TextSelect mode.
void PartTest::testClickUrlLinkWhileInSelectionMode()
{
QVariantList dummyArgs;
Okular::Part part(nullptr, dummyArgs);
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/pdf_with_links.pdf")));
// resize window to avoid problem with selection areas
part.widget()->resize(800, 600);
part.widget()->show();
QVERIFY(QTest::qWaitForWindowExposed(part.widget()));
part.m_document->setViewportPage(0);
// wait for pixmap
QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView));
const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width();
const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height();
// enter text-selection mode
QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseTextSelect"));
// overwrite urlHandler for 'mailto' urls
QDesktopServices::setUrlHandler(QStringLiteral("mailto"), this, "urlHandler");
QSignalSpy openUrlSignalSpy(this, &PartTest::urlHandler);
// click on url
QTest::mouseMove(part.m_pageView->viewport(), QPoint(width * 0.250, height * 0.127));
QTest::mouseClick(part.m_pageView->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(width * 0.250, height * 0.127));
// expect that the urlHandler signal was called
QTRY_COMPARE(openUrlSignalSpy.count(), 1);
QList<QVariant> arguments = openUrlSignalSpy.takeFirst();
QCOMPARE(arguments.at(0).value<QUrl>(), QUrl(QStringLiteral("mailto:foo@foo.bar")));
}
void PartTest::testeTextSelectionOverAndAcrossLinks_data()
{
QTest::addColumn<double>("mouseStartX");
QTest::addColumn<double>("mouseEndX");
QTest::addColumn<QString>("expectedResult");
// can text-select "over and across" hyperlink.
QTest::newRow("start selection before link") << 0.1564 << 0.2943 << QStringLiteral(" a link: foo@foo.b");
// can text-select starting at text and ending selection in middle of hyperlink.
QTest::newRow("start selection in the middle of the link") << 0.28 << 0.382 << QStringLiteral("o.bar");
// text selection works when selecting left to right or right to left
QTest::newRow("start selection after link") << 0.40 << 0.05 << QStringLiteral("This is a link: foo@foo.bar");
}
// can text-select "over and across" hyperlink.
void PartTest::testeTextSelectionOverAndAcrossLinks()
{
QVariantList dummyArgs;
Okular::Part part(nullptr, dummyArgs);
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/pdf_with_links.pdf")));
// resize window to avoid problem with selection areas
part.widget()->resize(800, 600);
part.widget()->show();
QVERIFY(QTest::qWaitForWindowExposed(part.widget()));
part.m_document->setViewportPage(0);
// wait for pixmap
QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView));
const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width();
const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height();
// enter text-selection mode
QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseTextSelect"));
const double mouseY = height * 0.127;
QFETCH(double, mouseStartX);
QFETCH(double, mouseEndX);
mouseStartX = width * mouseStartX;
mouseEndX = width * mouseEndX;
simulateMouseSelection(mouseStartX, mouseY, mouseEndX, mouseY, part.m_pageView->viewport());
QApplication::clipboard()->clear();
QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "copyTextSelection"));
QFETCH(QString, expectedResult);
QCOMPARE(QApplication::clipboard()->text(), expectedResult);
}
// can jump to link while there's an active selection of text.
void PartTest::testClickUrlLinkWhileLinkTextIsSelected()
{
QVariantList dummyArgs;
Okular::Part part(nullptr, dummyArgs);
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/pdf_with_links.pdf")));
// resize window to avoid problem with selection areas
part.widget()->resize(800, 600);
part.widget()->show();
QVERIFY(QTest::qWaitForWindowExposed(part.widget()));
part.m_document->setViewportPage(0);
// wait for pixmap
QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView));
const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width();
const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height();
// enter text-selection mode
QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseTextSelect"));
const double mouseY = height * 0.127;
const double mouseStartX = width * 0.13;
const double mouseEndX = width * 0.40;
simulateMouseSelection(mouseStartX, mouseY, mouseEndX, mouseY, part.m_pageView->viewport());
// overwrite urlHandler for 'mailto' urls
QDesktopServices::setUrlHandler(QStringLiteral("mailto"), this, "urlHandler");
QSignalSpy openUrlSignalSpy(this, &PartTest::urlHandler);
// click on url
const double mouseClickX = width * 0.2997;
const double mouseClickY = height * 0.1293;
QTest::mouseMove(part.m_pageView->viewport(), QPoint(mouseClickX, mouseClickY));
QTest::mouseClick(part.m_pageView->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(mouseClickX, mouseClickY), 1000);
// expect that the urlHandler signal was called
QTRY_COMPARE(openUrlSignalSpy.count(), 1);
QList<QVariant> arguments = openUrlSignalSpy.takeFirst();
QCOMPARE(arguments.at(0).value<QUrl>(), QUrl(QStringLiteral("mailto:foo@foo.bar")));
}
// r-click on the selected text gives the "Go To:" content menu option
void PartTest::testRClickWhileLinkTextIsSelected()
{
QVariantList dummyArgs;
Okular::Part part(nullptr, dummyArgs);
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/pdf_with_links.pdf")));
// resize window to avoid problem with selection areas
part.widget()->resize(800, 600);
part.widget()->show();
QVERIFY(QTest::qWaitForWindowExposed(part.widget()));
part.m_document->setViewportPage(0);
// wait for pixmap
QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView));
const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width();
const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height();
// enter text-selection mode
QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseTextSelect"));
const double mouseY = height * 0.162;
const double mouseStartX = width * 0.42;
const double mouseEndX = width * 0.60;
simulateMouseSelection(mouseStartX, mouseY, mouseEndX, mouseY, part.m_pageView->viewport());
// Need to do this because the pop-menu will have his own mainloop and will block tests until
// the menu disappear
PageView *view = part.m_pageView;
bool menuClosed = false;
QTimer::singleShot(2000, view, [view, &menuClosed]() {
// check if popup menu is active and visible
QMenu *menu = qobject_cast<QMenu *>(view->findChild<QMenu *>(QStringLiteral("PopupMenu")));
QVERIFY(menu);
QVERIFY(menu->isVisible());
// check if the menu contains go-to link action
QAction *goToAction = qobject_cast<QAction *>(menu->findChild<QAction *>(QStringLiteral("GoToAction")));
QVERIFY(goToAction);
// check if the "follow this link" action is not visible
QAction *processLinkAction = qobject_cast<QAction *>(menu->findChild<QAction *>(QStringLiteral("ProcessLinkAction")));
QVERIFY(!processLinkAction);
// check if the "copy link address" action is not visible
QAction *copyLinkLocation = qobject_cast<QAction *>(menu->findChild<QAction *>(QStringLiteral("CopyLinkLocationAction")));
QVERIFY(!copyLinkLocation);
// close menu to continue test
menu->close();
menuClosed = true;
});
// click on url
const double mouseClickX = width * 0.425;
const double mouseClickY = height * 0.162;
QTest::mouseMove(part.m_pageView->viewport(), QPoint(mouseClickX, mouseClickY));
QTest::mouseClick(part.m_pageView->viewport(), Qt::RightButton, Qt::NoModifier, QPoint(mouseClickX, mouseClickY), 1000);
// will continue after pop-menu get closed
QTRY_VERIFY(menuClosed);
}
// r-click on the link gives the "follow this link" content menu option
void PartTest::testRClickOverLinkWhileLinkTextIsSelected()
{
QVariantList dummyArgs;
Okular::Part part(nullptr, dummyArgs);
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/pdf_with_links.pdf")));
// resize window to avoid problem with selection areas
part.widget()->resize(800, 600);
part.widget()->show();
QVERIFY(QTest::qWaitForWindowExposed(part.widget()));
part.m_document->setViewportPage(0);
// wait for pixmap
QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView));
const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width();
const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height();
// enter text-selection mode
QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseTextSelect"));
const double mouseY = height * 0.162;
const double mouseStartX = width * 0.42;
const double mouseEndX = width * 0.60;
simulateMouseSelection(mouseStartX, mouseY, mouseEndX, mouseY, part.m_pageView->viewport());
// Need to do this because the pop-menu will have his own mainloop and will block tests until
// the menu disappear
PageView *view = part.m_pageView;
bool menuClosed = false;
QTimer::singleShot(2000, view, [view, &menuClosed]() {
// check if popup menu is active and visible
QMenu *menu = qobject_cast<QMenu *>(view->findChild<QMenu *>(QStringLiteral("PopupMenu")));
QVERIFY(menu);
QVERIFY(menu->isVisible());
// check if the menu contains "follow this link" action
QAction *processLinkAction = qobject_cast<QAction *>(menu->findChild<QAction *>(QStringLiteral("ProcessLinkAction")));
QVERIFY(processLinkAction);
// check if the menu contains "copy link address" action
QAction *copyLinkLocation = qobject_cast<QAction *>(menu->findChild<QAction *>(QStringLiteral("CopyLinkLocationAction")));
QVERIFY(copyLinkLocation);
// close menu to continue test
menu->close();
menuClosed = true;
});
// click on url
const double mouseClickX = width * 0.593;
const double mouseClickY = height * 0.162;
QTest::mouseMove(part.m_pageView->viewport(), QPoint(mouseClickX, mouseClickY));
QTest::mouseClick(part.m_pageView->viewport(), Qt::RightButton, Qt::NoModifier, QPoint(mouseClickX, mouseClickY), 1000);
// will continue after pop-menu get closed
QTRY_VERIFY(menuClosed);
}
void PartTest::testRClickOnSelectionModeShoulShowFollowTheLinkMenu()
{
QVariantList dummyArgs;
Okular::Part part(nullptr, dummyArgs);
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/pdf_with_links.pdf")));
// resize window to avoid problem with selection areas
part.widget()->resize(800, 600);
part.widget()->show();
QVERIFY(QTest::qWaitForWindowExposed(part.widget()));
part.m_document->setViewportPage(0);
// wait for pixmap
QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView));
const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width();
const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height();
// enter text-selection mode
QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseTextSelect"));
// Need to do this because the pop-menu will have his own mainloop and will block tests until
// the menu disappear
PageView *view = part.m_pageView;
bool menuClosed = false;
QTimer::singleShot(2000, view, [view, &menuClosed]() {
// check if popup menu is active and visible
QMenu *menu = qobject_cast<QMenu *>(view->findChild<QMenu *>(QStringLiteral("PopupMenu")));
QVERIFY(menu);
QVERIFY(menu->isVisible());
// check if the menu contains "Follow this link" action
QAction *processLink = qobject_cast<QAction *>(menu->findChild<QAction *>(QStringLiteral("ProcessLinkAction")));
QVERIFY(processLink);
// chek if the menu contains "Copy Link Address" action
QAction *actCopyLinkLocation = qobject_cast<QAction *>(menu->findChild<QAction *>(QStringLiteral("CopyLinkLocationAction")));
QVERIFY(actCopyLinkLocation);
// close menu to continue test
menu->close();
menuClosed = true;
});
// r-click on url
const double mouseClickX = width * 0.604;
const double mouseClickY = height * 0.162;
QTest::mouseMove(part.m_pageView->viewport(), QPoint(mouseClickX, mouseClickY));
QTest::mouseClick(part.m_pageView->viewport(), Qt::RightButton, Qt::NoModifier, QPoint(mouseClickX, mouseClickY), 1000);
// will continue after pop-menu get closed
QTRY_VERIFY(menuClosed);
}
void PartTest::testClickAnywhereAfterSelectionShouldUnselect()
{
QVariantList dummyArgs;
Okular::Part part(nullptr, dummyArgs);
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/pdf_with_links.pdf")));
// resize window to avoid problem with selection areas
part.widget()->resize(800, 600);
part.widget()->show();
QVERIFY(QTest::qWaitForWindowExposed(part.widget()));
part.m_document->setViewportPage(0);
// wait for pixmap
QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView));
const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width();
const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height();
// enter text-selection mode
QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseTextSelect"));
const double mouseY = height * 0.162;
const double mouseStartX = width * 0.42;
const double mouseEndX = width * 0.60;
simulateMouseSelection(mouseStartX, mouseY, mouseEndX, mouseY, part.m_pageView->viewport());
// click on url
const double mouseClickX = width * 0.10;
QTest::mouseMove(part.m_pageView->viewport(), QPoint(mouseClickX, mouseY));
QTest::mouseClick(part.m_pageView->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(mouseClickX, mouseY), 1000);
QApplication::clipboard()->clear();
QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "copyTextSelection"));
// check if copied text is empty what means no text selected
QVERIFY(QApplication::clipboard()->text().isEmpty());
}
void PartTest::testeRectSelectionStartingOnLinks()
{
QVariantList dummyArgs;
Okular::Part part(nullptr, dummyArgs);
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/pdf_with_links.pdf")));
// hide info messages as they interfere with selection area
Okular::Settings::self()->setShowEmbeddedContentMessages(false);
Okular::Settings::self()->setShowOSD(false);
part.widget()->show();
QVERIFY(QTest::qWaitForWindowExposed(part.widget()));
part.m_document->setViewportPage(0);
// wait for pixmap
QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView));
const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width();
const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height();
// enter text-selection mode
QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseSelect"));
const double mouseStartY = height * 0.127;
const double mouseEndY = height * 0.127;
const double mouseStartX = width * 0.28;
const double mouseEndX = width * 0.382;
// Need to do this because the pop-menu will have his own mainloop and will block tests until
// the menu disappear
PageView *view = part.m_pageView;
bool menuClosed = false;
QTimer::singleShot(2000, view, [view, &menuClosed]() {
QApplication::clipboard()->clear();
// check if popup menu is active and visible
QMenu *menu = qobject_cast<QMenu *>(view->findChild<QMenu *>(QStringLiteral("PopupMenu")));
QVERIFY(menu);
QVERIFY(menu->isVisible());
// check if the copy selected text to clipboard is present
QAction *copyAct = qobject_cast<QAction *>(menu->findChild<QAction *>(QStringLiteral("CopyTextToClipboard")));
QVERIFY(copyAct);
menu->close();
menuClosed = true;
});
simulateMouseSelection(mouseStartX, mouseStartY, mouseEndX, mouseEndY, part.m_pageView->viewport());
// wait menu get closed
QTRY_VERIFY(menuClosed);
}
void PartTest::simulateMouseSelection(double startX, double startY, double endX, double endY, QWidget *target)
{
const int steps = 5;
const double diffX = endX - startX;
const double diffY = endY - startY;
const double diffXStep = diffX / steps;
const double diffYStep = diffY / steps;
QTestEventList events;
events.addMouseMove(QPoint(startX, startY));
events.addMousePress(Qt::LeftButton, Qt::NoModifier, QPoint(startX, startY));
for (int i = 0; i < steps - 1; ++i) {
events.addMouseMove(QPoint(startX + i * diffXStep, startY + i * diffYStep));
events.addDelay(100);
}
events.addMouseMove(QPoint(endX, endY));
events.addDelay(100);
events.addMouseRelease(Qt::LeftButton, Qt::NoModifier, QPoint(endX, endY));
events.simulate(target);
}
void PartTest::testSaveAsToNonExistingPath()
{
Okular::Part part(nullptr, {});
part.openDocument(QStringLiteral(KDESRCDIR "data/file1.pdf"));
QString saveFilePath;
{
QTemporaryFile saveFile(QStringLiteral("%1/okrXXXXXX.pdf").arg(QDir::tempPath()));
saveFile.open();
saveFilePath = saveFile.fileName();
// QTemporaryFile is destroyed and the file it created is gone, this is a TOCTOU but who cares
}
QVERIFY(!QFileInfo::exists(saveFilePath));
QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFilePath), Part::NoSaveAsFlags));
QFile::remove(saveFilePath);
}
void PartTest::testSaveAsToSymlink()
{
#ifdef Q_OS_UNIX
Okular::Part part(nullptr, {});
part.openDocument(QStringLiteral(KDESRCDIR "data/file1.pdf"));
QTemporaryFile newFile(QStringLiteral("%1/okrXXXXXX.pdf").arg(QDir::tempPath()));
newFile.open();
QString linkFilePath;
{
QTemporaryFile linkFile(QStringLiteral("%1/okrXXXXXX.pdf").arg(QDir::tempPath()));
linkFile.open();
linkFilePath = linkFile.fileName();
// QTemporaryFile is destroyed and the file it created is gone, this is a TOCTOU but who cares
}
QFile::link(newFile.fileName(), linkFilePath);
QVERIFY(QFileInfo(linkFilePath).isSymLink());
QVERIFY(part.saveAs(QUrl::fromLocalFile(linkFilePath), Part::NoSaveAsFlags));
QVERIFY(QFileInfo(linkFilePath).isSymLink());
QFile::remove(linkFilePath);
#endif
}
void PartTest::testSaveIsSymlink()
{
#ifdef Q_OS_UNIX
Okular::Part part(nullptr, {});
QString newFilePath;
{
QTemporaryFile newFile(QStringLiteral("%1/okrXXXXXX.pdf").arg(QDir::tempPath()));
newFile.open();
newFilePath = newFile.fileName();
// QTemporaryFile is destroyed and the file it created is gone, this is a TOCTOU but who cares
}
QFile::copy(QStringLiteral(KDESRCDIR "data/file1.pdf"), newFilePath);
QString linkFilePath;
{
QTemporaryFile linkFile(QStringLiteral("%1/okrXXXXXX.pdf").arg(QDir::tempPath()));
linkFile.open();
linkFilePath = linkFile.fileName();
// QTemporaryFile is destroyed and the file it created is gone, this is a TOCTOU but who cares
}
QFile::link(newFilePath, linkFilePath);
QVERIFY(QFileInfo(linkFilePath).isSymLink());
part.openDocument(linkFilePath);
QVERIFY(part.saveAs(QUrl::fromLocalFile(linkFilePath), Part::NoSaveAsFlags));
QVERIFY(QFileInfo(linkFilePath).isSymLink());
QFile::remove(newFilePath);
QFile::remove(linkFilePath);
#endif
}
void PartTest::testSaveAs()
{
QFETCH(QString, file);
QFETCH(QString, extension);
QFETCH(bool, nativelySupportsAnnotations);
QFETCH(bool, canSwapBackingFile);
QScopedPointer<TestingUtils::CloseDialogHelper> closeDialogHelper;
QString annotName;
QTemporaryFile archiveSave(QStringLiteral("%1/okrXXXXXX.okular").arg(QDir::tempPath()));
QTemporaryFile nativeDirectSave(QStringLiteral("%1/okrXXXXXX.%2").arg(QDir::tempPath(), extension));
QTemporaryFile nativeFromArchiveFile(QStringLiteral("%1/okrXXXXXX.%2").arg(QDir::tempPath(), extension));
QVERIFY(archiveSave.open());
archiveSave.close();
QVERIFY(nativeDirectSave.open());
nativeDirectSave.close();
QVERIFY(nativeFromArchiveFile.open());
nativeFromArchiveFile.close();
qDebug() << "Open file, add annotation and save both natively and to .okular";
{
Okular::Part part(nullptr, {});
part.openDocument(file);
part.m_document->documentInfo();
QCOMPARE(part.m_document->canSwapBackingFile(), canSwapBackingFile);
Okular::Annotation *annot = new Okular::TextAnnotation();
annot->setBoundingRectangle(Okular::NormalizedRect(0.1, 0.1, 0.15, 0.15));
annot->setContents(QStringLiteral("annot contents"));
part.m_document->addPageAnnotation(0, annot);
annotName = annot->uniqueName();
if (canSwapBackingFile) {
if (!nativelySupportsAnnotations) {
closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "you're going to lose the annotations" dialog
}
QVERIFY(part.saveAs(QUrl::fromLocalFile(nativeDirectSave.fileName()), Part::NoSaveAsFlags));
// For backends that don't support annotations natively we mark the part as still modified
// after a save because we keep the annotation around but it will get lost if the user closes the app
// so we want to give her a last chance to save on close with the "you have changes dialog"
QCOMPARE(part.isModified(), !nativelySupportsAnnotations);
QVERIFY(part.saveAs(QUrl::fromLocalFile(archiveSave.fileName()), Part::SaveAsOkularArchive));
} else {
// We need to save to archive first otherwise we lose the annotation
closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::Yes)); // this is the "you're going to lose the undo/redo stack" dialog
QVERIFY(part.saveAs(QUrl::fromLocalFile(archiveSave.fileName()), Part::SaveAsOkularArchive));
if (!nativelySupportsAnnotations) {
closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "you're going to lose the annotations" dialog
}
QVERIFY(part.saveAs(QUrl::fromLocalFile(nativeDirectSave.fileName()), Part::NoSaveAsFlags));
}
QCOMPARE(part.m_document->documentInfo().get(Okular::DocumentInfo::FilePath), part.m_document->currentDocument().toDisplayString());
part.closeUrl();
}
qDebug() << "Open the .okular, check that the annotation is present and save to native";
{
Okular::Part part(nullptr, {});
part.openDocument(archiveSave.fileName());
part.m_document->documentInfo();
QCOMPARE(part.m_document->page(0)->annotations().size(), 1);
QCOMPARE(part.m_document->page(0)->annotations().first()->uniqueName(), annotName);
if (!nativelySupportsAnnotations) {
closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "you're going to lose the annotations" dialog
}
QVERIFY(part.saveAs(QUrl::fromLocalFile(nativeFromArchiveFile.fileName()), Part::NoSaveAsFlags));
if (canSwapBackingFile && !nativelySupportsAnnotations) {
// For backends that don't support annotations natively we mark the part as still modified
// after a save because we keep the annotation around but it will get lost if the user closes the app
// so we want to give her a last chance to save on close with the "you have changes dialog"
closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "do you want to save or discard" dialog
}
QCOMPARE(part.m_document->documentInfo().get(Okular::DocumentInfo::FilePath), part.m_document->currentDocument().toDisplayString());
part.closeUrl();
}
qDebug() << "Open the native file saved directly, and check that the annot"
<< "is there iff we expect it";
{
Okular::Part part(nullptr, {});
part.openDocument(nativeDirectSave.fileName());
QCOMPARE(part.m_document->page(0)->annotations().size(), nativelySupportsAnnotations ? 1 : 0);
if (nativelySupportsAnnotations) {
QCOMPARE(part.m_document->page(0)->annotations().first()->uniqueName(), annotName);
}
part.closeUrl();
}
qDebug() << "Open the native file saved from the .okular, and check that the annot"
<< "is there iff we expect it";
{
Okular::Part part(nullptr, {});
part.openDocument(nativeFromArchiveFile.fileName());
QCOMPARE(part.m_document->page(0)->annotations().size(), nativelySupportsAnnotations ? 1 : 0);
if (nativelySupportsAnnotations) {
QCOMPARE(part.m_document->page(0)->annotations().first()->uniqueName(), annotName);
}
part.closeUrl();
}
}
void PartTest::testSaveAs_data()
{
QTest::addColumn<QString>("file");
QTest::addColumn<QString>("extension");
QTest::addColumn<bool>("nativelySupportsAnnotations");
QTest::addColumn<bool>("canSwapBackingFile");
QTest::newRow("pdf") << KDESRCDIR "data/file1.pdf"
<< "pdf" << true << true;
QTest::newRow("pdf.gz") << KDESRCDIR "data/file1.pdf.gz"
<< "pdf" << true << true;
QTest::newRow("epub") << KDESRCDIR "data/contents.epub"
<< "epub" << false << false;
QTest::newRow("jpg") << KDESRCDIR "data/potato.jpg"
<< "jpg" << false << true;
}
void PartTest::testSidebarItemAfterSaving()
{
Okular::Part part(nullptr, {});
QWidget *currentSidebarItem = part.m_sidebar->currentItem(); // thumbnails
openDocument(&part, QStringLiteral(KDESRCDIR "data/tocreload.pdf"));
// since it has TOC it changes to TOC
QVERIFY(currentSidebarItem != part.m_sidebar->currentItem());
// now change back to thumbnails
part.m_sidebar->setCurrentItem(currentSidebarItem);
part.saveAs(QUrl::fromLocalFile(QStringLiteral(KDESRCDIR "data/tocreload.pdf")));
// Check it is still thumbnails after saving
QCOMPARE(currentSidebarItem, part.m_sidebar->currentItem());
}
void PartTest::testViewModeSavingPerFile()
{
Okular::Part part(nullptr, {});
// Open some file
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file1.pdf")));
// Switch to 'continuous' view mode
part.m_pageView->setCapability(Okular::View::ViewCapability::Continuous, QVariant(true));
// Close document
part.closeUrl();
// Open another file
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file2.pdf")));
// Switch to 'non-continuous' mode
part.m_pageView->setCapability(Okular::View::ViewCapability::Continuous, QVariant(false));
// Close that document, too
part.closeUrl();
// Open first document again
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file1.pdf")));
// If per-file view mode saving works, the view mode should be 'continuous' again.
QVERIFY(part.m_pageView->capability(Okular::View::ViewCapability::Continuous).toBool());
}
void PartTest::testSaveAsUndoStackAnnotations()
{
QFETCH(QString, file);
QFETCH(QString, extension);
QFETCH(bool, nativelySupportsAnnotations);
QFETCH(bool, canSwapBackingFile);
QFETCH(bool, saveToArchive);
const Part::SaveAsFlag saveFlags = saveToArchive ? Part::SaveAsOkularArchive : Part::NoSaveAsFlags;
QScopedPointer<TestingUtils::CloseDialogHelper> closeDialogHelper;
// closeDialogHelper relies on the availability of the "Continue" button to drop changes
// when saving to a file format not supporting those. However, this button is only sensible
// and available for "Save As", but not for "Save". By alternately saving to saveFile1 and
// saveFile2 we always force "Save As", so closeDialogHelper keeps working.
QTemporaryFile saveFile1(QStringLiteral("%1/okrXXXXXX_1.%2").arg(QDir::tempPath(), extension));
QVERIFY(saveFile1.open());
saveFile1.close();
QTemporaryFile saveFile2(QStringLiteral("%1/okrXXXXXX_2.%2").arg(QDir::tempPath(), extension));
QVERIFY(saveFile2.open());
saveFile2.close();
Okular::Part part(nullptr, {});
part.openDocument(file);
QCOMPARE(part.m_document->canSwapBackingFile(), canSwapBackingFile);
Okular::Annotation *annot = new Okular::TextAnnotation();
annot->setBoundingRectangle(Okular::NormalizedRect(0.1, 0.1, 0.15, 0.15));
annot->setContents(QStringLiteral("annot contents"));
part.m_document->addPageAnnotation(0, annot);
QString annotName = annot->uniqueName();
if (!nativelySupportsAnnotations && !saveToArchive) {
closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "you're going to lose the annotations" dialog
}
QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile1.fileName()), saveFlags));
if (!canSwapBackingFile) {
// The undo/redo stack gets lost if you can not swap the backing file
QVERIFY(!part.m_document->canUndo());
QVERIFY(!part.m_document->canRedo());
return;
}
// Check we can still undo the annot add after save
QVERIFY(part.m_document->canUndo());
part.m_document->undo();
QVERIFY(!part.m_document->canUndo());
QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile1.fileName()), saveFlags));
QVERIFY(part.m_document->page(0)->annotations().isEmpty());
// Check we can redo the annot add after save
QVERIFY(part.m_document->canRedo());
part.m_document->redo();
QVERIFY(!part.m_document->canRedo());
if (nativelySupportsAnnotations) {
// If the annots are provided by the backend we need to refetch the pointer after save
annot = part.m_document->page(0)->annotation(annotName);
QVERIFY(annot);
}
// Remove the annotation, creates another undo command
QVERIFY(part.m_document->canRemovePageAnnotation(annot));
part.m_document->removePageAnnotation(0, annot);
QVERIFY(part.m_document->page(0)->annotations().isEmpty());
// Check we can still undo the annot remove after save
QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile1.fileName()), saveFlags));
QVERIFY(part.m_document->canUndo());
part.m_document->undo();
QVERIFY(part.m_document->canUndo());
QCOMPARE(part.m_document->page(0)->annotations().count(), 1);
// Check we can still undo the annot add after save
if (!nativelySupportsAnnotations && !saveToArchive) {
closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "you're going to lose the annotations" dialog
}
QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile2.fileName()), saveFlags));
QVERIFY(part.m_document->canUndo());
part.m_document->undo();
QVERIFY(!part.m_document->canUndo());
QVERIFY(part.m_document->page(0)->annotations().isEmpty());
// Redo the add annotation
QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile1.fileName()), saveFlags));
QVERIFY(part.m_document->canRedo());
part.m_document->redo();
QVERIFY(part.m_document->canUndo());
QVERIFY(part.m_document->canRedo());
if (nativelySupportsAnnotations) {
// If the annots are provided by the backend we need to refetch the pointer after save
annot = part.m_document->page(0)->annotation(annotName);
QVERIFY(annot);
}
// Add translate, adjust and modify commands
part.m_document->translatePageAnnotation(0, annot, Okular::NormalizedPoint(0.1, 0.1));
part.m_document->adjustPageAnnotation(0, annot, Okular::NormalizedPoint(0.1, 0.1), Okular::NormalizedPoint(0.1, 0.1));
part.m_document->prepareToModifyAnnotationProperties(annot);
part.m_document->modifyPageAnnotationProperties(0, annot);
// Now check we can still undo/redo/save at all the intermediate states and things still work
if (!nativelySupportsAnnotations && !saveToArchive) {
closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "you're going to lose the annotations" dialog
}
QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile2.fileName()), saveFlags));
QVERIFY(part.m_document->canUndo());
part.m_document->undo();
QVERIFY(part.m_document->canUndo());
if (!nativelySupportsAnnotations && !saveToArchive) {
closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "you're going to lose the annotations" dialog
}
QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile1.fileName()), saveFlags));
QVERIFY(part.m_document->canUndo());
part.m_document->undo();
QVERIFY(part.m_document->canUndo());
if (!nativelySupportsAnnotations && !saveToArchive) {
closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "you're going to lose the annotations" dialog
}
QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile2.fileName()), saveFlags));
QVERIFY(part.m_document->canUndo());
part.m_document->undo();
QVERIFY(part.m_document->canUndo());
if (!nativelySupportsAnnotations && !saveToArchive) {
closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "you're going to lose the annotations" dialog
}
QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile1.fileName()), saveFlags));
QVERIFY(part.m_document->canUndo());
part.m_document->undo();
QVERIFY(!part.m_document->canUndo());
QVERIFY(part.m_document->canRedo());
QVERIFY(part.m_document->page(0)->annotations().isEmpty());
QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile1.fileName()), saveFlags));
QVERIFY(part.m_document->canRedo());
part.m_document->redo();
QVERIFY(part.m_document->canRedo());
if (!nativelySupportsAnnotations && !saveToArchive) {
closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "you're going to lose the annotations" dialog
}
QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile2.fileName()), saveFlags));
QVERIFY(part.m_document->canRedo());
part.m_document->redo();
QVERIFY(part.m_document->canRedo());
if (!nativelySupportsAnnotations && !saveToArchive) {
closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "you're going to lose the annotations" dialog
}
QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile1.fileName()), saveFlags));
QVERIFY(part.m_document->canRedo());
part.m_document->redo();
QVERIFY(part.m_document->canRedo());
if (!nativelySupportsAnnotations && !saveToArchive) {
closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "you're going to lose the annotations" dialog
}
QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile2.fileName()), saveFlags));
QVERIFY(part.m_document->canRedo());
part.m_document->redo();
QVERIFY(!part.m_document->canRedo());
closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "do you want to save or discard" dialog
part.closeUrl();
}
void PartTest::testSaveAsUndoStackAnnotations_data()
{
QTest::addColumn<QString>("file");
QTest::addColumn<QString>("extension");
QTest::addColumn<bool>("nativelySupportsAnnotations");
QTest::addColumn<bool>("canSwapBackingFile");
QTest::addColumn<bool>("saveToArchive");
QTest::newRow("pdf") << KDESRCDIR "data/file1.pdf"
<< "pdf" << true << true << false;
QTest::newRow("epub") << KDESRCDIR "data/contents.epub"
<< "epub" << false << false << false;
QTest::newRow("jpg") << KDESRCDIR "data/potato.jpg"
<< "jpg" << false << true << false;
QTest::newRow("pdfarchive") << KDESRCDIR "data/file1.pdf"
<< "okular" << true << true << true;
QTest::newRow("jpgarchive") << KDESRCDIR "data/potato.jpg"
<< "okular" << false << true << true;
}
void PartTest::testSaveAsUndoStackForms()
{
QFETCH(QString, file);
QFETCH(QString, extension);
QFETCH(bool, saveToArchive);
const Part::SaveAsFlag saveFlags = saveToArchive ? Part::SaveAsOkularArchive : Part::NoSaveAsFlags;
QTemporaryFile saveFile(QStringLiteral("%1/okrXXXXXX.%2").arg(QDir::tempPath(), extension));
QVERIFY(saveFile.open());
saveFile.close();
Okular::Part part(nullptr, {});
part.openDocument(file);
const QList<Okular::FormField *> pageFormFields = part.m_document->page(0)->formFields();
for (FormField *ff : pageFormFields) {
if (ff->id() == 65537) {
QCOMPARE(ff->type(), FormField::FormText);
FormFieldText *fft = static_cast<FormFieldText *>(ff);
part.m_document->editFormText(0, fft, QStringLiteral("BlaBla"), 6, 0, 0);
} else if (ff->id() == 65538) {
QCOMPARE(ff->type(), FormField::FormButton);
FormFieldButton *ffb = static_cast<FormFieldButton *>(ff);
QCOMPARE(ffb->buttonType(), FormFieldButton::Radio);
part.m_document->editFormButtons(0, QList<FormFieldButton *>() << ffb, QList<bool>() << true);
} else if (ff->id() == 65542) {
QCOMPARE(ff->type(), FormField::FormChoice);
FormFieldChoice *ffc = static_cast<FormFieldChoice *>(ff);
QCOMPARE(ffc->choiceType(), FormFieldChoice::ListBox);
part.m_document->editFormList(0, ffc, QList<int>() << 1);
} else if (ff->id() == 65543) {
QCOMPARE(ff->type(), FormField::FormChoice);
FormFieldChoice *ffc = static_cast<FormFieldChoice *>(ff);
QCOMPARE(ffc->choiceType(), FormFieldChoice::ComboBox);
part.m_document->editFormCombo(0, ffc, QStringLiteral("combo2"), 3, 0, 0);
}
}
QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile.fileName()), saveFlags));
QVERIFY(part.m_document->canUndo());
part.m_document->undo();
QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile.fileName()), saveFlags));
QVERIFY(part.m_document->canUndo());
part.m_document->undo();
QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile.fileName()), saveFlags));
QVERIFY(part.m_document->canUndo());
part.m_document->undo();
QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile.fileName()), saveFlags));
QVERIFY(part.m_document->canUndo());
part.m_document->undo();
QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile.fileName()), saveFlags));
QVERIFY(!part.m_document->canUndo());
QVERIFY(part.m_document->canRedo());
part.m_document->redo();
QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile.fileName()), saveFlags));
QVERIFY(part.m_document->canRedo());
part.m_document->redo();
QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile.fileName()), saveFlags));
QVERIFY(part.m_document->canRedo());
part.m_document->redo();
QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile.fileName()), saveFlags));
QVERIFY(part.m_document->canRedo());
part.m_document->redo();
QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile.fileName()), saveFlags));
}
void PartTest::testSaveAsUndoStackForms_data()
{
QTest::addColumn<QString>("file");
QTest::addColumn<QString>("extension");
QTest::addColumn<bool>("saveToArchive");
QTest::newRow("pdf") << KDESRCDIR "data/formSamples.pdf"
<< "pdf" << false;
QTest::newRow("pdfarchive") << KDESRCDIR "data/formSamples.pdf"
<< "okular" << true;
}
void PartTest::testOpenUrlArguments()
{
Okular::Part part(nullptr, {});
KParts::OpenUrlArguments args;
args.setMimeType(QStringLiteral("text/rtf"));
part.setArguments(args);
part.openUrl(QUrl::fromLocalFile(QStringLiteral(KDESRCDIR "data/file1.pdf")));
QCOMPARE(part.arguments().mimeType(), QStringLiteral("text/rtf"));
}
void PartTest::test388288()
{
Okular::Part part(nullptr, {});
part.openUrl(QUrl::fromLocalFile(QStringLiteral(KDESRCDIR "data/file1.pdf")));
part.widget()->show();
QVERIFY(QTest::qWaitForWindowExposed(part.widget()));
QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseNormal"));
auto annot = new Okular::HighlightAnnotation();
annot->setHighlightType(Okular::HighlightAnnotation::Highlight);
const Okular::NormalizedRect r(0.36, 0.16, 0.51, 0.17);
annot->setBoundingRectangle(r);
Okular::HighlightAnnotation::Quad q;
q.setCapStart(false);
q.setCapEnd(false);
q.setFeather(1.0);
q.setPoint(Okular::NormalizedPoint(r.left, r.bottom), 0);
q.setPoint(Okular::NormalizedPoint(r.right, r.bottom), 1);
q.setPoint(Okular::NormalizedPoint(r.right, r.top), 2);
q.setPoint(Okular::NormalizedPoint(r.left, r.top), 3);
annot->highlightQuads().append(q);
part.m_document->addPageAnnotation(0, annot);
const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width();
const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height();
QTest::mouseMove(part.m_pageView->viewport(), QPoint(width * 0.5, height * 0.5));
QTRY_COMPARE(part.m_pageView->cursor().shape(), Qt::OpenHandCursor);
QTest::mouseMove(part.m_pageView->viewport(), QPoint(width * 0.4, height * 0.165));
QTRY_COMPARE(part.m_pageView->cursor().shape(), Qt::ArrowCursor);
QTest::mouseMove(part.m_pageView->viewport(), QPoint(width * 0.1, height * 0.165));
part.m_document->undo();
annot = new Okular::HighlightAnnotation();
annot->setHighlightType(Okular::HighlightAnnotation::Highlight);
annot->setBoundingRectangle(r);
annot->highlightQuads().append(q);
part.m_document->addPageAnnotation(0, annot);
QTest::mouseMove(part.m_pageView->viewport(), QPoint(width * 0.5, height * 0.5));
QTRY_COMPARE(part.m_pageView->cursor().shape(), Qt::OpenHandCursor);
}
void PartTest::testCheckBoxReadOnly()
{
const QString testFile = QStringLiteral(KDESRCDIR "data/checkbox_ro.pdf");
Okular::Part part(nullptr, {});
part.openDocument(testFile);
// The test document uses the activation action of checkboxes
// to update the read only state. For this we need the part so that
// undo / redo activates the activation action.
QVERIFY(part.m_document->isOpened());
const Okular::Page *page = part.m_document->page(0);
QMap<QString, Okular::FormField *> fields;
// Field names in test document are:
// CBMakeRW, CBMakeRO, TargetDefaultRO, TargetDefaultRW
const QList<Okular::FormField *> pageFormFields = page->formFields();
for (Okular::FormField *ff : pageFormFields) {
fields.insert(ff->name(), static_cast<Okular::FormField *>(ff));
}
// First grab all fields and check that the setup is as expected.
auto cbMakeRW = dynamic_cast<Okular::FormFieldButton *>(fields[QStringLiteral("CBMakeRW")]);
auto cbMakeRO = dynamic_cast<Okular::FormFieldButton *>(fields[QStringLiteral("CBMakeRO")]);
auto targetDefaultRW = dynamic_cast<Okular::FormFieldText *>(fields[QStringLiteral("TargetDefaultRw")]);
auto targetDefaultRO = dynamic_cast<Okular::FormFieldText *>(fields[QStringLiteral("TargetDefaultRo")]);
QVERIFY(cbMakeRW);
QVERIFY(cbMakeRO);
QVERIFY(targetDefaultRW);
QVERIFY(targetDefaultRO);
QVERIFY(!cbMakeRW->state());
QVERIFY(!cbMakeRO->state());
QVERIFY(!targetDefaultRW->isReadOnly());
QVERIFY(targetDefaultRO->isReadOnly());
QList<Okular::FormFieldButton *> btns;
btns << cbMakeRW << cbMakeRO;
// Now check both boxes
QList<bool> btnStates;
btnStates << true << true;
part.m_document->editFormButtons(0, btns, btnStates);
// Read only should be inverted
QVERIFY(targetDefaultRW->isReadOnly());
QVERIFY(!targetDefaultRO->isReadOnly());
// Test that undo / redo works
QVERIFY(part.m_document->canUndo());
part.m_document->undo();
QVERIFY(!targetDefaultRW->isReadOnly());
QVERIFY(targetDefaultRO->isReadOnly());
part.m_document->redo();
QVERIFY(targetDefaultRW->isReadOnly());
QVERIFY(!targetDefaultRO->isReadOnly());
btnStates.clear();
btnStates << false << true;
part.m_document->editFormButtons(0, btns, btnStates);
QVERIFY(targetDefaultRW->isReadOnly());
QVERIFY(targetDefaultRO->isReadOnly());
// Now set both to checked again and confirm that
// save / load works.
btnStates.clear();
btnStates << true << true;
part.m_document->editFormButtons(0, btns, btnStates);
QTemporaryFile saveFile(QStringLiteral("%1/okrXXXXXX.pdf").arg(QDir::tempPath()));
QVERIFY(saveFile.open());
saveFile.close();
// Save
QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile.fileName()), Part::NoSaveAsFlags));
part.closeUrl();
// Load
part.openDocument(saveFile.fileName());
QVERIFY(part.m_document->isOpened());
page = part.m_document->page(0);
fields.clear();
{
const QList<Okular::FormField *> pageFormFields = page->formFields();
for (Okular::FormField *ff : pageFormFields) {
fields.insert(ff->name(), static_cast<Okular::FormField *>(ff));
}
}
cbMakeRW = dynamic_cast<Okular::FormFieldButton *>(fields[QStringLiteral("CBMakeRW")]);
cbMakeRO = dynamic_cast<Okular::FormFieldButton *>(fields[QStringLiteral("CBMakeRO")]);
targetDefaultRW = dynamic_cast<Okular::FormFieldText *>(fields[QStringLiteral("TargetDefaultRw")]);
targetDefaultRO = dynamic_cast<Okular::FormFieldText *>(fields[QStringLiteral("TargetDefaultRo")]);
QVERIFY(cbMakeRW->state());
QVERIFY(cbMakeRO->state());
QVERIFY(targetDefaultRW->isReadOnly());
QVERIFY(!targetDefaultRO->isReadOnly());
}
void PartTest::testCrashTextEditDestroy()
{
const QString testFile = QStringLiteral(KDESRCDIR "data/formSamples.pdf");
Okular::Part part(nullptr, {});
part.openDocument(testFile);
part.widget()->show();
QVERIFY(QTest::qWaitForWindowExposed(part.widget()));
part.widget()->findChild<QTextEdit *>()->setText(QStringLiteral("HOLA"));
part.actionCollection()->action(QStringLiteral("view_toggle_forms"))->trigger();
}
void PartTest::testAnnotWindow()
{
Okular::Part part(nullptr, {});
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file1.pdf")));
part.widget()->show();
part.widget()->resize(800, 600);
QVERIFY(QTest::qWaitForWindowExposed(part.widget()));
part.m_document->setViewportPage(0);
QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseNormal"));
QCOMPARE(part.m_document->currentPage(), 0u);
// Create two distinct text annotations
Okular::Annotation *annot1 = new Okular::TextAnnotation();
annot1->setBoundingRectangle(Okular::NormalizedRect(0.8, 0.1, 0.85, 0.15));
annot1->setContents(QStringLiteral("Annot contents 111111"));
Okular::Annotation *annot2 = new Okular::TextAnnotation();
annot2->setBoundingRectangle(Okular::NormalizedRect(0.8, 0.3, 0.85, 0.35));
annot2->setContents(QStringLiteral("Annot contents 222222"));
// Add annot1 and annot2 to document
part.m_document->addPageAnnotation(0, annot1);
part.m_document->addPageAnnotation(0, annot2);
QVERIFY(part.m_document->page(0)->annotations().size() == 2);
QTimer *delayResizeEventTimer = part.m_pageView->findChildren<QTimer *>(QStringLiteral("delayResizeEventTimer")).at(0);
QVERIFY(delayResizeEventTimer->isActive());
QTest::qWait(delayResizeEventTimer->interval() * 2);
// wait for pixmap
QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView));
const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width();
const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height();
// Double click the first annotation to open its window (move mouse for visual feedback)
const NormalizedPoint annot1pt = annot1->boundingRectangle().center();
QTest::mouseMove(part.m_pageView->viewport(), QPoint(width * annot1pt.x, height * annot1pt.y));
QTest::mouseDClick(part.m_pageView->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(width * annot1pt.x, height * annot1pt.y));
QTRY_COMPARE(part.m_pageView->findChildren<QFrame *>(QStringLiteral("AnnotWindow")).size(), 1);
// Verify that the window is visible
QFrame *win1 = part.m_pageView->findChild<QFrame *>(QStringLiteral("AnnotWindow"));
QVERIFY(!win1->visibleRegion().isEmpty());
// Double click the second annotation to open its window (move mouse for visual feedback)
const NormalizedPoint annot2pt = annot2->boundingRectangle().center();
QTest::mouseMove(part.m_pageView->viewport(), QPoint(width * annot2pt.x, height * annot2pt.y));
QTest::mouseDClick(part.m_pageView->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(width * annot2pt.x, height * annot2pt.y));
QTRY_COMPARE(part.m_pageView->findChildren<QFrame *>(QStringLiteral("AnnotWindow")).size(), 2);
// Verify that the first window is hidden covered by the second, which is visible
QList<QFrame *> lstWin = part.m_pageView->findChildren<QFrame *>(QStringLiteral("AnnotWindow"));
QFrame *win2;
if (lstWin[0] == win1) {
win2 = lstWin[1];
} else {
win2 = lstWin[0];
}
QVERIFY(win1->visibleRegion().isEmpty());
QVERIFY(!win2->visibleRegion().isEmpty());
// Double click the first annotation to raise its window (move mouse for visual feedback)
QTest::mouseMove(part.m_pageView->viewport(), QPoint(width * annot1pt.x, height * annot1pt.y));
QTest::mouseDClick(part.m_pageView->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(width * annot1pt.x, height * annot1pt.y));
// Verify that the second window is hidden covered by the first, which is visible
QVERIFY(!win1->visibleRegion().isEmpty());
QVERIFY(win2->visibleRegion().isEmpty());
// Move annotation window 1 to partially show annotation window 2
win1->move(QPoint(win2->pos().x(), win2->pos().y() + 50));
// Verify that both windows are partially visible
QVERIFY(!win1->visibleRegion().isEmpty());
QVERIFY(!win2->visibleRegion().isEmpty());
// Click the second annotation window to raise it (move mouse for visual feedback)
auto widget = win2->window()->childAt(win2->mapTo(win2->window(), QPoint(10, 10)));
QTest::mouseMove(win2->window(), win2->mapTo(win2->window(), QPoint(10, 10)));
QTest::mouseClick(widget, Qt::LeftButton, Qt::NoModifier, widget->mapFrom(win2, QPoint(10, 10)));
QVERIFY(win1->visibleRegion().rectCount() == 3);
QVERIFY(win2->visibleRegion().rectCount() == 4);
}
// Helper for testAdditionalActionTriggers
static void verifyTargetStates(const QString &triggerName, const QMap<QString, Okular::FormField *> &fields, bool focusVisible, bool cursorVisible, bool mouseVisible, int line)
{
Okular::FormField *focusTarget = fields.value(triggerName + QStringLiteral("_focus_target"));
Okular::FormField *cursorTarget = fields.value(triggerName + QStringLiteral("_cursor_target"));
Okular::FormField *mouseTarget = fields.value(triggerName + QStringLiteral("_mouse_target"));
QVERIFY(focusTarget);
QVERIFY(cursorTarget);
QVERIFY(mouseTarget);
QTRY_VERIFY2(focusTarget->isVisible() == focusVisible, QStringLiteral("line: %1 focus for %2 not matched. Expected %3 Actual %4").arg(line).arg(triggerName).arg(focusTarget->isVisible()).arg(focusVisible).toUtf8().constData());
QTRY_VERIFY2(cursorTarget->isVisible() == cursorVisible, QStringLiteral("line: %1 cursor for %2 not matched. Actual %3 Expected %4").arg(line).arg(triggerName).arg(cursorTarget->isVisible()).arg(cursorVisible).toUtf8().constData());
QTRY_VERIFY2(mouseTarget->isVisible() == mouseVisible, QStringLiteral("line: %1 mouse for %2 not matched. Expected %3 Actual %4").arg(line).arg(triggerName).arg(mouseTarget->isVisible()).arg(mouseVisible).toUtf8().constData());
}
void PartTest::testAdditionalActionTriggers()
{
const QString testFile = QStringLiteral(KDESRCDIR "data/additionalFormActions.pdf");
Okular::Part part(nullptr, QVariantList());
part.openDocument(testFile);
part.widget()->resize(800, 600);
part.widget()->show();
QVERIFY(QTest::qWaitForWindowExposed(part.widget()));
QTimer *delayResizeEventTimer = part.m_pageView->findChildren<QTimer *>(QStringLiteral("delayResizeEventTimer")).at(0);
QVERIFY(delayResizeEventTimer->isActive());
QTest::qWait(delayResizeEventTimer->interval() * 2);
part.m_document->setViewportPage(0);
// wait for pixmap
QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView));
QMap<QString, Okular::FormField *> fields;
// Field names in test document are:
// For trigger fields: tf, cb, rb, dd, pb
// For target fields: <trigger_name>_focus_target, <trigger_name>_cursor_target,
// <trigger_name>_mouse_target
const Okular::Page *page = part.m_document->page(0);
const QList<Okular::FormField *> pageFormFields = page->formFields();
for (Okular::FormField *ff : pageFormFields) {
fields.insert(ff->name(), static_cast<Okular::FormField *>(ff));
}
// Verify that everything is set up.
verifyTargetStates(QStringLiteral("tf"), fields, true, true, true, __LINE__);
verifyTargetStates(QStringLiteral("cb"), fields, true, true, true, __LINE__);
verifyTargetStates(QStringLiteral("rb"), fields, true, true, true, __LINE__);
verifyTargetStates(QStringLiteral("dd"), fields, true, true, true, __LINE__);
verifyTargetStates(QStringLiteral("pb"), fields, true, true, true, __LINE__);
const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width();
const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height();
part.actionCollection()->action(QStringLiteral("view_toggle_forms"))->trigger();
QPoint tfPos(width * 0.045, height * 0.05);
QPoint cbPos(width * 0.045, height * 0.08);
QPoint rbPos(width * 0.045, height * 0.12);
QPoint ddPos(width * 0.045, height * 0.16);
QPoint pbPos(width * 0.045, height * 0.26);
// Test text field
auto widget = part.m_pageView->viewport()->childAt(tfPos);
QVERIFY(widget);
QTest::mouseMove(part.m_pageView->viewport(), QPoint(tfPos));
verifyTargetStates(QStringLiteral("tf"), fields, true, false, true, __LINE__);
QTest::mousePress(widget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5));
verifyTargetStates(QStringLiteral("tf"), fields, false, false, false, __LINE__);
QTest::mouseRelease(widget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5));
verifyTargetStates(QStringLiteral("tf"), fields, false, false, true, __LINE__);
// Checkbox
widget = part.m_pageView->viewport()->childAt(cbPos);
QVERIFY(widget);
QTest::mouseMove(part.m_pageView->viewport(), QPoint(cbPos));
verifyTargetStates(QStringLiteral("cb"), fields, true, false, true, __LINE__);
QTest::mousePress(widget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5));
verifyTargetStates(QStringLiteral("cb"), fields, false, false, false, __LINE__);
// Confirm that the textfield no longer has any invisible
verifyTargetStates(QStringLiteral("tf"), fields, true, true, true, __LINE__);
QTest::mouseRelease(widget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5));
verifyTargetStates(QStringLiteral("cb"), fields, false, false, true, __LINE__);
// Radio
widget = part.m_pageView->viewport()->childAt(rbPos);
QVERIFY(widget);
QTest::mouseMove(part.m_pageView->viewport(), QPoint(rbPos));
verifyTargetStates(QStringLiteral("rb"), fields, true, false, true, __LINE__);
QTest::mousePress(widget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5));
verifyTargetStates(QStringLiteral("rb"), fields, false, false, false, __LINE__);
QTest::mouseRelease(widget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5));
verifyTargetStates(QStringLiteral("rb"), fields, false, false, true, __LINE__);
// Dropdown
widget = part.m_pageView->viewport()->childAt(ddPos);
QVERIFY(widget);
QTest::mouseMove(part.m_pageView->viewport(), QPoint(ddPos));
verifyTargetStates(QStringLiteral("dd"), fields, true, false, true, __LINE__);
QTest::mousePress(widget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5));
verifyTargetStates(QStringLiteral("dd"), fields, false, false, false, __LINE__);
QTest::mouseRelease(widget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5));
verifyTargetStates(QStringLiteral("dd"), fields, false, false, true, __LINE__);
// Pushbutton
widget = part.m_pageView->viewport()->childAt(pbPos);
QVERIFY(widget);
QTest::mouseMove(part.m_pageView->viewport(), QPoint(pbPos));
verifyTargetStates(QStringLiteral("pb"), fields, true, false, true, __LINE__);
QTest::mousePress(widget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5));
verifyTargetStates(QStringLiteral("pb"), fields, false, false, false, __LINE__);
QTest::mouseRelease(widget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5));
verifyTargetStates(QStringLiteral("pb"), fields, false, false, true, __LINE__);
// Confirm that a mouse release outside does not trigger the show action.
QTest::mousePress(widget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5));
verifyTargetStates(QStringLiteral("pb"), fields, false, false, false, __LINE__);
QTest::mouseRelease(part.m_pageView->viewport(), Qt::LeftButton, Qt::NoModifier, tfPos);
verifyTargetStates(QStringLiteral("pb"), fields, false, false, false, __LINE__);
}
void PartTest::testTypewriterAnnotTool()
{
Okular::Part part(nullptr, QVariantList());
part.openUrl(QUrl::fromLocalFile(QStringLiteral(KDESRCDIR "data/file1.pdf")));
part.widget()->show();
QVERIFY(QTest::qWaitForWindowExposed(part.widget()));
const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width();
const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height();
part.m_document->setViewportPage(0);
// Find the TypeWriter annotation
QAction *typeWriterAction = part.actionCollection()->action(QStringLiteral("annotation_typewriter"));
QVERIFY(typeWriterAction);
typeWriterAction->trigger();
QTest::qWait(1000); // Wait for the "add new note" dialog to appear
TestingUtils::CloseDialogHelper closeDialogHelper(QDialogButtonBox::Ok);
QTest::mouseClick(part.m_pageView->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(width * 0.5, height * 0.2));
Annotation *annot = part.m_document->page(0)->annotations().first();
TextAnnotation *ta = static_cast<TextAnnotation *>(annot);
QVERIFY(annot);
QVERIFY(ta);
QCOMPARE(annot->subType(), Okular::Annotation::AText);
QCOMPARE(annot->style().color(), QColor(255, 255, 255, 0));
QCOMPARE(ta->textType(), Okular::TextAnnotation::InPlace);
QCOMPARE(ta->inplaceIntent(), Okular::TextAnnotation::TypeWriter);
}
void PartTest::testJumpToPage()
{
const QString testFile = QStringLiteral(KDESRCDIR "data/simple-multipage.pdf");
const int targetPage = 25;
Okular::Part part(nullptr, QVariantList());
part.openDocument(testFile);
part.widget()->resize(800, 600);
part.widget()->show();
QVERIFY(QTest::qWaitForWindowExposed(part.widget()));
part.m_document->pages();
part.m_document->setViewportPage(targetPage);
/* Document::setViewportPage triggers pixmap rendering in another thread.
* We want to test how things look AFTER finished signal arrives back,
* because PageView::slotRelayoutPages may displace the viewport again.
*/
QTRY_VERIFY(part.m_document->page(targetPage)->hasPixmap(part.m_pageView));
const int contentAreaHeight = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height();
const int pageWithSpaceTop = contentAreaHeight / part.m_document->pages() * targetPage;
/*
* This is a test for a "known by trial" displacement.
* We'd need access to part.m_pageView->d->items[targetPage]->croppedGeometry().top(),
* to determine the expected viewport position, but we don't have access.
*/
QCOMPARE(part.m_pageView->verticalScrollBar()->value(), pageWithSpaceTop - 4);
}
void PartTest::testOpenAtPage()
{
const QString testFile = QStringLiteral(KDESRCDIR "data/simple-multipage.pdf");
QUrl url = QUrl::fromLocalFile(testFile);
Okular::Part part(nullptr, QVariantList());
const uint targetPageNumA = 25;
const uint expectedPageA = targetPageNumA - 1;
url.setFragment(QString::number(targetPageNumA));
part.openUrl(url);
QCOMPARE(part.m_document->currentPage(), expectedPageA);
// 'page=<pagenum>' param as specified in RFC 3778
const uint targetPageNumB = 15;
const uint expectedPageB = targetPageNumB - 1;
url.setFragment(QStringLiteral("page=") + QString::number(targetPageNumB));
part.openUrl(url);
QCOMPARE(part.m_document->currentPage(), expectedPageB);
}
void PartTest::testForwardBackwardNavigation()
{
const QString testFile = QStringLiteral(KDESRCDIR "data/simple-multipage.pdf");
Okular::Part part(nullptr, QVariantList());
part.openDocument(testFile);
part.widget()->resize(800, 600);
part.widget()->show();
QVERIFY(QTest::qWaitForWindowExposed(part.widget()));
// Go to some page
const int targetPageA = 15;
part.m_document->setViewportPage(targetPageA);
QVERIFY(part.m_document->viewport() == DocumentViewport(targetPageA));
// Go to some other page
const int targetPageB = 25;
part.m_document->setViewportPage(targetPageB);
QVERIFY(part.m_document->viewport() == DocumentViewport(targetPageB));
// Go back to page A
QVERIFY(QMetaObject::invokeMethod(&part, "slotHistoryBack"));
QVERIFY(part.m_document->viewport().pageNumber == targetPageA);
// Go back to page B
QVERIFY(QMetaObject::invokeMethod(&part, "slotHistoryNext"));
QVERIFY(part.m_document->viewport().pageNumber == targetPageB);
}
void PartTest::testTabletProximityBehavior()
{
QVariantList dummyArgs;
Okular::Part part {nullptr, dummyArgs};
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file1.pdf")));
part.slotShowPresentation();
PresentationWidget *w = part.m_presentationWidget;
QVERIFY(w);
part.widget()->show();
// close the KMessageBox "There are two ways of exiting[...]"
TestingUtils::CloseDialogHelper closeDialogHelper(w, QDialogButtonBox::Ok); // confirm the "To leave, press ESC"
auto pointingDevice = new QPointingDevice(QStringLiteral("test"), 42, QInputDevice::DeviceType::Stylus, QPointingDevice::PointerType::Pen, QInputDevice::Capability::All, 3, 3);
QTabletEvent enterProximityEvent {QEvent::TabletEnterProximity, pointingDevice, QPointF(10, 10), QPointF(10, 10), 1., 0, 0, 1., 1., 0, Qt::NoModifier, Qt::NoButton, Qt::NoButton};
QTabletEvent leaveProximityEvent {QEvent::TabletLeaveProximity, pointingDevice, QPointF(10, 10), QPointF(10, 10), 1., 0, 0, 1., 1., 0, Qt::NoModifier, Qt::NoButton, Qt::NoButton};
// Test with the Okular::Settings::EnumSlidesCursor::Visible setting
Okular::Settings::self()->setSlidesCursor(Okular::Settings::EnumSlidesCursor::Visible);
// Send an enterProximity event
qApp->notify(qApp, &enterProximityEvent);
// The cursor should be a cross-hair
QVERIFY(w->cursor().shape() == Qt::CursorShape(Qt::CrossCursor));
// Send a leaveProximity event
qApp->notify(qApp, &leaveProximityEvent);
// After the leaveProximityEvent, the cursor should be an arrow again, because
// we have set the slidesCursor mode to 'Visible'
QVERIFY(w->cursor().shape() == Qt::CursorShape(Qt::ArrowCursor));
// Test with the Okular::Settings::EnumSlidesCursor::Hidden setting
Okular::Settings::self()->setSlidesCursor(Okular::Settings::EnumSlidesCursor::Hidden);
qApp->notify(qApp, &enterProximityEvent);
QVERIFY(w->cursor().shape() == Qt::CursorShape(Qt::CrossCursor));
qApp->notify(qApp, &leaveProximityEvent);
QVERIFY(w->cursor().shape() == Qt::CursorShape(Qt::BlankCursor));
// Moving the mouse should not bring the cursor back
QTest::mouseMove(w, QPoint(100, 100));
QVERIFY(w->cursor().shape() == Qt::CursorShape(Qt::BlankCursor));
// First test with the Okular::Settings::EnumSlidesCursor::HiddenDelay setting
Okular::Settings::self()->setSlidesCursor(Okular::Settings::EnumSlidesCursor::HiddenDelay);
qApp->notify(qApp, &enterProximityEvent);
QVERIFY(w->cursor().shape() == Qt::CursorShape(Qt::CrossCursor));
qApp->notify(qApp, &leaveProximityEvent);
// After the leaveProximityEvent, the cursor should be blank, because
// we have set the slidesCursor mode to 'HiddenDelay'
QVERIFY(w->cursor().shape() == Qt::CursorShape(Qt::BlankCursor));
// Moving the mouse should bring the cursor back
QTest::mouseMove(w, QPoint(150, 150));
QVERIFY(w->cursor().shape() == Qt::CursorShape(Qt::ArrowCursor));
}
void PartTest::testOpenPrintPreview()
{
QVariantList dummyArgs;
Okular::Part part(nullptr, dummyArgs);
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file1.pdf")));
part.widget()->show();
QVERIFY(QTest::qWaitForWindowExposed(part.widget()));
TestingUtils::CloseDialogHelper closeDialogHelper(QDialogButtonBox::Close);
part.slotPrintPreview();
}
void PartTest::testMouseModeMenu()
{
QVariantList dummyArgs;
Okular::Part part(nullptr, dummyArgs);
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file1.pdf")));
QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseNormal");
// Get mouse mode menu action
QAction *mouseModeAction = part.actionCollection()->action(QStringLiteral("mouse_selecttools"));
QVERIFY(mouseModeAction);
QMenu *mouseModeActionMenu = mouseModeAction->menu();
// Test that actions are usable (not disabled)
QVERIFY(mouseModeActionMenu->actions().at(0)->isEnabled());
QVERIFY(mouseModeActionMenu->actions().at(1)->isEnabled());
QVERIFY(mouseModeActionMenu->actions().at(2)->isEnabled());
// Test activating area selection mode
mouseModeActionMenu->actions().at(0)->trigger();
QCOMPARE(Okular::Settings::mouseMode(), (int)Okular::Settings::EnumMouseMode::RectSelect);
// Test activating text selection mode
mouseModeActionMenu->actions().at(1)->trigger();
QCOMPARE(Okular::Settings::mouseMode(), (int)Okular::Settings::EnumMouseMode::TextSelect);
// Test activating table selection mode
mouseModeActionMenu->actions().at(2)->trigger();
QCOMPARE(Okular::Settings::mouseMode(), (int)Okular::Settings::EnumMouseMode::TableSelect);
}
void PartTest::testFullScreenRequest()
{
QVariantList dummyArgs;
Okular::Part part(nullptr, dummyArgs);
// Open file. For this particular file, a dialog has to appear asking whether
// one wants to comply with the wish to go to presentation mode directly.
// Answer 'no'
auto dialogHelper = std::make_unique<TestingUtils::CloseDialogHelper>(&part, QDialogButtonBox::No);
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/RequestFullScreen.pdf")));
// Check that we are not in presentation mode
QEXPECT_FAIL("", "The presentation widget should not be shown because we clicked No in the dialog", Continue);
QTRY_VERIFY_WITH_TIMEOUT(part.m_presentationWidget, 1000);
// Reload the file. The initial dialog should no appear again.
// (This is https://bugs.kde.org/show_bug.cgi?id=361740)
part.reload();
// Open the file again. Now we answer "yes, go to presentation mode"
dialogHelper = std::make_unique<TestingUtils::CloseDialogHelper>(&part, QDialogButtonBox::Yes);
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/RequestFullScreen.pdf")));
// Test whether we really are in presentation mode
QTRY_VERIFY(part.m_presentationWidget);
}
void PartTest::testZoomInFacingPages()
{
QVariantList dummyArgs;
Okular::Part part(nullptr, dummyArgs);
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file2.pdf")));
QAction *facingAction = part.m_pageView->findChild<QAction *>(QStringLiteral("view_render_mode_facing"));
KSelectAction *zoomSelectAction = part.m_pageView->findChild<KSelectAction *>(QStringLiteral("zoom_to"));
part.widget()->resize(600, 400);
part.widget()->show();
QVERIFY(QTest::qWaitForWindowExposed(part.widget()));
facingAction->trigger();
while (zoomSelectAction->currentText() != QStringLiteral("12%")) {
QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotZoomOut"));
}
QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView));
QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotZoomIn"));
QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotZoomIn"));
QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotZoomIn"));
QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotZoomIn"));
QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotZoomIn"));
QTRY_COMPARE(zoomSelectAction->currentText(), QStringLiteral("66%"));
// Back to single mode
part.m_pageView->findChild<QAction *>(QStringLiteral("view_render_mode_single"))->trigger();
}
void PartTest::testZoomWithCrop()
{
// We test that all zoom levels can be achieved with cropped pages, bug 342003
QVariantList dummyArgs;
Okular::Part part(nullptr, dummyArgs);
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file2.pdf")));
KActionMenu *cropMenu = part.m_pageView->findChild<KActionMenu *>(QStringLiteral("view_trim_mode"));
KToggleAction *cropAction = cropMenu->menu()->findChild<KToggleAction *>(QStringLiteral("view_trim_margins"));
KSelectAction *zoomSelectAction = part.m_pageView->findChild<KSelectAction *>(QStringLiteral("zoom_to"));
part.widget()->resize(600, 400);
part.widget()->show();
QVERIFY(QTest::qWaitForWindowExposed(part.widget()));
// Activate "Trim Margins"
QVERIFY(!Okular::Settings::trimMargins());
cropAction->trigger();
QVERIFY(Okular::Settings::trimMargins());
// Wait for the bounding boxes
QTRY_VERIFY(part.m_document->page(0)->isBoundingBoxKnown());
QTRY_VERIFY(part.m_document->page(1)->isBoundingBoxKnown());
// Zoom out
for (int i = 0; i < 20; i++) {
QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotZoomOut"));
}
QCOMPARE(zoomSelectAction->currentText(), QStringLiteral("12%"));
// Zoom in and out and check that all zoom levels appear
QSet<QString> zooms_ref {QStringLiteral("12%"),
QStringLiteral("25%"),
QStringLiteral("33%"),
QStringLiteral("50%"),
QStringLiteral("66%"),
QStringLiteral("75%"),
QStringLiteral("100%"),
QStringLiteral("125%"),
QStringLiteral("150%"),
QStringLiteral("200%"),
QStringLiteral("400%"),
QStringLiteral("800%"),
QStringLiteral("1,600%"),
QStringLiteral("2,500%"),
QStringLiteral("5,000%"),
QStringLiteral("10,000%")};
for (int j = 0; j < 2; j++) {
QSet<QString> zooms;
for (int i = 0; i < 18; i++) {
zooms << zoomSelectAction->currentText();
QVERIFY(QMetaObject::invokeMethod(part.m_pageView, j == 0 ? "slotZoomIn" : "slotZoomOut"));
}
QVERIFY(zooms.contains(zooms_ref));
}
// Deactivate "Trim Margins"
QVERIFY(Okular::Settings::trimMargins());
cropAction->trigger();
QVERIFY(!Okular::Settings::trimMargins());
}
void PartTest::testLinkWithCrop()
{
// We test that link targets are correct with cropping, related to bug 198427
QVariantList dummyArgs;
Okular::Part part(nullptr, dummyArgs);
QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/pdf_with_internal_links.pdf")));
KActionMenu *cropMenu = part.m_pageView->findChild<KActionMenu *>(QStringLiteral("view_trim_mode"));
KToggleAction *cropAction = cropMenu->menu()->findChild<KToggleAction *>(QStringLiteral("view_trim_selection"));
part.widget()->resize(600, 400);
part.widget()->show();
QVERIFY(QTest::qWaitForWindowExposed(part.widget()));
// wait for pixmap
QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView));
const int width = part.m_pageView->viewport()->width();
const int height = part.m_pageView->viewport()->height();
// Move to a location without a link
QTest::mouseMove(part.m_pageView->viewport(), QPoint(width * 0.1, width * 0.1));
// The cursor should be normal
QTRY_COMPARE(part.m_pageView->cursor().shape(), Qt::CursorShape(Qt::OpenHandCursor));
// Activate "Trim Margins"
cropAction->trigger();
// The cursor should be a cross-hair
QTRY_COMPARE(part.m_pageView->cursor().shape(), Qt::CursorShape(Qt::CrossCursor));
const int mouseStartY = height * 0.2;
const int mouseEndY = height * 0.8;
const int mouseStartX = width * 0.2;
const int mouseEndX = width * 0.8;
// Trim the page
simulateMouseSelection(mouseStartX, mouseStartY, mouseEndX, mouseEndY, part.m_pageView->viewport());
// We seem to have a trimmed view where we just by sheer luck ends up with mouse over a link at least sometimes
// So move the mouse
QTest::mouseMove(part.m_pageView->viewport(), QPoint(width * 0.1, width * 0.1));
// The cursor should be normal again
QTRY_COMPARE(part.m_pageView->cursor().shape(), Qt::CursorShape(Qt::OpenHandCursor));
// Click a link
const QPoint click(width * 0.2, height * 0.2);
QTest::mouseMove(part.m_pageView->viewport(), click);
QTest::mouseClick(part.m_pageView->viewport(), Qt::LeftButton, Qt::NoModifier, click);
QTRY_VERIFY2_WITH_TIMEOUT(qAbs(part.m_document->viewport().rePos.normalizedY - 0.167102333237) < 0.01, qPrintable(QStringLiteral("We are at %1").arg(part.m_document->viewport().rePos.normalizedY)), 500);
// Deactivate "Trim Margins"
cropAction->trigger();
}
void PartTest::testFieldFormatting()
{
// Test field formatting. This has to be a parttest so that we
// can properly test focus in / out which triggers formatting.
const QString testFile = QStringLiteral(KDESRCDIR "data/fieldFormat.pdf");
Okular::Part part(nullptr, QVariantList());
part.openDocument(testFile);
part.widget()->resize(800, 600);
part.widget()->show();
QVERIFY(QTest::qWaitForWindowExposed(part.widget()));
// Field names in test document are:
//
// us_currency_fmt for formatting like "$ 1,234.56"
// de_currency_fmt for formatting like "1.234,56 €"
// de_simple_sum for calculation test and formatting like "1.234,56€"
// date_mm_dd_yyyy for dates like "18/06/2018"
// date_dd_mm_yyyy for dates like "06/18/2018"
// percent_fmt for percent format like "100,00%" if you enter 1
// time_HH_MM_fmt for times like "23:12"
// time_HH_MM_ss_fmt for times like "23:12:34"
// special_phone_number for an example of a special format selectable in Acrobat.
QMap<QString, Okular::FormField *> fields;
const Okular::Page *page = part.m_document->page(0);
const auto formFields = page->formFields();
for (Okular::FormField *ff : formFields) {
fields.insert(ff->name(), static_cast<Okular::FormField *>(ff));
}
const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width();
const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height();
part.m_document->setViewportPage(0);
// wait for pixmap
QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView));
part.actionCollection()->action(QStringLiteral("view_toggle_forms"))->trigger();
// Note as of version 1.5:
// The test document is prepared for future extensions to formatting for dates etc.
// Currently we only have the number format to test.
const auto ff_us = dynamic_cast<Okular::FormFieldText *>(fields.value(QStringLiteral("us_currency_fmt")));
const auto ff_de = dynamic_cast<Okular::FormFieldText *>(fields.value(QStringLiteral("de_currency_fmt")));
const auto ff_sum = dynamic_cast<Okular::FormFieldText *>(fields.value(QStringLiteral("de_simple_sum")));
const QPoint usPos(width * 0.25, height * 0.025);
const QPoint dePos(width * 0.25, height * 0.05);
const QPoint deSumPos(width * 0.25, height * 0.075);
const auto viewport = part.m_pageView->viewport();
QVERIFY(viewport);
auto usCurrencyWidget = dynamic_cast<QLineEdit *>(viewport->childAt(usPos));
auto deCurrencyWidget = dynamic_cast<QLineEdit *>(viewport->childAt(dePos));
auto sumCurrencyWidget = dynamic_cast<QLineEdit *>(viewport->childAt(deSumPos));
// Check that the widgets were found at the right position
QVERIFY(usCurrencyWidget);
QVERIFY(deCurrencyWidget);
QVERIFY(sumCurrencyWidget);
QTest::mousePress(usCurrencyWidget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5));
QTRY_VERIFY(usCurrencyWidget->hasFocus());
// locale is en_US for this test. Enter a value and check it.
usCurrencyWidget->setText(QStringLiteral("1234.56"));
// Check that the internal text matches
QCOMPARE(ff_us->text(), QStringLiteral("1234.56"));
// Now move the focus to trigger formatting.
QTest::mousePress(deCurrencyWidget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5));
QTRY_VERIFY(deCurrencyWidget->hasFocus());
QCOMPARE(usCurrencyWidget->text(), QStringLiteral("$ 1,234.56"));
QCOMPARE(ff_us->text(), QStringLiteral("1234.56"));
// And again with an invalid number
QTest::mousePress(usCurrencyWidget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5));
QTRY_VERIFY(usCurrencyWidget->hasFocus());
usCurrencyWidget->setText(QStringLiteral("131.234,567"));
QTest::mousePress(deCurrencyWidget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5));
QTRY_VERIFY(deCurrencyWidget->hasFocus());
// Check that the internal text still contains it.
QCOMPARE(ff_us->text(), QStringLiteral("131.234,567"));
// Just check that the text does not match the internal text.
// We don't check for a concrete value to keep NaN handling flexible
QVERIFY(ff_us->text() != usCurrencyWidget->text());
// Move the focus back and modify it a bit more
QTest::mousePress(usCurrencyWidget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5));
QTRY_VERIFY(usCurrencyWidget->hasFocus());
usCurrencyWidget->setText(QStringLiteral("1,234.567"));
QTest::mousePress(deCurrencyWidget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5));
QTRY_VERIFY(deCurrencyWidget->hasFocus());
QCOMPARE(usCurrencyWidget->text(), QStringLiteral("$ 1,234.57"));
// Sum should already match
QCOMPARE(sumCurrencyWidget->text(), QStringLiteral("1.234,57€"));
// Set a text in the de field
deCurrencyWidget->setText(QStringLiteral("1,123,234.567"));
QTest::mousePress(usCurrencyWidget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5));
QTRY_VERIFY(usCurrencyWidget->hasFocus());
QCOMPARE(deCurrencyWidget->text(), QStringLiteral("1.123.234,57 €"));
QCOMPARE(ff_de->text(), QStringLiteral("1,123,234.567"));
QCOMPARE(sumCurrencyWidget->text(), QStringLiteral("1.124.469,13€"));
QCOMPARE(ff_sum->text(), QStringLiteral("1,124,469.1340000000782310962677002"));
}
} // namespace Okular
int main(int argc, char *argv[])
{
// Force consistent locale
QLocale locale(QStringLiteral("en_US.UTF-8"));
if (locale == QLocale::c()) { // This is the way to check if the above worked
locale = QLocale(QLocale::English, QLocale::UnitedStates);
}
QLocale::setDefault(locale);
qputenv("LC_ALL", "en_US.UTF-8"); // For UNIX, third-party libraries
// Ensure consistent configs/caches
QTemporaryDir homeDir; // QTemporaryDir automatically cleans up when it goes out of scope
Q_ASSERT(homeDir.isValid());
QByteArray homePath = QFile::encodeName(homeDir.path());
qDebug() << homePath;
qputenv("USERPROFILE", homePath);
qputenv("HOME", homePath);
qputenv("XDG_DATA_HOME", QByteArray(homePath + "/.local"));
qputenv("XDG_CONFIG_HOME", QByteArray(homePath + "/.kde-unit-test/xdg/config"));
// Disable fancy debug output
qunsetenv("QT_MESSAGE_PATTERN");
Okular::Settings::instance(QStringLiteral("okularparttest"));
QApplication app(argc, argv);
app.setApplicationName(QStringLiteral("okularparttest"));
app.setOrganizationDomain(QStringLiteral("kde.org"));
app.setQuitOnLastWindowClosed(false);
qRegisterMetaType<QUrl>(); /*as done by kapplication*/
qRegisterMetaType<QList<QUrl>>();
Okular::PartTest test;
return QTest::qExec(&test, argc, argv);
}
#include "parttest.moc"