212 lines
9.9 KiB
C++
212 lines
9.9 KiB
C++
#include "AcceptanceFixtures.h"
|
|
#include "SelectionCanvasFixtures.h"
|
|
#include "UI/ColorPickerSheet.h"
|
|
#include "UI/ColorRangeSheet.h"
|
|
#include "UI/SampleButton.h"
|
|
#include <QCheckBox>
|
|
#include <QLabel>
|
|
#include <QPushButton>
|
|
#include <QSlider>
|
|
|
|
// Swift 1.3.4's Color Range: canvas and panel.
|
|
namespace {
|
|
// Red left, blue right, filling the canvas.
|
|
struct Ranged : Canvas {
|
|
Ranged()
|
|
{
|
|
QImage image(400, 300, QImage::Format_RGBA8888_Premultiplied);
|
|
image.fill(QColor(200, 30, 30));
|
|
for (int y = 0; y < 300; ++y)
|
|
for (int x = 200; x < 400; ++x)
|
|
image.setPixelColor(x, y, QColor(30, 40, 210));
|
|
session.insert(ImportedImage(image, image, QStringLiteral("Halves")));
|
|
session.beginColorRange();
|
|
canvas->synchronizeDisplay();
|
|
}
|
|
const ColorRangeEdit &edit() const { return session.colorRange().value(); }
|
|
};
|
|
|
|
QWidget *panel()
|
|
{
|
|
for (QWidget *widget : QApplication::topLevelWidgets())
|
|
if (widget->objectName() == QStringLiteral("colorRangePanel") && widget->isVisible())
|
|
return widget;
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
class ColorRangeCanvasTests : public QObject {
|
|
Q_OBJECT
|
|
private slots:
|
|
void initTestCase() { QStandardPaths::setTestModeEnabled(true); }
|
|
void aClickTakesTheColourShiftAddsAltTakesAway();
|
|
void theEyedropperSaysWhatAClickWillDo();
|
|
void theSheetDrivesTheEdit();
|
|
void thePanelFollowsTheEditAndTheMenu();
|
|
};
|
|
|
|
void ColorRangeCanvasTests::aClickTakesTheColourShiftAddsAltTakesAway()
|
|
{
|
|
Ranged shown;
|
|
shown.click(QPointF(50, 50));
|
|
QCOMPARE(shown.edit().include, (std::vector<uchar>{200, 30, 30}));
|
|
shown.click(QPointF(300, 50), Qt::ShiftModifier);
|
|
QCOMPARE(shown.edit().include, (std::vector<uchar>{200, 30, 30, 30, 40, 210}));
|
|
shown.click(QPointF(50, 50), Qt::AltModifier);
|
|
QCOMPARE(shown.edit().exclude, (std::vector<uchar>{200, 30, 30}));
|
|
// Alt with Shift takes away too, as Swift's order reads.
|
|
shown.click(QPointF(300, 50), Qt::AltModifier | Qt::ShiftModifier);
|
|
QCOMPARE(shown.edit().exclude, (std::vector<uchar>{200, 30, 30, 30, 40, 210}));
|
|
// With Space held the press pans instead.
|
|
const QSizeF pan = shown.session.viewport.pan;
|
|
QTest::keyPress(shown.canvas, Qt::Key_Space);
|
|
shown.drag(QPointF(100, 100), QPointF(110, 120));
|
|
QTest::keyRelease(shown.canvas, Qt::Key_Space);
|
|
QCOMPARE(shown.session.viewport.pan, pan + QSizeF(10, 20));
|
|
QCOMPARE(shown.edit().exclude.size(), size_t(6));
|
|
// The match lands on the canvas as the selection.
|
|
shown.click(QPointF(300, 50));
|
|
QTRY_VERIFY(shown.session.selection() && shown.coverage(300, 50) == 255);
|
|
QCOMPARE(shown.coverage(50, 50), 0);
|
|
// Under the Eyedropper the click is the range's alone.
|
|
shown.session.cancelColorRange();
|
|
shown.session.selectTool(NavigationTool::eyedropper);
|
|
shown.session.beginColorRange();
|
|
const PaletteColor foreground = shown.session.foregroundColor();
|
|
shown.click(QPointF(50, 50));
|
|
QVERIFY(shown.session.foregroundColor() == foreground && shown.edit().include == (std::vector<uchar>{200, 30, 30}));
|
|
}
|
|
|
|
void ColorRangeCanvasTests::theEyedropperSaysWhatAClickWillDo()
|
|
{
|
|
Ranged shown;
|
|
const double ratio = shown.canvas->devicePixelRatio();
|
|
const QCursor plain = CanvasView::eyedropperCursor(ratio), add = CanvasView::eyedropperCursor(ratio, HueSampleMode::add),
|
|
remove = CanvasView::eyedropperCursor(ratio, HueSampleMode::remove);
|
|
QVERIFY(shown.shows(plain));
|
|
// A disc bottom right: white, a black sign across.
|
|
const QImage badged = image(add);
|
|
const auto gray = [ratio](const QCursor &cursor, QPoint at) { return qGray(image(cursor).pixel(at * ratio)); };
|
|
QVERIFY(gray(add, QPoint(20, 16)) > 230 && gray(add, QPoint(18, 18)) < 80 && gray(remove, QPoint(18, 18)) < 80);
|
|
// A plus has its upright; a minus has none.
|
|
QVERIFY(gray(add, QPoint(18, 15)) < 80 && gray(remove, QPoint(18, 15)) > 230);
|
|
// The bar spans the disc; a black rim rings it.
|
|
QVERIFY(gray(add, QPoint(16, 18)) < 80 && gray(remove, QPoint(20, 18)) < 80 && gray(remove, QPoint(18, 23)) < 80);
|
|
QCOMPARE(image(plain).pixelColor(QPoint(20, 16) * ratio).alpha(), 0);
|
|
// Held keys show it; the chosen eyedropper too.
|
|
QTest::keyPress(shown.canvas, Qt::Key_Shift);
|
|
QCOMPARE(shown.edit().held, std::optional(HueSampleMode::add));
|
|
QVERIFY(shown.shows(add));
|
|
QTest::keyPress(shown.canvas, Qt::Key_Alt, Qt::ShiftModifier);
|
|
QCOMPARE(shown.edit().held, std::optional(HueSampleMode::remove));
|
|
QVERIFY(shown.shows(remove));
|
|
// QTest would let go of Shift too: Alt alone goes.
|
|
QKeyEvent alt(QEvent::KeyRelease, Qt::Key_Alt, Qt::ShiftModifier);
|
|
QApplication::sendEvent(shown.canvas, &alt);
|
|
QCOMPARE(shown.edit().held, std::optional(HueSampleMode::add));
|
|
QTest::keyRelease(shown.canvas, Qt::Key_Shift);
|
|
QCOMPARE(shown.edit().held, std::optional<HueSampleMode>());
|
|
QVERIFY(shown.shows(plain));
|
|
shown.session.setColorRangeSampleMode(HueSampleMode::remove);
|
|
shown.canvas->synchronizeDisplay();
|
|
QVERIFY(shown.shows(remove));
|
|
// Closed, the tool's own cursor returns.
|
|
shown.session.cancelColorRange();
|
|
shown.canvas->synchronizeDisplay();
|
|
QVERIFY(!shown.shows(plain) && !shown.shows(remove));
|
|
}
|
|
|
|
void ColorRangeCanvasTests::theSheetDrivesTheEdit()
|
|
{
|
|
Ranged shown;
|
|
ColorRangeSheet sheet(shown.session);
|
|
sheet.show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(&sheet));
|
|
const auto dropper = [&sheet](const char *mode) { return sheet.findChild<SampleButton *>(QStringLiteral("colorRange") + QString::fromLatin1(mode)); };
|
|
QVERIFY(dropper("Sample")->isChecked() && !dropper("Add")->isChecked());
|
|
QCOMPARE(dropper("Add")->toolTip(), QStringLiteral("Click the image to add that color to the selection"));
|
|
QCOMPARE(dropper("Remove")->accessibleName(), QStringLiteral("Remove color"));
|
|
dropper("Add")->click();
|
|
QVERIFY(shown.edit().sampleMode == HueSampleMode::add && dropper("Add")->isChecked() && !dropper("Sample")->isChecked());
|
|
// Held keys light the eyedropper a click will use.
|
|
shown.session.setColorRangeHeld(HueSampleMode::remove);
|
|
QVERIFY(dropper("Remove")->isChecked() && !dropper("Add")->isChecked());
|
|
shown.session.setColorRangeHeld(std::nullopt);
|
|
QLabel *caption = sheet.findChild<QLabel *>(QStringLiteral("colorRangeCaption"));
|
|
QCOMPARE(caption->text(), QStringLiteral("Click the image to pick the color to select."));
|
|
shown.session.sampleColorRange(QPointF(50, 50), false, false);
|
|
QCOMPARE(caption->text(), QStringLiteral("Shift-click adds a color, Alt-click takes one away."));
|
|
// Fuzziness: the slider, the field, the title's scrub.
|
|
QSlider *slider = sheet.findChild<QSlider *>(QStringLiteral("fuzzinessSlider"));
|
|
QVERIFY(slider->minimum() == 0 && slider->maximum() == 200 && slider->value() == 40);
|
|
slider->setValue(120);
|
|
QCOMPARE(shown.edit().fuzziness, 120.0);
|
|
PickerField *field = sheet.findChild<PickerField *>(QStringLiteral("fuzzinessField"));
|
|
QCOMPARE(field->text(), QStringLiteral("120"));
|
|
field->setFocus();
|
|
field->selectAll();
|
|
QTest::keyClicks(field, QStringLiteral("33.6"));
|
|
QTest::keyClick(field, Qt::Key_Return);
|
|
QVERIFY(shown.edit().fuzziness == 34 && slider->value() == 34);
|
|
QLabel *title = nullptr;
|
|
for (QLabel *label : sheet.findChildren<QLabel *>())
|
|
title = label->text() == QStringLiteral("Fuzziness") ? label : title;
|
|
QVERIFY(title && title->buddy() == slider);
|
|
QTest::mousePress(title, Qt::LeftButton, Qt::NoModifier, QPoint(2, 2));
|
|
QMouseEvent move(QEvent::MouseMove, QPointF(22, 2), title->mapToGlobal(QPointF(22, 2)), Qt::NoButton, Qt::LeftButton, Qt::NoModifier);
|
|
QApplication::sendEvent(title, &move);
|
|
QTest::mouseRelease(title, Qt::LeftButton, Qt::NoModifier, QPoint(22, 2));
|
|
QCOMPARE(shown.edit().fuzziness, 54.0);
|
|
// Invert, and an error shown in the warning colour.
|
|
QCheckBox *invert = sheet.findChild<QCheckBox *>(QStringLiteral("colorRangeInvert"));
|
|
invert->click();
|
|
QVERIFY(shown.edit().invert);
|
|
QLabel *error = sheet.findChild<QLabel *>(QStringLiteral("colorRangeError"));
|
|
QVERIFY(error->isHidden() && error->foregroundRole() == QPalette::BrightText);
|
|
// OK keeps the match as a step; the edit closes.
|
|
QTRY_VERIFY(!shown.edit().preview.isNull());
|
|
QPushButton *ok = nullptr;
|
|
for (QPushButton *button : sheet.findChildren<QPushButton *>())
|
|
ok = button->text() == QStringLiteral("OK") ? button : ok;
|
|
QVERIFY(ok && ok->isDefault());
|
|
ok->click();
|
|
QVERIFY(!shown.session.colorRange());
|
|
QCOMPARE(shown.session.history.undoName(), QStringLiteral("Color Range"));
|
|
}
|
|
|
|
void ColorRangeCanvasTests::thePanelFollowsTheEditAndTheMenu()
|
|
{
|
|
App app;
|
|
QVERIFY(scene(400, 300).save(app.path("scene.png")));
|
|
app.session().createDocument(400, 300);
|
|
app.importFile(app.path("scene.png"));
|
|
QAction &entry = app.action("colorRange");
|
|
QCOMPARE(entry.text(), QStringLiteral("Color Range…"));
|
|
QVERIFY(entry.isEnabled());
|
|
entry.trigger();
|
|
QVERIFY(app.session().colorRange());
|
|
QTRY_VERIFY(panel());
|
|
QCOMPARE(panel()->windowTitle(), QStringLiteral("Color Range"));
|
|
QVERIFY(panel()->findChild<ColorRangeSheet *>());
|
|
QVERIFY(!entry.isEnabled());
|
|
// Escape is Cancel: the panel and the edit go.
|
|
QTRY_COMPARE(QApplication::activeWindow(), panel());
|
|
QTest::keyClick(panel(), Qt::Key_Escape);
|
|
QTRY_VERIFY(!panel());
|
|
QVERIFY(!app.session().colorRange() && entry.isEnabled());
|
|
// Reopened: a canvas click samples; the panel keeps the keys.
|
|
entry.trigger();
|
|
QTRY_VERIFY(panel());
|
|
app.window.activateWindow();
|
|
QTRY_COMPARE(QApplication::activeWindow(), &app.window);
|
|
app.press(QPointF(200, 150));
|
|
app.release(QPointF(200, 150));
|
|
QVERIFY(app.session().colorRange().value().hasColors());
|
|
QTRY_COMPARE(QApplication::activeWindow(), panel());
|
|
app.session().commitColorRange();
|
|
QTRY_VERIFY(!panel());
|
|
}
|
|
|
|
QTEST_MAIN(ColorRangeCanvasTests)
|
|
#include "ColorRangeCanvasTests.moc"
|