From 2f72c286b0eb0f2d4a8705f1311f86268dec91f7 Mon Sep 17 00:00:00 2001 From: Mitch Curtis Date: Wed, 27 Dec 2017 12:26:07 +0100 Subject: [PATCH] Add Auto Export feature Closes #11 --- app/layeredimageproject.cpp | 54 ++++++++++++++++++++++++++---- app/layeredimageproject.h | 9 ++++- app/qml/ui/+windows/MenuBar.qml | 12 ++++++- app/qml/ui/MenuBar.qml | 12 ++++++- tests/testhelper.cpp | 3 +- tests/tst_app.cpp | 59 +++++++++++++++++++++++++++++++++ 6 files changed, 138 insertions(+), 11 deletions(-) diff --git a/app/layeredimageproject.cpp b/app/layeredimageproject.cpp index fd5e666b..630e0968 100644 --- a/app/layeredimageproject.cpp +++ b/app/layeredimageproject.cpp @@ -36,7 +36,8 @@ LayeredImageProject::LayeredImageProject() : mCurrentLayerIndex(0), - mLayersCreated(0) + mLayersCreated(0), + mAutoExportEnabled(false) { setObjectName(QLatin1String("LayeredImageProject")); qCDebug(lcProjectLifecycle) << "constructing" << this; @@ -135,6 +136,32 @@ QImage LayeredImageProject::flattenedImage(std::function layerSubst return finalImage; } +bool LayeredImageProject::isAutoExportEnabled() const +{ + return mAutoExportEnabled; +} + +void LayeredImageProject::setAutoExportEnabled(bool autoExportEnabled) +{ + if (autoExportEnabled == mAutoExportEnabled) + return; + + mAutoExportEnabled = autoExportEnabled; + emit autoExportEnabledChanged(); +} + +QString LayeredImageProject::autoExportFilePath(const QUrl &projectUrl) +{ + const QString filePath = projectUrl.toLocalFile(); + QString path = filePath; + const int lastPeriodIndex = filePath.lastIndexOf(QLatin1Char('.')); + if (lastPeriodIndex != -1) + path.replace(lastPeriodIndex + 1, filePath.size() - lastPeriodIndex - 1, QLatin1String("png")); + else + path.append(QLatin1String("png")); + return path; +} + void LayeredImageProject::createNew(int imageWidth, int imageHeight, bool transparentBackground) { if (hasLoaded()) { @@ -195,6 +222,9 @@ void LayeredImageProject::load(const QUrl &url) } readGuides(projectObject); + + mAutoExportEnabled = projectObject.value("autoExportEnabled").toBool(false); + mCachedProjectJson = projectObject; setUrl(url); @@ -280,6 +310,13 @@ void LayeredImageProject::saveAs(const QUrl &url) writeGuides(projectObject); emit readyForWritingToJson(&projectObject); + if (mAutoExportEnabled) { + projectObject.insert("autoExportEnabled", mAutoExportEnabled); + + if (!exportImage(QUrl::fromLocalFile(autoExportFilePath(url)))) + return; + } + rootJson.insert("project", projectObject); QJsonDocument jsonDoc(rootJson); @@ -305,27 +342,30 @@ void LayeredImageProject::saveAs(const QUrl &url) mHadUnsavedChangesBeforeMacroBegan = false; } -void LayeredImageProject::exportImage(const QUrl &url) +// Returns true because the auto-export feature in saveAs() needs to know whether or not it should return early. +bool LayeredImageProject::exportImage(const QUrl &url) { if (!hasLoaded()) - return; + return false; if (url.isEmpty()) - return; + return false; const QString filePath = url.toLocalFile(); const QFileInfo projectSaveFileInfo(filePath); if (mTempDir.isValid()) { if (projectSaveFileInfo.dir().path() == mTempDir.path()) { error(QLatin1String("Cannot save project in internal temporary directory")); - return; + return false; } } if (!flattenedImage().save(filePath)) { error(QString::fromLatin1("Failed to save project's image to:\n\n%1").arg(filePath)); - return; + return false; } + + return true; } void LayeredImageProject::resize(int width, int height) @@ -543,7 +583,7 @@ ImageLayer *LayeredImageProject::takeLayer(int index) QDebug operator<<(QDebug debug, const LayeredImageProject *project) { debug.nospace() << "LayeredImageProject currentLayerIndex=" << project->mCurrentLayerIndex - << ", layers:"; + << ", layers:"; foreach (ImageLayer *layer, project->mLayers) { debug << "\n name=" << layer->name() << " visible=" << layer->isVisible() diff --git a/app/layeredimageproject.h b/app/layeredimageproject.h index a34c56ea..1e973164 100644 --- a/app/layeredimageproject.h +++ b/app/layeredimageproject.h @@ -33,6 +33,7 @@ class LayeredImageProject : public Project Q_PROPERTY(int currentLayerIndex READ currentLayerIndex WRITE setCurrentLayerIndex NOTIFY currentLayerIndexChanged) Q_PROPERTY(ImageLayer *currentLayer READ currentLayer NOTIFY postCurrentLayerChanged) Q_PROPERTY(int layerCount READ layerCount NOTIFY layerCountChanged) + Q_PROPERTY(bool autoExportEnabled READ isAutoExportEnabled WRITE setAutoExportEnabled NOTIFY autoExportEnabledChanged) public: LayeredImageProject(); @@ -53,11 +54,16 @@ class LayeredImageProject : public Project QImage flattenedImage(std::function layerSubstituteFunction = nullptr) const; + bool isAutoExportEnabled() const; + void setAutoExportEnabled(bool autoExportEnabled); + static QString autoExportFilePath(const QUrl &projectUrl); + signals: void currentLayerIndexChanged(); void preCurrentLayerChanged(); void postCurrentLayerChanged(); void layerCountChanged(); + void autoExportEnabledChanged(); void preLayersCleared(); void postLayersCleared(); @@ -74,7 +80,7 @@ public slots: void load(const QUrl &url) override; void close() override; void saveAs(const QUrl &url) override; - void exportImage(const QUrl &url); + bool exportImage(const QUrl &url); void resize(int width, int height); void addNewLayer(); @@ -113,6 +119,7 @@ public slots: int mCurrentLayerIndex; // Give each layer a unique name based on the layers created so far. int mLayersCreated; + bool mAutoExportEnabled; }; #endif // LAYEREDIMAGEPROJECT_H diff --git a/app/qml/ui/+windows/MenuBar.qml b/app/qml/ui/+windows/MenuBar.qml index 63860289..86b2894a 100644 --- a/app/qml/ui/+windows/MenuBar.qml +++ b/app/qml/ui/+windows/MenuBar.qml @@ -53,12 +53,22 @@ MenuBar { } MenuItem { + id: exportMenuButton objectName: "exportMenuButton" text: qsTr("Export") - enabled: project ? project.loaded && projectType === Project.LayeredImageType : false + enabled: project && project.loaded && projectType === Project.LayeredImageType onClicked: exportDialog.open() } + MenuItem { + objectName: "autoExportMenuButton" + text: qsTr("Auto Export") + checkable: true + checked: enabled && project.autoExportEnabled + enabled: exportMenuButton.enabled + onClicked: project.autoExportEnabled = !project.autoExportEnabled + } + MenuItem { objectName: "closeMenuButton" text: qsTr("Close") diff --git a/app/qml/ui/MenuBar.qml b/app/qml/ui/MenuBar.qml index 8587cf90..ea18d9ff 100644 --- a/app/qml/ui/MenuBar.qml +++ b/app/qml/ui/MenuBar.qml @@ -46,12 +46,22 @@ Item { } Platform.MenuItem { + id: exportMenuButton objectName: "exportMenuButton" text: qsTr("Export") - enabled: project ? project.loaded && projectType === Project.LayeredImageType : false + enabled: project && project.loaded && projectType === Project.LayeredImageType onTriggered: exportDialog.open() } + Platform.MenuItem { + objectName: "autoExportMenuButton" + text: qsTr("Auto Export") + checkable: true + checked: enabled && project.autoExportEnabled + enabled: exportMenuButton.enabled + onTriggered: project.autoExportEnabled = !project.autoExportEnabled + } + Platform.MenuItem { objectName: "closeMenuButton" text: qsTr("Close") diff --git a/tests/testhelper.cpp b/tests/testhelper.cpp index c37477ea..544541b3 100644 --- a/tests/testhelper.cpp +++ b/tests/testhelper.cpp @@ -438,6 +438,7 @@ void TestHelper::changeToolSize(int size) ++sliderHandlePos.rx()) { QTest::mouseMove(toolSizeSlider->window(), sliderHandlePos, 5); } + --sliderHandlePos.rx(); QTest::mouseRelease(toolSizeSlider->window(), Qt::LeftButton, Qt::NoModifier, sliderHandlePos); QCOMPARE(toolSizeSlider->property("pressed").toBool(), false); QCOMPARE(sliderValue(toolSizeSlider), size); @@ -725,7 +726,7 @@ void TestHelper::triggerShortcut(const QString &objectName, const QString &seque QSignalSpy activatedSpy(shortcut, SIGNAL(activated())); QVERIFY(activatedSpy.isValid()); - QTest::qWaitForWindowActive(window); + QVERIFY(QTest::qWaitForWindowActive(window)); const int value = QKeySequence(sequenceAsString)[0]; Qt::KeyboardModifiers mods = (Qt::KeyboardModifiers)(value & Qt::KeyboardModifierMask); QTest::keyClick(window, value & ~mods, mods); diff --git a/tests/tst_app.cpp b/tests/tst_app.cpp index bbbb8044..7f502796 100644 --- a/tests/tst_app.cpp +++ b/tests/tst_app.cpp @@ -113,6 +113,7 @@ private Q_SLOTS: void layerVisibilityAfterMoving(); void undoAfterAddLayer(); void selectionConfirmedWhenSwitchingLayers(); + void autoExport(); }; tst_App::tst_App(int &argc, char **argv) : @@ -3023,6 +3024,64 @@ void tst_App::selectionConfirmedWhenSwitchingLayers() QCOMPARE(snapshotAfterSwitchingLayers.pixelColor(0, 0), Qt::red); } +void tst_App::autoExport() +{ + // Create a new layered image project with the dimensions of the clipboard contents. + createNewLayeredImageProject(10, 10); + + panTopLeftTo(0, 0); + + QCOMPARE(layeredImageProject->isAutoExportEnabled(), false); + + // Don't have a shortcut for it yet, so have to change it manually, but we can still + // check that the menus update accordingly. +#ifdef NON_NATIVE_MENUS + // TODO: can we just use the QObject-based code below instead? + QQuickItem *autoExportMenuButton = window->findChild("autoExportMenuButton"); + QVERIFY(autoExportMenuButton); +#else + QObject *autoExportMenuButton = window->findChild("autoExportMenuButton"); + QVERIFY(autoExportMenuButton); +#endif + QCOMPARE(autoExportMenuButton->property("checked"), false); + + layeredImageProject->setAutoExportEnabled(true); + QCOMPARE(autoExportMenuButton->property("checked"), true); + + QCOMPARE(layeredImageProject->canSave(), true); + + // Save the project so that the auto-export is triggered. + const QString savedProjectPath = tempProjectDir->path() + "/autoExport-project.slp"; + layeredImageProject->saveAs(QUrl::fromLocalFile(savedProjectPath)); + + // The image file should exist now. + const QString autoExportFilePath = LayeredImageProject::autoExportFilePath(layeredImageProject->url()); + QVERIFY(QFile::exists(autoExportFilePath)); + + QImage exportedImage(autoExportFilePath); + QVERIFY(!exportedImage.isNull()); + + QImage expectedExportedImage(10, 10, QImage::Format_ARGB32); + expectedExportedImage.fill(Qt::white); + QCOMPARE(exportedImage, expectedExportedImage); + + // Disable auto-export. + layeredImageProject->setAutoExportEnabled(false); + QCOMPARE(autoExportMenuButton->property("checked"), false); + + // Draw something. + setCursorPosInScenePixels(2, 2); + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, cursorWindowPos); + QCOMPARE(layeredImageProject->currentLayer()->image()->pixelColor(2, 2), Qt::black); + + // Save again. + layeredImageProject->saveAs(QUrl::fromLocalFile(savedProjectPath)); + + // No export should have happened and so the exported image shouldn't have changed. + exportedImage = QImage(autoExportFilePath); + QCOMPARE(exportedImage, expectedExportedImage); +} + int main(int argc, char *argv[]) { tst_App test(argc, argv);