14.9: Filter › Last Filter on Ctrl+Meta+F
This commit is contained in:
14 files changed
+306
-9
No files matched your search
@@ -0,0 +1,212 @@
|
||||
#include "ContentView.h"
|
||||
#include "MenuFixtures.h"
|
||||
#include "Document/DocumentLimits.h"
|
||||
#include <QPainter>
|
||||
|
||||
// Filter › Last Filter: the last filter again, same settings.
|
||||
namespace {
|
||||
// Swift's fixture: 40 by 20, the left half white.
|
||||
std::unique_ptr<EditorSession> halfWhite()
|
||||
{
|
||||
auto session = std::make_unique<EditorSession>();
|
||||
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 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->identity() != once.asset->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->identity() == once.asset->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()->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()->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<EditorSession>();
|
||||
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<QWidget *>("filterPanel") && bar.window.findChild<QWidget *>("filterPanel")->isVisible());
|
||||
QVERIFY(committed(bar.session()));
|
||||
QTRY_VERIFY(!bar.window.findChild<QWidget *>("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<QWidget *>("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()->repeating && !session->filterEdit()->preview);
|
||||
session->cancelFilter();
|
||||
session->beginFilter(FilterKind::removeBackground, true);
|
||||
QVERIFY(session->filterEdit()->repeating && session->filterEdit()->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());
|
||||
}
|
||||
|
||||
QTEST_MAIN(LastFilterTests)
|
||||
#include "LastFilterTests.moc"
|
||||
Reference in new issue
Block a user