From b13b3934f812caf6c4c062140929b87419418fdb Mon Sep 17 00:00:00 2001 From: Uyanide Date: Wed, 25 Feb 2026 22:41:50 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A7=20wip:=20chekkupointo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Tests/CMakeLists.txt | 17 +- WallReel/Core/CMakeLists.txt | 3 +- WallReel/Core/Config/data.hpp | 4 + WallReel/Core/Config/manager.cpp | 6 + WallReel/Core/Image/model.cpp | 309 +++++++++++++----- WallReel/Core/Image/model.hpp | 70 +++- WallReel/Core/Service/manager.hpp | 104 ++++++ .../wallpaper.cpp} | 22 +- .../wallpaper.hpp} | 4 +- WallReel/UI/CMakeLists.txt | 7 + WallReel/UI/Components/WRTextButton.qml | 24 ++ WallReel/UI/Main.qml | 3 +- WallReel/UI/Modules/BottomBar.qml | 78 +++++ WallReel/UI/Modules/ColorControl.qml | 87 +++++ WallReel/UI/Modules/SearchBar.qml | 82 +++++ WallReel/UI/Modules/SortControl.qml | 51 +++ WallReel/UI/Modules/TopBar.qml | 67 ++++ WallReel/UI/Pages/CarouselScreen.qml | 129 +++++--- WallReel/UI/Pages/LoadingScreen.qml | 1 + WallReel/UI/Providers/CarouselProvider.qml | 86 +++++ WallReel/main.cpp | 30 +- 21 files changed, 1013 insertions(+), 171 deletions(-) create mode 100644 WallReel/Core/Service/manager.hpp rename WallReel/Core/{wallpaperservice.cpp => Service/wallpaper.cpp} (82%) rename WallReel/Core/{wallpaperservice.hpp => Service/wallpaper.hpp} (93%) create mode 100644 WallReel/UI/Components/WRTextButton.qml create mode 100644 WallReel/UI/Modules/BottomBar.qml create mode 100644 WallReel/UI/Modules/ColorControl.qml create mode 100644 WallReel/UI/Modules/SearchBar.qml create mode 100644 WallReel/UI/Modules/SortControl.qml create mode 100644 WallReel/UI/Modules/TopBar.qml create mode 100644 WallReel/UI/Providers/CarouselProvider.qml diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 159d5bb..c0a8b41 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -7,19 +7,18 @@ add_executable(tst_configmgr tst_configmgr.cpp ) -add_executable(tst_imagemodel - tst_imagemodel.cpp -) - +# add_executable(tst_imagemodel +# tst_imagemodel.cpp +# ) add_test(NAME tst_configmgr COMMAND tst_configmgr) -add_test(NAME tst_imagemodel COMMAND tst_imagemodel) +# add_test(NAME tst_imagemodel COMMAND tst_imagemodel) target_link_libraries(tst_configmgr PRIVATE Qt6::Test ${CORELIB_NAME} ) -target_link_libraries(tst_imagemodel PRIVATE - Qt6::Test - ${CORELIB_NAME} -) +# target_link_libraries(tst_imagemodel PRIVATE +# Qt6::Test +# ${CORELIB_NAME} +# ) diff --git a/WallReel/Core/CMakeLists.txt b/WallReel/Core/CMakeLists.txt index c91d5d1..b14ffc6 100644 --- a/WallReel/Core/CMakeLists.txt +++ b/WallReel/Core/CMakeLists.txt @@ -10,7 +10,8 @@ qt_add_qml_module(${CORELIB_NAME} Config/data.hpp Config/manager.hpp Config/manager.cpp logger.hpp logger.cpp - wallpaperservice.hpp wallpaperservice.cpp + Service/manager.hpp + Service/wallpaper.hpp Service/wallpaper.cpp appoptions.hpp appoptions.cpp ) diff --git a/WallReel/Core/Config/data.hpp b/WallReel/Core/Config/data.hpp index fe8f209..4ed4e00 100644 --- a/WallReel/Core/Config/data.hpp +++ b/WallReel/Core/Config/data.hpp @@ -1,6 +1,8 @@ #ifndef WALLREEL_CONFIG_DATA_HPP #define WALLREEL_CONFIG_DATA_HPP +#include + #include #include #include @@ -34,6 +36,7 @@ // action.saveState[].cmd string "" Command that outputs(to stdout) the value to save when executed // action.saveState[].timeout number 3000 Timeout for executing "cmd" in milliseconds. 0 or negative means no timeout // action.onRestore string "" Command to execute on restore ({{ key }} -> value defined or obtained in saveState) +// action.quitOnSelected boolean false Whether to quit the application after confirming a wallpaper // // style.image_width number 320 Width of each image // style.image_height number 200 Height of each image @@ -99,6 +102,7 @@ struct ActionConfigItems { int previewDebounceTime = 300; // milliseconds bool printSelected = true; bool printPreview = false; + bool quitOnSelected = false; }; struct StyleConfigItems { diff --git a/WallReel/Core/Config/manager.cpp b/WallReel/Core/Config/manager.cpp index 7418724..43197d2 100644 --- a/WallReel/Core/Config/manager.cpp +++ b/WallReel/Core/Config/manager.cpp @@ -224,6 +224,12 @@ void WallReel::Core::Config::Manager::_loadActionConfig(const QJsonObject& root) m_actionConfig.onPreview = val.toString(); } } + if (config.contains("quitOnSelected")) { + const auto& val = config["quitOnSelected"]; + if (val.isBool()) { + m_actionConfig.quitOnSelected = val.toBool(); + } + } } void WallReel::Core::Config::Manager::_loadStyleConfig(const QJsonObject& root) { diff --git a/WallReel/Core/Image/model.cpp b/WallReel/Core/Image/model.cpp index ec40e0e..cff8931 100644 --- a/WallReel/Core/Image/model.cpp +++ b/WallReel/Core/Image/model.cpp @@ -4,6 +4,7 @@ #include #include "data.hpp" +#include "logger.hpp" WallReel::Core::Image::Model::Model( Provider& provider, @@ -13,7 +14,8 @@ WallReel::Core::Image::Model::Model( : QAbstractListModel(parent), m_provider(provider), m_sortConfig(sortConfig), - m_thumbnailSize(thumbnailSize) { + m_thumbnailSize(thumbnailSize), + m_currentSortType(sortConfig.type) { connect( &m_watcher, &QFutureWatcher::finished, @@ -26,28 +28,43 @@ WallReel::Core::Image::Model::Model( [this]() { emit progressChanged(); }); + + // Update state when sort method changes + connect(this, &Model::currentSortTypeChanged, this, &Model::_onSortMethodChanged); + connect(this, &Model::currentSortReverseChanged, this, &Model::_onSortMethodChanged); + + m_sortIndices.resize(4); // None, Name, Date, Size + + // Search text + connect(this, &Model::searchTextChanged, this, &Model::_onSearchTextChanged); } WallReel::Core::Image::Model::~Model() { m_watcher.cancel(); m_watcher.waitForFinished(); qDeleteAll(m_data); - m_data.clear(); } int WallReel::Core::Image::Model::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { return 0; } - return m_data.count(); + return m_filteredIndices.count(); } QVariant WallReel::Core::Image::Model::data(const QModelIndex& index, int role) const { - if (!index.isValid() || index.row() >= m_data.count()) { + if (!index.isValid() || index.row() >= m_filteredIndices.count()) { + Logger::debug("Invalid index requested: " + QString::number(index.row())); return QVariant(); } - const auto& item = m_data[index.row()]; + int actualIndex = fromProxyIndex(index.row()); + if (actualIndex < 0 || actualIndex >= m_data.count()) { + Logger::debug("Actual index out of bounds: " + QString::number(actualIndex)); + return QVariant(); + } + // Logger::debug("Data requested for index: " + QString::number(index.row()) + ", actual index: " + QString::number(actualIndex) + ", role: " + QString::number(role)); + const auto& item = m_data[actualIndex]; switch (role) { case IdRole: return item->getId(); @@ -60,8 +77,94 @@ QVariant WallReel::Core::Image::Model::data(const QModelIndex& index, int role) } } +QString WallReel::Core::Image::Model::currentSortType() const { + switch (m_currentSortType) { + case Config::SortType::None: + return "None"; + case Config::SortType::Name: + return "Name"; + case Config::SortType::Date: + return "Date"; + case Config::SortType::Size: + return "Size"; + default: + return "Unknown"; + } +} + +void WallReel::Core::Image::Model::setCurrentSortType(const QString& type) { + Config::SortType newSortType = Config::SortType::None; + if (type == "None") { + newSortType = Config::SortType::None; + } else if (type == "Name") { + newSortType = Config::SortType::Name; + } else if (type == "Date") { + newSortType = Config::SortType::Date; + } else if (type == "Size") { + newSortType = Config::SortType::Size; + } + + if (m_currentSortType != newSortType) { + m_currentSortType = newSortType; + emit currentSortTypeChanged(); + } +} + +void WallReel::Core::Image::Model::setCurrentSortReverse(bool reverse) { + if (m_currentSortReverse != reverse) { + m_currentSortReverse = reverse; + emit currentSortReverseChanged(); + } +} + +void WallReel::Core::Image::Model::setSearchText(const QString& text) { + // Logger::debug("Search text changed: " + text); + if (m_searchText != text) { + m_searchText = text; + _applySearchFilter(); + emit searchTextChanged(); + } +} + +const WallReel::Core::Image::Data* WallReel::Core::Image::Model::getDataPtrAt(int index) const { + if (index < 0 || index >= m_filteredIndices.count()) { + Logger::debug("Invalid index requested: " + QString::number(index)); + return nullptr; + } + int actualIndex = fromProxyIndex(index); + if (actualIndex < 0 || actualIndex >= m_data.count()) { + Logger::debug("Actual index out of bounds: " + QString::number(actualIndex)); + return nullptr; + } + return m_data[actualIndex]; +} + +QVariant WallReel::Core::Image::Model::dataAt(int index, const QString& roleName) const { + if (index < 0 || index >= m_filteredIndices.count()) { + Logger::debug("Invalid index requested: " + QString::number(index)); + return QVariant(); + } + + int actualIndex = fromProxyIndex(index); + if (actualIndex < 0 || actualIndex >= m_data.count()) { + Logger::debug("Actual index out of bounds: " + QString::number(actualIndex)); + return QVariant(); + } + const auto& item = m_data[actualIndex]; + if (roleName == "imgId") { + return item->getId(); + } else if (roleName == "imgPath") { + return item->getFullPath(); + } else if (roleName == "imgName") { + return item->getFileName(); + } else { + return QVariant(); + } +} + void WallReel::Core::Image::Model::loadAndProcess(const QStringList& paths) { if (m_isLoading) { + Logger::warn("Already loading images. Ignoring new load request."); return; } m_isLoading = true; @@ -82,12 +185,107 @@ void WallReel::Core::Image::Model::loadAndProcess(const QStringList& paths) { emit totalCountChanged(); } +int WallReel::Core::Image::Model::fromProxyIndex(int proxyIndex) const { + if (proxyIndex < 0 || proxyIndex >= m_filteredIndices.size()) { + Logger::debug("Invalid proxy index requested: " + QString::number(proxyIndex)); + return -1; + } + return m_filteredIndices[m_currentSortReverse ? (m_filteredIndices.size() - 1 - proxyIndex) : proxyIndex]; +} + +void WallReel::Core::Image::Model::focusOnIndex(int index) { + if (index < 0 || index >= m_filteredIndices.count()) { + Logger::debug("Invalid index to focus on: " + QString::number(index)); + return; + } + int actualIndex = fromProxyIndex(index); + if (actualIndex < 0 || actualIndex >= m_data.count()) { + Logger::debug("Actual index out of bounds for focus: " + QString::number(actualIndex)); + return; + } + m_focusedIndex = index; + _updateFocusedName(); +} + void WallReel::Core::Image::Model::stop() { if (m_isLoading) { + Logger::info("Stopping image loading..."); m_watcher.cancel(); + } else { + Logger::warn("No loading operation to stop."); } } +void WallReel::Core::Image::Model::_clearData() { + beginResetModel(); + m_provider.clear(); + qDeleteAll(m_data); + m_data.clear(); + for (auto& i : m_sortIndices) { + i.clear(); + } + m_filteredIndices.clear(); + endResetModel(); +} + +void WallReel::Core::Image::Model::_updateSortIndices(Config::SortType type) { + QList& indices = m_sortIndices[static_cast(type)]; + indices.resize(m_data.count()); + std::iota(indices.begin(), indices.end(), 0); + if (type == Config::SortType::None) { + return; + } + + const auto& compareFunc = [this, type](int idx1, int idx2) { + const Data* data1 = m_data[idx1]; + const Data* data2 = m_data[idx2]; + if (!data1 || !data2) { + return false; + } + switch (type) { + case Config::SortType::Name: + return QString::compare(data1->getFileName(), data2->getFileName(), Qt::CaseInsensitive) < 0; + case Config::SortType::Date: + return data1->getLastModified() < data2->getLastModified(); + case Config::SortType::Size: + return data1->getSize() < data2->getSize(); + default: + return false; + } + }; + + std::sort(indices.begin(), indices.end(), compareFunc); +} + +void WallReel::Core::Image::Model::_updateFocusedName() { + if (m_focusedIndex < 0 || m_focusedIndex >= m_data.count()) { + m_focusedName = ""; + } else { + int actualIndex = fromProxyIndex(m_focusedIndex); + if (actualIndex < 0 || actualIndex >= m_data.count()) { + m_focusedName = ""; + } else { + const auto& item = m_data[actualIndex]; + m_focusedName = item->getFileName(); + } + } + emit focusedNameChanged(); +} + +void WallReel::Core::Image::Model::_applySearchFilter() { + emit layoutAboutToBeChanged(); + m_filteredIndices.clear(); + const auto& sortedIndices = m_sortIndices[static_cast(m_currentSortType)]; + for (int i = 0; i < sortedIndices.size(); ++i) { + int actualIndex = sortedIndices[i]; + const auto& item = m_data[actualIndex]; + if (item->getFileName().contains(m_searchText, Qt::CaseInsensitive)) { + m_filteredIndices.append(actualIndex); + } + } + emit layoutChanged(); +} + void WallReel::Core::Image::Model::_onProgressValueChanged(int value) { Q_UNUSED(value); emit progressChanged(); @@ -95,101 +293,44 @@ void WallReel::Core::Image::Model::_onProgressValueChanged(int value) { void WallReel::Core::Image::Model::_onProcessingFinished() { auto results = m_watcher.future().results(); + + beginResetModel(); + for (auto& data : results) { if (data && data->isValid()) { m_data.append(data); + m_provider.insert(data); } else { + Logger::warn("Failed to load image: " + (data ? data->getFullPath() : "null")); delete data; data = nullptr; } } - sortUpdate(); + for (int i = 0; i < m_sortIndices.size(); ++i) { + _updateSortIndices(static_cast(i)); + } + + _applySearchFilter(); + + endResetModel(); + + Logger::info("Finished loading images. Total valid images: " + QString::number(m_data.count())); m_isLoading = false; m_progressUpdateTimer.stop(); emit progressChanged(); // emit isLoadingChanged(); - QTimer::singleShot(s_IsLoadingUpdateIntervalMs, this, [this]() { - emit isLoadingChanged(); - }); + // QTimer::singleShot(s_IsLoadingUpdateIntervalMs, this, [this]() { + // emit isLoadingChanged(); + // }); + emit isLoadingChanged(); } -void WallReel::Core::Image::Model::sortUpdate() { - const auto type = m_sortConfig.type; - const auto reverse = m_sortConfig.reverse; - std::sort(m_data.begin(), m_data.end(), [type, reverse](Data* a, Data* b) { - if (!a || !b) { - return false; - } - if (a == b) { - return false; - } - - Data* first = reverse ? b : a; - Data* second = reverse ? a : b; - - switch (type) { - case Config::SortType::Name: - return QString::compare(first->getFileName(), second->getFileName(), Qt::CaseInsensitive) < 0; - case Config::SortType::Date: - return first->getLastModified() < second->getLastModified(); - case Config::SortType::Size: - return first->getSize() < second->getSize(); - default: - return false; - } - }); - - beginResetModel(); - m_provider.clear(); - for (const auto& item : m_data) { - m_provider.insert(item); - } - endResetModel(); +void WallReel::Core::Image::Model::_onSortMethodChanged() { + _applySearchFilter(); } -QVariant WallReel::Core::Image::Model::dataAt(int index, const QString& roleName) const { - if (index < 0 || index >= m_data.count()) { - return QVariant(); - } - - const auto& item = m_data[index]; - if (roleName == "imgId") { - return item->getId(); - } else if (roleName == "imgPath") { - return item->getFullPath(); - } else if (roleName == "imgName") { - return item->getFileName(); - } else { - return QVariant(); - } -} - -void WallReel::Core::Image::Model::_clearData() { - beginResetModel(); - m_provider.clear(); - qDeleteAll(m_data); - m_data.clear(); - endResetModel(); -} - -void WallReel::Core::Image::Model::selectImage(int index) { - if (index < 0 || index >= m_data.count()) { - return; - } - const auto& item = m_data[index]; - if (item) { - emit imageSelected(*item); - } -} - -void WallReel::Core::Image::Model::previewImage(int index) { - if (index < 0 || index >= m_data.count()) { - return; - } - const auto& item = m_data[index]; - if (item) { - emit imagePreviewed(*item); - } +void WallReel::Core::Image::Model::_onSearchTextChanged() { + _updateFocusedName(); } diff --git a/WallReel/Core/Image/model.hpp b/WallReel/Core/Image/model.hpp index 1efe318..827aad8 100644 --- a/WallReel/Core/Image/model.hpp +++ b/WallReel/Core/Image/model.hpp @@ -1,6 +1,8 @@ #ifndef WALLREEL_IMAGEMODEL_HPP #define WALLREEL_IMAGEMODEL_HPP +#include + #include #include #include @@ -17,8 +19,13 @@ class Model : public QAbstractListModel { Q_PROPERTY(bool isLoading READ isLoading NOTIFY isLoadingChanged) Q_PROPERTY(int processedCount READ processedCount NOTIFY progressChanged) Q_PROPERTY(int totalCount READ totalCount NOTIFY totalCountChanged) + Q_PROPERTY(QString currentSortType READ currentSortType WRITE setCurrentSortType NOTIFY currentSortTypeChanged) + Q_PROPERTY(bool currentSortReverse READ currentSortReverse WRITE setCurrentSortReverse NOTIFY currentSortReverseChanged) + Q_PROPERTY(QString focusedName READ focusedName NOTIFY focusedNameChanged) public: + // Types + enum Roles { IdRole = Qt::UserRole + 1, PathRole, @@ -33,6 +40,8 @@ class Model : public QAbstractListModel { }; } + // Constructor / Destructor + Model( Provider& provider, const Config::SortConfigItems& sortConfig, @@ -41,56 +50,93 @@ class Model : public QAbstractListModel { ~Model(); + // QAbstractListModel + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + + // Properties + bool isLoading() const { return m_isLoading; } int processedCount() const { return m_processedCount.load(std::memory_order_relaxed); } int totalCount() const { return m_watcher.progressMaximum(); } - void sortUpdate(); + QString currentSortType() const; - int rowCount(const QModelIndex& parent = QModelIndex()) const override; + void setCurrentSortType(const QString& type); - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + bool currentSortReverse() const { return m_currentSortReverse; } - void loadAndProcess(const QStringList& paths); + void setCurrentSortReverse(bool reverse); - Q_INVOKABLE void stop(); + QString focusedName() const { return m_focusedName; } + + // Methods + + Q_INVOKABLE void setSearchText(const QString& text); + + const Data* getDataPtrAt(int index) const; Q_INVOKABLE QVariant dataAt(int index, const QString& roleName) const; - Q_INVOKABLE void selectImage(int index); + void loadAndProcess(const QStringList& paths); - Q_INVOKABLE void previewImage(int index); + int fromProxyIndex(int proxyIndex) const; + + Q_INVOKABLE void focusOnIndex(int index); + + Q_INVOKABLE void stop(); private: void _clearData(); + void _updateSortIndices(Config::SortType type); + void _updateFocusedName(); + void _applySearchFilter(); signals: void isLoadingChanged(); void progressChanged(); void totalCountChanged(); - void imageSelected(const Data& imageData); - void imagePreviewed(const Data& imageData); + void currentSortTypeChanged(); + void currentSortReverseChanged(); + void focusedNameChanged(); + void searchTextChanged(); private slots: void _onProgressValueChanged(int value); void _onProcessingFinished(); + void _onSortMethodChanged(); + void _onSearchTextChanged(); private: Provider& m_provider; const Config::SortConfigItems& m_sortConfig; QSize m_thumbnailSize; - QVector m_data; + QList m_data; + + QList> m_sortIndices; + Config::SortType m_currentSortType; + bool m_currentSortReverse; + + QString m_focusedName{}; + + QList m_filteredIndices; + QString m_searchText{}; + // QTimer m_searchDebounceTimer; + // static constexpr int s_SearchDebounceIntervalMs = 300; QFutureWatcher m_watcher; bool m_isLoading = false; + int m_focusedIndex = -1; + std::atomic m_processedCount{0}; QTimer m_progressUpdateTimer; - static constexpr int s_ProgressUpdateIntervalMs = 30; - static constexpr int s_IsLoadingUpdateIntervalMs = 50; + static constexpr int s_ProgressUpdateIntervalMs = 30; }; } // namespace WallReel::Core::Image diff --git a/WallReel/Core/Service/manager.hpp b/WallReel/Core/Service/manager.hpp new file mode 100644 index 0000000..dc751a3 --- /dev/null +++ b/WallReel/Core/Service/manager.hpp @@ -0,0 +1,104 @@ +#ifndef WALLREEL_SERVICE_MANAGER_HPP +#define WALLREEL_SERVICE_MANAGER_HPP + +#include +#include + +#include "Config/data.hpp" +#include "Image/model.hpp" +#include "Service/wallpaper.hpp" +#include "logger.hpp" + +namespace WallReel::Core::Service { + +class Manager : public QObject { + Q_OBJECT + + Q_PROPERTY(bool isProcessing READ isProcessing NOTIFY isProcessingChanged) + + public: + Manager( + const Config::ActionConfigItems& actionConfig, + const Image::Model& imageModel, + QObject* parent = nullptr) : m_imageModel(imageModel) { + m_wallpaperService = new WallpaperService(actionConfig, this); + + // Forward signals + // Direct signal 2 signal connection + connect(m_wallpaperService, &WallpaperService::previewCompleted, this, &Manager::previewCompleted); + // Signal 2 slot connection to handle processing state + connect(m_wallpaperService, &WallpaperService::selectCompleted, this, &Manager::_onSelectCompleted); + connect(m_wallpaperService, &WallpaperService::restoreCompleted, this, &Manager::_onRestoreCompleted); + } + + Q_INVOKABLE void selectWallpaper(int index) { + if (m_isProcessing) { + Logger::debug("Already processing an action, ignoring select request"); + return; + } + m_isProcessing = true; + emit isProcessingChanged(); + const auto* data = m_imageModel.getDataPtrAt(index); + if (data) { + m_wallpaperService->select(*data); + } else { + m_isProcessing = false; + emit isProcessingChanged(); + emit selectCompleted(); + } + } + + Q_INVOKABLE void previewWallpaper(int index) { + const auto* data = m_imageModel.getDataPtrAt(index); + if (data) { + m_wallpaperService->preview(*data); + } else { + emit previewCompleted(); + } + } + + Q_INVOKABLE void restore() { + if (m_isProcessing) { + Logger::debug("Already processing an action, ignoring restore request"); + return; + } + m_isProcessing = true; + emit isProcessingChanged(); + m_wallpaperService->restore(); + } + + bool isProcessing() const { return m_isProcessing; } + + private slots: + + void _onSelectCompleted() { + _onProcessCompleted(); + emit selectCompleted(); + } + + void _onRestoreCompleted() { + _onProcessCompleted(); + emit restoreCompleted(); + } + + void _onProcessCompleted() { + m_isProcessing = false; + emit isProcessingChanged(); + } + + signals: + void isProcessingChanged(); + void selectCompleted(); + void previewCompleted(); + void restoreCompleted(); + + private: + WallpaperService* m_wallpaperService; + const Image::Model& m_imageModel; + + bool m_isProcessing = false; +}; + +} // namespace WallReel::Core::Service + +#endif // WALLREEL_SERVICE_MANAGER_HPP diff --git a/WallReel/Core/wallpaperservice.cpp b/WallReel/Core/Service/wallpaper.cpp similarity index 82% rename from WallReel/Core/wallpaperservice.cpp rename to WallReel/Core/Service/wallpaper.cpp index 46b1b8c..8f33fb0 100644 --- a/WallReel/Core/wallpaperservice.cpp +++ b/WallReel/Core/Service/wallpaper.cpp @@ -1,4 +1,4 @@ -#include "wallpaperservice.hpp" +#include "Service/wallpaper.hpp" #include #include @@ -6,7 +6,7 @@ #include "Utils/texttemplate.hpp" #include "logger.hpp" -WallReel::Core::WallpaperService::WallpaperService( +WallReel::Core::Service::WallpaperService::WallpaperService( const Config::ActionConfigItems& actionConfig, QObject* parent) : QObject(parent), m_actionConfig(actionConfig) { @@ -48,12 +48,12 @@ WallReel::Core::WallpaperService::WallpaperService( }); } -void WallReel::Core::WallpaperService::preview(const Image::Data& imageData) { +void WallReel::Core::Service::WallpaperService::preview(const Image::Data& imageData) { m_pendingImageData = &imageData; m_previewDebounceTimer->start(); } -void WallReel::Core::WallpaperService::select(const Image::Data& imageData) { +void WallReel::Core::Service::WallpaperService::select(const Image::Data& imageData) { if (m_selectProcess->state() != QProcess::NotRunning) { Logger::warn("Previous select command is still running. Ignoring new command."); return; @@ -61,7 +61,7 @@ void WallReel::Core::WallpaperService::select(const Image::Data& imageData) { _doSelect(imageData); } -void WallReel::Core::WallpaperService::restore() { +void WallReel::Core::Service::WallpaperService::restore() { if (m_restoreProcess->state() != QProcess::NotRunning) { Logger::warn("Previous restore command is still running. Ignoring new command."); return; @@ -69,10 +69,11 @@ void WallReel::Core::WallpaperService::restore() { _doRestore(); } -void WallReel::Core::WallpaperService::_doPreview(const Image::Data& imageData) { +void WallReel::Core::Service::WallpaperService::_doPreview(const Image::Data& imageData) { QString path = imageData.getFullPath(); if (path.isEmpty()) { + emit previewCompleted(); return; } @@ -86,6 +87,7 @@ void WallReel::Core::WallpaperService::_doPreview(const Image::Data& imageData) }; auto command = Utils::renderTemplate(m_actionConfig.onPreview, variables); if (command.isEmpty()) { + emit previewCompleted(); return; } @@ -96,10 +98,11 @@ void WallReel::Core::WallpaperService::_doPreview(const Image::Data& imageData) m_previewProcess->start("sh", QStringList() << "-c" << command); } -void WallReel::Core::WallpaperService::_doSelect(const Image::Data& imageData) { +void WallReel::Core::Service::WallpaperService::_doSelect(const Image::Data& imageData) { QString path = imageData.getFullPath(); if (path.isEmpty()) { + emit selectCompleted(); return; } @@ -113,18 +116,21 @@ void WallReel::Core::WallpaperService::_doSelect(const Image::Data& imageData) { }; auto command = Utils::renderTemplate(m_actionConfig.onSelected, variables); if (command.isEmpty()) { + emit selectCompleted(); return; } m_selectProcess->start("sh", QStringList() << "-c" << command); } -void WallReel::Core::WallpaperService::_doRestore() { +void WallReel::Core::Service::WallpaperService::_doRestore() { if (m_actionConfig.onRestore.isEmpty()) { + emit restoreCompleted(); return; } const QString command = Utils::renderTemplate(m_actionConfig.onRestore, m_actionConfig.saveState); if (command.isEmpty()) { + emit restoreCompleted(); return; } m_restoreProcess->start("sh", QStringList() << "-c" << command); diff --git a/WallReel/Core/wallpaperservice.hpp b/WallReel/Core/Service/wallpaper.hpp similarity index 93% rename from WallReel/Core/wallpaperservice.hpp rename to WallReel/Core/Service/wallpaper.hpp index a323292..8ab0ff8 100644 --- a/WallReel/Core/wallpaperservice.hpp +++ b/WallReel/Core/Service/wallpaper.hpp @@ -7,7 +7,7 @@ #include "Config/data.hpp" #include "Image/data.hpp" -namespace WallReel::Core { +namespace WallReel::Core::Service { class WallpaperService : public QObject { Q_OBJECT @@ -40,6 +40,6 @@ class WallpaperService : public QObject { QProcess* m_restoreProcess; }; -} // namespace WallReel::Core +} // namespace WallReel::Core::Service #endif // WALLREEL_WALLPAPERSERVICE_HPP diff --git a/WallReel/UI/CMakeLists.txt b/WallReel/UI/CMakeLists.txt index ff8924a..68f34ee 100644 --- a/WallReel/UI/CMakeLists.txt +++ b/WallReel/UI/CMakeLists.txt @@ -5,6 +5,13 @@ qt_add_qml_module(${UILIB_NAME} Main.qml Pages/LoadingScreen.qml Pages/CarouselScreen.qml + Providers/CarouselProvider.qml Modules/Carousel.qml Modules/TitleBar.qml + Modules/SearchBar.qml + Modules/SortControl.qml + Modules/ColorControl.qml + Modules/TopBar.qml + Modules/BottomBar.qml + Components/WRTextButton.qml ) diff --git a/WallReel/UI/Components/WRTextButton.qml b/WallReel/UI/Components/WRTextButton.qml new file mode 100644 index 0000000..94a6a0f --- /dev/null +++ b/WallReel/UI/Components/WRTextButton.qml @@ -0,0 +1,24 @@ +import QtQuick +import QtQuick.Controls + +Button { + //// Inherited from Button: + // bool enabled + // var onClicked + + property alias displayedText: label.text + property color foregroundColor: "#89b4fa" + property color disabledColor: "#585b70" + + flat: true + focusPolicy: Qt.NoFocus + + contentItem: Label { + id: label + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: parent.enabled ? parent.foregroundColor : parent.disabledColor + } + +} diff --git a/WallReel/UI/Main.qml b/WallReel/UI/Main.qml index 052fe2c..6934ff1 100644 --- a/WallReel/UI/Main.qml +++ b/WallReel/UI/Main.qml @@ -11,7 +11,7 @@ ApplicationWindow { // minimumHeight: height // maximumHeight: height visible: true - title: qsTr("Hello World") + title: qsTr("WallReel") LoadingScreen { visible: ImageModel.isLoading @@ -25,7 +25,6 @@ ApplicationWindow { active: !ImageModel.isLoading sourceComponent: CarouselScreen { - visible: !ImageModel.isLoading } } diff --git a/WallReel/UI/Modules/BottomBar.qml b/WallReel/UI/Modules/BottomBar.qml new file mode 100644 index 0000000..7417a0f --- /dev/null +++ b/WallReel/UI/Modules/BottomBar.qml @@ -0,0 +1,78 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import WallReel.UI.Components + +Item { + id: root + + required property bool actionsEnabled + property alias availablePalettes: colorCtrl.availablePalettes + property alias selectedPalette: colorCtrl.selectedPalette + property alias availableColors: colorCtrl.availableColors + property alias selectedColor: colorCtrl.selectedColor + property alias colorName: colorCtrl.colorName + property alias colorHex: colorCtrl.colorHex + property alias colorValue: colorCtrl.colorValue + + signal paletteSelected(var palette) + signal colorSelected(var colorItem) + signal restoreClicked() + signal confirmClicked() + signal cancelClicked() + + implicitHeight: row.implicitHeight + + RowLayout { + id: row + + anchors.fill: parent + spacing: 12 + + ColorControl { + id: colorCtrl + + Layout.alignment: Qt.AlignVCenter + onPaletteSelected: (p) => { + return root.paletteSelected(p); + } + onColorSelected: (c) => { + return root.colorSelected(c); + } + } + + // Flexible spacer + Item { + Layout.fillWidth: true + } + + // Action buttons + RowLayout { + spacing: 20 + Layout.alignment: Qt.AlignVCenter + + WRTextButton { + displayedText: "Restore" + onClicked: root.restoreClicked() + enabled: root.actionsEnabled + foregroundColor: "#fab387" + } + + WRTextButton { + displayedText: "Confirm" + onClicked: root.confirmClicked() + enabled: root.actionsEnabled + foregroundColor: "#a6e3a1" + } + + WRTextButton { + displayedText: "Cancel" + onClicked: root.cancelClicked() + foregroundColor: "#f38ba8" + } + + } + + } + +} diff --git a/WallReel/UI/Modules/ColorControl.qml b/WallReel/UI/Modules/ColorControl.qml new file mode 100644 index 0000000..22a6f7f --- /dev/null +++ b/WallReel/UI/Modules/ColorControl.qml @@ -0,0 +1,87 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: root + + property var availablePalettes: [] + property var selectedPalette: null + property var availableColors: [] // list of ColorItem { name, color } + property var selectedColor: null // null = "Auto" + property string colorName: "Auto" + property string colorHex: "" + property color colorValue: "transparent" + + signal paletteSelected(var palette) + signal colorSelected(var colorItem) + + implicitWidth: row.implicitWidth + implicitHeight: row.implicitHeight + + RowLayout { + id: row + + anchors.fill: parent + spacing: 6 + + ComboBox { + id: paletteCombo + + implicitWidth: 110 + // -1 means nothing selected + currentIndex: -1 + displayText: currentIndex < 0 ? "— palette —" : currentText + model: root.availablePalettes.map((p) => { + return p.name; + }) + onActivated: (idx) => { + root.paletteSelected(idx >= 0 ? root.availablePalettes[idx] : null); + } + } + + ComboBox { + id: colorCombo + + implicitWidth: 100 + enabled: root.availableColors.length > 0 + model: ["Auto"].concat(root.availableColors.map((c) => { + return c.name; + })) + currentIndex: { + if (!root.selectedColor) + return 0; + + const idx = root.availableColors.findIndex((c) => { + return c.name === root.selectedColor.name; + }); + return idx >= 0 ? idx + 1 : 0; + } + onActivated: (idx) => { + root.colorSelected(idx === 0 ? null : root.availableColors[idx - 1]); + } + } + + Rectangle { + width: 14 + height: 14 + radius: 7 + color: root.colorValue + border.color: palette.mid + border.width: 1 + } + + Label { + font.pixelSize: 11 + text: { + if (root.colorHex.length > 0) + return root.colorName.length > 0 ? root.colorName + " " + root.colorHex : root.colorHex; + + return root.colorName == "Auto" ? "" : root.colorName; + } + visible: root.colorName.length > 0 || root.colorHex.length > 0 + } + + } + +} diff --git a/WallReel/UI/Modules/SearchBar.qml b/WallReel/UI/Modules/SearchBar.qml new file mode 100644 index 0000000..8f3d92f --- /dev/null +++ b/WallReel/UI/Modules/SearchBar.qml @@ -0,0 +1,82 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: root + + readonly property string typedText: field.text + + // Emitted when leave the field. + signal dismissed() + + function requestFocus() { + field.forceActiveFocus(); + } + + implicitWidth: row.implicitWidth + implicitHeight: row.implicitHeight + + RowLayout { + id: row + + anchors.fill: parent + spacing: 2 + + ToolButton { + icon.name: "edit-find" + icon.width: 16 + icon.height: 16 + focusPolicy: Qt.NoFocus + onClicked: root.requestFocus() + ToolTip.visible: hovered + ToolTip.delay: 600 + ToolTip.text: "Search (/)" + } + + TextField { + id: field + + Layout.preferredWidth: activeFocus || text.length > 0 ? 180 : 0 + clip: true + placeholderText: "Search…" + leftPadding: 6 + rightPadding: 6 + Keys.onReturnPressed: { + focus = false; + root.dismissed(); + } + Keys.onEscapePressed: { + text = ""; + focus = false; + root.dismissed(); + } + + Behavior on Layout.preferredWidth { + NumberAnimation { + duration: 160 + easing.type: Easing.OutCubic + } + + } + + } + + ToolButton { + icon.name: "edit-clear" + icon.width: 16 + icon.height: 16 + focusPolicy: Qt.NoFocus + visible: field.text.length > 0 + onClicked: { + field.text = ""; + field.forceActiveFocus(); + } + ToolTip.visible: hovered + ToolTip.delay: 600 + ToolTip.text: "Clear" + } + + } + +} diff --git a/WallReel/UI/Modules/SortControl.qml b/WallReel/UI/Modules/SortControl.qml new file mode 100644 index 0000000..4276f0b --- /dev/null +++ b/WallReel/UI/Modules/SortControl.qml @@ -0,0 +1,51 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: root + + property var availableSortTypes: [] + property string selectedSortType: "" + property bool isReverse: false + + signal sortTypeSelected(string sortType) + signal isReverseToggled(bool reverse) + + implicitWidth: row.implicitWidth + implicitHeight: row.implicitHeight + + RowLayout { + id: row + + anchors.fill: parent + spacing: 4 + + Label { + text: "Sort by" + font.pixelSize: 12 + } + + ComboBox { + id: sortCombo + + implicitWidth: 90 + model: root.availableSortTypes + currentIndex: root.availableSortTypes.indexOf(root.selectedSortType) + onActivated: root.sortTypeSelected(currentText) + } + + ToolButton { + icon.name: root.isReverse ? "view-sort-descending" : "view-sort-ascending" + icon.width: 16 + icon.height: 16 + focusPolicy: Qt.NoFocus + onClicked: root.isReverseToggled(!root.isReverse) + ToolTip.visible: hovered + ToolTip.delay: 600 + ToolTip.text: root.isReverse ? "Descending order" : "Ascending order" + } + + } + +} diff --git a/WallReel/UI/Modules/TopBar.qml b/WallReel/UI/Modules/TopBar.qml new file mode 100644 index 0000000..3e2006c --- /dev/null +++ b/WallReel/UI/Modules/TopBar.qml @@ -0,0 +1,67 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: root + + property int currentIndex: 0 + property int totalCount: 0 + property string title: "" + property int maxTitleLength: 50 + readonly property string searchText: searchBar.typedText + property alias availableSortTypes: sortCtrl.availableSortTypes + property alias selectedSortType: sortCtrl.selectedSortType + property alias isSortReverse: sortCtrl.isReverse + + signal sortTypeSelected(string sortType) + signal sortReverseToggled(bool reverse) + signal searchDismissed() + + function requestSearchFocus() { + searchBar.requestFocus(); + } + + implicitHeight: row.implicitHeight + + RowLayout { + id: row + + anchors.fill: parent + spacing: 8 + + Label { + text: (root.currentIndex + 1) + " / " + root.totalCount + font.pixelSize: 12 + Layout.preferredWidth: implicitWidth + } + + Label { + text: root.title.length > root.maxTitleLength ? root.title.substring(0, root.maxTitleLength) + "…" : root.title + font.pixelSize: 12 + elide: Text.ElideRight + Layout.fillWidth: true + } + + SearchBar { + id: searchBar + + Layout.alignment: Qt.AlignVCenter + onDismissed: root.searchDismissed() + } + + SortControl { + id: sortCtrl + + Layout.alignment: Qt.AlignVCenter + onSortTypeSelected: (t) => { + return root.sortTypeSelected(t); + } + onIsReverseToggled: (r) => { + return root.sortReverseToggled(r); + } + } + + } + +} diff --git a/WallReel/UI/Pages/CarouselScreen.qml b/WallReel/UI/Pages/CarouselScreen.qml index b71138e..85c7105 100644 --- a/WallReel/UI/Pages/CarouselScreen.qml +++ b/WallReel/UI/Pages/CarouselScreen.qml @@ -1,51 +1,79 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts -import WallReel.Core import WallReel.UI.Modules +import WallReel.UI.Providers Item { id: root + Component.onCompleted: root.forceActiveFocus() Keys.onPressed: (e) => { - if (e.key === Qt.Key_Left) { - if (carousel.currentIndex > 0) - carousel.currentIndex--; + if (e.key === Qt.Key_Slash) { + topBar.requestSearchFocus(); + } else if (e.key === Qt.Key_Left) { + if (provider.currentIndex > 0) + provider.currentIndex--; } else if (e.key === Qt.Key_Right) { - if (carousel.currentIndex < carousel.count - 1) - carousel.currentIndex++; + if (provider.currentIndex < carousel.count - 1) + provider.currentIndex++; - } else if (e.key === Qt.Key_Escape) - Qt.quit(); - else if (e.key === Qt.Key_Return || e.key === Qt.Key_Enter) - ImageModel.selectImage(carousel.currentIndex); + } else if (e.key === Qt.Key_Return || e.key === Qt.Key_Enter) + provider.confirm(); + else if (e.key === Qt.Key_Escape) + provider.cancel(); else e.accepted = false; } - Component.onCompleted: { - ImageModel.previewImage(carousel.currentIndex); - root.forceActiveFocus(); - } Connections { - function onCurrentIndexChanged() { - ImageModel.previewImage(carousel.currentIndex); + function onSearchDismissed() { + root.forceActiveFocus(); } - target: carousel + target: topBar + } + + CarouselProvider { + id: provider + } + + // ViewModel → Carousel sync (assignment, not binding, to avoid breakage) + Connections { + function onCurrentIndexChanged() { + if (carousel.currentIndex !== provider.currentIndex) + carousel.currentIndex = provider.currentIndex; + + } + + target: provider } ColumnLayout { anchors.fill: parent - anchors.margins: 20 - spacing: 20 + anchors.margins: 12 + spacing: 8 + + TopBar { + id: topBar - TitleBar { - title: ImageModel.dataAt(carousel.currentIndex, "imgName") ?? "" - index: carousel.currentIndex - totalCount: carousel.count Layout.fillWidth: true + currentIndex: provider.currentIndex + totalCount: carousel.count + title: provider.focusedName + availableSortTypes: provider.availableSortTypes + selectedSortType: provider.selectedSortType + isSortReverse: provider.isSortReverse + onSortTypeSelected: (t) => { + return provider.setSortType(t); + } + onSortReverseToggled: (r) => { + return provider.setSortReverse(r); + } + onSearchTextChanged: () => { + return provider.setSearchText(topBar.searchText); + } } Carousel { @@ -53,27 +81,33 @@ Item { Layout.fillWidth: true Layout.fillHeight: true - model: ImageModel - itemWidth: Config.imageWidth - itemHeight: Config.imageHeight - focusedItemWidth: Config.imageWidth * Config.imageFocusScale - focusedItemHeight: Config.imageHeight * Config.imageFocusScale + model: provider.imageModel + itemWidth: provider.imageWidth + itemHeight: provider.imageHeight + focusedItemWidth: provider.imageWidth * provider.imageFocusScale + focusedItemHeight: provider.imageHeight * provider.imageFocusScale + onCurrentIndexChanged: { + if (provider.currentIndex !== currentIndex) + provider.currentIndex = currentIndex; + + } MouseArea { anchors.fill: parent onWheel: (e) => { if (e.angleDelta.y > 0) { - if (carousel.currentIndex > 0) - carousel.currentIndex--; + if (provider.currentIndex > 0) + provider.currentIndex--; } else if (e.angleDelta.y < 0) { - if (carousel.currentIndex < carousel.count - 1) - carousel.currentIndex++; + if (provider.currentIndex < carousel.count - 1) + provider.currentIndex++; } } + // Fallthrough to Carousel onPressed: (e) => { - carousel.forceActiveFocus(); + root.forceActiveFocus(); e.accepted = false; } onPositionChanged: (e) => { @@ -87,17 +121,32 @@ Item { } Slider { - id: progressBar - Layout.fillWidth: true from: 0 - to: carousel.count - 1 - value: carousel.currentIndex - onMoved: { - if (carousel.currentIndex !== value) - carousel.currentIndex = value; + to: Math.max(0, carousel.count - 1) + value: provider.currentIndex + onMoved: provider.currentIndex = Math.round(value) + } + BottomBar { + Layout.fillWidth: true + availablePalettes: provider.availablePalettes + selectedPalette: provider.selectedPalette + availableColors: provider.availableColors + selectedColor: provider.selectedColor + colorName: provider.colorName + colorHex: provider.colorHex + colorValue: provider.colorValue + onPaletteSelected: (p) => { + return provider.selectPalette(p); } + onColorSelected: (c) => { + return provider.selectColor(c); + } + onRestoreClicked: provider.restore() + onConfirmClicked: provider.confirm() + onCancelClicked: provider.cancel() + actionsEnabled: !provider.isProcessing } } diff --git a/WallReel/UI/Pages/LoadingScreen.qml b/WallReel/UI/Pages/LoadingScreen.qml index 2637e95..6d02bf7 100644 --- a/WallReel/UI/Pages/LoadingScreen.qml +++ b/WallReel/UI/Pages/LoadingScreen.qml @@ -1,5 +1,6 @@ import QtQuick import QtQuick.Controls +import WallReel.Core Item { property int currentValue: 0 diff --git a/WallReel/UI/Providers/CarouselProvider.qml b/WallReel/UI/Providers/CarouselProvider.qml new file mode 100644 index 0000000..4a98925 --- /dev/null +++ b/WallReel/UI/Providers/CarouselProvider.qml @@ -0,0 +1,86 @@ +import QtQuick +import WallReel.Core + +QtObject { + id: root + + //// Image model + readonly property var imageModel: ImageModel + readonly property bool isLoading: ImageModel.isLoading + readonly property int processedCount: ImageModel.processedCount + readonly property int totalCount: ImageModel.totalCount + // Image display dimensions (from Config) + readonly property int imageWidth: Config.imageWidth + readonly property int imageHeight: Config.imageHeight + readonly property real imageFocusScale: Config.imageFocusScale + // Shared carousel selection state + property int currentIndex: 0 + // Image name + readonly property string focusedName: ImageModel.focusedName + //// Sort + readonly property var availableSortTypes: ["None", "Name", "Date", "Size"] + property string selectedSortType: ImageModel.currentSortType + property bool isSortReverse: ImageModel.currentSortReverse + //// Palette / Color + readonly property var availablePalettes: [] + property var selectedPalette: null // PaletteItem | null + readonly property var availableColors: selectedPalette ? selectedPalette.colors : [] + property var selectedColor: null // ColorItem | null (null means "auto") + readonly property string colorName: selectedColor ? selectedColor.name : "Auto" + readonly property string colorHex: selectedColor ? selectedColor.color.toString().toUpperCase() : "" + readonly property color colorValue: selectedColor ? selectedColor.color : "transparent" + //// Actions state + readonly property bool isProcessing: ServiceManager.isProcessing + + // Actions + function confirm() { + ServiceManager.selectWallpaper(currentIndex); + } + + function restore() { + ServiceManager.restore(); + } + + function cancel() { + Qt.quit(); + } + + function focusSearch() { + searchBar.requestFocus(); + } + + function setSortType(type) { + ImageModel.currentSortType = type; + } + + function setSortReverse(reverse) { + ImageModel.currentSortReverse = reverse; + } + + function selectPalette(palette) { + selectedPalette = palette; + selectedColor = null; // reset color when palette changes + } + + function selectColor(colorItem) { + selectedColor = colorItem; + } + + function setSearchText(text) { + ImageModel.setSearchText(text); + currentIndex = 0; // reset index when search text changes + } + + onCurrentIndexChanged: () => { + if (!isLoading) { + ServiceManager.previewWallpaper(currentIndex); + ImageModel.focusOnIndex(currentIndex); + } + } + Component.onCompleted: () => { + if (!isLoading) { + ServiceManager.previewWallpaper(currentIndex); + ImageModel.focusOnIndex(currentIndex); + } + } +} diff --git a/WallReel/main.cpp b/WallReel/main.cpp index f036258..b1edcd1 100644 --- a/WallReel/main.cpp +++ b/WallReel/main.cpp @@ -7,10 +7,10 @@ #include "Core/Image/provider.hpp" #include "Core/Palette/data.hpp" #include "Core/Palette/manager.hpp" +#include "Core/Service/manager.hpp" #include "Core/Utils/misc.hpp" #include "Core/appoptions.hpp" #include "Core/logger.hpp" -#include "Core/wallpaperservice.hpp" #include "version.h" using namespace WallReel::Core; @@ -65,19 +65,23 @@ int main(int argc, char* argv[]) { "ImageModel", imageModel); - auto wallpaperService = new WallpaperService( + auto Service = new Service::Manager( config->getActionConfig(), - config); - QObject::connect( - imageModel, - &Image::Model::imageSelected, - wallpaperService, - &WallpaperService::select); - QObject::connect( - imageModel, - &Image::Model::imagePreviewed, - wallpaperService, - &WallpaperService::preview); + *imageModel, + imageModel); + qmlRegisterSingletonInstance( + COREMODULE_URI, + MODULE_VERSION_MAJOR, + MODULE_VERSION_MINOR, + "ServiceManager", + Service); + if (config->getActionConfig().quitOnSelected) { + QObject::connect( + Service, + &Service::Manager::selectCompleted, + &a, + []() { QCoreApplication::quit(); }); + } QObject::connect( &engine,