File indexing completed on 2024-05-12 16:06:05
0001 /* 0002 SPDX-FileCopyrightText: 2013 Albert Astals Cid <aacid@kde.org> 0003 0004 Work sponsored by the LiMux project of the city of Munich: 0005 SPDX-FileCopyrightText: 2017 Klarälvdalens Datakonsult AB a KDAB Group company <info@kdab.com> 0006 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 0010 // clazy:excludeall=qstring-allocations 0011 0012 #include <QSignalSpy> 0013 #include <QTest> 0014 0015 #include "../core/annotations.h" 0016 #include "../core/document_p.h" 0017 #include "../core/form.h" 0018 #include "../core/page.h" 0019 #include "../part/pageview.h" 0020 #include "../part/part.h" 0021 #include "../part/presentationwidget.h" 0022 #include "../part/sidebar.h" 0023 #include "../part/toc.h" 0024 #include "../part/toggleactionmenu.h" 0025 #include "../settings.h" 0026 #include "closedialoghelper.h" 0027 0028 #include <KActionCollection> 0029 #include <KConfigDialog> 0030 #include <KParts/OpenUrlArguments> 0031 0032 #include <QApplication> 0033 #include <QClipboard> 0034 #include <QDesktopServices> 0035 #include <QLineEdit> 0036 #include <QMenu> 0037 #include <QMessageBox> 0038 #include <QPushButton> 0039 #include <QScrollBar> 0040 #include <QTabletEvent> 0041 #include <QTemporaryDir> 0042 #include <QTemporaryFile> 0043 #include <QTextEdit> 0044 #include <QTimer> 0045 #include <QToolBar> 0046 #include <QTreeView> 0047 #include <QUrl> 0048 0049 namespace Okular 0050 { 0051 class PartTest : public QObject 0052 { 0053 Q_OBJECT 0054 0055 static bool openDocument(Okular::Part *part, const QString &filePath); 0056 0057 Q_SIGNALS: 0058 void urlHandler(const QUrl &url); // NOLINT(readability-inconsistent-declaration-parameter-name) 0059 0060 private Q_SLOTS: 0061 void init(); 0062 0063 void testZoomWithCrop(); 0064 void testReload(); 0065 void testCanceledReload(); 0066 void testTOCReload(); 0067 void testForwardPDF(); 0068 void testForwardPDF_data(); 0069 void testGeneratorPreferences(); 0070 void testSelectText(); 0071 void testClickInternalLink(); 0072 void testScrollBarAndMouseWheel(); 0073 void testOpenUrlArguments(); 0074 void test388288(); 0075 void testSaveAs(); 0076 void testSaveAs_data(); 0077 void testSaveAsToNonExistingPath(); 0078 void testSaveAsToSymlink(); 0079 void testSaveIsSymlink(); 0080 void testSidebarItemAfterSaving(); 0081 void testViewModeSavingPerFile(); 0082 void testSaveAsUndoStackAnnotations(); 0083 void testSaveAsUndoStackAnnotations_data(); 0084 void testSaveAsUndoStackForms(); 0085 void testSaveAsUndoStackForms_data(); 0086 void testMouseMoveOverLinkWhileInSelectionMode(); 0087 void testClickUrlLinkWhileInSelectionMode(); 0088 void testeTextSelectionOverAndAcrossLinks_data(); 0089 void testeTextSelectionOverAndAcrossLinks(); 0090 void testClickUrlLinkWhileLinkTextIsSelected(); 0091 void testRClickWhileLinkTextIsSelected(); 0092 void testRClickOverLinkWhileLinkTextIsSelected(); 0093 void testRClickOnSelectionModeShoulShowFollowTheLinkMenu(); 0094 void testClickAnywhereAfterSelectionShouldUnselect(); 0095 void testeRectSelectionStartingOnLinks(); 0096 void testCheckBoxReadOnly(); 0097 void testCrashTextEditDestroy(); 0098 void testAnnotWindow(); 0099 void testAdditionalActionTriggers(); 0100 void testTypewriterAnnotTool(); 0101 void testJumpToPage(); 0102 void testOpenAtPage(); 0103 void testForwardBackwardNavigation(); 0104 void testTabletProximityBehavior(); 0105 void testOpenPrintPreview(); 0106 void testMouseModeMenu(); 0107 void testFullScreenRequest(); 0108 void testZoomInFacingPages(); 0109 void testLinkWithCrop(); 0110 void testFieldFormatting(); 0111 0112 private: 0113 void simulateMouseSelection(double startX, double startY, double endX, double endY, QWidget *target); 0114 }; 0115 0116 class PartThatHijacksQueryClose : public Okular::Part 0117 { 0118 Q_OBJECT 0119 public: 0120 PartThatHijacksQueryClose(QObject *parent, const QVariantList &args) 0121 : Okular::Part(parent, args) 0122 , behavior(PassThru) 0123 { 0124 } 0125 0126 enum Behavior { PassThru, ReturnTrue, ReturnFalse }; 0127 0128 void setQueryCloseBehavior(Behavior new_behavior) 0129 { 0130 behavior = new_behavior; 0131 } 0132 0133 bool queryClose() override 0134 { 0135 if (behavior == PassThru) { 0136 return Okular::Part::queryClose(); 0137 } else { // ReturnTrue or ReturnFalse 0138 return (behavior == ReturnTrue); 0139 } 0140 } 0141 0142 private: 0143 Behavior behavior; 0144 }; 0145 0146 bool PartTest::openDocument(Okular::Part *part, const QString &filePath) 0147 { 0148 part->openDocument(filePath); 0149 return part->m_document->isOpened(); 0150 } 0151 0152 void PartTest::init() 0153 { 0154 // Default settings for every test 0155 Okular::Settings::self()->setDefaults(); 0156 0157 // Clean docdatas 0158 const QList<QUrl> urls = {QUrl::fromUserInput(QStringLiteral("file://" KDESRCDIR "data/file1.pdf")), 0159 QUrl::fromUserInput(QStringLiteral("file://" KDESRCDIR "data/file2.pdf")), 0160 QUrl::fromUserInput(QStringLiteral("file://" KDESRCDIR "data/simple-multipage.pdf")), 0161 QUrl::fromUserInput(QStringLiteral("file://" KDESRCDIR "data/tocreload.pdf")), 0162 QUrl::fromUserInput(QStringLiteral("file://" KDESRCDIR "data/pdf_with_links.pdf")), 0163 QUrl::fromUserInput(QStringLiteral("file://" KDESRCDIR "data/RequestFullScreen.pdf"))}; 0164 0165 for (const QUrl &url : urls) { 0166 QFileInfo fileReadTest(url.toLocalFile()); 0167 const QString docDataPath = Okular::DocumentPrivate::docDataFileName(url, fileReadTest.size()); 0168 QFile::remove(docDataPath); 0169 } 0170 } 0171 0172 // Test that Okular doesn't crash after a successful reload 0173 void PartTest::testReload() 0174 { 0175 QVariantList dummyArgs; 0176 Okular::Part part(nullptr, dummyArgs); 0177 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file1.pdf"))); 0178 part.reload(); 0179 qApp->processEvents(); 0180 } 0181 0182 // Test that Okular doesn't crash after a canceled reload 0183 void PartTest::testCanceledReload() 0184 { 0185 QVariantList dummyArgs; 0186 PartThatHijacksQueryClose part(nullptr, dummyArgs); 0187 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file1.pdf"))); 0188 0189 // When queryClose() returns false, the reload operation is canceled (as if 0190 // the user had chosen Cancel in the "Save changes?" message box) 0191 part.setQueryCloseBehavior(PartThatHijacksQueryClose::ReturnFalse); 0192 0193 part.reload(); 0194 0195 qApp->processEvents(); 0196 } 0197 0198 void PartTest::testTOCReload() 0199 { 0200 QVariantList dummyArgs; 0201 Okular::Part part(nullptr, dummyArgs); 0202 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/tocreload.pdf"))); 0203 QCOMPARE(part.m_toc->expandedNodes().count(), 0); 0204 part.m_toc->m_treeView->expandAll(); 0205 QCOMPARE(part.m_toc->expandedNodes().count(), 3); 0206 part.reload(); 0207 qApp->processEvents(); 0208 QCOMPARE(part.m_toc->expandedNodes().count(), 3); 0209 } 0210 0211 void PartTest::testForwardPDF() 0212 { 0213 QFETCH(QString, dir); 0214 0215 QVariantList dummyArgs; 0216 Okular::Part part(nullptr, dummyArgs); 0217 0218 // Create temp dir named like this: ${system temp dir}/${random string}/${dir} 0219 const QTemporaryDir tempDir; 0220 const QDir workDir(QDir(tempDir.path()).filePath(dir)); 0221 workDir.mkpath(QStringLiteral(".")); 0222 0223 const QString pdfResult = workDir.path() + QStringLiteral("/synctextest.pdf"); 0224 QVERIFY(QFile::copy(QStringLiteral(KDESRCDIR "data/synctextest.pdf"), pdfResult)); 0225 const QString gzDestination = workDir.path() + QStringLiteral("/synctextest.synctex.gz"); 0226 QVERIFY(QFile::copy(QStringLiteral(KDESRCDIR "data/synctextest.synctex.gz"), gzDestination)); 0227 0228 QVERIFY(openDocument(&part, pdfResult)); 0229 part.m_document->setViewportPage(0); 0230 QCOMPARE(part.m_document->currentPage(), 0u); 0231 part.closeUrl(); 0232 0233 QUrl u(QUrl::fromLocalFile(pdfResult)); 0234 // Update this if you regenerate the synctextest.pdf somewhere else 0235 u.setFragment(QStringLiteral("src:100/home/tsdgeos/devel/kde/okular/autotests/data/synctextest.tex")); 0236 part.openUrl(u); 0237 QCOMPARE(part.m_document->currentPage(), 1u); 0238 } 0239 0240 void PartTest::testForwardPDF_data() 0241 { 0242 QTest::addColumn<QString>("dir"); 0243 0244 QTest::newRow("non-utf8") << QStringLiteral("synctextest"); 0245 // QStringliteral is broken on windows with non ascii chars so using QString::fromUtf8 0246 QTest::newRow("utf8") << QString::fromUtf8("ßðđđŋßðđŋ"); 0247 } 0248 0249 void PartTest::testGeneratorPreferences() 0250 { 0251 KConfigDialog *dialog; 0252 QVariantList dummyArgs; 0253 Okular::Part part(nullptr, dummyArgs); 0254 0255 // Test that we don't crash while opening the dialog 0256 dialog = part.slotGeneratorPreferences(); 0257 qApp->processEvents(); 0258 delete dialog; // closes the dialog and recursively destroys all widgets 0259 0260 // Test that we don't crash while opening a new instance of the dialog 0261 // This catches attempts to reuse widgets that have been destroyed 0262 dialog = part.slotGeneratorPreferences(); 0263 qApp->processEvents(); 0264 delete dialog; 0265 } 0266 0267 void PartTest::testSelectText() 0268 { 0269 QVariantList dummyArgs; 0270 Okular::Part part(nullptr, dummyArgs); 0271 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file2.pdf"))); 0272 part.widget()->show(); 0273 QVERIFY(QTest::qWaitForWindowExposed(part.widget())); 0274 0275 part.m_document->setViewportPage(0); 0276 0277 // wait for pixmap 0278 QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView)); 0279 0280 const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width(); 0281 const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height(); 0282 0283 QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseTextSelect")); 0284 0285 const int mouseY = height * 0.052; 0286 const int mouseStartX = width * 0.12; 0287 const int mouseEndX = width * 0.7; 0288 0289 simulateMouseSelection(mouseStartX, mouseY, mouseEndX, mouseY, part.m_pageView->viewport()); 0290 0291 QApplication::clipboard()->clear(); 0292 QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "copyTextSelection")); 0293 0294 QCOMPARE(QApplication::clipboard()->text(), QStringLiteral("Hola que tal")); 0295 } 0296 0297 void PartTest::testClickInternalLink() 0298 { 0299 QVariantList dummyArgs; 0300 Okular::Part part(nullptr, dummyArgs); 0301 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file2.pdf"))); 0302 part.widget()->show(); 0303 QVERIFY(QTest::qWaitForWindowExposed(part.widget())); 0304 0305 part.m_document->setViewportPage(0); 0306 0307 // wait for pixmap 0308 QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView)); 0309 0310 const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width(); 0311 const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height(); 0312 0313 QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseNormal")); 0314 0315 QCOMPARE(part.m_document->currentPage(), 0u); 0316 QTest::mouseMove(part.m_pageView->viewport(), QPoint(width * 0.17, height * 0.05)); 0317 QTest::mouseClick(part.m_pageView->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(width * 0.17, height * 0.05)); 0318 QTRY_COMPARE(part.m_document->currentPage(), 1u); 0319 0320 // make sure cursor goes back to being an open hand again. Bug 421437 0321 QTRY_COMPARE_WITH_TIMEOUT(part.m_pageView->cursor().shape(), Qt::OpenHandCursor, 1000); 0322 } 0323 0324 // Test for bug 421159, which is: When scrolling down with the scroll bar 0325 // followed by scrolling down with the mouse wheel, the mouse wheel scrolling 0326 // will make the viewport jump back to the first page. 0327 void PartTest::testScrollBarAndMouseWheel() 0328 { 0329 QVariantList dummyArgs; 0330 Okular::Part part(nullptr, dummyArgs); 0331 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/simple-multipage.pdf"))); 0332 part.widget()->show(); 0333 QVERIFY(QTest::qWaitForWindowExposed(part.widget())); 0334 0335 part.m_document->setViewportPage(0); 0336 0337 // wait for pixmap 0338 QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView)); 0339 0340 // Make sure we are on the first page 0341 QCOMPARE(part.m_document->currentPage(), 0u); 0342 0343 // Two clicks on the vertical scrollbar 0344 auto scrollBar = part.m_pageView->verticalScrollBar(); 0345 0346 QTest::mouseClick(scrollBar, Qt::LeftButton); 0347 QTest::qWait(QApplication::doubleClickInterval() * 2); // Wait a tiny bit 0348 QTest::mouseClick(scrollBar, Qt::LeftButton); 0349 0350 // We have scrolled enough to be on the second page now 0351 QCOMPARE(part.m_document->currentPage(), 1u); 0352 0353 // Scroll further down using the mouse wheel 0354 auto wheelDown = new QWheelEvent({}, {}, {}, {0, -150}, Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, false); 0355 QCoreApplication::postEvent(part.m_pageView->viewport(), wheelDown); 0356 0357 // Wait a little for the scrolling to actually happen. 0358 // We should still be on the second page after that. 0359 QTest::qWait(1000); 0360 0361 QCOMPARE(part.m_document->currentPage(), 1u); 0362 } 0363 0364 // cursor switches to Hand when hovering over link in TextSelect mode. 0365 void PartTest::testMouseMoveOverLinkWhileInSelectionMode() 0366 { 0367 QVariantList dummyArgs; 0368 Okular::Part part(nullptr, dummyArgs); 0369 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/pdf_with_links.pdf"))); 0370 // resize window to avoid problem with selection areas 0371 part.widget()->resize(800, 600); 0372 part.widget()->show(); 0373 QVERIFY(QTest::qWaitForWindowExposed(part.widget())); 0374 0375 part.m_document->setViewportPage(0); 0376 0377 // wait for pixmap 0378 QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView)); 0379 0380 const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width(); 0381 const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height(); 0382 0383 // enter text-selection mode 0384 QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseTextSelect")); 0385 0386 // move mouse over link 0387 QTest::mouseMove(part.m_pageView->viewport(), QPoint(width * 0.250, height * 0.127)); 0388 0389 // check if mouse icon changed to proper icon 0390 QTRY_COMPARE(part.m_pageView->cursor().shape(), Qt::PointingHandCursor); 0391 } 0392 0393 // clicking on hyperlink jumps to destination in TextSelect mode. 0394 void PartTest::testClickUrlLinkWhileInSelectionMode() 0395 { 0396 QVariantList dummyArgs; 0397 Okular::Part part(nullptr, dummyArgs); 0398 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/pdf_with_links.pdf"))); 0399 // resize window to avoid problem with selection areas 0400 part.widget()->resize(800, 600); 0401 part.widget()->show(); 0402 QVERIFY(QTest::qWaitForWindowExposed(part.widget())); 0403 0404 part.m_document->setViewportPage(0); 0405 0406 // wait for pixmap 0407 QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView)); 0408 0409 const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width(); 0410 const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height(); 0411 0412 // enter text-selection mode 0413 QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseTextSelect")); 0414 0415 // overwrite urlHandler for 'mailto' urls 0416 QDesktopServices::setUrlHandler(QStringLiteral("mailto"), this, "urlHandler"); 0417 QSignalSpy openUrlSignalSpy(this, &PartTest::urlHandler); 0418 0419 // click on url 0420 QTest::mouseMove(part.m_pageView->viewport(), QPoint(width * 0.250, height * 0.127)); 0421 QTest::mouseClick(part.m_pageView->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(width * 0.250, height * 0.127)); 0422 0423 // expect that the urlHandler signal was called 0424 QTRY_COMPARE(openUrlSignalSpy.count(), 1); 0425 QList<QVariant> arguments = openUrlSignalSpy.takeFirst(); 0426 QCOMPARE(arguments.at(0).value<QUrl>(), QUrl(QStringLiteral("mailto:foo@foo.bar"))); 0427 } 0428 0429 void PartTest::testeTextSelectionOverAndAcrossLinks_data() 0430 { 0431 QTest::addColumn<double>("mouseStartX"); 0432 QTest::addColumn<double>("mouseEndX"); 0433 QTest::addColumn<QString>("expectedResult"); 0434 0435 // can text-select "over and across" hyperlink. 0436 QTest::newRow("start selection before link") << 0.1564 << 0.2943 << QStringLiteral(" a link: foo@foo.b"); 0437 // can text-select starting at text and ending selection in middle of hyperlink. 0438 QTest::newRow("start selection in the middle of the link") << 0.28 << 0.382 << QStringLiteral("o.bar"); 0439 // text selection works when selecting left to right or right to left 0440 QTest::newRow("start selection after link") << 0.40 << 0.05 << QStringLiteral("This is a link: foo@foo.bar"); 0441 } 0442 0443 // can text-select "over and across" hyperlink. 0444 void PartTest::testeTextSelectionOverAndAcrossLinks() 0445 { 0446 QVariantList dummyArgs; 0447 Okular::Part part(nullptr, dummyArgs); 0448 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/pdf_with_links.pdf"))); 0449 // resize window to avoid problem with selection areas 0450 part.widget()->resize(800, 600); 0451 part.widget()->show(); 0452 QVERIFY(QTest::qWaitForWindowExposed(part.widget())); 0453 0454 part.m_document->setViewportPage(0); 0455 0456 // wait for pixmap 0457 QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView)); 0458 0459 const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width(); 0460 const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height(); 0461 0462 // enter text-selection mode 0463 QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseTextSelect")); 0464 0465 const double mouseY = height * 0.127; 0466 QFETCH(double, mouseStartX); 0467 QFETCH(double, mouseEndX); 0468 0469 mouseStartX = width * mouseStartX; 0470 mouseEndX = width * mouseEndX; 0471 0472 simulateMouseSelection(mouseStartX, mouseY, mouseEndX, mouseY, part.m_pageView->viewport()); 0473 0474 QApplication::clipboard()->clear(); 0475 QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "copyTextSelection")); 0476 0477 QFETCH(QString, expectedResult); 0478 QCOMPARE(QApplication::clipboard()->text(), expectedResult); 0479 } 0480 0481 // can jump to link while there's an active selection of text. 0482 void PartTest::testClickUrlLinkWhileLinkTextIsSelected() 0483 { 0484 QVariantList dummyArgs; 0485 Okular::Part part(nullptr, dummyArgs); 0486 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/pdf_with_links.pdf"))); 0487 // resize window to avoid problem with selection areas 0488 part.widget()->resize(800, 600); 0489 part.widget()->show(); 0490 QVERIFY(QTest::qWaitForWindowExposed(part.widget())); 0491 0492 part.m_document->setViewportPage(0); 0493 0494 // wait for pixmap 0495 QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView)); 0496 0497 const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width(); 0498 const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height(); 0499 0500 // enter text-selection mode 0501 QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseTextSelect")); 0502 0503 const double mouseY = height * 0.127; 0504 const double mouseStartX = width * 0.13; 0505 const double mouseEndX = width * 0.40; 0506 0507 simulateMouseSelection(mouseStartX, mouseY, mouseEndX, mouseY, part.m_pageView->viewport()); 0508 0509 // overwrite urlHandler for 'mailto' urls 0510 QDesktopServices::setUrlHandler(QStringLiteral("mailto"), this, "urlHandler"); 0511 QSignalSpy openUrlSignalSpy(this, &PartTest::urlHandler); 0512 0513 // click on url 0514 const double mouseClickX = width * 0.2997; 0515 const double mouseClickY = height * 0.1293; 0516 0517 QTest::mouseMove(part.m_pageView->viewport(), QPoint(mouseClickX, mouseClickY)); 0518 QTest::mouseClick(part.m_pageView->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(mouseClickX, mouseClickY), 1000); 0519 0520 // expect that the urlHandler signal was called 0521 QTRY_COMPARE(openUrlSignalSpy.count(), 1); 0522 QList<QVariant> arguments = openUrlSignalSpy.takeFirst(); 0523 QCOMPARE(arguments.at(0).value<QUrl>(), QUrl(QStringLiteral("mailto:foo@foo.bar"))); 0524 } 0525 0526 // r-click on the selected text gives the "Go To:" content menu option 0527 void PartTest::testRClickWhileLinkTextIsSelected() 0528 { 0529 QVariantList dummyArgs; 0530 Okular::Part part(nullptr, dummyArgs); 0531 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/pdf_with_links.pdf"))); 0532 // resize window to avoid problem with selection areas 0533 part.widget()->resize(800, 600); 0534 part.widget()->show(); 0535 QVERIFY(QTest::qWaitForWindowExposed(part.widget())); 0536 0537 part.m_document->setViewportPage(0); 0538 0539 // wait for pixmap 0540 QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView)); 0541 0542 const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width(); 0543 const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height(); 0544 0545 // enter text-selection mode 0546 QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseTextSelect")); 0547 0548 const double mouseY = height * 0.162; 0549 const double mouseStartX = width * 0.42; 0550 const double mouseEndX = width * 0.60; 0551 0552 simulateMouseSelection(mouseStartX, mouseY, mouseEndX, mouseY, part.m_pageView->viewport()); 0553 0554 // Need to do this because the pop-menu will have his own mainloop and will block tests until 0555 // the menu disappear 0556 PageView *view = part.m_pageView; 0557 bool menuClosed = false; 0558 QTimer::singleShot(2000, view, [view, &menuClosed]() { 0559 // check if popup menu is active and visible 0560 QMenu *menu = qobject_cast<QMenu *>(view->findChild<QMenu *>(QStringLiteral("PopupMenu"))); 0561 QVERIFY(menu); 0562 QVERIFY(menu->isVisible()); 0563 0564 // check if the menu contains go-to link action 0565 QAction *goToAction = qobject_cast<QAction *>(menu->findChild<QAction *>(QStringLiteral("GoToAction"))); 0566 QVERIFY(goToAction); 0567 0568 // check if the "follow this link" action is not visible 0569 QAction *processLinkAction = qobject_cast<QAction *>(menu->findChild<QAction *>(QStringLiteral("ProcessLinkAction"))); 0570 QVERIFY(!processLinkAction); 0571 0572 // check if the "copy link address" action is not visible 0573 QAction *copyLinkLocation = qobject_cast<QAction *>(menu->findChild<QAction *>(QStringLiteral("CopyLinkLocationAction"))); 0574 QVERIFY(!copyLinkLocation); 0575 0576 // close menu to continue test 0577 menu->close(); 0578 menuClosed = true; 0579 }); 0580 0581 // click on url 0582 const double mouseClickX = width * 0.425; 0583 const double mouseClickY = height * 0.162; 0584 0585 QTest::mouseMove(part.m_pageView->viewport(), QPoint(mouseClickX, mouseClickY)); 0586 QTest::mouseClick(part.m_pageView->viewport(), Qt::RightButton, Qt::NoModifier, QPoint(mouseClickX, mouseClickY), 1000); 0587 0588 // will continue after pop-menu get closed 0589 QTRY_VERIFY(menuClosed); 0590 } 0591 0592 // r-click on the link gives the "follow this link" content menu option 0593 void PartTest::testRClickOverLinkWhileLinkTextIsSelected() 0594 { 0595 QVariantList dummyArgs; 0596 Okular::Part part(nullptr, dummyArgs); 0597 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/pdf_with_links.pdf"))); 0598 // resize window to avoid problem with selection areas 0599 part.widget()->resize(800, 600); 0600 part.widget()->show(); 0601 QVERIFY(QTest::qWaitForWindowExposed(part.widget())); 0602 0603 part.m_document->setViewportPage(0); 0604 0605 // wait for pixmap 0606 QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView)); 0607 0608 const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width(); 0609 const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height(); 0610 0611 // enter text-selection mode 0612 QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseTextSelect")); 0613 0614 const double mouseY = height * 0.162; 0615 const double mouseStartX = width * 0.42; 0616 const double mouseEndX = width * 0.60; 0617 0618 simulateMouseSelection(mouseStartX, mouseY, mouseEndX, mouseY, part.m_pageView->viewport()); 0619 0620 // Need to do this because the pop-menu will have his own mainloop and will block tests until 0621 // the menu disappear 0622 PageView *view = part.m_pageView; 0623 bool menuClosed = false; 0624 QTimer::singleShot(2000, view, [view, &menuClosed]() { 0625 // check if popup menu is active and visible 0626 QMenu *menu = qobject_cast<QMenu *>(view->findChild<QMenu *>(QStringLiteral("PopupMenu"))); 0627 QVERIFY(menu); 0628 QVERIFY(menu->isVisible()); 0629 0630 // check if the menu contains "follow this link" action 0631 QAction *processLinkAction = qobject_cast<QAction *>(menu->findChild<QAction *>(QStringLiteral("ProcessLinkAction"))); 0632 QVERIFY(processLinkAction); 0633 0634 // check if the menu contains "copy link address" action 0635 QAction *copyLinkLocation = qobject_cast<QAction *>(menu->findChild<QAction *>(QStringLiteral("CopyLinkLocationAction"))); 0636 QVERIFY(copyLinkLocation); 0637 0638 // close menu to continue test 0639 menu->close(); 0640 menuClosed = true; 0641 }); 0642 0643 // click on url 0644 const double mouseClickX = width * 0.593; 0645 const double mouseClickY = height * 0.162; 0646 0647 QTest::mouseMove(part.m_pageView->viewport(), QPoint(mouseClickX, mouseClickY)); 0648 QTest::mouseClick(part.m_pageView->viewport(), Qt::RightButton, Qt::NoModifier, QPoint(mouseClickX, mouseClickY), 1000); 0649 0650 // will continue after pop-menu get closed 0651 QTRY_VERIFY(menuClosed); 0652 } 0653 0654 void PartTest::testRClickOnSelectionModeShoulShowFollowTheLinkMenu() 0655 { 0656 QVariantList dummyArgs; 0657 Okular::Part part(nullptr, dummyArgs); 0658 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/pdf_with_links.pdf"))); 0659 // resize window to avoid problem with selection areas 0660 part.widget()->resize(800, 600); 0661 part.widget()->show(); 0662 QVERIFY(QTest::qWaitForWindowExposed(part.widget())); 0663 0664 part.m_document->setViewportPage(0); 0665 0666 // wait for pixmap 0667 QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView)); 0668 0669 const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width(); 0670 const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height(); 0671 0672 // enter text-selection mode 0673 QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseTextSelect")); 0674 0675 // Need to do this because the pop-menu will have his own mainloop and will block tests until 0676 // the menu disappear 0677 PageView *view = part.m_pageView; 0678 bool menuClosed = false; 0679 QTimer::singleShot(2000, view, [view, &menuClosed]() { 0680 // check if popup menu is active and visible 0681 QMenu *menu = qobject_cast<QMenu *>(view->findChild<QMenu *>(QStringLiteral("PopupMenu"))); 0682 QVERIFY(menu); 0683 QVERIFY(menu->isVisible()); 0684 0685 // check if the menu contains "Follow this link" action 0686 QAction *processLink = qobject_cast<QAction *>(menu->findChild<QAction *>(QStringLiteral("ProcessLinkAction"))); 0687 QVERIFY(processLink); 0688 0689 // chek if the menu contains "Copy Link Address" action 0690 QAction *actCopyLinkLocation = qobject_cast<QAction *>(menu->findChild<QAction *>(QStringLiteral("CopyLinkLocationAction"))); 0691 QVERIFY(actCopyLinkLocation); 0692 0693 // close menu to continue test 0694 menu->close(); 0695 menuClosed = true; 0696 }); 0697 0698 // r-click on url 0699 const double mouseClickX = width * 0.604; 0700 const double mouseClickY = height * 0.162; 0701 0702 QTest::mouseMove(part.m_pageView->viewport(), QPoint(mouseClickX, mouseClickY)); 0703 QTest::mouseClick(part.m_pageView->viewport(), Qt::RightButton, Qt::NoModifier, QPoint(mouseClickX, mouseClickY), 1000); 0704 0705 // will continue after pop-menu get closed 0706 QTRY_VERIFY(menuClosed); 0707 } 0708 0709 void PartTest::testClickAnywhereAfterSelectionShouldUnselect() 0710 { 0711 QVariantList dummyArgs; 0712 Okular::Part part(nullptr, dummyArgs); 0713 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/pdf_with_links.pdf"))); 0714 // resize window to avoid problem with selection areas 0715 part.widget()->resize(800, 600); 0716 part.widget()->show(); 0717 QVERIFY(QTest::qWaitForWindowExposed(part.widget())); 0718 0719 part.m_document->setViewportPage(0); 0720 0721 // wait for pixmap 0722 QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView)); 0723 0724 const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width(); 0725 const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height(); 0726 0727 // enter text-selection mode 0728 QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseTextSelect")); 0729 0730 const double mouseY = height * 0.162; 0731 const double mouseStartX = width * 0.42; 0732 const double mouseEndX = width * 0.60; 0733 0734 simulateMouseSelection(mouseStartX, mouseY, mouseEndX, mouseY, part.m_pageView->viewport()); 0735 0736 // click on url 0737 const double mouseClickX = width * 0.10; 0738 0739 QTest::mouseMove(part.m_pageView->viewport(), QPoint(mouseClickX, mouseY)); 0740 QTest::mouseClick(part.m_pageView->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(mouseClickX, mouseY), 1000); 0741 0742 QApplication::clipboard()->clear(); 0743 QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "copyTextSelection")); 0744 0745 // check if copied text is empty what means no text selected 0746 QVERIFY(QApplication::clipboard()->text().isEmpty()); 0747 } 0748 0749 void PartTest::testeRectSelectionStartingOnLinks() 0750 { 0751 QVariantList dummyArgs; 0752 Okular::Part part(nullptr, dummyArgs); 0753 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/pdf_with_links.pdf"))); 0754 // hide info messages as they interfere with selection area 0755 Okular::Settings::self()->setShowEmbeddedContentMessages(false); 0756 Okular::Settings::self()->setShowOSD(false); 0757 0758 part.widget()->show(); 0759 QVERIFY(QTest::qWaitForWindowExposed(part.widget())); 0760 0761 part.m_document->setViewportPage(0); 0762 0763 // wait for pixmap 0764 QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView)); 0765 0766 const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width(); 0767 const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height(); 0768 0769 // enter text-selection mode 0770 QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseSelect")); 0771 0772 const double mouseStartY = height * 0.127; 0773 const double mouseEndY = height * 0.127; 0774 const double mouseStartX = width * 0.28; 0775 const double mouseEndX = width * 0.382; 0776 0777 // Need to do this because the pop-menu will have his own mainloop and will block tests until 0778 // the menu disappear 0779 PageView *view = part.m_pageView; 0780 bool menuClosed = false; 0781 QTimer::singleShot(2000, view, [view, &menuClosed]() { 0782 QApplication::clipboard()->clear(); 0783 0784 // check if popup menu is active and visible 0785 QMenu *menu = qobject_cast<QMenu *>(view->findChild<QMenu *>(QStringLiteral("PopupMenu"))); 0786 QVERIFY(menu); 0787 QVERIFY(menu->isVisible()); 0788 0789 // check if the copy selected text to clipboard is present 0790 QAction *copyAct = qobject_cast<QAction *>(menu->findChild<QAction *>(QStringLiteral("CopyTextToClipboard"))); 0791 QVERIFY(copyAct); 0792 0793 menu->close(); 0794 menuClosed = true; 0795 }); 0796 0797 simulateMouseSelection(mouseStartX, mouseStartY, mouseEndX, mouseEndY, part.m_pageView->viewport()); 0798 0799 // wait menu get closed 0800 QTRY_VERIFY(menuClosed); 0801 } 0802 0803 void PartTest::simulateMouseSelection(double startX, double startY, double endX, double endY, QWidget *target) 0804 { 0805 const int steps = 5; 0806 const double diffX = endX - startX; 0807 const double diffY = endY - startY; 0808 const double diffXStep = diffX / steps; 0809 const double diffYStep = diffY / steps; 0810 0811 QTestEventList events; 0812 events.addMouseMove(QPoint(startX, startY)); 0813 events.addMousePress(Qt::LeftButton, Qt::NoModifier, QPoint(startX, startY)); 0814 for (int i = 0; i < steps - 1; ++i) { 0815 events.addMouseMove(QPoint(startX + i * diffXStep, startY + i * diffYStep)); 0816 events.addDelay(100); 0817 } 0818 events.addMouseMove(QPoint(endX, endY)); 0819 events.addDelay(100); 0820 events.addMouseRelease(Qt::LeftButton, Qt::NoModifier, QPoint(endX, endY)); 0821 0822 events.simulate(target); 0823 } 0824 0825 void PartTest::testSaveAsToNonExistingPath() 0826 { 0827 Okular::Part part(nullptr, {}); 0828 part.openDocument(QStringLiteral(KDESRCDIR "data/file1.pdf")); 0829 0830 QString saveFilePath; 0831 { 0832 QTemporaryFile saveFile(QStringLiteral("%1/okrXXXXXX.pdf").arg(QDir::tempPath())); 0833 saveFile.open(); 0834 saveFilePath = saveFile.fileName(); 0835 // QTemporaryFile is destroyed and the file it created is gone, this is a TOCTOU but who cares 0836 } 0837 0838 QVERIFY(!QFileInfo::exists(saveFilePath)); 0839 0840 QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFilePath), Part::NoSaveAsFlags)); 0841 0842 QFile::remove(saveFilePath); 0843 } 0844 0845 void PartTest::testSaveAsToSymlink() 0846 { 0847 #ifdef Q_OS_UNIX 0848 Okular::Part part(nullptr, {}); 0849 part.openDocument(QStringLiteral(KDESRCDIR "data/file1.pdf")); 0850 0851 QTemporaryFile newFile(QStringLiteral("%1/okrXXXXXX.pdf").arg(QDir::tempPath())); 0852 newFile.open(); 0853 0854 QString linkFilePath; 0855 { 0856 QTemporaryFile linkFile(QStringLiteral("%1/okrXXXXXX.pdf").arg(QDir::tempPath())); 0857 linkFile.open(); 0858 linkFilePath = linkFile.fileName(); 0859 // QTemporaryFile is destroyed and the file it created is gone, this is a TOCTOU but who cares 0860 } 0861 0862 QFile::link(newFile.fileName(), linkFilePath); 0863 0864 QVERIFY(QFileInfo(linkFilePath).isSymLink()); 0865 0866 QVERIFY(part.saveAs(QUrl::fromLocalFile(linkFilePath), Part::NoSaveAsFlags)); 0867 0868 QVERIFY(QFileInfo(linkFilePath).isSymLink()); 0869 0870 QFile::remove(linkFilePath); 0871 #endif 0872 } 0873 0874 void PartTest::testSaveIsSymlink() 0875 { 0876 #ifdef Q_OS_UNIX 0877 Okular::Part part(nullptr, {}); 0878 0879 QString newFilePath; 0880 { 0881 QTemporaryFile newFile(QStringLiteral("%1/okrXXXXXX.pdf").arg(QDir::tempPath())); 0882 newFile.open(); 0883 newFilePath = newFile.fileName(); 0884 // QTemporaryFile is destroyed and the file it created is gone, this is a TOCTOU but who cares 0885 } 0886 0887 QFile::copy(QStringLiteral(KDESRCDIR "data/file1.pdf"), newFilePath); 0888 0889 QString linkFilePath; 0890 { 0891 QTemporaryFile linkFile(QStringLiteral("%1/okrXXXXXX.pdf").arg(QDir::tempPath())); 0892 linkFile.open(); 0893 linkFilePath = linkFile.fileName(); 0894 // QTemporaryFile is destroyed and the file it created is gone, this is a TOCTOU but who cares 0895 } 0896 0897 QFile::link(newFilePath, linkFilePath); 0898 0899 QVERIFY(QFileInfo(linkFilePath).isSymLink()); 0900 0901 part.openDocument(linkFilePath); 0902 QVERIFY(part.saveAs(QUrl::fromLocalFile(linkFilePath), Part::NoSaveAsFlags)); 0903 0904 QVERIFY(QFileInfo(linkFilePath).isSymLink()); 0905 0906 QFile::remove(newFilePath); 0907 QFile::remove(linkFilePath); 0908 #endif 0909 } 0910 0911 void PartTest::testSaveAs() 0912 { 0913 QFETCH(QString, file); 0914 QFETCH(QString, extension); 0915 QFETCH(bool, nativelySupportsAnnotations); 0916 QFETCH(bool, canSwapBackingFile); 0917 0918 QScopedPointer<TestingUtils::CloseDialogHelper> closeDialogHelper; 0919 0920 QString annotName; 0921 QTemporaryFile archiveSave(QStringLiteral("%1/okrXXXXXX.okular").arg(QDir::tempPath())); 0922 QTemporaryFile nativeDirectSave(QStringLiteral("%1/okrXXXXXX.%2").arg(QDir::tempPath(), extension)); 0923 QTemporaryFile nativeFromArchiveFile(QStringLiteral("%1/okrXXXXXX.%2").arg(QDir::tempPath(), extension)); 0924 QVERIFY(archiveSave.open()); 0925 archiveSave.close(); 0926 QVERIFY(nativeDirectSave.open()); 0927 nativeDirectSave.close(); 0928 QVERIFY(nativeFromArchiveFile.open()); 0929 nativeFromArchiveFile.close(); 0930 0931 qDebug() << "Open file, add annotation and save both natively and to .okular"; 0932 { 0933 Okular::Part part(nullptr, {}); 0934 part.openDocument(file); 0935 part.m_document->documentInfo(); 0936 0937 QCOMPARE(part.m_document->canSwapBackingFile(), canSwapBackingFile); 0938 0939 Okular::Annotation *annot = new Okular::TextAnnotation(); 0940 annot->setBoundingRectangle(Okular::NormalizedRect(0.1, 0.1, 0.15, 0.15)); 0941 annot->setContents(QStringLiteral("annot contents")); 0942 part.m_document->addPageAnnotation(0, annot); 0943 annotName = annot->uniqueName(); 0944 0945 if (canSwapBackingFile) { 0946 if (!nativelySupportsAnnotations) { 0947 closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "you're going to lose the annotations" dialog 0948 } 0949 QVERIFY(part.saveAs(QUrl::fromLocalFile(nativeDirectSave.fileName()), Part::NoSaveAsFlags)); 0950 // For backends that don't support annotations natively we mark the part as still modified 0951 // after a save because we keep the annotation around but it will get lost if the user closes the app 0952 // so we want to give her a last chance to save on close with the "you have changes dialog" 0953 QCOMPARE(part.isModified(), !nativelySupportsAnnotations); 0954 QVERIFY(part.saveAs(QUrl::fromLocalFile(archiveSave.fileName()), Part::SaveAsOkularArchive)); 0955 } else { 0956 // We need to save to archive first otherwise we lose the annotation 0957 0958 closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::Yes)); // this is the "you're going to lose the undo/redo stack" dialog 0959 QVERIFY(part.saveAs(QUrl::fromLocalFile(archiveSave.fileName()), Part::SaveAsOkularArchive)); 0960 0961 if (!nativelySupportsAnnotations) { 0962 closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "you're going to lose the annotations" dialog 0963 } 0964 QVERIFY(part.saveAs(QUrl::fromLocalFile(nativeDirectSave.fileName()), Part::NoSaveAsFlags)); 0965 } 0966 0967 QCOMPARE(part.m_document->documentInfo().get(Okular::DocumentInfo::FilePath), part.m_document->currentDocument().toDisplayString()); 0968 part.closeUrl(); 0969 } 0970 0971 qDebug() << "Open the .okular, check that the annotation is present and save to native"; 0972 { 0973 Okular::Part part(nullptr, {}); 0974 part.openDocument(archiveSave.fileName()); 0975 part.m_document->documentInfo(); 0976 0977 QCOMPARE(part.m_document->page(0)->annotations().size(), 1); 0978 QCOMPARE(part.m_document->page(0)->annotations().first()->uniqueName(), annotName); 0979 0980 if (!nativelySupportsAnnotations) { 0981 closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "you're going to lose the annotations" dialog 0982 } 0983 QVERIFY(part.saveAs(QUrl::fromLocalFile(nativeFromArchiveFile.fileName()), Part::NoSaveAsFlags)); 0984 0985 if (canSwapBackingFile && !nativelySupportsAnnotations) { 0986 // For backends that don't support annotations natively we mark the part as still modified 0987 // after a save because we keep the annotation around but it will get lost if the user closes the app 0988 // so we want to give her a last chance to save on close with the "you have changes dialog" 0989 closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "do you want to save or discard" dialog 0990 } 0991 0992 QCOMPARE(part.m_document->documentInfo().get(Okular::DocumentInfo::FilePath), part.m_document->currentDocument().toDisplayString()); 0993 part.closeUrl(); 0994 } 0995 0996 qDebug() << "Open the native file saved directly, and check that the annot" 0997 << "is there iff we expect it"; 0998 { 0999 Okular::Part part(nullptr, {}); 1000 part.openDocument(nativeDirectSave.fileName()); 1001 1002 QCOMPARE(part.m_document->page(0)->annotations().size(), nativelySupportsAnnotations ? 1 : 0); 1003 if (nativelySupportsAnnotations) { 1004 QCOMPARE(part.m_document->page(0)->annotations().first()->uniqueName(), annotName); 1005 } 1006 1007 part.closeUrl(); 1008 } 1009 1010 qDebug() << "Open the native file saved from the .okular, and check that the annot" 1011 << "is there iff we expect it"; 1012 { 1013 Okular::Part part(nullptr, {}); 1014 part.openDocument(nativeFromArchiveFile.fileName()); 1015 1016 QCOMPARE(part.m_document->page(0)->annotations().size(), nativelySupportsAnnotations ? 1 : 0); 1017 if (nativelySupportsAnnotations) { 1018 QCOMPARE(part.m_document->page(0)->annotations().first()->uniqueName(), annotName); 1019 } 1020 1021 part.closeUrl(); 1022 } 1023 } 1024 1025 void PartTest::testSaveAs_data() 1026 { 1027 QTest::addColumn<QString>("file"); 1028 QTest::addColumn<QString>("extension"); 1029 QTest::addColumn<bool>("nativelySupportsAnnotations"); 1030 QTest::addColumn<bool>("canSwapBackingFile"); 1031 1032 QTest::newRow("pdf") << KDESRCDIR "data/file1.pdf" 1033 << "pdf" << true << true; 1034 QTest::newRow("pdf.gz") << KDESRCDIR "data/file1.pdf.gz" 1035 << "pdf" << true << true; 1036 QTest::newRow("epub") << KDESRCDIR "data/contents.epub" 1037 << "epub" << false << false; 1038 QTest::newRow("jpg") << KDESRCDIR "data/potato.jpg" 1039 << "jpg" << false << true; 1040 } 1041 1042 void PartTest::testSidebarItemAfterSaving() 1043 { 1044 Okular::Part part(nullptr, {}); 1045 QWidget *currentSidebarItem = part.m_sidebar->currentItem(); // thumbnails 1046 openDocument(&part, QStringLiteral(KDESRCDIR "data/tocreload.pdf")); 1047 // since it has TOC it changes to TOC 1048 QVERIFY(currentSidebarItem != part.m_sidebar->currentItem()); 1049 // now change back to thumbnails 1050 part.m_sidebar->setCurrentItem(currentSidebarItem); 1051 1052 part.saveAs(QUrl::fromLocalFile(QStringLiteral(KDESRCDIR "data/tocreload.pdf"))); 1053 1054 // Check it is still thumbnails after saving 1055 QCOMPARE(currentSidebarItem, part.m_sidebar->currentItem()); 1056 } 1057 1058 void PartTest::testViewModeSavingPerFile() 1059 { 1060 Okular::Part part(nullptr, {}); 1061 1062 // Open some file 1063 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file1.pdf"))); 1064 1065 // Switch to 'continuous' view mode 1066 part.m_pageView->setCapability(Okular::View::ViewCapability::Continuous, QVariant(true)); 1067 1068 // Close document 1069 part.closeUrl(); 1070 1071 // Open another file 1072 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file2.pdf"))); 1073 1074 // Switch to 'non-continuous' mode 1075 part.m_pageView->setCapability(Okular::View::ViewCapability::Continuous, QVariant(false)); 1076 1077 // Close that document, too 1078 part.closeUrl(); 1079 1080 // Open first document again 1081 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file1.pdf"))); 1082 1083 // If per-file view mode saving works, the view mode should be 'continuous' again. 1084 QVERIFY(part.m_pageView->capability(Okular::View::ViewCapability::Continuous).toBool()); 1085 } 1086 1087 void PartTest::testSaveAsUndoStackAnnotations() 1088 { 1089 QFETCH(QString, file); 1090 QFETCH(QString, extension); 1091 QFETCH(bool, nativelySupportsAnnotations); 1092 QFETCH(bool, canSwapBackingFile); 1093 QFETCH(bool, saveToArchive); 1094 1095 const Part::SaveAsFlag saveFlags = saveToArchive ? Part::SaveAsOkularArchive : Part::NoSaveAsFlags; 1096 1097 QScopedPointer<TestingUtils::CloseDialogHelper> closeDialogHelper; 1098 1099 // closeDialogHelper relies on the availability of the "Continue" button to drop changes 1100 // when saving to a file format not supporting those. However, this button is only sensible 1101 // and available for "Save As", but not for "Save". By alternately saving to saveFile1 and 1102 // saveFile2 we always force "Save As", so closeDialogHelper keeps working. 1103 QTemporaryFile saveFile1(QStringLiteral("%1/okrXXXXXX_1.%2").arg(QDir::tempPath(), extension)); 1104 QVERIFY(saveFile1.open()); 1105 saveFile1.close(); 1106 QTemporaryFile saveFile2(QStringLiteral("%1/okrXXXXXX_2.%2").arg(QDir::tempPath(), extension)); 1107 QVERIFY(saveFile2.open()); 1108 saveFile2.close(); 1109 1110 Okular::Part part(nullptr, {}); 1111 part.openDocument(file); 1112 1113 QCOMPARE(part.m_document->canSwapBackingFile(), canSwapBackingFile); 1114 1115 Okular::Annotation *annot = new Okular::TextAnnotation(); 1116 annot->setBoundingRectangle(Okular::NormalizedRect(0.1, 0.1, 0.15, 0.15)); 1117 annot->setContents(QStringLiteral("annot contents")); 1118 part.m_document->addPageAnnotation(0, annot); 1119 QString annotName = annot->uniqueName(); 1120 1121 if (!nativelySupportsAnnotations && !saveToArchive) { 1122 closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "you're going to lose the annotations" dialog 1123 } 1124 1125 QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile1.fileName()), saveFlags)); 1126 1127 if (!canSwapBackingFile) { 1128 // The undo/redo stack gets lost if you can not swap the backing file 1129 QVERIFY(!part.m_document->canUndo()); 1130 QVERIFY(!part.m_document->canRedo()); 1131 return; 1132 } 1133 1134 // Check we can still undo the annot add after save 1135 QVERIFY(part.m_document->canUndo()); 1136 part.m_document->undo(); 1137 QVERIFY(!part.m_document->canUndo()); 1138 1139 QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile1.fileName()), saveFlags)); 1140 QVERIFY(part.m_document->page(0)->annotations().isEmpty()); 1141 1142 // Check we can redo the annot add after save 1143 QVERIFY(part.m_document->canRedo()); 1144 part.m_document->redo(); 1145 QVERIFY(!part.m_document->canRedo()); 1146 1147 if (nativelySupportsAnnotations) { 1148 // If the annots are provided by the backend we need to refetch the pointer after save 1149 annot = part.m_document->page(0)->annotation(annotName); 1150 QVERIFY(annot); 1151 } 1152 1153 // Remove the annotation, creates another undo command 1154 QVERIFY(part.m_document->canRemovePageAnnotation(annot)); 1155 part.m_document->removePageAnnotation(0, annot); 1156 QVERIFY(part.m_document->page(0)->annotations().isEmpty()); 1157 1158 // Check we can still undo the annot remove after save 1159 QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile1.fileName()), saveFlags)); 1160 QVERIFY(part.m_document->canUndo()); 1161 part.m_document->undo(); 1162 QVERIFY(part.m_document->canUndo()); 1163 QCOMPARE(part.m_document->page(0)->annotations().count(), 1); 1164 1165 // Check we can still undo the annot add after save 1166 if (!nativelySupportsAnnotations && !saveToArchive) { 1167 closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "you're going to lose the annotations" dialog 1168 } 1169 QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile2.fileName()), saveFlags)); 1170 QVERIFY(part.m_document->canUndo()); 1171 part.m_document->undo(); 1172 QVERIFY(!part.m_document->canUndo()); 1173 QVERIFY(part.m_document->page(0)->annotations().isEmpty()); 1174 1175 // Redo the add annotation 1176 QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile1.fileName()), saveFlags)); 1177 QVERIFY(part.m_document->canRedo()); 1178 part.m_document->redo(); 1179 QVERIFY(part.m_document->canUndo()); 1180 QVERIFY(part.m_document->canRedo()); 1181 1182 if (nativelySupportsAnnotations) { 1183 // If the annots are provided by the backend we need to refetch the pointer after save 1184 annot = part.m_document->page(0)->annotation(annotName); 1185 QVERIFY(annot); 1186 } 1187 1188 // Add translate, adjust and modify commands 1189 part.m_document->translatePageAnnotation(0, annot, Okular::NormalizedPoint(0.1, 0.1)); 1190 part.m_document->adjustPageAnnotation(0, annot, Okular::NormalizedPoint(0.1, 0.1), Okular::NormalizedPoint(0.1, 0.1)); 1191 part.m_document->prepareToModifyAnnotationProperties(annot); 1192 part.m_document->modifyPageAnnotationProperties(0, annot); 1193 1194 // Now check we can still undo/redo/save at all the intermediate states and things still work 1195 if (!nativelySupportsAnnotations && !saveToArchive) { 1196 closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "you're going to lose the annotations" dialog 1197 } 1198 QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile2.fileName()), saveFlags)); 1199 QVERIFY(part.m_document->canUndo()); 1200 part.m_document->undo(); 1201 QVERIFY(part.m_document->canUndo()); 1202 1203 if (!nativelySupportsAnnotations && !saveToArchive) { 1204 closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "you're going to lose the annotations" dialog 1205 } 1206 QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile1.fileName()), saveFlags)); 1207 QVERIFY(part.m_document->canUndo()); 1208 part.m_document->undo(); 1209 QVERIFY(part.m_document->canUndo()); 1210 1211 if (!nativelySupportsAnnotations && !saveToArchive) { 1212 closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "you're going to lose the annotations" dialog 1213 } 1214 QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile2.fileName()), saveFlags)); 1215 QVERIFY(part.m_document->canUndo()); 1216 part.m_document->undo(); 1217 QVERIFY(part.m_document->canUndo()); 1218 1219 if (!nativelySupportsAnnotations && !saveToArchive) { 1220 closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "you're going to lose the annotations" dialog 1221 } 1222 QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile1.fileName()), saveFlags)); 1223 QVERIFY(part.m_document->canUndo()); 1224 part.m_document->undo(); 1225 QVERIFY(!part.m_document->canUndo()); 1226 QVERIFY(part.m_document->canRedo()); 1227 QVERIFY(part.m_document->page(0)->annotations().isEmpty()); 1228 1229 QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile1.fileName()), saveFlags)); 1230 QVERIFY(part.m_document->canRedo()); 1231 part.m_document->redo(); 1232 QVERIFY(part.m_document->canRedo()); 1233 1234 if (!nativelySupportsAnnotations && !saveToArchive) { 1235 closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "you're going to lose the annotations" dialog 1236 } 1237 QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile2.fileName()), saveFlags)); 1238 QVERIFY(part.m_document->canRedo()); 1239 part.m_document->redo(); 1240 QVERIFY(part.m_document->canRedo()); 1241 1242 if (!nativelySupportsAnnotations && !saveToArchive) { 1243 closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "you're going to lose the annotations" dialog 1244 } 1245 QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile1.fileName()), saveFlags)); 1246 QVERIFY(part.m_document->canRedo()); 1247 part.m_document->redo(); 1248 QVERIFY(part.m_document->canRedo()); 1249 1250 if (!nativelySupportsAnnotations && !saveToArchive) { 1251 closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "you're going to lose the annotations" dialog 1252 } 1253 QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile2.fileName()), saveFlags)); 1254 QVERIFY(part.m_document->canRedo()); 1255 part.m_document->redo(); 1256 QVERIFY(!part.m_document->canRedo()); 1257 1258 closeDialogHelper.reset(new TestingUtils::CloseDialogHelper(&part, QDialogButtonBox::No)); // this is the "do you want to save or discard" dialog 1259 part.closeUrl(); 1260 } 1261 1262 void PartTest::testSaveAsUndoStackAnnotations_data() 1263 { 1264 QTest::addColumn<QString>("file"); 1265 QTest::addColumn<QString>("extension"); 1266 QTest::addColumn<bool>("nativelySupportsAnnotations"); 1267 QTest::addColumn<bool>("canSwapBackingFile"); 1268 QTest::addColumn<bool>("saveToArchive"); 1269 1270 QTest::newRow("pdf") << KDESRCDIR "data/file1.pdf" 1271 << "pdf" << true << true << false; 1272 QTest::newRow("epub") << KDESRCDIR "data/contents.epub" 1273 << "epub" << false << false << false; 1274 QTest::newRow("jpg") << KDESRCDIR "data/potato.jpg" 1275 << "jpg" << false << true << false; 1276 QTest::newRow("pdfarchive") << KDESRCDIR "data/file1.pdf" 1277 << "okular" << true << true << true; 1278 QTest::newRow("jpgarchive") << KDESRCDIR "data/potato.jpg" 1279 << "okular" << false << true << true; 1280 } 1281 1282 void PartTest::testSaveAsUndoStackForms() 1283 { 1284 QFETCH(QString, file); 1285 QFETCH(QString, extension); 1286 QFETCH(bool, saveToArchive); 1287 1288 const Part::SaveAsFlag saveFlags = saveToArchive ? Part::SaveAsOkularArchive : Part::NoSaveAsFlags; 1289 1290 QTemporaryFile saveFile(QStringLiteral("%1/okrXXXXXX.%2").arg(QDir::tempPath(), extension)); 1291 QVERIFY(saveFile.open()); 1292 saveFile.close(); 1293 1294 Okular::Part part(nullptr, {}); 1295 part.openDocument(file); 1296 1297 const QList<Okular::FormField *> pageFormFields = part.m_document->page(0)->formFields(); 1298 for (FormField *ff : pageFormFields) { 1299 if (ff->id() == 65537) { 1300 QCOMPARE(ff->type(), FormField::FormText); 1301 FormFieldText *fft = static_cast<FormFieldText *>(ff); 1302 part.m_document->editFormText(0, fft, QStringLiteral("BlaBla"), 6, 0, 0); 1303 } else if (ff->id() == 65538) { 1304 QCOMPARE(ff->type(), FormField::FormButton); 1305 FormFieldButton *ffb = static_cast<FormFieldButton *>(ff); 1306 QCOMPARE(ffb->buttonType(), FormFieldButton::Radio); 1307 part.m_document->editFormButtons(0, QList<FormFieldButton *>() << ffb, QList<bool>() << true); 1308 } else if (ff->id() == 65542) { 1309 QCOMPARE(ff->type(), FormField::FormChoice); 1310 FormFieldChoice *ffc = static_cast<FormFieldChoice *>(ff); 1311 QCOMPARE(ffc->choiceType(), FormFieldChoice::ListBox); 1312 part.m_document->editFormList(0, ffc, QList<int>() << 1); 1313 } else if (ff->id() == 65543) { 1314 QCOMPARE(ff->type(), FormField::FormChoice); 1315 FormFieldChoice *ffc = static_cast<FormFieldChoice *>(ff); 1316 QCOMPARE(ffc->choiceType(), FormFieldChoice::ComboBox); 1317 part.m_document->editFormCombo(0, ffc, QStringLiteral("combo2"), 3, 0, 0); 1318 } 1319 } 1320 1321 QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile.fileName()), saveFlags)); 1322 1323 QVERIFY(part.m_document->canUndo()); 1324 part.m_document->undo(); 1325 QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile.fileName()), saveFlags)); 1326 1327 QVERIFY(part.m_document->canUndo()); 1328 part.m_document->undo(); 1329 QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile.fileName()), saveFlags)); 1330 1331 QVERIFY(part.m_document->canUndo()); 1332 part.m_document->undo(); 1333 QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile.fileName()), saveFlags)); 1334 1335 QVERIFY(part.m_document->canUndo()); 1336 part.m_document->undo(); 1337 QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile.fileName()), saveFlags)); 1338 QVERIFY(!part.m_document->canUndo()); 1339 1340 QVERIFY(part.m_document->canRedo()); 1341 part.m_document->redo(); 1342 QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile.fileName()), saveFlags)); 1343 1344 QVERIFY(part.m_document->canRedo()); 1345 part.m_document->redo(); 1346 QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile.fileName()), saveFlags)); 1347 1348 QVERIFY(part.m_document->canRedo()); 1349 part.m_document->redo(); 1350 QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile.fileName()), saveFlags)); 1351 1352 QVERIFY(part.m_document->canRedo()); 1353 part.m_document->redo(); 1354 QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile.fileName()), saveFlags)); 1355 } 1356 1357 void PartTest::testSaveAsUndoStackForms_data() 1358 { 1359 QTest::addColumn<QString>("file"); 1360 QTest::addColumn<QString>("extension"); 1361 QTest::addColumn<bool>("saveToArchive"); 1362 1363 QTest::newRow("pdf") << KDESRCDIR "data/formSamples.pdf" 1364 << "pdf" << false; 1365 QTest::newRow("pdfarchive") << KDESRCDIR "data/formSamples.pdf" 1366 << "okular" << true; 1367 } 1368 1369 void PartTest::testOpenUrlArguments() 1370 { 1371 Okular::Part part(nullptr, {}); 1372 1373 KParts::OpenUrlArguments args; 1374 args.setMimeType(QStringLiteral("text/rtf")); 1375 1376 part.setArguments(args); 1377 1378 part.openUrl(QUrl::fromLocalFile(QStringLiteral(KDESRCDIR "data/file1.pdf"))); 1379 1380 QCOMPARE(part.arguments().mimeType(), QStringLiteral("text/rtf")); 1381 } 1382 1383 void PartTest::test388288() 1384 { 1385 Okular::Part part(nullptr, {}); 1386 1387 part.openUrl(QUrl::fromLocalFile(QStringLiteral(KDESRCDIR "data/file1.pdf"))); 1388 1389 part.widget()->show(); 1390 QVERIFY(QTest::qWaitForWindowExposed(part.widget())); 1391 1392 QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseNormal")); 1393 1394 auto annot = new Okular::HighlightAnnotation(); 1395 annot->setHighlightType(Okular::HighlightAnnotation::Highlight); 1396 const Okular::NormalizedRect r(0.36, 0.16, 0.51, 0.17); 1397 annot->setBoundingRectangle(r); 1398 Okular::HighlightAnnotation::Quad q; 1399 q.setCapStart(false); 1400 q.setCapEnd(false); 1401 q.setFeather(1.0); 1402 q.setPoint(Okular::NormalizedPoint(r.left, r.bottom), 0); 1403 q.setPoint(Okular::NormalizedPoint(r.right, r.bottom), 1); 1404 q.setPoint(Okular::NormalizedPoint(r.right, r.top), 2); 1405 q.setPoint(Okular::NormalizedPoint(r.left, r.top), 3); 1406 annot->highlightQuads().append(q); 1407 1408 part.m_document->addPageAnnotation(0, annot); 1409 1410 const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width(); 1411 const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height(); 1412 1413 QTest::mouseMove(part.m_pageView->viewport(), QPoint(width * 0.5, height * 0.5)); 1414 QTRY_COMPARE(part.m_pageView->cursor().shape(), Qt::OpenHandCursor); 1415 1416 QTest::mouseMove(part.m_pageView->viewport(), QPoint(width * 0.4, height * 0.165)); 1417 QTRY_COMPARE(part.m_pageView->cursor().shape(), Qt::ArrowCursor); 1418 1419 QTest::mouseMove(part.m_pageView->viewport(), QPoint(width * 0.1, height * 0.165)); 1420 1421 part.m_document->undo(); 1422 1423 annot = new Okular::HighlightAnnotation(); 1424 annot->setHighlightType(Okular::HighlightAnnotation::Highlight); 1425 annot->setBoundingRectangle(r); 1426 annot->highlightQuads().append(q); 1427 1428 part.m_document->addPageAnnotation(0, annot); 1429 1430 QTest::mouseMove(part.m_pageView->viewport(), QPoint(width * 0.5, height * 0.5)); 1431 QTRY_COMPARE(part.m_pageView->cursor().shape(), Qt::OpenHandCursor); 1432 } 1433 1434 void PartTest::testCheckBoxReadOnly() 1435 { 1436 const QString testFile = QStringLiteral(KDESRCDIR "data/checkbox_ro.pdf"); 1437 Okular::Part part(nullptr, {}); 1438 part.openDocument(testFile); 1439 1440 // The test document uses the activation action of checkboxes 1441 // to update the read only state. For this we need the part so that 1442 // undo / redo activates the activation action. 1443 1444 QVERIFY(part.m_document->isOpened()); 1445 1446 const Okular::Page *page = part.m_document->page(0); 1447 1448 QMap<QString, Okular::FormField *> fields; 1449 1450 // Field names in test document are: 1451 // CBMakeRW, CBMakeRO, TargetDefaultRO, TargetDefaultRW 1452 1453 const QList<Okular::FormField *> pageFormFields = page->formFields(); 1454 for (Okular::FormField *ff : pageFormFields) { 1455 fields.insert(ff->name(), static_cast<Okular::FormField *>(ff)); 1456 } 1457 1458 // First grab all fields and check that the setup is as expected. 1459 auto cbMakeRW = dynamic_cast<Okular::FormFieldButton *>(fields[QStringLiteral("CBMakeRW")]); 1460 auto cbMakeRO = dynamic_cast<Okular::FormFieldButton *>(fields[QStringLiteral("CBMakeRO")]); 1461 1462 auto targetDefaultRW = dynamic_cast<Okular::FormFieldText *>(fields[QStringLiteral("TargetDefaultRw")]); 1463 auto targetDefaultRO = dynamic_cast<Okular::FormFieldText *>(fields[QStringLiteral("TargetDefaultRo")]); 1464 1465 QVERIFY(cbMakeRW); 1466 QVERIFY(cbMakeRO); 1467 QVERIFY(targetDefaultRW); 1468 QVERIFY(targetDefaultRO); 1469 1470 QVERIFY(!cbMakeRW->state()); 1471 QVERIFY(!cbMakeRO->state()); 1472 1473 QVERIFY(!targetDefaultRW->isReadOnly()); 1474 QVERIFY(targetDefaultRO->isReadOnly()); 1475 1476 QList<Okular::FormFieldButton *> btns; 1477 btns << cbMakeRW << cbMakeRO; 1478 1479 // Now check both boxes 1480 QList<bool> btnStates; 1481 btnStates << true << true; 1482 1483 part.m_document->editFormButtons(0, btns, btnStates); 1484 1485 // Read only should be inverted 1486 QVERIFY(targetDefaultRW->isReadOnly()); 1487 QVERIFY(!targetDefaultRO->isReadOnly()); 1488 1489 // Test that undo / redo works 1490 QVERIFY(part.m_document->canUndo()); 1491 part.m_document->undo(); 1492 QVERIFY(!targetDefaultRW->isReadOnly()); 1493 QVERIFY(targetDefaultRO->isReadOnly()); 1494 1495 part.m_document->redo(); 1496 QVERIFY(targetDefaultRW->isReadOnly()); 1497 QVERIFY(!targetDefaultRO->isReadOnly()); 1498 1499 btnStates.clear(); 1500 btnStates << false << true; 1501 1502 part.m_document->editFormButtons(0, btns, btnStates); 1503 QVERIFY(targetDefaultRW->isReadOnly()); 1504 QVERIFY(targetDefaultRO->isReadOnly()); 1505 1506 // Now set both to checked again and confirm that 1507 // save / load works. 1508 btnStates.clear(); 1509 btnStates << true << true; 1510 part.m_document->editFormButtons(0, btns, btnStates); 1511 1512 QTemporaryFile saveFile(QStringLiteral("%1/okrXXXXXX.pdf").arg(QDir::tempPath())); 1513 QVERIFY(saveFile.open()); 1514 saveFile.close(); 1515 1516 // Save 1517 QVERIFY(part.saveAs(QUrl::fromLocalFile(saveFile.fileName()), Part::NoSaveAsFlags)); 1518 part.closeUrl(); 1519 1520 // Load 1521 part.openDocument(saveFile.fileName()); 1522 QVERIFY(part.m_document->isOpened()); 1523 1524 page = part.m_document->page(0); 1525 1526 fields.clear(); 1527 1528 { 1529 const QList<Okular::FormField *> pageFormFields = page->formFields(); 1530 for (Okular::FormField *ff : pageFormFields) { 1531 fields.insert(ff->name(), static_cast<Okular::FormField *>(ff)); 1532 } 1533 } 1534 1535 cbMakeRW = dynamic_cast<Okular::FormFieldButton *>(fields[QStringLiteral("CBMakeRW")]); 1536 cbMakeRO = dynamic_cast<Okular::FormFieldButton *>(fields[QStringLiteral("CBMakeRO")]); 1537 1538 targetDefaultRW = dynamic_cast<Okular::FormFieldText *>(fields[QStringLiteral("TargetDefaultRw")]); 1539 targetDefaultRO = dynamic_cast<Okular::FormFieldText *>(fields[QStringLiteral("TargetDefaultRo")]); 1540 1541 QVERIFY(cbMakeRW->state()); 1542 QVERIFY(cbMakeRO->state()); 1543 QVERIFY(targetDefaultRW->isReadOnly()); 1544 QVERIFY(!targetDefaultRO->isReadOnly()); 1545 } 1546 1547 void PartTest::testCrashTextEditDestroy() 1548 { 1549 const QString testFile = QStringLiteral(KDESRCDIR "data/formSamples.pdf"); 1550 Okular::Part part(nullptr, {}); 1551 part.openDocument(testFile); 1552 part.widget()->show(); 1553 QVERIFY(QTest::qWaitForWindowExposed(part.widget())); 1554 1555 part.widget()->findChild<QTextEdit *>()->setText(QStringLiteral("HOLA")); 1556 part.actionCollection()->action(QStringLiteral("view_toggle_forms"))->trigger(); 1557 } 1558 1559 void PartTest::testAnnotWindow() 1560 { 1561 Okular::Part part(nullptr, {}); 1562 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file1.pdf"))); 1563 part.widget()->show(); 1564 part.widget()->resize(800, 600); 1565 QVERIFY(QTest::qWaitForWindowExposed(part.widget())); 1566 1567 part.m_document->setViewportPage(0); 1568 1569 QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseNormal")); 1570 1571 QCOMPARE(part.m_document->currentPage(), 0u); 1572 1573 // Create two distinct text annotations 1574 Okular::Annotation *annot1 = new Okular::TextAnnotation(); 1575 annot1->setBoundingRectangle(Okular::NormalizedRect(0.8, 0.1, 0.85, 0.15)); 1576 annot1->setContents(QStringLiteral("Annot contents 111111")); 1577 1578 Okular::Annotation *annot2 = new Okular::TextAnnotation(); 1579 annot2->setBoundingRectangle(Okular::NormalizedRect(0.8, 0.3, 0.85, 0.35)); 1580 annot2->setContents(QStringLiteral("Annot contents 222222")); 1581 1582 // Add annot1 and annot2 to document 1583 part.m_document->addPageAnnotation(0, annot1); 1584 part.m_document->addPageAnnotation(0, annot2); 1585 QVERIFY(part.m_document->page(0)->annotations().size() == 2); 1586 1587 QTimer *delayResizeEventTimer = part.m_pageView->findChildren<QTimer *>(QStringLiteral("delayResizeEventTimer")).at(0); 1588 QVERIFY(delayResizeEventTimer->isActive()); 1589 QTest::qWait(delayResizeEventTimer->interval() * 2); 1590 1591 // wait for pixmap 1592 QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView)); 1593 1594 const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width(); 1595 const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height(); 1596 1597 // Double click the first annotation to open its window (move mouse for visual feedback) 1598 const NormalizedPoint annot1pt = annot1->boundingRectangle().center(); 1599 QTest::mouseMove(part.m_pageView->viewport(), QPoint(width * annot1pt.x, height * annot1pt.y)); 1600 QTest::mouseDClick(part.m_pageView->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(width * annot1pt.x, height * annot1pt.y)); 1601 QTRY_COMPARE(part.m_pageView->findChildren<QFrame *>(QStringLiteral("AnnotWindow")).size(), 1); 1602 // Verify that the window is visible 1603 QFrame *win1 = part.m_pageView->findChild<QFrame *>(QStringLiteral("AnnotWindow")); 1604 QVERIFY(!win1->visibleRegion().isEmpty()); 1605 1606 // Double click the second annotation to open its window (move mouse for visual feedback) 1607 const NormalizedPoint annot2pt = annot2->boundingRectangle().center(); 1608 QTest::mouseMove(part.m_pageView->viewport(), QPoint(width * annot2pt.x, height * annot2pt.y)); 1609 QTest::mouseDClick(part.m_pageView->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(width * annot2pt.x, height * annot2pt.y)); 1610 QTRY_COMPARE(part.m_pageView->findChildren<QFrame *>(QStringLiteral("AnnotWindow")).size(), 2); 1611 // Verify that the first window is hidden covered by the second, which is visible 1612 QList<QFrame *> lstWin = part.m_pageView->findChildren<QFrame *>(QStringLiteral("AnnotWindow")); 1613 QFrame *win2; 1614 if (lstWin[0] == win1) { 1615 win2 = lstWin[1]; 1616 } else { 1617 win2 = lstWin[0]; 1618 } 1619 QVERIFY(win1->visibleRegion().isEmpty()); 1620 QVERIFY(!win2->visibleRegion().isEmpty()); 1621 1622 // Double click the first annotation to raise its window (move mouse for visual feedback) 1623 QTest::mouseMove(part.m_pageView->viewport(), QPoint(width * annot1pt.x, height * annot1pt.y)); 1624 QTest::mouseDClick(part.m_pageView->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(width * annot1pt.x, height * annot1pt.y)); 1625 // Verify that the second window is hidden covered by the first, which is visible 1626 QVERIFY(!win1->visibleRegion().isEmpty()); 1627 QVERIFY(win2->visibleRegion().isEmpty()); 1628 1629 // Move annotation window 1 to partially show annotation window 2 1630 win1->move(QPoint(win2->pos().x(), win2->pos().y() + 50)); 1631 // Verify that both windows are partially visible 1632 QVERIFY(!win1->visibleRegion().isEmpty()); 1633 QVERIFY(!win2->visibleRegion().isEmpty()); 1634 1635 // Click the second annotation window to raise it (move mouse for visual feedback) 1636 auto widget = win2->window()->childAt(win2->mapTo(win2->window(), QPoint(10, 10))); 1637 QTest::mouseMove(win2->window(), win2->mapTo(win2->window(), QPoint(10, 10))); 1638 QTest::mouseClick(widget, Qt::LeftButton, Qt::NoModifier, widget->mapFrom(win2, QPoint(10, 10))); 1639 QVERIFY(win1->visibleRegion().rectCount() == 3); 1640 QVERIFY(win2->visibleRegion().rectCount() == 4); 1641 } 1642 1643 // Helper for testAdditionalActionTriggers 1644 static void verifyTargetStates(const QString &triggerName, const QMap<QString, Okular::FormField *> &fields, bool focusVisible, bool cursorVisible, bool mouseVisible, int line) 1645 { 1646 Okular::FormField *focusTarget = fields.value(triggerName + QStringLiteral("_focus_target")); 1647 Okular::FormField *cursorTarget = fields.value(triggerName + QStringLiteral("_cursor_target")); 1648 Okular::FormField *mouseTarget = fields.value(triggerName + QStringLiteral("_mouse_target")); 1649 1650 QVERIFY(focusTarget); 1651 QVERIFY(cursorTarget); 1652 QVERIFY(mouseTarget); 1653 1654 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()); 1655 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()); 1656 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()); 1657 } 1658 1659 void PartTest::testAdditionalActionTriggers() 1660 { 1661 const QString testFile = QStringLiteral(KDESRCDIR "data/additionalFormActions.pdf"); 1662 Okular::Part part(nullptr, QVariantList()); 1663 part.openDocument(testFile); 1664 part.widget()->resize(800, 600); 1665 1666 part.widget()->show(); 1667 QVERIFY(QTest::qWaitForWindowExposed(part.widget())); 1668 1669 QTimer *delayResizeEventTimer = part.m_pageView->findChildren<QTimer *>(QStringLiteral("delayResizeEventTimer")).at(0); 1670 QVERIFY(delayResizeEventTimer->isActive()); 1671 QTest::qWait(delayResizeEventTimer->interval() * 2); 1672 1673 part.m_document->setViewportPage(0); 1674 1675 // wait for pixmap 1676 QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView)); 1677 1678 QMap<QString, Okular::FormField *> fields; 1679 // Field names in test document are: 1680 // For trigger fields: tf, cb, rb, dd, pb 1681 // For target fields: <trigger_name>_focus_target, <trigger_name>_cursor_target, 1682 // <trigger_name>_mouse_target 1683 const Okular::Page *page = part.m_document->page(0); 1684 const QList<Okular::FormField *> pageFormFields = page->formFields(); 1685 for (Okular::FormField *ff : pageFormFields) { 1686 fields.insert(ff->name(), static_cast<Okular::FormField *>(ff)); 1687 } 1688 1689 // Verify that everything is set up. 1690 verifyTargetStates(QStringLiteral("tf"), fields, true, true, true, __LINE__); 1691 verifyTargetStates(QStringLiteral("cb"), fields, true, true, true, __LINE__); 1692 verifyTargetStates(QStringLiteral("rb"), fields, true, true, true, __LINE__); 1693 verifyTargetStates(QStringLiteral("dd"), fields, true, true, true, __LINE__); 1694 verifyTargetStates(QStringLiteral("pb"), fields, true, true, true, __LINE__); 1695 1696 const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width(); 1697 const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height(); 1698 1699 part.actionCollection()->action(QStringLiteral("view_toggle_forms"))->trigger(); 1700 1701 QPoint tfPos(width * 0.045, height * 0.05); 1702 QPoint cbPos(width * 0.045, height * 0.08); 1703 QPoint rbPos(width * 0.045, height * 0.12); 1704 QPoint ddPos(width * 0.045, height * 0.16); 1705 QPoint pbPos(width * 0.045, height * 0.26); 1706 1707 // Test text field 1708 auto widget = part.m_pageView->viewport()->childAt(tfPos); 1709 QVERIFY(widget); 1710 1711 QTest::mouseMove(part.m_pageView->viewport(), QPoint(tfPos)); 1712 verifyTargetStates(QStringLiteral("tf"), fields, true, false, true, __LINE__); 1713 QTest::mousePress(widget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); 1714 verifyTargetStates(QStringLiteral("tf"), fields, false, false, false, __LINE__); 1715 QTest::mouseRelease(widget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); 1716 verifyTargetStates(QStringLiteral("tf"), fields, false, false, true, __LINE__); 1717 1718 // Checkbox 1719 widget = part.m_pageView->viewport()->childAt(cbPos); 1720 QVERIFY(widget); 1721 1722 QTest::mouseMove(part.m_pageView->viewport(), QPoint(cbPos)); 1723 verifyTargetStates(QStringLiteral("cb"), fields, true, false, true, __LINE__); 1724 QTest::mousePress(widget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); 1725 verifyTargetStates(QStringLiteral("cb"), fields, false, false, false, __LINE__); 1726 // Confirm that the textfield no longer has any invisible 1727 verifyTargetStates(QStringLiteral("tf"), fields, true, true, true, __LINE__); 1728 QTest::mouseRelease(widget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); 1729 verifyTargetStates(QStringLiteral("cb"), fields, false, false, true, __LINE__); 1730 1731 // Radio 1732 widget = part.m_pageView->viewport()->childAt(rbPos); 1733 QVERIFY(widget); 1734 1735 QTest::mouseMove(part.m_pageView->viewport(), QPoint(rbPos)); 1736 verifyTargetStates(QStringLiteral("rb"), fields, true, false, true, __LINE__); 1737 QTest::mousePress(widget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); 1738 verifyTargetStates(QStringLiteral("rb"), fields, false, false, false, __LINE__); 1739 QTest::mouseRelease(widget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); 1740 verifyTargetStates(QStringLiteral("rb"), fields, false, false, true, __LINE__); 1741 1742 // Dropdown 1743 widget = part.m_pageView->viewport()->childAt(ddPos); 1744 QVERIFY(widget); 1745 1746 QTest::mouseMove(part.m_pageView->viewport(), QPoint(ddPos)); 1747 verifyTargetStates(QStringLiteral("dd"), fields, true, false, true, __LINE__); 1748 QTest::mousePress(widget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); 1749 verifyTargetStates(QStringLiteral("dd"), fields, false, false, false, __LINE__); 1750 QTest::mouseRelease(widget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); 1751 verifyTargetStates(QStringLiteral("dd"), fields, false, false, true, __LINE__); 1752 1753 // Pushbutton 1754 widget = part.m_pageView->viewport()->childAt(pbPos); 1755 QVERIFY(widget); 1756 1757 QTest::mouseMove(part.m_pageView->viewport(), QPoint(pbPos)); 1758 verifyTargetStates(QStringLiteral("pb"), fields, true, false, true, __LINE__); 1759 QTest::mousePress(widget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); 1760 verifyTargetStates(QStringLiteral("pb"), fields, false, false, false, __LINE__); 1761 QTest::mouseRelease(widget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); 1762 verifyTargetStates(QStringLiteral("pb"), fields, false, false, true, __LINE__); 1763 1764 // Confirm that a mouse release outside does not trigger the show action. 1765 QTest::mousePress(widget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); 1766 verifyTargetStates(QStringLiteral("pb"), fields, false, false, false, __LINE__); 1767 QTest::mouseRelease(part.m_pageView->viewport(), Qt::LeftButton, Qt::NoModifier, tfPos); 1768 verifyTargetStates(QStringLiteral("pb"), fields, false, false, false, __LINE__); 1769 } 1770 1771 void PartTest::testTypewriterAnnotTool() 1772 { 1773 Okular::Part part(nullptr, QVariantList()); 1774 1775 part.openUrl(QUrl::fromLocalFile(QStringLiteral(KDESRCDIR "data/file1.pdf"))); 1776 1777 part.widget()->show(); 1778 QVERIFY(QTest::qWaitForWindowExposed(part.widget())); 1779 1780 const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width(); 1781 const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height(); 1782 1783 part.m_document->setViewportPage(0); 1784 1785 // Find the TypeWriter annotation 1786 QAction *typeWriterAction = part.actionCollection()->action(QStringLiteral("annotation_typewriter")); 1787 QVERIFY(typeWriterAction); 1788 1789 typeWriterAction->trigger(); 1790 1791 QTest::qWait(1000); // Wait for the "add new note" dialog to appear 1792 TestingUtils::CloseDialogHelper closeDialogHelper(QDialogButtonBox::Ok); 1793 1794 QTest::mouseClick(part.m_pageView->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(width * 0.5, height * 0.2)); 1795 1796 Annotation *annot = part.m_document->page(0)->annotations().first(); 1797 TextAnnotation *ta = static_cast<TextAnnotation *>(annot); 1798 QVERIFY(annot); 1799 QVERIFY(ta); 1800 QCOMPARE(annot->subType(), Okular::Annotation::AText); 1801 QCOMPARE(annot->style().color(), QColor(255, 255, 255, 0)); 1802 QCOMPARE(ta->textType(), Okular::TextAnnotation::InPlace); 1803 QCOMPARE(ta->inplaceIntent(), Okular::TextAnnotation::TypeWriter); 1804 } 1805 1806 void PartTest::testJumpToPage() 1807 { 1808 const QString testFile = QStringLiteral(KDESRCDIR "data/simple-multipage.pdf"); 1809 const int targetPage = 25; 1810 Okular::Part part(nullptr, QVariantList()); 1811 part.openDocument(testFile); 1812 part.widget()->resize(800, 600); 1813 part.widget()->show(); 1814 QVERIFY(QTest::qWaitForWindowExposed(part.widget())); 1815 1816 part.m_document->pages(); 1817 part.m_document->setViewportPage(targetPage); 1818 1819 /* Document::setViewportPage triggers pixmap rendering in another thread. 1820 * We want to test how things look AFTER finished signal arrives back, 1821 * because PageView::slotRelayoutPages may displace the viewport again. 1822 */ 1823 QTRY_VERIFY(part.m_document->page(targetPage)->hasPixmap(part.m_pageView)); 1824 1825 const int contentAreaHeight = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height(); 1826 const int pageWithSpaceTop = contentAreaHeight / part.m_document->pages() * targetPage; 1827 1828 /* 1829 * This is a test for a "known by trial" displacement. 1830 * We'd need access to part.m_pageView->d->items[targetPage]->croppedGeometry().top(), 1831 * to determine the expected viewport position, but we don't have access. 1832 */ 1833 QCOMPARE(part.m_pageView->verticalScrollBar()->value(), pageWithSpaceTop - 4); 1834 } 1835 1836 void PartTest::testOpenAtPage() 1837 { 1838 const QString testFile = QStringLiteral(KDESRCDIR "data/simple-multipage.pdf"); 1839 QUrl url = QUrl::fromLocalFile(testFile); 1840 Okular::Part part(nullptr, QVariantList()); 1841 1842 const uint targetPageNumA = 25; 1843 const uint expectedPageA = targetPageNumA - 1; 1844 url.setFragment(QString::number(targetPageNumA)); 1845 part.openUrl(url); 1846 QCOMPARE(part.m_document->currentPage(), expectedPageA); 1847 1848 // 'page=<pagenum>' param as specified in RFC 3778 1849 const uint targetPageNumB = 15; 1850 const uint expectedPageB = targetPageNumB - 1; 1851 url.setFragment(QStringLiteral("page=") + QString::number(targetPageNumB)); 1852 part.openUrl(url); 1853 QCOMPARE(part.m_document->currentPage(), expectedPageB); 1854 } 1855 1856 void PartTest::testForwardBackwardNavigation() 1857 { 1858 const QString testFile = QStringLiteral(KDESRCDIR "data/simple-multipage.pdf"); 1859 Okular::Part part(nullptr, QVariantList()); 1860 part.openDocument(testFile); 1861 part.widget()->resize(800, 600); 1862 part.widget()->show(); 1863 QVERIFY(QTest::qWaitForWindowExposed(part.widget())); 1864 1865 // Go to some page 1866 const int targetPageA = 15; 1867 part.m_document->setViewportPage(targetPageA); 1868 1869 QVERIFY(part.m_document->viewport() == DocumentViewport(targetPageA)); 1870 1871 // Go to some other page 1872 const int targetPageB = 25; 1873 part.m_document->setViewportPage(targetPageB); 1874 QVERIFY(part.m_document->viewport() == DocumentViewport(targetPageB)); 1875 1876 // Go back to page A 1877 QVERIFY(QMetaObject::invokeMethod(&part, "slotHistoryBack")); 1878 QVERIFY(part.m_document->viewport().pageNumber == targetPageA); 1879 1880 // Go back to page B 1881 QVERIFY(QMetaObject::invokeMethod(&part, "slotHistoryNext")); 1882 QVERIFY(part.m_document->viewport().pageNumber == targetPageB); 1883 } 1884 1885 void PartTest::testTabletProximityBehavior() 1886 { 1887 QVariantList dummyArgs; 1888 Okular::Part part {nullptr, dummyArgs}; 1889 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file1.pdf"))); 1890 part.slotShowPresentation(); 1891 PresentationWidget *w = part.m_presentationWidget; 1892 QVERIFY(w); 1893 part.widget()->show(); 1894 1895 // close the KMessageBox "There are two ways of exiting[...]" 1896 TestingUtils::CloseDialogHelper closeDialogHelper(w, QDialogButtonBox::Ok); // confirm the "To leave, press ESC" 1897 1898 auto pointingDevice = new QPointingDevice(QStringLiteral("test"), 42, QInputDevice::DeviceType::Stylus, QPointingDevice::PointerType::Pen, QInputDevice::Capability::All, 3, 3); 1899 QTabletEvent enterProximityEvent {QEvent::TabletEnterProximity, pointingDevice, QPointF(10, 10), QPointF(10, 10), 1., 0, 0, 1., 1., 0, Qt::NoModifier, Qt::NoButton, Qt::NoButton}; 1900 QTabletEvent leaveProximityEvent {QEvent::TabletLeaveProximity, pointingDevice, QPointF(10, 10), QPointF(10, 10), 1., 0, 0, 1., 1., 0, Qt::NoModifier, Qt::NoButton, Qt::NoButton}; 1901 1902 // Test with the Okular::Settings::EnumSlidesCursor::Visible setting 1903 Okular::Settings::self()->setSlidesCursor(Okular::Settings::EnumSlidesCursor::Visible); 1904 1905 // Send an enterProximity event 1906 qApp->notify(qApp, &enterProximityEvent); 1907 1908 // The cursor should be a cross-hair 1909 QVERIFY(w->cursor().shape() == Qt::CursorShape(Qt::CrossCursor)); 1910 1911 // Send a leaveProximity event 1912 qApp->notify(qApp, &leaveProximityEvent); 1913 1914 // After the leaveProximityEvent, the cursor should be an arrow again, because 1915 // we have set the slidesCursor mode to 'Visible' 1916 QVERIFY(w->cursor().shape() == Qt::CursorShape(Qt::ArrowCursor)); 1917 1918 // Test with the Okular::Settings::EnumSlidesCursor::Hidden setting 1919 Okular::Settings::self()->setSlidesCursor(Okular::Settings::EnumSlidesCursor::Hidden); 1920 1921 qApp->notify(qApp, &enterProximityEvent); 1922 QVERIFY(w->cursor().shape() == Qt::CursorShape(Qt::CrossCursor)); 1923 qApp->notify(qApp, &leaveProximityEvent); 1924 QVERIFY(w->cursor().shape() == Qt::CursorShape(Qt::BlankCursor)); 1925 1926 // Moving the mouse should not bring the cursor back 1927 QTest::mouseMove(w, QPoint(100, 100)); 1928 QVERIFY(w->cursor().shape() == Qt::CursorShape(Qt::BlankCursor)); 1929 1930 // First test with the Okular::Settings::EnumSlidesCursor::HiddenDelay setting 1931 Okular::Settings::self()->setSlidesCursor(Okular::Settings::EnumSlidesCursor::HiddenDelay); 1932 1933 qApp->notify(qApp, &enterProximityEvent); 1934 QVERIFY(w->cursor().shape() == Qt::CursorShape(Qt::CrossCursor)); 1935 qApp->notify(qApp, &leaveProximityEvent); 1936 1937 // After the leaveProximityEvent, the cursor should be blank, because 1938 // we have set the slidesCursor mode to 'HiddenDelay' 1939 QVERIFY(w->cursor().shape() == Qt::CursorShape(Qt::BlankCursor)); 1940 1941 // Moving the mouse should bring the cursor back 1942 QTest::mouseMove(w, QPoint(150, 150)); 1943 QVERIFY(w->cursor().shape() == Qt::CursorShape(Qt::ArrowCursor)); 1944 } 1945 1946 void PartTest::testOpenPrintPreview() 1947 { 1948 QVariantList dummyArgs; 1949 Okular::Part part(nullptr, dummyArgs); 1950 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file1.pdf"))); 1951 part.widget()->show(); 1952 QVERIFY(QTest::qWaitForWindowExposed(part.widget())); 1953 TestingUtils::CloseDialogHelper closeDialogHelper(QDialogButtonBox::Close); 1954 part.slotPrintPreview(); 1955 } 1956 1957 void PartTest::testMouseModeMenu() 1958 { 1959 QVariantList dummyArgs; 1960 Okular::Part part(nullptr, dummyArgs); 1961 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file1.pdf"))); 1962 1963 QMetaObject::invokeMethod(part.m_pageView, "slotSetMouseNormal"); 1964 1965 // Get mouse mode menu action 1966 QAction *mouseModeAction = part.actionCollection()->action(QStringLiteral("mouse_selecttools")); 1967 QVERIFY(mouseModeAction); 1968 QMenu *mouseModeActionMenu = mouseModeAction->menu(); 1969 1970 // Test that actions are usable (not disabled) 1971 QVERIFY(mouseModeActionMenu->actions().at(0)->isEnabled()); 1972 QVERIFY(mouseModeActionMenu->actions().at(1)->isEnabled()); 1973 QVERIFY(mouseModeActionMenu->actions().at(2)->isEnabled()); 1974 1975 // Test activating area selection mode 1976 mouseModeActionMenu->actions().at(0)->trigger(); 1977 QCOMPARE(Okular::Settings::mouseMode(), (int)Okular::Settings::EnumMouseMode::RectSelect); 1978 1979 // Test activating text selection mode 1980 mouseModeActionMenu->actions().at(1)->trigger(); 1981 QCOMPARE(Okular::Settings::mouseMode(), (int)Okular::Settings::EnumMouseMode::TextSelect); 1982 1983 // Test activating table selection mode 1984 mouseModeActionMenu->actions().at(2)->trigger(); 1985 QCOMPARE(Okular::Settings::mouseMode(), (int)Okular::Settings::EnumMouseMode::TableSelect); 1986 } 1987 1988 void PartTest::testFullScreenRequest() 1989 { 1990 QVariantList dummyArgs; 1991 Okular::Part part(nullptr, dummyArgs); 1992 1993 // Open file. For this particular file, a dialog has to appear asking whether 1994 // one wants to comply with the wish to go to presentation mode directly. 1995 // Answer 'no' 1996 auto dialogHelper = std::make_unique<TestingUtils::CloseDialogHelper>(&part, QDialogButtonBox::No); 1997 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/RequestFullScreen.pdf"))); 1998 1999 // Check that we are not in presentation mode 2000 QEXPECT_FAIL("", "The presentation widget should not be shown because we clicked No in the dialog", Continue); 2001 QTRY_VERIFY_WITH_TIMEOUT(part.m_presentationWidget, 1000); 2002 2003 // Reload the file. The initial dialog should no appear again. 2004 // (This is https://bugs.kde.org/show_bug.cgi?id=361740) 2005 part.reload(); 2006 2007 // Open the file again. Now we answer "yes, go to presentation mode" 2008 dialogHelper = std::make_unique<TestingUtils::CloseDialogHelper>(&part, QDialogButtonBox::Yes); 2009 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/RequestFullScreen.pdf"))); 2010 2011 // Test whether we really are in presentation mode 2012 QTRY_VERIFY(part.m_presentationWidget); 2013 } 2014 2015 void PartTest::testZoomInFacingPages() 2016 { 2017 QVariantList dummyArgs; 2018 Okular::Part part(nullptr, dummyArgs); 2019 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file2.pdf"))); 2020 QAction *facingAction = part.m_pageView->findChild<QAction *>(QStringLiteral("view_render_mode_facing")); 2021 KSelectAction *zoomSelectAction = part.m_pageView->findChild<KSelectAction *>(QStringLiteral("zoom_to")); 2022 part.widget()->resize(600, 400); 2023 part.widget()->show(); 2024 QVERIFY(QTest::qWaitForWindowExposed(part.widget())); 2025 facingAction->trigger(); 2026 while (zoomSelectAction->currentText() != QStringLiteral("12%")) { 2027 QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotZoomOut")); 2028 } 2029 QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView)); 2030 QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotZoomIn")); 2031 QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotZoomIn")); 2032 QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotZoomIn")); 2033 QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotZoomIn")); 2034 QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotZoomIn")); 2035 QTRY_COMPARE(zoomSelectAction->currentText(), QStringLiteral("66%")); 2036 2037 // Back to single mode 2038 part.m_pageView->findChild<QAction *>(QStringLiteral("view_render_mode_single"))->trigger(); 2039 } 2040 2041 void PartTest::testZoomWithCrop() 2042 { 2043 // We test that all zoom levels can be achieved with cropped pages, bug 342003 2044 2045 QVariantList dummyArgs; 2046 Okular::Part part(nullptr, dummyArgs); 2047 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/file2.pdf"))); 2048 2049 KActionMenu *cropMenu = part.m_pageView->findChild<KActionMenu *>(QStringLiteral("view_trim_mode")); 2050 KToggleAction *cropAction = cropMenu->menu()->findChild<KToggleAction *>(QStringLiteral("view_trim_margins")); 2051 KSelectAction *zoomSelectAction = part.m_pageView->findChild<KSelectAction *>(QStringLiteral("zoom_to")); 2052 2053 part.widget()->resize(600, 400); 2054 part.widget()->show(); 2055 QVERIFY(QTest::qWaitForWindowExposed(part.widget())); 2056 2057 // Activate "Trim Margins" 2058 QVERIFY(!Okular::Settings::trimMargins()); 2059 cropAction->trigger(); 2060 QVERIFY(Okular::Settings::trimMargins()); 2061 2062 // Wait for the bounding boxes 2063 QTRY_VERIFY(part.m_document->page(0)->isBoundingBoxKnown()); 2064 QTRY_VERIFY(part.m_document->page(1)->isBoundingBoxKnown()); 2065 2066 // Zoom out 2067 for (int i = 0; i < 20; i++) { 2068 QVERIFY(QMetaObject::invokeMethod(part.m_pageView, "slotZoomOut")); 2069 } 2070 QCOMPARE(zoomSelectAction->currentText(), QStringLiteral("12%")); 2071 2072 // Zoom in and out and check that all zoom levels appear 2073 QSet<QString> zooms_ref {QStringLiteral("12%"), 2074 QStringLiteral("25%"), 2075 QStringLiteral("33%"), 2076 QStringLiteral("50%"), 2077 QStringLiteral("66%"), 2078 QStringLiteral("75%"), 2079 QStringLiteral("100%"), 2080 QStringLiteral("125%"), 2081 QStringLiteral("150%"), 2082 QStringLiteral("200%"), 2083 QStringLiteral("400%"), 2084 QStringLiteral("800%"), 2085 QStringLiteral("1,600%"), 2086 QStringLiteral("2,500%"), 2087 QStringLiteral("5,000%"), 2088 QStringLiteral("10,000%")}; 2089 2090 for (int j = 0; j < 2; j++) { 2091 QSet<QString> zooms; 2092 for (int i = 0; i < 18; i++) { 2093 zooms << zoomSelectAction->currentText(); 2094 QVERIFY(QMetaObject::invokeMethod(part.m_pageView, j == 0 ? "slotZoomIn" : "slotZoomOut")); 2095 } 2096 2097 QVERIFY(zooms.contains(zooms_ref)); 2098 } 2099 2100 // Deactivate "Trim Margins" 2101 QVERIFY(Okular::Settings::trimMargins()); 2102 cropAction->trigger(); 2103 QVERIFY(!Okular::Settings::trimMargins()); 2104 } 2105 2106 void PartTest::testLinkWithCrop() 2107 { 2108 // We test that link targets are correct with cropping, related to bug 198427 2109 2110 QVariantList dummyArgs; 2111 Okular::Part part(nullptr, dummyArgs); 2112 QVERIFY(openDocument(&part, QStringLiteral(KDESRCDIR "data/pdf_with_internal_links.pdf"))); 2113 2114 KActionMenu *cropMenu = part.m_pageView->findChild<KActionMenu *>(QStringLiteral("view_trim_mode")); 2115 KToggleAction *cropAction = cropMenu->menu()->findChild<KToggleAction *>(QStringLiteral("view_trim_selection")); 2116 2117 part.widget()->resize(600, 400); 2118 part.widget()->show(); 2119 QVERIFY(QTest::qWaitForWindowExposed(part.widget())); 2120 2121 // wait for pixmap 2122 QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView)); 2123 2124 const int width = part.m_pageView->viewport()->width(); 2125 const int height = part.m_pageView->viewport()->height(); 2126 2127 // Move to a location without a link 2128 QTest::mouseMove(part.m_pageView->viewport(), QPoint(width * 0.1, width * 0.1)); 2129 2130 // The cursor should be normal 2131 QTRY_COMPARE(part.m_pageView->cursor().shape(), Qt::CursorShape(Qt::OpenHandCursor)); 2132 2133 // Activate "Trim Margins" 2134 cropAction->trigger(); 2135 2136 // The cursor should be a cross-hair 2137 QTRY_COMPARE(part.m_pageView->cursor().shape(), Qt::CursorShape(Qt::CrossCursor)); 2138 2139 const int mouseStartY = height * 0.2; 2140 const int mouseEndY = height * 0.8; 2141 const int mouseStartX = width * 0.2; 2142 const int mouseEndX = width * 0.8; 2143 2144 // Trim the page 2145 simulateMouseSelection(mouseStartX, mouseStartY, mouseEndX, mouseEndY, part.m_pageView->viewport()); 2146 2147 // We seem to have a trimmed view where we just by sheer luck ends up with mouse over a link at least sometimes 2148 // So move the mouse 2149 QTest::mouseMove(part.m_pageView->viewport(), QPoint(width * 0.1, width * 0.1)); 2150 2151 // The cursor should be normal again 2152 QTRY_COMPARE(part.m_pageView->cursor().shape(), Qt::CursorShape(Qt::OpenHandCursor)); 2153 2154 // Click a link 2155 const QPoint click(width * 0.2, height * 0.2); 2156 QTest::mouseMove(part.m_pageView->viewport(), click); 2157 QTest::mouseClick(part.m_pageView->viewport(), Qt::LeftButton, Qt::NoModifier, click); 2158 2159 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); 2160 2161 // Deactivate "Trim Margins" 2162 cropAction->trigger(); 2163 } 2164 2165 void PartTest::testFieldFormatting() 2166 { 2167 // Test field formatting. This has to be a parttest so that we 2168 // can properly test focus in / out which triggers formatting. 2169 const QString testFile = QStringLiteral(KDESRCDIR "data/fieldFormat.pdf"); 2170 Okular::Part part(nullptr, QVariantList()); 2171 part.openDocument(testFile); 2172 part.widget()->resize(800, 600); 2173 2174 part.widget()->show(); 2175 QVERIFY(QTest::qWaitForWindowExposed(part.widget())); 2176 2177 // Field names in test document are: 2178 // 2179 // us_currency_fmt for formatting like "$ 1,234.56" 2180 // de_currency_fmt for formatting like "1.234,56 €" 2181 // de_simple_sum for calculation test and formatting like "1.234,56€" 2182 // date_mm_dd_yyyy for dates like "18/06/2018" 2183 // date_dd_mm_yyyy for dates like "06/18/2018" 2184 // percent_fmt for percent format like "100,00%" if you enter 1 2185 // time_HH_MM_fmt for times like "23:12" 2186 // time_HH_MM_ss_fmt for times like "23:12:34" 2187 // special_phone_number for an example of a special format selectable in Acrobat. 2188 QMap<QString, Okular::FormField *> fields; 2189 const Okular::Page *page = part.m_document->page(0); 2190 const auto formFields = page->formFields(); 2191 for (Okular::FormField *ff : formFields) { 2192 fields.insert(ff->name(), static_cast<Okular::FormField *>(ff)); 2193 } 2194 2195 const int width = part.m_pageView->horizontalScrollBar()->maximum() + part.m_pageView->viewport()->width(); 2196 const int height = part.m_pageView->verticalScrollBar()->maximum() + part.m_pageView->viewport()->height(); 2197 2198 part.m_document->setViewportPage(0); 2199 2200 // wait for pixmap 2201 QTRY_VERIFY(part.m_document->page(0)->hasPixmap(part.m_pageView)); 2202 2203 part.actionCollection()->action(QStringLiteral("view_toggle_forms"))->trigger(); 2204 2205 // Note as of version 1.5: 2206 // The test document is prepared for future extensions to formatting for dates etc. 2207 // Currently we only have the number format to test. 2208 const auto ff_us = dynamic_cast<Okular::FormFieldText *>(fields.value(QStringLiteral("us_currency_fmt"))); 2209 const auto ff_de = dynamic_cast<Okular::FormFieldText *>(fields.value(QStringLiteral("de_currency_fmt"))); 2210 const auto ff_sum = dynamic_cast<Okular::FormFieldText *>(fields.value(QStringLiteral("de_simple_sum"))); 2211 2212 const QPoint usPos(width * 0.25, height * 0.025); 2213 const QPoint dePos(width * 0.25, height * 0.05); 2214 const QPoint deSumPos(width * 0.25, height * 0.075); 2215 2216 const auto viewport = part.m_pageView->viewport(); 2217 2218 QVERIFY(viewport); 2219 2220 auto usCurrencyWidget = dynamic_cast<QLineEdit *>(viewport->childAt(usPos)); 2221 auto deCurrencyWidget = dynamic_cast<QLineEdit *>(viewport->childAt(dePos)); 2222 auto sumCurrencyWidget = dynamic_cast<QLineEdit *>(viewport->childAt(deSumPos)); 2223 2224 // Check that the widgets were found at the right position 2225 QVERIFY(usCurrencyWidget); 2226 QVERIFY(deCurrencyWidget); 2227 QVERIFY(sumCurrencyWidget); 2228 2229 QTest::mousePress(usCurrencyWidget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); 2230 QTRY_VERIFY(usCurrencyWidget->hasFocus()); 2231 // locale is en_US for this test. Enter a value and check it. 2232 usCurrencyWidget->setText(QStringLiteral("1234.56")); 2233 // Check that the internal text matches 2234 QCOMPARE(ff_us->text(), QStringLiteral("1234.56")); 2235 2236 // Now move the focus to trigger formatting. 2237 QTest::mousePress(deCurrencyWidget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); 2238 QTRY_VERIFY(deCurrencyWidget->hasFocus()); 2239 2240 QCOMPARE(usCurrencyWidget->text(), QStringLiteral("$ 1,234.56")); 2241 QCOMPARE(ff_us->text(), QStringLiteral("1234.56")); 2242 2243 // And again with an invalid number 2244 QTest::mousePress(usCurrencyWidget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); 2245 QTRY_VERIFY(usCurrencyWidget->hasFocus()); 2246 2247 usCurrencyWidget->setText(QStringLiteral("131.234,567")); 2248 QTest::mousePress(deCurrencyWidget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); 2249 QTRY_VERIFY(deCurrencyWidget->hasFocus()); 2250 // Check that the internal text still contains it. 2251 QCOMPARE(ff_us->text(), QStringLiteral("131.234,567")); 2252 2253 // Just check that the text does not match the internal text. 2254 // We don't check for a concrete value to keep NaN handling flexible 2255 QVERIFY(ff_us->text() != usCurrencyWidget->text()); 2256 2257 // Move the focus back and modify it a bit more 2258 QTest::mousePress(usCurrencyWidget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); 2259 QTRY_VERIFY(usCurrencyWidget->hasFocus()); 2260 2261 usCurrencyWidget->setText(QStringLiteral("1,234.567")); 2262 QTest::mousePress(deCurrencyWidget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); 2263 QTRY_VERIFY(deCurrencyWidget->hasFocus()); 2264 2265 QCOMPARE(usCurrencyWidget->text(), QStringLiteral("$ 1,234.57")); 2266 2267 // Sum should already match 2268 QCOMPARE(sumCurrencyWidget->text(), QStringLiteral("1.234,57€")); 2269 2270 // Set a text in the de field 2271 deCurrencyWidget->setText(QStringLiteral("1,123,234.567")); 2272 QTest::mousePress(usCurrencyWidget, Qt::LeftButton, Qt::NoModifier, QPoint(5, 5)); 2273 QTRY_VERIFY(usCurrencyWidget->hasFocus()); 2274 2275 QCOMPARE(deCurrencyWidget->text(), QStringLiteral("1.123.234,57 €")); 2276 QCOMPARE(ff_de->text(), QStringLiteral("1,123,234.567")); 2277 QCOMPARE(sumCurrencyWidget->text(), QStringLiteral("1.124.469,13€")); 2278 QCOMPARE(ff_sum->text(), QStringLiteral("1,124,469.1340000000782310962677002")); 2279 } 2280 2281 } // namespace Okular 2282 2283 int main(int argc, char *argv[]) 2284 { 2285 // Force consistent locale 2286 QLocale locale(QStringLiteral("en_US.UTF-8")); 2287 if (locale == QLocale::c()) { // This is the way to check if the above worked 2288 locale = QLocale(QLocale::English, QLocale::UnitedStates); 2289 } 2290 2291 QLocale::setDefault(locale); 2292 qputenv("LC_ALL", "en_US.UTF-8"); // For UNIX, third-party libraries 2293 2294 // Ensure consistent configs/caches 2295 QTemporaryDir homeDir; // QTemporaryDir automatically cleans up when it goes out of scope 2296 Q_ASSERT(homeDir.isValid()); 2297 QByteArray homePath = QFile::encodeName(homeDir.path()); 2298 qDebug() << homePath; 2299 qputenv("USERPROFILE", homePath); 2300 qputenv("HOME", homePath); 2301 qputenv("XDG_DATA_HOME", QByteArray(homePath + "/.local")); 2302 qputenv("XDG_CONFIG_HOME", QByteArray(homePath + "/.kde-unit-test/xdg/config")); 2303 2304 // Disable fancy debug output 2305 qunsetenv("QT_MESSAGE_PATTERN"); 2306 2307 Okular::Settings::instance(QStringLiteral("okularparttest")); 2308 2309 QApplication app(argc, argv); 2310 app.setApplicationName(QStringLiteral("okularparttest")); 2311 app.setOrganizationDomain(QStringLiteral("kde.org")); 2312 app.setQuitOnLastWindowClosed(false); 2313 2314 qRegisterMetaType<QUrl>(); /*as done by kapplication*/ 2315 qRegisterMetaType<QList<QUrl>>(); 2316 2317 Okular::PartTest test; 2318 2319 return QTest::qExec(&test, argc, argv); 2320 } 2321 2322 #include "parttest.moc"