Files
OmaPhoto/tests/NativeLayerListTests.cpp
T

496 lines
24 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "UI/NativeLayerList.h"
#include "SelectionFixtures.h"
#include <QAction>
#include <QMenu>
#include <QtTest>
// The layer rows: what they show, select, rename and key.
namespace {
ImportedImage filled(int width, int height, QRgb premultiplied, const QString &name)
{
QImage image(width, height, QImage::Format_RGBA8888_Premultiplied);
image.fill(QColor::fromRgba(premultiplied));
return ImportedImage(image, image, name);
}
std::unique_ptr<EditorSession> sessionWithThreeLayers()
{
auto session = std::make_unique<EditorSession>();
session->createDocument(800, 600);
for (int each = 0; each < 3; ++each)
session->addBlankLayer();
return session;
}
// A shown list in an active window.
struct Shown {
EditorSession &session;
NativeLayerList list;
explicit Shown(EditorSession &session) : session(session), list(session)
{
list.resize(252, 400);
list.show();
if (!QTest::qWaitForWindowActive(&list))
throw std::runtime_error("the list never became active");
}
LayerCell &row(int index) { return *list.cells().at(size_t(index)); }
QString name(int index) { return row(index).findChild<QLabel *>("layerName")->text(); }
QString dimensions(int index) { return row(index).findChild<QLabel *>("layerDimensions")->text(); }
// A click on the row's name, away from every control.
void click(int index, Qt::KeyboardModifiers modifiers = Qt::NoModifier)
{
QTest::mouseClick(&row(index), Qt::LeftButton, modifiers, QPoint(row(index).width() - 20, 20));
}
QSet<QUuid> selected() { return session.selectedLayerIDs(); }
QUuid id(int index) { return row(index).layerID(); }
};
}
class NativeLayerListTests : public QObject {
Q_OBJECT
private slots:
void rowsFollowTheHierarchyTopDown();
void nativeSelectionDoesNotReloadRowsAndReorderKeepsIdentity();
void clicksSelectExtendAndRangeAndTargetPixelsOverAMask();
void thumbnailsTargetTheLayerOrItsMaskAndShiftTogglesIt();
void theEyeTogglesAndSwipesInOneStep();
void disclosureLinkAndTheMenuReachTheSession();
void renamingTypesInTheRowAndReturnsFocus();
void layerListToolKeysAndTransformNudge();
void altClickOnTheStripClipsToTheLayerBelow();
void thumbnailsRedrawOnlyWhenTheirPictureChanges();
void ctrlClicksOnThumbnailsLoadSelections();
void deleteClearsASelectionOrDeletesTheLayer();
};
void NativeLayerListTests::rowsFollowTheHierarchyTopDown()
{
EditorSession session;
Shown shown(session);
QCOMPARE(shown.list.cells().size(), size_t(0));
session.createDocument(400, 200);
session.insert(filled(100, 50, qRgba(255, 0, 0, 255), "Red"));
session.addBlankLayer();
QCOMPARE(shown.list.cells().size(), size_t(2));
// The top of the document is the top row.
QCOMPARE(shown.name(0), QString("Layer 1"));
QCOMPARE(shown.name(1), QString("Red"));
QCOMPARE(shown.dimensions(0), QString("400 × 200 px"));
// A name shows as typed, never as markup.
QCOMPARE(shown.row(0).findChild<QLabel *>("layerName")->textFormat(), Qt::PlainText);
QCOMPARE(shown.row(0).findChild<QLabel *>("layerDimensions")->textFormat(), Qt::PlainText);
QCOMPARE(shown.dimensions(1), QString("100 × 50 px"));
// The canvas box, 36 wide, at ratio 2.
QCOMPARE(shown.row(1).thumbnail().icon().availableSizes(), QList<QSize>{QSize(72, 36)});
QVERIFY(!shown.row(1).maskThumbnail().isVisible());
QVERIFY(!shown.row(1).findChild<QToolButton *>("layerDisclosure")->isVisible());
// A folder: its child steps in; collapsed, the child goes.
session.selectLayer(shown.id(1));
session.groupSelectedLayers();
QCOMPARE(shown.list.cells().size(), size_t(3));
QCOMPARE(shown.dimensions(1), QString("Folder"));
QVERIFY(shown.row(1).findChild<QToolButton *>("layerDisclosure")->isVisible());
QCOMPARE(shown.name(2), QString("Red"));
const int stepped = shown.row(2).findChild<QToolButton *>("layerDisclosure")->x();
QCOMPARE(stepped - shown.row(1).findChild<QToolButton *>("layerDisclosure")->x(), 24);
session.toggleGroupExpansion(shown.id(1));
QCOMPARE(shown.list.cells().size(), size_t(2));
session.toggleGroupExpansion(shown.id(1));
// A clipped layer shows its mark and its source.
session.selectLayer(shown.id(0));
session.addBlankLayer();
QVERIFY(session.linkMask(shown.id(1), shown.id(0)));
QCOMPARE(shown.name(0), QString("↳ Layer 2"));
QCOMPARE(shown.dimensions(0), QString("Clipped to Layer 1"));
QCOMPARE(shown.row(0).thumbnail().x() - shown.row(1).thumbnail().x(), 24);
// A hidden layer fades; a mask shows thumbnail and link.
session.toggleLayerVisibility(shown.id(1));
QCOMPARE(shown.row(1).graphicsEffect()->property("opacity").toDouble(), 0.35);
QCOMPARE(shown.row(0).graphicsEffect()->property("opacity").toDouble(), 1.0);
session.selectLayer(shown.id(1));
session.addLayerMask();
QVERIFY(shown.row(1).maskThumbnail().isVisible());
QVERIFY(shown.row(1).findChild<QToolButton *>("maskLink")->isVisible());
QCOMPARE(shown.row(1).maskThumbnail().icon().availableSizes(), QList<QSize>{QSize(60, 30)});
session.toggleLayerMask();
QVERIFY(shown.row(1).findChild<QLabel *>("maskDisabledMark")->isVisible());
}
void NativeLayerListTests::nativeSelectionDoesNotReloadRowsAndReorderKeepsIdentity()
{
const auto session = sessionWithThreeLayers();
Shown shown(*session);
const std::vector<LayerCell *> cells = shown.list.cells();
const QUuid bottom = session->document().value().layers.front().id;
shown.click(2);
QCOMPARE(session->activeLayerID(), std::optional(bottom));
QCOMPARE(shown.list.cells(), cells);
QVERIFY(session->placeLayer(bottom, std::nullopt));
QCOMPARE(shown.id(0), bottom);
QVERIFY(session->selectedLayerIDs().contains(bottom));
QVERIFY(session->placeLayer(bottom, std::nullopt, std::nullopt, true));
QCOMPARE(shown.id(2), bottom);
QVERIFY(!session->placeLayer(QUuid::createUuid(), std::nullopt));
QVERIFY(!session->placeLayer(bottom, std::nullopt, QUuid::createUuid()));
session->setIsImporting(true);
QVERIFY(!session->placeLayer(bottom, std::nullopt));
}
void NativeLayerListTests::clicksSelectExtendAndRangeAndTargetPixelsOverAMask()
{
const auto session = sessionWithThreeLayers();
Shown shown(*session);
shown.click(0);
QCOMPARE(shown.selected(), QSet<QUuid>{shown.id(0)});
QVERIFY(shown.list.hasFocus());
shown.click(2, Qt::ControlModifier);
QCOMPARE(shown.selected(), (QSet<QUuid>{shown.id(0), shown.id(2)}));
shown.click(2, Qt::ControlModifier);
QCOMPARE(shown.selected(), QSet<QUuid>{shown.id(0)});
shown.click(2, Qt::ShiftModifier);
QCOMPARE(shown.selected(), (QSet<QUuid>{shown.id(0), shown.id(1), shown.id(2)}));
QCOMPARE(session->activeLayerID(), std::optional(shown.id(2)));
// A plain click inside a multi-selection keeps it.
shown.click(1);
QCOMPARE(shown.selected().size(), 3);
shown.click(1, Qt::ControlModifier);
QCOMPARE(shown.selected().size(), 2);
shown.click(1);
QCOMPARE(shown.selected(), QSet<QUuid>{shown.id(1)});
// With the mask targeted, the name takes the layer back.
session->addLayerMask();
QVERIFY(session->isMaskSelected());
shown.click(1);
QVERIFY(!session->isMaskSelected());
QCOMPARE(shown.selected(), QSet<QUuid>{shown.id(1)});
// Inside a multi-selection the mask target stays put.
QTest::mouseClick(&shown.row(1).maskThumbnail(), Qt::LeftButton);
session->selectLayers({shown.id(1), shown.id(2)}, shown.id(1));
QVERIFY(session->isMaskSelected());
shown.click(1);
QVERIFY(session->isMaskSelected());
QCOMPARE(shown.selected(), (QSet<QUuid>{shown.id(1), shown.id(2)}));
}
void NativeLayerListTests::thumbnailsTargetTheLayerOrItsMaskAndShiftTogglesIt()
{
const auto session = sessionWithThreeLayers();
Shown shown(*session);
shown.click(0);
session->addLayerMask();
QVERIFY(session->isMaskSelected());
QTest::mouseClick(&shown.row(0).thumbnail(), Qt::LeftButton);
QVERIFY(!session->isMaskSelected());
QCOMPARE(shown.selected(), QSet<QUuid>{shown.id(0)});
QTest::mouseClick(&shown.row(0).maskThumbnail(), Qt::LeftButton);
QVERIFY(session->isMaskSelected());
QVERIFY(session->activeLayer().value().mask.value().isEnabled);
QTest::mouseClick(&shown.row(0).maskThumbnail(), Qt::LeftButton, Qt::ShiftModifier);
QVERIFY(!session->activeLayer().value().mask.value().isEnabled && session->isMaskSelected());
QVERIFY(shown.row(0).maskThumbnail().isTargeted() && !shown.row(0).thumbnail().isTargeted());
// Shift on the layer thumbnail ranges the rows, targets nothing.
QTest::mouseClick(&shown.row(2).thumbnail(), Qt::LeftButton, Qt::ShiftModifier);
QCOMPARE(shown.selected(), (QSet<QUuid>{shown.id(0), shown.id(1), shown.id(2)}));
QVERIFY(!layerWith(*session, shown.id(0)).mask.value().isEnabled);
QCOMPARE(session->activeLayerID(), std::optional(shown.id(2)));
// Alt on a thumbnail is the row's; the target stays.
session->selectLayers({shown.id(0)}, shown.id(0));
QTest::mouseClick(&shown.row(0).maskThumbnail(), Qt::LeftButton);
QTest::mouseClick(&shown.row(0).thumbnail(), Qt::LeftButton, Qt::AltModifier);
QVERIFY(session->isMaskSelected());
// Another row's thumbnail selects that row; Ctrl selects none.
QTest::mouseClick(&shown.row(0).thumbnail(), Qt::LeftButton);
QTest::mouseClick(&shown.row(2).thumbnail(), Qt::LeftButton);
QCOMPARE(shown.selected(), QSet<QUuid>{shown.id(2)});
QVERIFY(!session->isMaskSelected());
QVERIFY(shown.row(2).thumbnail().isTargeted() && !shown.row(0).thumbnail().isTargeted());
// A multi-selection shows no ring.
session->extendSelection(shown.id(0));
QVERIFY(!shown.row(2).thumbnail().isTargeted() && !shown.row(0).thumbnail().isTargeted());
session->selectLayers({shown.id(2)}, shown.id(2));
QTest::mouseClick(&shown.row(0).thumbnail(), Qt::LeftButton, Qt::ControlModifier);
QCOMPARE(shown.selected(), QSet<QUuid>{shown.id(2)});
// Importing, thumbnails answer no click.
session->setIsImporting(true);
QVERIFY(!shown.row(0).thumbnail().isEnabled() && !shown.row(0).maskThumbnail().isEnabled());
QTest::mouseClick(&shown.row(0).thumbnail(), Qt::LeftButton);
QCOMPARE(shown.selected(), QSet<QUuid>{shown.id(2)});
}
void NativeLayerListTests::theEyeTogglesAndSwipesInOneStep()
{
const auto session = sessionWithThreeLayers();
Shown shown(*session);
auto *eye = shown.row(0).findChild<EyeSwipeButton *>("layerEye");
QCOMPARE(eye->accessibleName(), QString("Hide Layer 3"));
QTest::mouseClick(eye, Qt::LeftButton);
QVERIFY(!session->document().value().layers.back().isVisible);
QCOMPARE(eye->accessibleName(), QString("Show Layer 3"));
QCOMPARE(session->history.undoName(), QString("Hide Layer"));
// Dragged down a row: the eyes passed change, no other.
session->toggleLayerVisibility(shown.id(1));
session->toggleLayerVisibility(shown.id(2));
const int steps = session->history.undoCount();
const QPoint end(10, 16 + LayerCell::rowHeight);
QTest::mousePress(eye, Qt::LeftButton, Qt::NoModifier, QPoint(10, 16));
QMouseEvent move(QEvent::MouseMove, QPointF(end), eye->mapToGlobal(end), Qt::NoButton, Qt::LeftButton, Qt::NoModifier);
QApplication::sendEvent(eye, &move);
QTest::mouseRelease(eye, Qt::LeftButton, Qt::NoModifier, end);
QVERIFY(layerWith(*session, shown.id(0)).isVisible && layerWith(*session, shown.id(1)).isVisible);
QVERIFY(!layerWith(*session, shown.id(2)).isVisible);
QCOMPARE(session->history.undoCount(), steps + 1);
session->undo();
QVERIFY(!layerWith(*session, shown.id(0)).isVisible && !layerWith(*session, shown.id(1)).isVisible);
// Busy, the eye rests once the indicator shows.
session->setIsProjectBusy(true);
QTRY_VERIFY(session->showsBusy() && !eye->isEnabled());
}
void NativeLayerListTests::disclosureLinkAndTheMenuReachTheSession()
{
EditorSession session;
session.createDocument(20, 10);
session.insert(filled(4, 4, qRgba(0, 0, 255, 255), "Blue"));
session.insert(filled(4, 4, qRgba(0, 255, 0, 255), "Green"));
session.groupSelectedLayers();
Shown shown(session);
const QUuid folder = shown.id(0), green = shown.id(1), blue = shown.id(2);
QTest::mouseClick(shown.row(0).findChild<QToolButton *>("layerDisclosure"), Qt::LeftButton);
QVERIFY(session.collapsedGroupIDs().contains(folder));
QCOMPARE(shown.list.cells().size(), size_t(2));
QTest::mouseClick(shown.row(0).findChild<QToolButton *>("layerDisclosure"), Qt::LeftButton);
QCOMPARE(shown.list.cells().size(), size_t(3));
// The green row's menu acts on green, whatever is active.
session.selectLayer(blue);
const auto trigger = [&](int row, const char *name) {
LayerCell &cell = shown.row(row);
shown.list.menuFor(cell, QPoint(cell.width() - 20, 20))->findChild<QAction *>(QString::fromLatin1(name))->trigger();
};
trigger(1, "addBlackMask");
QVERIFY(layerWith(session, green).mask.has_value());
QCOMPARE(layerWith(session, green).mask.value().asset.image().pixelColor(0, 0), QColor(0, 0, 0));
QCOMPARE(session.activeLayerID(), std::optional(green));
QTest::mouseClick(shown.row(1).findChild<QToolButton *>("maskLink"), Qt::LeftButton);
QVERIFY(!layerWith(session, green).mask.value().isLinked);
trigger(1, "toggleMask");
QVERIFY(!layerWith(session, green).mask.value().isEnabled);
trigger(1, "deleteMask");
QVERIFY(!layerWith(session, green).mask.has_value());
trigger(1, "addWhiteMask");
QCOMPARE(layerWith(session, green).mask.value().asset.image().pixelColor(0, 0), QColor(255, 255, 255));
trigger(1, "layerVisibility");
QVERIFY(!layerWith(session, green).isVisible);
QCOMPARE(layerWith(session, green).parentID, std::optional(folder));
trigger(1, "moveOut");
QVERIFY(!layerWith(session, green).parentID.has_value());
const auto rowOf = [&](QUuid id) {
for (int row = 0; row < 3; ++row) {
if (shown.id(row) == id)
return row;
}
throw std::runtime_error("no row");
};
trigger(rowOf(folder), "renameLayer");
QCOMPARE(session.renamingLayerID(), std::optional(folder));
session.setRenamingLayerID(std::nullopt);
// Delete takes the row's layer, or its whole selection.
session.selectLayers({green, blue}, blue);
trigger(rowOf(green), "deleteLayer");
QCOMPARE(session.document().value().layers.size(), size_t(1));
trigger(0, "deleteLayer");
QVERIFY(session.document().value().layers.empty());
}
void NativeLayerListTests::renamingTypesInTheRowAndReturnsFocus()
{
const auto session = sessionWithThreeLayers();
Shown shown(*session);
shown.click(1);
QTest::mouseDClick(&shown.row(1), Qt::LeftButton, Qt::NoModifier, QPoint(shown.row(1).width() - 20, 20));
QCOMPARE(session->renamingLayerID(), std::optional(shown.id(1)));
auto *editor = shown.row(1).findChild<QLineEdit *>("layerNameEditor");
QTRY_VERIFY(editor->isVisible() && editor->hasFocus());
QCOMPARE(editor->selectedText(), QString("Layer 2"));
QTest::keyClicks(editor, "Sky");
QTest::keyClick(editor, Qt::Key_Return);
QCOMPARE(layerWith(*session, shown.id(1)).name, QString("Sky"));
QVERIFY(!session->renamingLayerID().has_value());
QVERIFY(!editor->isVisible() && shown.list.hasFocus());
QCOMPARE(shown.name(1), QString("Sky"));
// Escape keeps the old name; leaving keeps what was typed.
session->setRenamingLayerID(shown.id(1));
QTRY_VERIFY(editor->isVisible());
QTest::keyClicks(editor, "Nope");
QTest::keyClick(editor, Qt::Key_Escape);
QCOMPARE(layerWith(*session, shown.id(1)).name, QString("Sky"));
QVERIFY(!session->renamingLayerID().has_value());
session->setRenamingLayerID(shown.id(1));
QTRY_VERIFY(editor->isVisible());
editor->selectAll();
QTest::keyClicks(editor, "Sea");
shown.list.setFocus();
QCOMPARE(layerWith(*session, shown.id(1)).name, QString("Sea"));
// A pending rename closes the edits: nothing moves under it.
session->setRenamingLayerID(shown.id(1));
QTRY_VERIFY(editor->isVisible());
QVERIFY(!session->placeLayer(shown.id(1), std::nullopt));
QVERIFY(!session->canEditLayers());
QTest::keyClick(editor, Qt::Key_Escape);
// A pixel thumbnail's double click renames; busy, nothing.
QTest::mouseDClick(&shown.row(1).thumbnail(), Qt::LeftButton);
QCOMPARE(session->renamingLayerID(), std::optional(shown.id(1)));
QTest::keyClick(editor, Qt::Key_Escape);
session->setIsProjectBusy(true);
session->setRenamingLayerID(shown.id(1));
QVERIFY(!editor->isVisible());
}
void NativeLayerListTests::layerListToolKeysAndTransformNudge()
{
EditorSession session;
session.createDocument(100, 100);
session.insert(filled(10, 10, qRgba(255, 0, 0, 255), "Red"));
Shown shown(session);
shown.list.setFocus();
QTest::keyClick(&shown.list, Qt::Key_H);
QCOMPARE(session.tool(), NavigationTool::hand);
// T chooses Type, since Swift 1.1.6.
QTest::keyClick(&shown.list, Qt::Key_T);
QCOMPARE(session.tool(), NavigationTool::type);
QTest::keyClick(&shown.list, Qt::Key_V);
QCOMPARE(session.tool(), NavigationTool::move);
session.beginTransform();
QTest::keyClick(&shown.list, Qt::Key_Right);
QCOMPARE(session.transformEdit().value().draft.origin.x(), 46.0);
QCOMPARE(session.activeLayer().value().origin().x(), 45.0);
QTest::keyClick(&shown.list, Qt::Key_Escape);
QVERIFY(!session.transformEdit().has_value());
QCOMPARE(session.activeLayer().value().origin().x(), 45.0);
QTest::keyClick(&shown.list, Qt::Key_Down, Qt::ShiftModifier);
QCOMPARE(session.activeLayer().value().origin().y(), 55.0);
QTest::keyClick(&shown.list, Qt::Key_Return);
QVERIFY(!session.transformEdit().has_value());
QCOMPARE(session.activeLayer().value().origin().y(), 55.0);
// Ctrl-letters are the menu's; other tools' arrows walk rows.
QTest::keyClick(&shown.list, Qt::Key_H, Qt::ControlModifier);
QCOMPARE(session.tool(), NavigationTool::move);
session.addBlankLayer();
session.selectTool(NavigationTool::brush);
QCOMPARE(session.activeLayerID(), std::optional(shown.id(0)));
QTest::keyClick(&shown.list, Qt::Key_Down);
QCOMPARE(session.activeLayerID(), std::optional(shown.id(1)));
QTest::keyClick(&shown.list, Qt::Key_Down);
QCOMPARE(session.activeLayerID(), std::optional(shown.id(1)));
QTest::keyClick(&shown.list, Qt::Key_Up);
QCOMPARE(session.activeLayerID(), std::optional(shown.id(0)));
QTest::keyClick(&shown.list, Qt::Key_E);
QCOMPARE(session.tool(), NavigationTool::brush);
}
void NativeLayerListTests::altClickOnTheStripClipsToTheLayerBelow()
{
const auto session = sessionWithThreeLayers();
Shown shown(*session);
const QPoint strip(shown.row(0).width() - 20, LayerCell::rowHeight - 4);
QTest::mouseClick(&shown.row(0), Qt::LeftButton, Qt::AltModifier, strip);
QCOMPARE(layerWith(*session, shown.id(0)).maskSourceID, std::optional(shown.id(1)));
QCOMPARE(shown.name(0), QString("↳ Layer 3"));
QTest::mouseClick(&shown.row(0), Qt::LeftButton, Qt::AltModifier, strip);
QVERIFY(!layerWith(*session, shown.id(0)).maskSourceID.has_value());
// Above the strip Alt selects plainly; Ctrl-Alt too.
QTest::mouseClick(&shown.row(2), Qt::LeftButton, Qt::AltModifier, QPoint(shown.row(2).width() - 20, 20));
QCOMPARE(shown.selected(), QSet<QUuid>{shown.id(2)});
QVERIFY(!layerWith(*session, shown.id(2)).maskSourceID.has_value());
QTest::mouseClick(&shown.row(0), Qt::LeftButton, Qt::AltModifier, QPoint(shown.row(0).width() - 20, LayerCell::rowHeight - 14));
QCOMPARE(shown.selected(), QSet<QUuid>{shown.id(0)});
QVERIFY(!layerWith(*session, shown.id(0)).maskSourceID.has_value());
QTest::mouseClick(&shown.row(0), Qt::LeftButton, Qt::AltModifier | Qt::ControlModifier, strip);
QVERIFY(!layerWith(*session, shown.id(0)).maskSourceID.has_value());
}
void NativeLayerListTests::thumbnailsRedrawOnlyWhenTheirPictureChanges()
{
EditorSession session;
session.createDocument(40, 20);
session.insert(filled(10, 10, qRgba(255, 0, 0, 255), "Red"));
Shown shown(session);
const qint64 before = shown.row(0).thumbnail().icon().cacheKey();
session.renameLayer(shown.id(0), "Rose");
session.selectLayer(shown.id(0));
session.setLayerOpacity(0.5);
QCOMPARE(shown.row(0).thumbnail().icon().cacheKey(), before);
session.nudgeLayer(1, 0);
QVERIFY(shown.row(0).thumbnail().icon().cacheKey() != before);
const qint64 moved = shown.row(0).thumbnail().icon().cacheKey();
session.addLayerMask();
QCOMPARE(shown.row(0).thumbnail().icon().cacheKey(), moved);
const qint64 mask = shown.row(0).maskThumbnail().icon().cacheKey();
session.toggleLayerMask();
QCOMPARE(shown.row(0).maskThumbnail().icon().cacheKey(), mask);
}
void NativeLayerListTests::deleteClearsASelectionOrDeletesTheLayer()
{
const auto session = sessionWithThreeLayers();
QImage red(8, 8, QImage::Format_RGBA8888_Premultiplied);
red.fill(Qt::red);
session->insert(ImportedImage(red, red, "Red"));
Shown shown(*session);
shown.list.setFocus();
// The red sits mid-canvas; the selection takes its left half.
const QPointF origin = session->activeLayer().value().transform.origin;
session->applySelection(rectPath(QRectF(origin, QSizeF(4, 8))), SelectionMode::replace, "Select");
QTest::keyClick(&shown.list, Qt::Key_Delete);
QTRY_COMPARE(session->history.undoName(), QString("Clear"));
session->deselect();
const int layers = int(session->document().value().layers.size());
QTest::keyClick(&shown.list, Qt::Key_Backspace);
QCOMPARE(int(session->document().value().layers.size()), layers - 1);
// With Ctrl the key is not Delete's.
QTest::keyClick(&shown.list, Qt::Key_Delete, Qt::ControlModifier);
QCOMPARE(int(session->document().value().layers.size()), layers - 1);
}
QTEST_MAIN(NativeLayerListTests)
#include "NativeLayerListTests.moc"
void NativeLayerListTests::ctrlClicksOnThumbnailsLoadSelections()
{
EditorSession session;
session.createDocument(400, 200);
session.insert(filled(100, 50, qRgba(255, 0, 0, 255), "Red"), QPointF(100, 100));
const QUuid red = session.activeLayerID().value();
QImage block(100, 50, QImage::Format_Grayscale8);
block.fill(Qt::white);
for (int y = 10; y < 30; ++y)
std::fill_n(block.scanLine(y) + 10, 20, uchar(0));
rewrite(session, [&](ProjectSnapshot &snapshot) { setMask(snapshot, red, LayerMask::assetFrom(block)); });
session.selectLayerTarget(red, false);
Shown shown(session);
LayerCell &row = shown.row(0);
QCOMPARE(row.thumbnail().toolTip(), QString("Select image pixels"));
QCOMPARE(row.maskThumbnail().toolTip(), QString("Select layer mask; Alt-click to view it alone; Shift-click to enable/disable; Ctrl-click to select its black areas (Ctrl-Shift adds, Ctrl-Alt subtracts)"));
// Ctrl on the picture: its opaque pixels, the row untouched.
QTest::mouseClick(&row.thumbnail(), Qt::LeftButton, Qt::ControlModifier);
QCOMPARE(session.history.undoName(), QString("Load Layer Selection"));
QCOMPARE(session.selection().value().path.boundingRect(), QRectF(50, 75, 100, 50));
QVERIFY(!session.isMaskSelected() && row.thumbnail().isTargeted());
// Ctrl-Alt on the mask subtracts; Ctrl-Shift adds back.
QTest::mouseClick(&row.maskThumbnail(), Qt::LeftButton, Qt::ControlModifier | Qt::AltModifier);
QCOMPARE(session.history.undoName(), QString("Load Mask Selection"));
QCOMPARE(int(session.selection().value().coverage(400, 200).constScanLine(90)[70]), 0);
QCOMPARE(int(session.selection().value().coverage(400, 200).constScanLine(90)[120]), 255);
QVERIFY(!session.isMaskSelected());
session.applySelection(rectPath(QRectF(0, 0, 10, 10)), SelectionMode::replace, "Select");
QTest::mouseClick(&row.maskThumbnail(), Qt::LeftButton, Qt::ControlModifier | Qt::ShiftModifier);
QCOMPARE(session.selection().value().path.boundingRect(), QRectF(0, 0, 80, 105));
// Plain Ctrl replaces; a Ctrl double click loads twice.
session.selectAll();
QTest::mouseClick(&row.maskThumbnail(), Qt::LeftButton, Qt::ControlModifier);
QCOMPARE(session.selection().value().path.boundingRect(), QRectF(60, 85, 20, 20));
const int count = session.history.undoCount();
QTest::mouseDClick(&row.thumbnail(), Qt::LeftButton, Qt::ControlModifier);
QCOMPARE(session.history.undoCount(), count + 1);
QVERIFY(!row.isRenaming());
}