#include "ContentView.h" #include "MenuFixtures.h" #include "Document/DocumentLimits.h" #include // Filter › Last Filter: the last filter again, same settings. namespace { // Swift's fixture: 40 by 20, the left half white. std::unique_ptr halfWhite() { auto session = std::make_unique(); session->createDocument(40, 20); QImage image = BrushRaster::context(40, 20, false); QPainter(&image).fillRect(QRect(0, 0, 20, 20), Qt::white); session->insert(ImportedImage(image, image, QStringLiteral("Half"))); return session; } bool committed(EditorSession &session) { bool done = false; session.commitFilter([&done] { done = true; }); return QTest::qWaitFor([&done] { return done; }, 20000); } bool repeated(EditorSession &session) { bool done = false; session.repeatLastFilter([&done] { done = true; }); return QTest::qWaitFor([&done] { return done; }, 20000); } ImageLayer active(const EditorSession &session) { return session.activeLayer().value(); } } class LastFilterTests : public QObject { Q_OBJECT private slots: void runsTheLastFilterAgainWithItsSettingsAsOneUndoStep(); void aCancelledFilterOrAnImageAdjustmentIsNotTheLastFilter(); void theGatesAndAPendingGradient(); void anAutomaticFilterRunsAgainThroughItsPreview(); void theMenuNamesItAndRunsWithoutThePanel(); void aRepeatThatCannotBeginOrLandLeavesNothingOpen(); void aNewerEditOutlastsTheRepeat(); }; void LastFilterTests::runsTheLastFilterAgainWithItsSettingsAsOneUndoStep() { const auto session = halfWhite(); QVERIFY(!session->canRepeatLastFilter() && !session->lastFilter()); session->beginFilter(FilterKind::gaussianBlur); FilterSettings settings; settings.radius = 3; session->updateFilter(settings, true); QVERIFY(committed(*session)); const ImageLayer once = active(*session); QVERIFY(session->lastFilter() == FilterKind::gaussianBlur && session->canRepeatLastFilter()); const int count = session->history.undoCount(); QVERIFY(repeated(*session)); const ImageLayer twice = active(*session); QVERIFY(!session->filterEdit() && session->history.undoCount() == count + 1 && session->history.undoName() == "Gaussian Blur"); QVERIFY(twice.asset.value().identity() != once.asset.value().identity()); // Blurred by 3 again, it spreads past the edge further. QVERIFY(twice.transform.size.width() > once.transform.size.width()); QCOMPARE(session->filterSettings().radius, 3.0); session->undo(); QVERIFY(active(*session).asset.value().identity() == once.asset.value().identity()); } void LastFilterTests::aCancelledFilterOrAnImageAdjustmentIsNotTheLastFilter() { const auto session = halfWhite(); session->beginFilter(FilterKind::addNoise); QVERIFY(committed(*session)); session->beginFilter(FilterKind::gaussianBlur); session->cancelFilter(); session->beginFilter(FilterKind::curves); session->cancelFilter(); QCOMPARE(session->lastFilter(), std::optional(FilterKind::addNoise)); // An Image menu adjustment committed is not recorded either. session->beginFilter(FilterKind::exposure); FilterSettings brighter = session->filterSettings(); brighter.exposure.exposure = 1; session->updateFilter(brighter, true); QVERIFY(committed(*session)); QCOMPARE(session->lastFilter(), std::optional(FilterKind::addNoise)); // Nothing to do: no step, and nothing left open. QSignalSpy changes(session.get(), &EditorSession::changed); session->setLastFilter(FilterKind::lensCorrection); QCOMPARE(changes.count(), 1); const int count = session->history.undoCount(); QVERIFY(repeated(*session)); QVERIFY(!session->filterEdit() && session->history.undoCount() == count && !session->isProjectBusy()); QVERIFY(!repeatsAsLastFilter(FilterKind::contentAwareFill) && !repeatsAsLastFilter(FilterKind::grain) && repeatsAsLastFilter(FilterKind::dither)); } void LastFilterTests::theGatesAndAPendingGradient() { const auto session = halfWhite(); session->setLastFilter(FilterKind::addNoise); QVERIFY(session->canRepeatLastFilter()); session->beginFilter(FilterKind::gaussianBlur); QVERIFY(!session->canRepeatLastFilter()); // A refused run warns and calls back, changing nothing. QTest::ignoreMessage(QtWarningMsg, "no last filter to run here"); QVERIFY(repeated(*session)); QCOMPARE(session->filterEdit().value().kind, FilterKind::gaussianBlur); session->cancelFilter(); session->beginHueSaturation(); QVERIFY(session->hueSaturation() && !session->canRepeatLastFilter()); session->cancelHueSaturation(); // Vignette asks Vignette's gate: an empty layer takes it. session->addBlankLayer(); QVERIFY(!session->canRepeatLastFilter()); session->setLastFilter(FilterKind::vignette); QVERIFY(session->canRepeatLastFilter()); // A pending gradient lands first, then the filter: two steps. session->setLastFilter(FilterKind::addNoise); session->selectLayer(session->document().value().layers.front().id); session->selectTool(NavigationTool::gradient); session->beginGradient(QPointF(2, 2)); session->moveGradient(std::nullopt, QPointF(30, 2)); session->endGradientDrag(); QVERIFY(session->gradientEdit()); const int count = session->history.undoCount(); QVERIFY(repeated(*session)); QVERIFY(!session->gradientEdit() && session->history.undoCount() == count + 2 && session->history.undoName() == "Add Noise"); } void LastFilterTests::anAutomaticFilterRunsAgainThroughItsPreview() { // Remove Background commits what its preview made. auto session = std::make_unique(); session->createDocument(400, 300); QImage image = BrushRaster::context(400, 300, false); image.fill(Qt::white); QPainter painter(&image); painter.setPen(Qt::NoPen); painter.setBrush(QColor(217, 26, 26)); painter.drawEllipse(QPointF(200, 150), 80, 80); painter.end(); session->insert(ImportedImage(image, image, QStringLiteral("Disc"))); session->setLastFilter(FilterKind::removeBackground); const int count = session->history.undoCount(); QVERIFY(repeated(*session)); QVERIFY(session->history.undoCount() == count + 1 && session->history.undoName() == "Remove Background"); QVERIFY(active(*session).mask && !session->filterEdit()); } void LastFilterTests::theMenuNamesItAndRunsWithoutThePanel() { Bar bar; bar.window.show(); QVERIFY(QTest::qWaitForWindowActive(&bar.window)); QAction &entry = bar.action("lastFilter"); QVERIFY(entry.text() == "Last Filter" && !entry.isEnabled()); QCOMPARE(entry.shortcut(), QKeySequence(Qt::CTRL | Qt::META | Qt::Key_F)); bar.session().createDocument(40, 20); bar.session().insert(white(), QPointF(20, 10)); bar.session().beginFilter(FilterKind::addNoise); QTRY_VERIFY(bar.window.findChild("filterPanel") && bar.window.findChild("filterPanel")->isVisible()); QVERIFY(committed(bar.session())); QTRY_VERIFY(!bar.window.findChild("filterPanel")->isVisible()); QVERIFY(entry.text() == "Last Filter: Add Noise" && entry.isEnabled()); // Its key runs it at once; the panel never shows. bool panelShown = false; QObject::connect(&bar.session(), &EditorSession::changed, &bar.window, [&] { panelShown = panelShown || bar.window.findChild("filterPanel")->isVisible(); }); const int count = bar.session().history.undoCount(); QTest::keyClick(&bar.window, Qt::Key_F, Qt::ControlModifier | Qt::MetaModifier); QTRY_COMPARE(bar.session().history.undoCount(), count + 1); QTRY_VERIFY(!bar.session().isProjectBusy()); QVERIFY(!panelShown && !bar.session().filterEdit()); // While a filter is open it rests. bar.session().beginFilter(FilterKind::gaussianBlur); QVERIFY(!entry.isEnabled()); bar.session().cancelFilter(); QVERIFY(entry.isEnabled()); } void LastFilterTests::aRepeatThatCannotBeginOrLandLeavesNothingOpen() { // A repeat begins without a preview, unless automatic. const auto session = halfWhite(); session->beginFilter(FilterKind::gaussianBlur, true); QVERIFY(session->filterEdit().value().repeating && !session->filterEdit().value().preview); session->cancelFilter(); session->beginFilter(FilterKind::removeBackground, true); QVERIFY(session->filterEdit().value().repeating && session->filterEdit().value().preview); session->cancelFilter(); // No subject: the preview fails, and the repeat closes it. session->setLastFilter(FilterKind::removeBackground); const int count = session->history.undoCount(); QVERIFY(repeated(*session)); QVERIFY(!session->filterEdit() && session->history.undoCount() == count && !session->isProjectBusy()); // A blur that cannot grow begins nothing: an error. EditorSession wide; wide.createDocument(100, 10); QImage line = BrushRaster::context(DocumentLimits::maxSide - 4, 1, false); line.fill(Qt::white); wide.insert(ImportedImage(line, QImage(), QStringLiteral("Line"))); wide.setLastFilter(FilterKind::gaussianBlur); QVERIFY(repeated(wide)); QVERIFY(!wide.filterEdit() && wide.brushError() && !wide.isProjectBusy()); } void LastFilterTests::aNewerEditOutlastsTheRepeat() { // A filter opened as the repeat lands stays open. const auto session = halfWhite(); session->setLastFilter(FilterKind::gaussianBlur); const int count = session->history.undoCount(); bool queued = false; QObject::connect(session.get(), &EditorSession::changed, session.get(), [&] { if (!queued && session->history.undoCount() == count + 1 && !session->isProjectBusy() && !session->filterEdit()) { queued = true; QMetaObject::invokeMethod(session.get(), [&] { session->beginFilter(FilterKind::addNoise); }, Qt::QueuedConnection); } }); QVERIFY(repeated(*session)); QVERIFY(queued); QCOMPARE(session->filterEdit().value().kind, FilterKind::addNoise); QVERIFY(!session->filterEdit().value().repeating); } QTEST_MAIN(LastFilterTests) #include "LastFilterTests.moc"