OmaPhoto 1.0.0: the Linux port of Compositor
Release / deb (push) Canceled after 0s
Release / rpm (push) Canceled after 0s
Release / arch (push) Canceled after 0s
Release / publish (push) Canceled after 0s

A layered image editor for Linux and Omarchy, ported from Compositor
(github.com/robbietilton/Compositor), whose Swift source is its
specification. It reads and writes Compositor's .comp projects.

Packages for Ubuntu, Fedora and Arch come from the release workflow;
scripts/install/ holds one install script per distribution family,
and flake.nix serves NixOS.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
雷电芽衣andClaude committed 2026-09-26 00:44:52 -04:00
commit c864304695
564 files changed
+100804

No files matched your search

+230
View File
@@ -0,0 +1,230 @@
#include "UI/NavigationToolHeader.h"
#include <QtTest>
// The Hand and Zoom tools' bar, and Zoom's percentage field.
namespace {
struct Bar {
EditorSession session;
std::unique_ptr<NavigationToolHeader> header;
QLineEdit *field = nullptr;
explicit Bar(bool withCanvas = true)
{
if (withCanvas)
session.createDocument(800, 600);
session.selectTool(NavigationTool::zoom);
header = std::make_unique<NavigationToolHeader>(session);
field = header->findChild<QLineEdit *>("zoomPercentage");
header->show();
// Focus is real only in an active window.
header->activateWindow();
active = QTest::qWaitForWindowActive(header.get());
}
bool active = false;
void type(const QString &text)
{
field->setFocus();
field->selectAll();
QTest::keyClicks(field, text);
}
};
}
class NavigationToolHeaderTests : public QObject {
Q_OBJECT
private slots:
void theTitleAndTheFieldFollowTheTool();
void theFieldShowsTheZoomWithoutIdleZeros();
void returnEscapeAndLeavingAllApplyWhatWasTyped();
void whatIsNoZoomIsPutBack();
void arrowsStepOnePercentAndShiftTen();
void theFieldNeedsACanvasAndAnIdleSession();
void anOutsideZoomLeavesTypingAlone();
};
void NavigationToolHeaderTests::theTitleAndTheFieldFollowTheTool()
{
Bar bar;
QVERIFY(bar.active);
QCOMPARE(bar.header->title->text(), QString("Zoom"));
QVERIFY(bar.field->isVisible());
QCOMPARE(bar.field->width(), 72);
QCOMPARE(bar.field->alignment(), Qt::AlignRight | Qt::AlignVCenter);
QCOMPARE(bar.field->accessibleName(), QString("Zoom percentage"));
QCOMPARE(bar.field->toolTip(), QString("Zoom percentage (0.1–3200%). Press Return to apply."));
QCOMPARE(bar.header->height(), 42);
// The unit sits right of its field, two pixels away.
const QList<QLabel *> labels = bar.header->findChildren<QLabel *>();
const auto unit = std::find_if(labels.begin(), labels.end(), [](const QLabel *label) { return label->text() == "%"; });
QVERIFY(unit != labels.end());
QCoreApplication::processEvents();
QCOMPARE((*unit)->geometry().left() - bar.field->geometry().right() - 1, 2);
bar.session.selectTool(NavigationTool::hand);
QCOMPARE(bar.header->title->text(), QString("Pan"));
QVERIFY(!bar.field->isVisible() && !(*unit)->isVisible());
bar.session.selectTool(NavigationTool::zoom);
QVERIFY(bar.field->isVisible() && (*unit)->isVisible());
}
void NavigationToolHeaderTests::theFieldShowsTheZoomWithoutIdleZeros()
{
Bar bar;
// A field in use is left alone; not this one.
bar.field->clearFocus();
// A bar made over a zoomed session shows that zoom.
bar.session.zoom(0.5);
NavigationToolHeader late(bar.session);
QCOMPARE(late.findChild<QLineEdit *>("zoomPercentage")->text(), QString("50"));
const auto shown = [&](double zoom) {
bar.session.zoom(zoom);
return bar.field->text();
};
QCOMPARE(shown(1), QString("100"));
QCOMPARE(shown(0.125), QString("12.5"));
QCOMPARE(shown(1.0 / 3), QString("33.33"));
QCOMPARE(shown(2.5), QString("250"));
QCOMPARE(shown(0.001), QString("0.1"));
QCOMPARE(shown(32), QString("3200"));
QCOMPARE(shown(0.1), QString("10"));
}
void NavigationToolHeaderTests::returnEscapeAndLeavingAllApplyWhatWasTyped()
{
Bar bar;
bar.type("250");
QTest::keyClick(bar.field, Qt::Key_Return);
QCOMPARE(bar.session.viewport.zoom(), 2.5);
QVERIFY(!bar.field->hasFocus());
QCOMPARE(bar.session.canvasFocusRequest(), 1);
// Applied once: Return asks for the canvas, zooms no more.
QSignalSpy changes(&bar.session, &EditorSession::changed);
bar.field->setFocus();
QTest::keyClick(bar.field, Qt::Key_Return);
QCOMPARE(changes.count(), 1);
QCOMPARE(bar.session.viewport.zoom(), 2.5);
QCOMPARE(bar.session.canvasFocusRequest(), 2);
bar.type(" 50% ");
QTest::keyClick(bar.field, Qt::Key_Escape);
QCOMPARE(bar.session.viewport.zoom(), 0.5);
QCOMPARE(bar.field->text(), QString("50"));
QVERIFY(!bar.field->hasFocus());
QCOMPARE(bar.session.canvasFocusRequest(), 3);
bar.type("75");
QTest::keyClick(bar.field, Qt::Key_Enter);
QCOMPARE(bar.session.viewport.zoom(), 0.75);
QCOMPARE(bar.session.canvasFocusRequest(), 4);
// Leaving the field applies as well, asking for no focus.
bar.type("125");
bar.field->clearFocus();
QCOMPARE(bar.session.viewport.zoom(), 1.25);
QCOMPARE(bar.session.canvasFocusRequest(), 4);
// Past the range the viewport decides; the field follows.
bar.type("99999");
QTest::keyClick(bar.field, Qt::Key_Return);
QCOMPARE(bar.session.viewport.zoom(), 32.0);
QCOMPARE(bar.field->text(), QString("3200"));
// Behind another window nothing loses focus: Return applies.
Bar behind;
QWidget front;
front.show();
front.activateWindow();
QVERIFY(QTest::qWaitForWindowActive(&front));
behind.field->setFocus();
behind.field->selectAll();
QTest::keyClicks(behind.field, "42");
QTest::keyClick(behind.field, Qt::Key_Return);
QCOMPARE(behind.session.viewport.zoom(), 0.42);
}
void NavigationToolHeaderTests::whatIsNoZoomIsPutBack()
{
Bar bar;
bar.session.zoom(2);
QSignalSpy changes(&bar.session, &EditorSession::changed);
for (const QString &bad : {QString("wide"), QString(""), QString("0"), QString("-40"), QString("nan"), QString("inf"), QString("1e999")}) {
bar.type(bad);
QTest::keyClick(bar.field, Qt::Key_Return);
QCOMPARE(bar.session.viewport.zoom(), 2.0);
QCOMPARE(bar.field->text(), QString("200"));
}
// Each Return asks for the canvas; none zooms.
QCOMPARE(changes.count(), 7);
QCOMPARE(bar.session.canvasFocusRequest(), 7);
// A busy project takes no zoom from the field.
bar.session.setIsProjectBusy(true);
bar.type("300");
QTest::keyClick(bar.field, Qt::Key_Return);
QCOMPARE(bar.session.viewport.zoom(), 2.0);
QCOMPARE(bar.field->text(), QString("200"));
// Untouched text is not applied: no rounding creeps in.
bar.session.setIsProjectBusy(false);
bar.session.zoom(1.0 / 3);
const double third = bar.session.viewport.zoom();
bar.field->setFocus();
QTest::keyClick(bar.field, Qt::Key_Return);
QCOMPARE(bar.session.viewport.zoom(), third);
}
void NavigationToolHeaderTests::arrowsStepOnePercentAndShiftTen()
{
Bar bar;
bar.session.zoom(1);
bar.field->setFocus();
QTest::keyClick(bar.field, Qt::Key_Up);
QCOMPARE(bar.session.viewport.zoom(), 1.01);
QCOMPARE(bar.field->text(), QString("101"));
QVERIFY(bar.field->hasFocus());
QTest::keyClick(bar.field, Qt::Key_Down, Qt::ShiftModifier);
QCOMPARE(bar.session.viewport.zoom(), 0.91);
QTest::keyClick(bar.field, Qt::Key_Up, Qt::ShiftModifier);
QTest::keyClick(bar.field, Qt::Key_Down);
QCOMPARE(bar.field->text(), QString("100"));
// A step starts from what is typed, percent sign included.
bar.type("40%");
QTest::keyClick(bar.field, Qt::Key_Up);
QCOMPARE(bar.session.viewport.zoom(), 0.41);
// The ends of the range hold.
bar.type("3195");
QTest::keyClick(bar.field, Qt::Key_Up, Qt::ShiftModifier);
QCOMPARE(bar.field->text(), QString("3200"));
bar.type("0.5");
QTest::keyClick(bar.field, Qt::Key_Down);
QCOMPARE(bar.field->text(), QString("0.1"));
// Text that is no number steps from the current zoom.
bar.session.zoom(2);
bar.type("wide");
QTest::keyClick(bar.field, Qt::Key_Up);
QCOMPARE(bar.field->text(), QString("201"));
}
void NavigationToolHeaderTests::theFieldNeedsACanvasAndAnIdleSession()
{
Bar bare(false);
QVERIFY(!bare.field->isEnabled());
bare.session.createDocument(8, 8);
QVERIFY(bare.field->isEnabled());
// Busy dims only once it shows, a quarter second in.
bare.session.setIsProjectBusy(true);
QVERIFY(bare.field->isEnabled());
QTRY_VERIFY(!bare.field->isEnabled());
bare.session.setIsProjectBusy(false);
QVERIFY(bare.field->isEnabled());
}
void NavigationToolHeaderTests::anOutsideZoomLeavesTypingAlone()
{
Bar bar;
bar.type("77");
// A zoom from elsewhere arrives while the user types.
bar.session.zoom(3);
QVERIFY(bar.field->hasFocus());
QCOMPARE(bar.field->text(), QString("77"));
QTest::keyClick(bar.field, Qt::Key_Return);
QCOMPARE(bar.session.viewport.zoom(), 0.77);
bar.session.zoom(3);
QCOMPARE(bar.field->text(), QString("300"));
}
QTEST_MAIN(NavigationToolHeaderTests)
#include "NavigationToolHeaderTests.moc"