diff --git a/WallReel/Core/CMakeLists.txt b/WallReel/Core/CMakeLists.txt index fb1d23a..4f307fb 100644 --- a/WallReel/Core/CMakeLists.txt +++ b/WallReel/Core/CMakeLists.txt @@ -3,9 +3,11 @@ qt_add_qml_module(${CORELIB_NAME} URI ${COREMODULE_URI} VERSION ${MODULE_VERSION_MAJOR}.${MODULE_VERSION_MINOR} SOURCES + Provider/carousel.hpp Provider/bootstrap.hpp Cache/manager.hpp Cache/manager.cpp Image/data.hpp Image/data.cpp - Image/model.hpp Image/model.cpp + Image/model.hpp Image/model.cpp Image/proxymodel.cpp + Image/manager.hpp Image/manager.cpp Palette/data.hpp Palette/manager.hpp Palette/manager.cpp Palette/domcolor.hpp Palette/domcolor.cpp diff --git a/WallReel/Core/Cache/manager.cpp b/WallReel/Core/Cache/manager.cpp index 74968e2..0e81316 100644 --- a/WallReel/Core/Cache/manager.cpp +++ b/WallReel/Core/Cache/manager.cpp @@ -38,12 +38,6 @@ Manager::~Manager() { } WR_DEBUG(u"Closing %1 cache db connection(s)"_s.arg(names.size())); for (const QString& connName : std::as_const(names)) { - { - // Scope: release the QSqlDatabase copy before removeDatabase() - QSqlDatabase db = QSqlDatabase::database(connName, false); - if (db.isOpen()) - db.close(); - } QSqlDatabase::removeDatabase(connName); } } diff --git a/WallReel/Core/Config/data.hpp b/WallReel/Core/Config/data.hpp index 6cc7a7f..df64e24 100644 --- a/WallReel/Core/Config/data.hpp +++ b/WallReel/Core/Config/data.hpp @@ -46,8 +46,8 @@ // style.window_width number 750 Initial window width // style.window_height number 500 Initial window height // -// sort.type string "name" Sorting type: "none", "name", "date", "size" -// sort.reverse boolean false Whether to reverse the sorting order +// sort.type string "date" Sorting type: "name", "date", "size" +// sort.descending boolean true Whether to reverse the sorting order // Normal order: name: lexicographical, e.g. "a.jpg" before "b.jpg" // date: older before newer // size: smaller before larger @@ -57,12 +57,38 @@ namespace WallReel::Core::Config { inline const QString s_DefaultConfigFileName = "config.json"; enum class SortType : int { - None = 0, // "none" - Name, // "name" - Date, // "date" - Size, // "size" + Name, // "name" + Date, // "date" + Size, // "size" }; +inline const QStringList s_availableSortTypes = {"Name", "Date", "Size"}; + +inline QString sortTypeToString(SortType type) { + switch (type) { + case SortType::Name: + return "Name"; + case SortType::Date: + return "Date"; + case SortType::Size: + return "Size"; + default: + return "Date"; + } +} + +inline SortType stringToSortType(const QString& str) { + if (str.compare("name", Qt::CaseInsensitive) == 0) { + return SortType::Name; + } else if (str.compare("date", Qt::CaseInsensitive) == 0) { + return SortType::Date; + } else if (str.compare("size", Qt::CaseInsensitive) == 0) { + return SortType::Size; + } else { + return SortType::Date; // default + } +} + struct WallpaperConfigItems { struct WallpaperDirConfigItem { QString path; @@ -118,8 +144,8 @@ struct StyleConfigItems { }; struct SortConfigItems { - SortType type = SortType::Name; - bool reverse = false; + SortType type = SortType::Date; + bool descending = true; }; } // namespace WallReel::Core::Config diff --git a/WallReel/Core/Config/manager.cpp b/WallReel/Core/Config/manager.cpp index 8125f65..1f78707 100644 --- a/WallReel/Core/Config/manager.cpp +++ b/WallReel/Core/Config/manager.cpp @@ -298,9 +298,7 @@ void WallReel::Core::Config::Manager::_loadSortConfig(const QJsonObject& root) { const auto& val = config["type"]; if (val.isString()) { QString type = val.toString().toLower(); - if (type == "none") { - m_sortConfig.type = SortType::None; - } else if (type == "name") { + if (type == "name") { m_sortConfig.type = SortType::Name; } else if (type == "date") { m_sortConfig.type = SortType::Date; @@ -311,10 +309,10 @@ void WallReel::Core::Config::Manager::_loadSortConfig(const QJsonObject& root) { } } } - if (config.contains("reverse")) { - const auto& val = config["reverse"]; + if (config.contains("descending")) { + const auto& val = config["descending"]; if (val.isBool()) { - m_sortConfig.reverse = val.toBool(); + m_sortConfig.descending = val.toBool(); } } } diff --git a/WallReel/Core/Config/manager.hpp b/WallReel/Core/Config/manager.hpp index a7628c3..248e922 100644 --- a/WallReel/Core/Config/manager.hpp +++ b/WallReel/Core/Config/manager.hpp @@ -19,12 +19,6 @@ namespace WallReel::Core::Config { class Manager : public QObject { Q_OBJECT - Q_PROPERTY(int imageWidth READ getImageWidth CONSTANT) - Q_PROPERTY(int imageHeight READ getImageHeight CONSTANT) - Q_PROPERTY(double imageFocusScale READ getImageFocusScale CONSTANT) - Q_PROPERTY(int windowWidth READ getWindowWidth CONSTANT) - Q_PROPERTY(int windowHeight READ getWindowHeight CONSTANT) - public: /** * @brief Construct a new Manager object @@ -63,23 +57,6 @@ class Manager : public QObject { const SortConfigItems& getSortConfig() const { return m_sortConfig; } - // Getters for Q_PROPERTY - - int getImageWidth() const { return m_styleConfig.imageWidth; } - - int getImageHeight() const { return m_styleConfig.imageHeight; } - - double getImageFocusScale() const { return m_styleConfig.imageFocusScale; } - - int getWindowWidth() const { return m_styleConfig.windowWidth; } - - int getWindowHeight() const { return m_styleConfig.windowHeight; } - - /** - * @brief A quick snippet to get the focused image size as a QSize - * - * @return QSize - */ QSize getFocusImageSize() const { return QSize{m_styleConfig.imageWidth, m_styleConfig.imageHeight} * m_styleConfig.imageFocusScale; } diff --git a/WallReel/Core/Image/manager.cpp b/WallReel/Core/Image/manager.cpp new file mode 100644 index 0000000..9cdfd89 --- /dev/null +++ b/WallReel/Core/Image/manager.cpp @@ -0,0 +1,113 @@ +#include "manager.hpp" + +#include +#include + +#include "data.hpp" +#include "logger.hpp" + +WALLREEL_DECLARE_SENDER("ImageManager") + +WallReel::Core::Image::Manager::Manager( + Cache::Manager& cacheMgr, + const QSize& thumbnailSize, + QObject* parent) + : QObject(parent), + m_cacheMgr(cacheMgr), + m_thumbnailSize(thumbnailSize) { + m_dataModel = new Model(this); + m_proxyModel = new ProxyModel(this); + m_proxyModel->setSourceModel(m_dataModel); + + connect( + &m_watcher, + &QFutureWatcher::finished, + this, + &Manager::_onProcessingFinished); + connect( + &m_progressUpdateTimer, + &QTimer::timeout, + this, + [this]() { + emit processedCountChanged(); + }); +} + +WallReel::Core::Image::Manager::~Manager() { + m_watcher.cancel(); + m_watcher.waitForFinished(); +} + +void WallReel::Core::Image::Manager::loadAndProcess(const QStringList& paths) { + if (m_isLoading) { + WR_WARN("Already loading images. Ignoring new load request."); + return; + } + m_isLoading = true; + emit isLoadingChanged(); + + _clearData(); + + m_processedCount = 0; + m_progressUpdateTimer.start(s_ProgressUpdateIntervalMs); + // These are all small objects so capturing by value should be fine + const auto thumbnailSize = m_thumbnailSize; + const auto counterPtr = &m_processedCount; + const auto cacheMgr = &m_cacheMgr; + QFuture future = + QtConcurrent::mapped(paths, [thumbnailSize, counterPtr, cacheMgr](const QString& path) { + auto data = Data::create(path, thumbnailSize, *cacheMgr); + counterPtr->fetch_add(1, std::memory_order_relaxed); + return data; + }); + m_watcher.setFuture(future); + emit totalCountChanged(); +} + +void WallReel::Core::Image::Manager::stop() { + if (m_isLoading) { + WR_INFO("Stopping image loading..."); + m_watcher.cancel(); + } else { + WR_WARN("No loading operation to stop."); + } +} + +void WallReel::Core::Image::Manager::_clearData() { + m_dataModel->clearData(); +} + +void WallReel::Core::Image::Manager::_onProgressValueChanged(int value) { + Q_UNUSED(value); + emit processedCountChanged(); +} + +void WallReel::Core::Image::Manager::_onProcessingFinished() { + auto results = m_watcher.future().results(); + + QList filteredResults; + filteredResults.reserve(results.size()); + for (Data* data : results) { + if (data && data->isValid()) { + filteredResults.append(data); + m_dataMap.insert(data->getId(), data); + } else { + if (data) { + WR_WARN(QString("Failed to load image data for path '%1'").arg(data->getFullPath())); + delete data; + } + } + } + + m_dataModel->insertData(filteredResults); + + WR_INFO("Finished loading images. Total valid images: " + QString::number(filteredResults.size())); + + m_isLoading = false; + m_progressUpdateTimer.stop(); + emit processedCountChanged(); + // QTimer::singleShot(s_IsLoadingUpdateIntervalMs, this, [this]() { + // emit isLoadingChanged(); + // }); + emit isLoadingChanged(); +} diff --git a/WallReel/Core/Image/manager.hpp b/WallReel/Core/Image/manager.hpp new file mode 100644 index 0000000..bb1a0a0 --- /dev/null +++ b/WallReel/Core/Image/manager.hpp @@ -0,0 +1,91 @@ +#ifndef WALLREEL_IMAGEMANAGER_HPP +#define WALLREEL_IMAGEMANAGER_HPP + +#include +#include +#include +#include +#include + +#include "Cache/manager.hpp" +#include "data.hpp" +#include "model.hpp" + +namespace WallReel::Core::Image { + +class Manager : public QObject { + Q_OBJECT + + public: + // Constructor / Destructor + + Manager( + Cache::Manager& cacheMgr, + const QSize& thumbnailSize, + QObject* parent = nullptr); + + ~Manager(); + + Image::ProxyModel* model() const { return m_proxyModel; } + + 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 setSortType(Config::SortType type) { m_proxyModel->setSortType(type); } + + void setSortDescending(bool descending) { m_proxyModel->setSortDescending(descending); } + + void setSearchText(const QString& text) { m_proxyModel->setSearchText(text); } + + Config::SortType sortType() const { return m_proxyModel->getSortType(); } + + bool sortDescending() const { return m_proxyModel->isSortDescending(); } + + QString searchText() const { return m_proxyModel->getSearchText(); } + + void loadAndProcess(const QStringList& paths); + + void stop(); + + Image::Data* imageAt(const QString& id) { + if (m_dataMap.contains(id)) { + return m_dataMap[id]; + } + return nullptr; + } + + private: + void _clearData(); + + signals: + // Properties + void isLoadingChanged(); + void processedCountChanged(); + void totalCountChanged(); + + private slots: + void _onProgressValueChanged(int value); + void _onProcessingFinished(); + + private: + Model* m_dataModel; + ProxyModel* m_proxyModel; + QHash m_dataMap; + + Cache::Manager& m_cacheMgr; + QSize m_thumbnailSize; + + QFutureWatcher m_watcher; + bool m_isLoading = false; + + std::atomic m_processedCount{0}; + QTimer m_progressUpdateTimer; + static constexpr int s_ProgressUpdateIntervalMs = 30; +}; + +} // namespace WallReel::Core::Image + +#endif // WALLREEL_IMAGEMANAGER_HPP diff --git a/WallReel/Core/Image/model.cpp b/WallReel/Core/Image/model.cpp index f760c87..163aa25 100644 --- a/WallReel/Core/Image/model.cpp +++ b/WallReel/Core/Image/model.cpp @@ -1,72 +1,32 @@ #include "model.hpp" -#include -#include - -#include "data.hpp" #include "logger.hpp" WALLREEL_DECLARE_SENDER("ImageModel") -WallReel::Core::Image::Model::Model( - const Config::SortConfigItems& sortConfig, - Cache::Manager& cacheMgr, - const QSize& thumbnailSize, - QObject* parent) - : QAbstractListModel(parent), - m_sortConfig(sortConfig), - m_cacheMgr(cacheMgr), - m_thumbnailSize(thumbnailSize), - m_currentSortType(sortConfig.type), - m_currentSortReverse(sortConfig.reverse) { - connect( - &m_watcher, - &QFutureWatcher::finished, - this, - &Model::_onProcessingFinished); - connect( - &m_progressUpdateTimer, - &QTimer::timeout, - this, - [this]() { - emit progressChanged(); - }); +namespace WallReel::Core::Image { - // Pipeline: sort -> filter -> update properties - connect(this, &Model::currentSortTypeChanged, this, &Model::_onSortMethodChanged); - connect(this, &Model::currentSortReverseChanged, this, &Model::_onSortMethodChanged); - connect(this, &Model::searchTextChanged, this, &Model::_onSearchTextChanged); - connect(this, &Model::focusedImageChanged, this, &Model::_updateFocusedProperties); +Model::Model(QObject* parent) : QAbstractListModel(parent) {} - m_sortIndices.resize(4); // None, Name, Date, Size -} - -WallReel::Core::Image::Model::~Model() { - m_watcher.cancel(); - m_watcher.waitForFinished(); +Model::~Model() { qDeleteAll(m_data); + m_data.clear(); } -int WallReel::Core::Image::Model::rowCount(const QModelIndex& parent) const { +int Model::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { return 0; } - return m_filteredIndices.count(); + return m_data.count(); } -QVariant WallReel::Core::Image::Model::data(const QModelIndex& index, int role) const { - if (!index.isValid() || index.row() >= m_filteredIndices.count()) { +QVariant Model::data(const QModelIndex& index, int role) const { + if (!index.isValid() || index.row() >= m_data.count() || index.row() < 0) { WR_DEBUG("Invalid index requested: " + QString::number(index.row())); return QVariant(); } - int actualIndex = _convertProxyIndex(index.row()); - if (actualIndex < 0 || actualIndex >= m_data.count()) { - WR_DEBUG("Actual index out of bounds: " + QString::number(actualIndex)); - return QVariant(); - } - // WR_DEBUG("Data requested for index: " + QString::number(index.row()) + ", actual index: " + QString::number(actualIndex) + ", role: " + QString::number(role)); - const auto& item = m_data[actualIndex]; + const auto& item = m_data[index.row()]; switch (role) { case IdRole: return item->getId(); @@ -76,89 +36,24 @@ QVariant WallReel::Core::Image::Model::data(const QModelIndex& index, int role) return item->getFullPath(); case NameRole: return item->getFileName(); + case SizeRole: + return item->getSize(); + case DateRole: + return item->getLastModified(); + case DomColorRole: + return item->getDominantColor(); default: return QVariant(); } } -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) { - // WR_DEBUG("Search text changed: " + text); - if (m_searchText != text) { - m_searchText = text; - _applySearchFilter(); - emit searchTextChanged(); - } -} - -WallReel::Core::Image::Data* WallReel::Core::Image::Model::imageAt(int index) { - if (index < 0 || index >= m_filteredIndices.count()) { - WR_DEBUG("Invalid index requested: " + QString::number(index)); - return nullptr; - } - int actualIndex = _convertProxyIndex(index); - if (actualIndex < 0 || actualIndex >= m_data.count()) { - WR_DEBUG("Actual index out of bounds: " + QString::number(actualIndex)); - return nullptr; - } - return m_data[actualIndex]; -} - -WallReel::Core::Image::Data* WallReel::Core::Image::Model::focusedImage() { - return imageAt(m_focusedIndex); -} - -QVariant WallReel::Core::Image::Model::dataAt(int index, const QString& roleName) const { - if (index < 0 || index >= m_filteredIndices.count()) { +QVariant Model::dataAt(int index, const QString& roleName) const { + if (index < 0 || index >= m_data.count()) { WR_DEBUG("Invalid index requested: " + QString::number(index)); return QVariant(); } - int actualIndex = _convertProxyIndex(index); - if (actualIndex < 0 || actualIndex >= m_data.count()) { - WR_DEBUG("Actual index out of bounds: " + QString::number(actualIndex)); - return QVariant(); - } - const auto& item = m_data[actualIndex]; + const auto& item = m_data[index]; if (roleName == "imgId") { return item->getId(); } else if (roleName == "imgUrl") { @@ -167,203 +62,34 @@ QVariant WallReel::Core::Image::Model::dataAt(int index, const QString& roleName return item->getFullPath(); } else if (roleName == "imgName") { return item->getFileName(); + } else if (roleName == "imgSize") { + return item->getSize(); + } else if (roleName == "imgDate") { + return item->getLastModified(); + } else if (roleName == "imgDomColor") { + return item->getDominantColor(); } else { return QVariant(); } } -void WallReel::Core::Image::Model::loadAndProcess(const QStringList& paths) { - if (m_isLoading) { - WR_WARN("Already loading images. Ignoring new load request."); +void Model::insertData(const QList& newData) { + if (newData.isEmpty()) { return; } - m_isLoading = true; - emit isLoadingChanged(); - - _clearData(); - - m_processedCount = 0; - m_progressUpdateTimer.start(s_ProgressUpdateIntervalMs); - // These are all small objects so capturing by value should be fine - const auto thumbnailSize = m_thumbnailSize; - const auto counterPtr = &m_processedCount; - const auto cacheMgr = &m_cacheMgr; - QFuture future = - QtConcurrent::mapped(paths, [thumbnailSize, counterPtr, cacheMgr](const QString& path) { - auto data = Data::create(path, thumbnailSize, *cacheMgr); - counterPtr->fetch_add(1, std::memory_order_relaxed); - return data; - }); - m_watcher.setFuture(future); - emit totalCountChanged(); + beginInsertRows(QModelIndex(), m_data.count(), m_data.count() + newData.count() - 1); + m_data.append(newData); + endInsertRows(); } -void WallReel::Core::Image::Model::focusOnIndex(int index) { - if (index < 0 || index >= m_filteredIndices.count()) { - WR_DEBUG("Invalid index to focus on: " + QString::number(index)); +void Model::clearData() { + if (m_data.isEmpty()) { return; } - int actualIndex = _convertProxyIndex(index); - if (actualIndex < 0 || actualIndex >= m_data.count()) { - WR_DEBUG("Actual index out of bounds for focus: " + QString::number(actualIndex)); - return; - } - if (m_focusedIndex != index) { - m_focusedIndex = index; - emit focusedIndexChanged(); - emit focusedImageChanged(); - _updateFocusedProperties(); - } -} - -void WallReel::Core::Image::Model::stop() { - if (m_isLoading) { - WR_INFO("Stopping image loading..."); - m_watcher.cancel(); - } else { - WR_WARN("No loading operation to stop."); - } -} - -int WallReel::Core::Image::Model::_convertProxyIndex(int proxyIndex) const { - if (proxyIndex < 0 || proxyIndex >= m_filteredIndices.size()) { - WR_DEBUG("Invalid proxy index requested: " + QString::number(proxyIndex)); - return -1; - } - return m_filteredIndices[proxyIndex]; -} - -void WallReel::Core::Image::Model::_clearData() { beginResetModel(); 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::_updateFocusedProperties() { - if (m_focusedIndex < 0 || m_focusedIndex >= m_filteredIndices.size()) { - m_focusedName = ""; - } else { - int actualIndex = _convertProxyIndex(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(bool informView) { - const auto& sortedIndices = m_sortIndices[static_cast(m_currentSortType)]; - int srcPos = 0, resPos = 0; - for (; srcPos < sortedIndices.size(); ++srcPos) { - int actualIndex = m_currentSortReverse ? sortedIndices[sortedIndices.size() - 1 - srcPos] : sortedIndices[srcPos]; - const auto& item = m_data[actualIndex]; - if (item->getFileName().contains(m_searchText, Qt::CaseInsensitive)) { - if (resPos >= m_filteredIndices.size() || m_filteredIndices[resPos] != actualIndex) { - break; - } - resPos++; - } - } - if (resPos == m_filteredIndices.size() && srcPos == sortedIndices.size()) { - return; // No change in filtered results - } - if (informView) { - emit layoutAboutToBeChanged(); - } - m_filteredIndices.resize(resPos); - for (int i = srcPos; i < sortedIndices.size(); ++i) { - int actualIndex = m_currentSortReverse ? sortedIndices[sortedIndices.size() - 1 - i] : sortedIndices[i]; - const auto& item = m_data[actualIndex]; - if (item->getFileName().contains(m_searchText, Qt::CaseInsensitive)) { - m_filteredIndices.append(actualIndex); - } - } - if (informView) { - emit layoutChanged(); - } -} - -void WallReel::Core::Image::Model::_onProgressValueChanged(int value) { - Q_UNUSED(value); - emit progressChanged(); -} - -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); - } else { - WR_WARN("Failed to load image: " + (data ? data->getFullPath() : "null")); - delete data; - data = nullptr; - } - } - - for (int i = 0; i < m_sortIndices.size(); ++i) { - _updateSortIndices(static_cast(i)); - } - - _applySearchFilter(false); - - endResetModel(); - - WR_INFO("Finished loading images. Total valid images: " + QString::number(m_data.count())); - - m_isLoading = false; - m_progressUpdateTimer.stop(); - emit progressChanged(); - // QTimer::singleShot(s_IsLoadingUpdateIntervalMs, this, [this]() { - // emit isLoadingChanged(); - // }); - emit isLoadingChanged(); -} - -void WallReel::Core::Image::Model::_onSortMethodChanged() { - _applySearchFilter(); - emit focusedImageChanged(); -} - -void WallReel::Core::Image::Model::_onSearchTextChanged() { - emit focusedImageChanged(); -} +} // namespace WallReel::Core::Image diff --git a/WallReel/Core/Image/model.hpp b/WallReel/Core/Image/model.hpp index 974dd86..2b0d72d 100644 --- a/WallReel/Core/Image/model.hpp +++ b/WallReel/Core/Image/model.hpp @@ -2,75 +2,25 @@ #define WALLREEL_IMAGEMODEL_HPP #include -#include -#include -#include -#include +#include -#include "Cache/manager.hpp" #include "Config/data.hpp" #include "data.hpp" -// Development note -/* -What "Proxy index" is: - -There are currently three layers of indices in the Model: -1. Actual index: The index of the image in the original data list (m_data), the order is not - guaranteed and can be considered random. -2. Sorted index: The index of the image after sorting, which is stored in m_sortIndices based on - different sort types. m_sortIndices are precomputed and does not change unless - m_data changes. In practice, the choise of which mapping from m_sortIndices to use - is determined by m_currentSortType. -3. Filtered index: The final mapping from the index exposed to the QML view to the actual data index, - which is stored in m_filteredIndices. m_filteredIndices is updated each time when - the sort type / sort order / search text changes, and only informs the layout to - update when its content actually changes. - -Therefore, when acquiring data, the "proxied" index must first be converted to the "actual" index -by looking up m_filteredIndices, and then the actual data can be accessed from m_data. - -*/ - namespace WallReel::Core::Image { -/** - * @brief An unrefactored (view)model class that manages and provides the image list and properties of the focused image. - * - */ class Model : public QAbstractListModel { Q_OBJECT - // Controls which of the main screen and the loading screen should be shown - // and triggers callbacks when loading finished - Q_PROPERTY(bool isLoading READ isLoading NOTIFY isLoadingChanged) - // Indicates the progress of loading, used to update the progress bar in the loading screen - // Not neccessarily updated on every image loaded, but should be updated frequently enough to make the progress bar smooth - Q_PROPERTY(int processedCount READ processedCount NOTIFY progressChanged) - // Total count of images to be loaded, used to calculate the progress percentage - Q_PROPERTY(int totalCount READ totalCount NOTIFY totalCountChanged) - // Current index - Q_PROPERTY(int currentIndex READ focusedIndex WRITE focusOnIndex NOTIFY focusedIndexChanged) - // Sorting related properties - // How this works: - // 1. User interact with QML control components - // 2. QML calls the setter of the corresponding property in the Model - // 3. Model changes its internal state and update the sort indices accordingly - // 4. Model emits signal and possibly update state on QML side (for stateless controls) - // 5. ... Continue on further updates (search filter / focused image properties / etc) - Q_PROPERTY(QString currentSortType READ currentSortType WRITE setCurrentSortType NOTIFY currentSortTypeChanged) - Q_PROPERTY(bool currentSortReverse READ currentSortReverse WRITE setCurrentSortReverse NOTIFY currentSortReverseChanged) - // Focused image related properties, updated when focused image changed - Q_PROPERTY(QString focusedName READ focusedName NOTIFY focusedNameChanged) - public: - // Types - enum Roles { IdRole = Qt::UserRole + 1, UrlRole, PathRole, - NameRole + NameRole, + SizeRole, + DateRole, + DomColorRole, }; QHash roleNames() const override { @@ -79,118 +29,57 @@ class Model : public QAbstractListModel { {UrlRole, "imgUrl"}, // file:///... {PathRole, "imgPath"}, {NameRole, "imgName"}, + {SizeRole, "imgSize"}, + {DateRole, "imgDate"}, + {DomColorRole, "imgDomColor"}, }; } - // Constructor / Destructor - - Model( - const Config::SortConfigItems& sortConfig, - Cache::Manager& cacheMgr, - const QSize& thumbnailSize, - QObject* parent = nullptr); + explicit Model(QObject* parent = nullptr); ~Model(); - // QAbstractListModel - int rowCount(const QModelIndex& parent = QModelIndex()) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - // Properties + QVariant dataAt(int index, const QString& roleName) const; - bool isLoading() const { return m_isLoading; } + void clearData(); - int processedCount() const { return m_processedCount.load(std::memory_order_relaxed); } - - int totalCount() const { return m_watcher.progressMaximum(); } - - int focusedIndex() const { return m_focusedIndex; } - - QString currentSortType() const; - - void setCurrentSortType(const QString& type); - - bool currentSortReverse() const { return m_currentSortReverse; } - - void setCurrentSortReverse(bool reverse); - - QString focusedName() const { return m_focusedName; } - - // Methods - - Q_INVOKABLE void setSearchText(const QString& text); - - Data* imageAt(int index); - - Data* focusedImage(); - - Q_INVOKABLE QVariant dataAt(int index, const QString& roleName) const; - - void loadAndProcess(const QStringList& paths); - - void focusOnIndex(int index); - - Q_INVOKABLE void stop(); + void insertData(const QList& newData); private: - int _convertProxyIndex(int proxyIndex) const; - void _clearData(); - // Update the corresponding mapping in m_sortIndices based on the current m_data and the given sort type - void _updateSortIndices(Config::SortType type); - // Reobtain the properties of the focused image and emit corresponding signals - void _updateFocusedProperties(); - // Update m_filteredIndices, only calls layoutAboutToBeChanged and layoutChanged when the filtered result - // actually changes and informView is true - void _applySearchFilter(bool informView = true); - - signals: - // Properties - void isLoadingChanged(); - void progressChanged(); - void totalCountChanged(); - void focusedIndexChanged(); - void currentSortTypeChanged(); // -> _onSortMethodChanged - void currentSortReverseChanged(); // -> _onSortMethodChanged - void focusedNameChanged(); - // emitted after search text changed and the filter is applied - void searchTextChanged(); // -> _onSearchTextChanged - // emiited when the focued image (is believed to be) changed - void focusedImageChanged(); - - private slots: - void _onProgressValueChanged(int value); - void _onProcessingFinished(); - void _onSortMethodChanged(); - void _onSearchTextChanged(); - - private: - const Config::SortConfigItems& m_sortConfig; - Cache::Manager& m_cacheMgr; - QSize m_thumbnailSize; - QList m_data; +}; - QList> m_sortIndices; - Config::SortType m_currentSortType; - bool m_currentSortReverse; +class ProxyModel : public QSortFilterProxyModel { + Q_OBJECT - QString m_focusedName{}; + public: + explicit ProxyModel(QObject* parent = nullptr); - QList m_filteredIndices; - QString m_searchText{}; - // QTimer m_searchDebounceTimer; - // static constexpr int s_SearchDebounceIntervalMs = 300; + void setSearchText(const QString& text); - QFutureWatcher m_watcher; - bool m_isLoading = false; + QString getSearchText() const { return m_searchText; } - int m_focusedIndex = -1; + void setSortType(Config::SortType type); - std::atomic m_processedCount{0}; - QTimer m_progressUpdateTimer; - static constexpr int s_ProgressUpdateIntervalMs = 30; + Config::SortType getSortType() const { return m_sortType; } + + void setSortDescending(bool descending); + + bool isSortDescending() const { return m_sortDescending; } + + protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; + + bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override; + + private: + QString m_searchText; + Config::SortType m_sortType; + bool m_sortDescending; }; } // namespace WallReel::Core::Image diff --git a/WallReel/Core/Image/proxymodel.cpp b/WallReel/Core/Image/proxymodel.cpp new file mode 100644 index 0000000..7f4eb44 --- /dev/null +++ b/WallReel/Core/Image/proxymodel.cpp @@ -0,0 +1,67 @@ +#include "model.hpp" + +namespace WallReel::Core::Image { + +ProxyModel::ProxyModel(QObject* parent) + : QSortFilterProxyModel(parent) { + setFilterCaseSensitivity(Qt::CaseInsensitive); + setDynamicSortFilter(true); +} + +void ProxyModel::setSearchText(const QString& text) { + if (m_searchText != text) { + beginFilterChange(); + m_searchText = text; + setFilterFixedString(text); + endFilterChange(); + } +} + +void ProxyModel::setSortType(Config::SortType type) { + if (m_sortType != type) { + m_sortType = type; + invalidate(); + sort(0, m_sortDescending ? Qt::DescendingOrder : Qt::AscendingOrder); + } +} + +void ProxyModel::setSortDescending(bool descending) { + if (m_sortDescending != descending) { + m_sortDescending = descending; + sort(0, m_sortDescending ? Qt::DescendingOrder : Qt::AscendingOrder); + } +} + +bool ProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const { + QModelIndex index = sourceModel()->index(source_row, 0, source_parent); + + QString imageName = sourceModel()->data(index, Model::NameRole).toString(); + + if (m_searchText.isEmpty()) return true; + return imageName.contains(m_searchText, Qt::CaseInsensitive); +} + +bool ProxyModel::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const { + switch (m_sortType) { + case Config::SortType::Name: { + QString leftName = sourceModel()->data(source_left, Model::NameRole).toString(); + QString rightName = sourceModel()->data(source_right, Model::NameRole).toString(); + return leftName < rightName; + } + case Config::SortType::Date: { + QDateTime leftDate = sourceModel()->data(source_left, Model::DateRole).toDateTime(); + QDateTime rightDate = sourceModel()->data(source_right, Model::DateRole).toDateTime(); + return leftDate < rightDate; + } + case Config::SortType::Size: { + qint64 leftSize = sourceModel()->data(source_left, Model::SizeRole).toLongLong(); + qint64 rightSize = sourceModel()->data(source_right, Model::SizeRole).toLongLong(); + return leftSize < rightSize; + } + default: + qDebug() << "Unknown sort type:" << static_cast(m_sortType); + return false; + } +} + +} // namespace WallReel::Core::Image diff --git a/WallReel/Core/Palette/manager.cpp b/WallReel/Core/Palette/manager.cpp index 72ad671..768a05d 100644 --- a/WallReel/Core/Palette/manager.cpp +++ b/WallReel/Core/Palette/manager.cpp @@ -1,5 +1,7 @@ #include "manager.hpp" +#include + #include "Palette/matchcolor.hpp" #include "Utils/misc.hpp" #include "logger.hpp" @@ -9,10 +11,8 @@ WALLREEL_DECLARE_SENDER("PaletteManager") WallReel::Core::Palette::Manager::Manager( const Config::ThemeConfigItems& config, - Image::Model& imageModel, - QObject* parent) : QObject(parent), m_imageModel(imageModel) { - connect(&m_imageModel, &Image::Model::focusedImageChanged, this, &Manager::updateColor); - + Image::Manager& imageManager, + QObject* parent) : QObject(parent), m_imageManager(imageManager) { // The new ones overrides the old ones, use a hashtable to track // the latest index of each palette name, then only insert the // ones whose index matches the latest index in the hashtable @@ -64,7 +64,7 @@ WallReel::Core::Palette::Manager::Manager( } } -void WallReel::Core::Palette::Manager::updateColor() { +void WallReel::Core::Palette::Manager::updateColor(const QString& imageId) { bool hasResult = false; Utils::Defer defer([&]() { if (!hasResult) { @@ -74,7 +74,7 @@ void WallReel::Core::Palette::Manager::updateColor() { emit colorChanged(); emit colorNameChanged(); }); - auto imageData = m_imageModel.focusedImage(); + auto imageData = m_imageManager.imageAt(imageId); if (!imageData || !imageData->isValid()) { WR_WARN("No valid focused image data. Cannot update palette color."); return; diff --git a/WallReel/Core/Palette/manager.hpp b/WallReel/Core/Palette/manager.hpp index 29ccc3a..903ff24 100644 --- a/WallReel/Core/Palette/manager.hpp +++ b/WallReel/Core/Palette/manager.hpp @@ -4,22 +4,17 @@ #include #include "Config/data.hpp" -#include "Image/model.hpp" +#include "Image/manager.hpp" #include "data.hpp" namespace WallReel::Core::Palette { class Manager : public QObject { Q_OBJECT - Q_PROPERTY(QList availablePalettes READ availablePalettes CONSTANT) - Q_PROPERTY(QVariant selectedPalette READ selectedPalette WRITE setSelectedPalette NOTIFY selectedPaletteChanged) - Q_PROPERTY(QVariant selectedColor READ selectedColor WRITE setSelectedColor NOTIFY selectedColorChanged) - Q_PROPERTY(QColor color READ color NOTIFY colorChanged) - Q_PROPERTY(QString colorName READ colorName NOTIFY colorNameChanged) public: Manager(const Config::ThemeConfigItems& config, - Image::Model& imageModel, + Image::Manager& imageManager, QObject* parent = nullptr); // Properties @@ -44,7 +39,7 @@ class Manager : public QObject { return QVariant(); } - Q_INVOKABLE void setSelectedPalette(const QVariant& paletteVar) { + void setSelectedPalette(const QVariant& paletteVar) { if (paletteVar.isNull() || !paletteVar.isValid()) { m_selectedPalette = std::nullopt; } else { @@ -53,17 +48,15 @@ class Manager : public QObject { m_selectedColor = std::nullopt; emit selectedPaletteChanged(); emit selectedColorChanged(); - updateColor(); } - Q_INVOKABLE void setSelectedColor(const QVariant& colorVar) { + void setSelectedColor(const QVariant& colorVar) { if (colorVar.isNull() || !colorVar.isValid()) { m_selectedColor = std::nullopt; } else { m_selectedColor = colorVar.value(); } emit selectedColorChanged(); - updateColor(); } // Getters @@ -99,7 +92,7 @@ class Manager : public QObject { } public slots: - void updateColor(); // <- Image::Model::focusedImageChanged + void updateColor(const QString& imageId); signals: void selectedPaletteChanged(); @@ -108,7 +101,7 @@ class Manager : public QObject { void colorNameChanged(); private: - Image::Model& m_imageModel; + Image::Manager& m_imageManager; QList m_palettes; // Null means auto diff --git a/WallReel/Core/Provider/bootstrap.hpp b/WallReel/Core/Provider/bootstrap.hpp new file mode 100644 index 0000000..cd81aa8 --- /dev/null +++ b/WallReel/Core/Provider/bootstrap.hpp @@ -0,0 +1,66 @@ +#ifndef WALLREEL_PROVIDER_BOOTSTRAP_HPP +#define WALLREEL_PROVIDER_BOOTSTRAP_HPP + +#include + +#include "Cache/manager.hpp" +#include "Config/manager.hpp" +#include "Image/manager.hpp" +#include "Palette/manager.hpp" +#include "Service/manager.hpp" +#include "Utils/misc.hpp" +#include "appoptions.hpp" + +namespace WallReel::Core::Provider { + +class Bootstrap { + friend class Carousel; + + public: + Bootstrap(const AppOptions& options) { + cacheMgr = new Cache::Manager(Utils::getCacheDir()); + + if (options.clearCache) { + cacheMgr->clearCache(); + return; + } + configMgr = new Config::Manager( + Utils::getConfigDir(), + options.appendDirs, + options.configPath); + + imageMgr = new Image::Manager( + *cacheMgr, + configMgr->getFocusImageSize()); + + paletteMgr = new Palette::Manager( + configMgr->getThemeConfig(), + *imageMgr); + qRegisterMetaType("PaletteItem"); + qRegisterMetaType("ColorItem"); + + ServiceMgr = new Service::Manager( + configMgr->getActionConfig(), + *imageMgr, + *paletteMgr); + } + + ~Bootstrap() { + delete ServiceMgr; + delete paletteMgr; + delete imageMgr; + delete configMgr; + delete cacheMgr; + } + + private: + Cache::Manager* cacheMgr; + Config::Manager* configMgr; + Image::Manager* imageMgr; + Palette::Manager* paletteMgr; + Service::Manager* ServiceMgr; +}; + +} // namespace WallReel::Core::Provider + +#endif // WALLREEL_PROVIDER_BOOTSTRAP_HPP diff --git a/WallReel/Core/Provider/carousel.hpp b/WallReel/Core/Provider/carousel.hpp new file mode 100644 index 0000000..6d997e1 --- /dev/null +++ b/WallReel/Core/Provider/carousel.hpp @@ -0,0 +1,274 @@ +#ifndef WALLREEL_PROVIDER_CAROUSEL_HPP +#define WALLREEL_PROVIDER_CAROUSEL_HPP + +#include + +#include + +#include "Config/data.hpp" +#include "Config/manager.hpp" +#include "Image/manager.hpp" +#include "Palette/data.hpp" +#include "Palette/manager.hpp" +#include "Service/manager.hpp" +#include "bootstrap.hpp" + +namespace WallReel::Core::Provider { + +class Carousel : public QObject { + Q_OBJECT + + // Image::Manager + + public: + Q_PROPERTY(Image::ProxyModel* imageModel READ imageModel CONSTANT) + Q_PROPERTY(bool isLoading READ isLoading NOTIFY isLoadingChanged) + Q_PROPERTY(int processedCount READ processedCount NOTIFY processedCountChanged) + Q_PROPERTY(int totalCount READ totalCount NOTIFY totalCountChanged) + Q_PROPERTY(QString sortType READ sortType NOTIFY sortTypeChanged) + Q_PROPERTY(bool sortDescending READ sortDescending NOTIFY sortDescendingChanged) + Q_PROPERTY(QString searchText READ searchText NOTIFY searchTextChanged) + + Image::ProxyModel* imageModel() const { return m_imageMgr->model(); } + + bool isLoading() const { return m_imageMgr->isLoading(); } + + int processedCount() const { return m_imageMgr->processedCount(); } + + QString sortType() const { return Config::sortTypeToString(m_imageMgr->sortType()); } + + bool sortDescending() const { return m_imageMgr->sortDescending(); } + + QString searchText() const { return m_imageMgr->searchText(); } + + int totalCount() const { return m_imageMgr->totalCount(); } + + Q_INVOKABLE void stopLoading() { m_imageMgr->stop(); } + + Q_INVOKABLE void setSortType(const QString& sortTypeStr) { + auto newSortType = Config::stringToSortType(sortTypeStr); + m_imageMgr->setSortType(newSortType); + emit sortTypeChanged(); + } + + Q_INVOKABLE void setSortType(Config::SortType sortType) { + m_imageMgr->setSortType(sortType); + emit sortTypeChanged(); + } + + Q_INVOKABLE void setSortDescending(bool descending) { + m_imageMgr->setSortDescending(descending); + emit sortDescendingChanged(); + } + + Q_INVOKABLE void setSearchText(const QString& text) { + m_imageMgr->setSearchText(text); + emit searchTextChanged(); + } + + signals: + void isLoadingChanged(); + void processedCountChanged(); + void totalCountChanged(); + void sortTypeChanged(); + void sortDescendingChanged(); + void searchTextChanged(); + + // Config::Manager + + public: + Q_PROPERTY(int imageWidth READ imageWidth CONSTANT) + Q_PROPERTY(int imageHeight READ imageHeight CONSTANT) + Q_PROPERTY(double imageFocusScale READ imageFocusScale CONSTANT) + Q_PROPERTY(int windowWidth READ windowWidth CONSTANT) + Q_PROPERTY(int windowHeight READ windowHeight CONSTANT) + Q_PROPERTY(QStringList availableSortTypes READ availableSortTypes CONSTANT) + + int imageWidth() const { return m_configMgr->getStyleConfig().imageWidth; } + + int imageHeight() const { return m_configMgr->getStyleConfig().imageHeight; } + + int windowWidth() const { return m_configMgr->getStyleConfig().windowWidth; } + + int windowHeight() const { return m_configMgr->getStyleConfig().windowHeight; } + + double imageFocusScale() const { return m_configMgr->getStyleConfig().imageFocusScale; } + + QStringList availableSortTypes() const { return Config::s_availableSortTypes; } + + // Palette::Manager + + public: + Q_PROPERTY(QList availablePalettes READ availablePalettes CONSTANT) + Q_PROPERTY(QVariant selectedPalette READ selectedPalette NOTIFY selectedPaletteChanged) + Q_PROPERTY(QVariant selectedColor READ selectedColor NOTIFY selectedColorChanged) + Q_PROPERTY(QColor color READ color NOTIFY colorChanged) + Q_PROPERTY(QString colorName READ colorName NOTIFY colorNameChanged) + + QList availablePalettes() const { return m_paletteMgr->availablePalettes(); } + + QVariant selectedPalette() const { return m_paletteMgr->selectedPalette(); } + + QVariant selectedColor() const { return m_paletteMgr->selectedColor(); } + + QColor color() const { return m_paletteMgr->color(); } + + QString colorName() const { return m_paletteMgr->colorName(); } + + Q_INVOKABLE void requestSelectPalette(const QVariant& palette) { m_paletteMgr->setSelectedPalette(palette); } + + Q_INVOKABLE void requestSelectColor(const QVariant& color) { m_paletteMgr->setSelectedColor(color); } + + signals: + void selectedPaletteChanged(); + void selectedColorChanged(); + void colorChanged(); + void colorNameChanged(); + + // Service::Manager + + public: + Q_PROPERTY(bool isProcessing READ isProcessing NOTIFY isProcessingChanged) + + bool isProcessing() const { return m_serviceMgr->isProcessing(); } + + Q_INVOKABLE void confirm() { + if (!m_currentImageId.isEmpty()) { + m_serviceMgr->selectWallpaper(m_currentImageId); + } + } + + Q_INVOKABLE void cancel() { m_serviceMgr->cancel(); } + + Q_INVOKABLE void restore() { m_serviceMgr->restore(); } + + Q_INVOKABLE bool hasSelected() const { return m_serviceMgr->hasSelected(); } + + signals: + void isProcessingChanged(); + void selectCompleted(); + void previewCompleted(); + void restoreCompleted(); + void cancelCompleted(); + + // Other states + + public: + Q_PROPERTY(QString currentImageId READ currentImageId NOTIFY currentImageIdChanged) + Q_PROPERTY(int currentIndex READ currentIndex NOTIFY currentIndexChanged) + + QString currentImageId() const { return m_currentImageId; } + + int currentIndex() const { return m_currentIndex; } + + Q_INVOKABLE void setCurrentImageId(const QString& imageId) { + if (m_currentImageId != imageId) { + m_currentImageId = imageId; + emit currentImageIdChanged(); + } + } + + Q_INVOKABLE void setCurrentIndex(int index) { + if (m_currentIndex != index) { + m_currentIndex = index; + emit currentIndexChanged(); + } + } + + signals: + void currentImageIdChanged(); + void currentIndexChanged(); + + private: + QString m_currentImageId; + int m_currentIndex = -1; + + // instances + + public: + Carousel(QApplication* app, + Bootstrap& bootstrap, + QObject* parent = nullptr) + : QObject(parent), + m_configMgr(bootstrap.configMgr), + m_imageMgr(bootstrap.imageMgr), + m_paletteMgr(bootstrap.paletteMgr), + m_serviceMgr(bootstrap.ServiceMgr) { + // Simply forward signals + connect(m_imageMgr, &Image::Manager::isLoadingChanged, this, &Carousel::isLoadingChanged); + connect(m_imageMgr, &Image::Manager::processedCountChanged, this, &Carousel::processedCountChanged); + connect(m_imageMgr, &Image::Manager::totalCountChanged, this, &Carousel::totalCountChanged); + connect(m_paletteMgr, &Palette::Manager::selectedPaletteChanged, this, &Carousel::selectedPaletteChanged); + connect(m_paletteMgr, &Palette::Manager::selectedColorChanged, this, &Carousel::selectedColorChanged); + connect(m_paletteMgr, &Palette::Manager::colorChanged, this, &Carousel::colorChanged); + connect(m_paletteMgr, &Palette::Manager::colorNameChanged, this, &Carousel::colorNameChanged); + connect(m_serviceMgr, &Service::Manager::isProcessingChanged, this, &Carousel::isProcessingChanged); + connect(m_serviceMgr, &Service::Manager::selectCompleted, this, &Carousel::selectCompleted); + connect(m_serviceMgr, &Service::Manager::previewCompleted, this, &Carousel::previewCompleted); + connect(m_serviceMgr, &Service::Manager::restoreCompleted, this, &Carousel::restoreCompleted); + connect(m_serviceMgr, &Service::Manager::cancelCompleted, this, &Carousel::cancelCompleted); + + // Preview on imageid change + connect(this, &Carousel::currentImageIdChanged, this, [this]() { + if (!m_currentImageId.isEmpty()) { + m_serviceMgr->previewWallpaper(m_currentImageId); + } + }); + // Update color when imageid changes + connect(this, &Carousel::currentImageIdChanged, this, [this]() { + if (!m_currentImageId.isEmpty()) { + m_paletteMgr->updateColor(m_currentImageId); + } + }); + // Update color when selected color changes + connect(this, &Carousel::selectedColorChanged, this, [this]() { + if (!m_currentImageId.isEmpty()) { + m_paletteMgr->updateColor(m_currentImageId); + } + }); + + // Quit on selected + if (m_configMgr->getActionConfig().quitOnSelected) { + QObject::connect( + this, + &Provider::Carousel::selectCompleted, + app, + &QApplication::quit, + Qt::QueuedConnection); + } + // Quit on cancel + QObject::connect( + this, + &Provider::Carousel::cancelCompleted, + app, + &QApplication::quit, + Qt::QueuedConnection); + // Restore on quit + if (m_configMgr->getActionConfig().restoreOnClose) { + QObject::connect( + app, + &QApplication::aboutToQuit, + m_serviceMgr, + &Service::Manager::restoreOnQuit); + } + + // Initial value of sort method + setSortType(m_configMgr->getSortConfig().type); + setSortDescending(m_configMgr->getSortConfig().descending); + } + + void start() { + m_configMgr->captureState(); + m_imageMgr->loadAndProcess(m_configMgr->getWallpapers()); + } + + private: + Config::Manager* m_configMgr; + Image::Manager* m_imageMgr; + Palette::Manager* m_paletteMgr; + Service::Manager* m_serviceMgr; +}; + +} // namespace WallReel::Core::Provider + +#endif // WALLREEL_PROVIDER_CAROUSEL_HPP diff --git a/WallReel/Core/Service/manager.hpp b/WallReel/Core/Service/manager.hpp index 271df36..92ea1af 100644 --- a/WallReel/Core/Service/manager.hpp +++ b/WallReel/Core/Service/manager.hpp @@ -5,7 +5,7 @@ #include #include "Config/data.hpp" -#include "Image/model.hpp" +#include "Image/manager.hpp" #include "Palette/manager.hpp" #include "Service/wallpaper.hpp" #include "logger.hpp" @@ -20,16 +20,11 @@ class Manager : public QObject { public: Manager( const Config::ActionConfigItems& actionConfig, - Image::Model& imageModel, + Image::Manager& imageManager, Palette::Manager& paletteManager, - QObject* parent = nullptr) : m_actionConfig(actionConfig), m_imageModel(imageModel), m_paletteManager(paletteManager) { + QObject* parent = nullptr) : m_actionConfig(actionConfig), m_imageManager(imageManager), m_paletteManager(paletteManager) { m_wallpaperService = new WallpaperService(m_actionConfig, m_paletteManager, this); - // Listen on image change - connect(&m_imageModel, &Image::Model::focusedImageChanged, this, &Manager::previewWallpaper); - // Listen on palette change - connect(&m_paletteManager, &Palette::Manager::colorChanged, this, &Manager::previewWallpaper); - connect(&m_paletteManager, &Palette::Manager::colorNameChanged, this, &Manager::previewWallpaper); // Forward signals // Direct signal 2 signal connection connect(m_wallpaperService, &WallpaperService::previewCompleted, this, &Manager::previewCompleted); @@ -38,26 +33,32 @@ class Manager : public QObject { connect(m_wallpaperService, &WallpaperService::restoreCompleted, this, &Manager::_onRestoreCompleted); } - Q_INVOKABLE void selectWallpaper(int index) { - Logger::debug("ServiceManager", QString("Select wallpaper at index %1").arg(index)); + bool isProcessing() const { return m_isProcessing; } + + bool hasSelected() const { return m_hasSelected; } + + public slots: + + void selectWallpaper(const QString& id) { + Logger::debug("ServiceManager", QString("Select wallpaper with id %1").arg(id)); if (m_isProcessing) { Logger::debug("ServiceManager", "Already processing an select action, ignoring new request"); return; } m_isProcessing = true; emit isProcessingChanged(); - const auto* data = m_imageModel.imageAt(index); + const auto* data = m_imageManager.imageAt(id); if (data) { m_wallpaperService->select(*data); } else { - Logger::warn("ServiceManager", QString("No image data at index %1. Skipping select action.").arg(index)); + Logger::warn("ServiceManager", QString("No image data at id %1. Skipping select action.").arg(id)); m_isProcessing = false; emit isProcessingChanged(); emit selectCompleted(); } } - Q_INVOKABLE void restore() { + void restore() { Logger::debug("ServiceManager", "Restore states"); if (m_isProcessing) { Logger::debug("ServiceManager", "Already processing an restore action, ignoring new request"); @@ -68,29 +69,36 @@ class Manager : public QObject { m_wallpaperService->restore(); } - Q_INVOKABLE void cancel() { + void cancel() { Logger::debug("ServiceManager", "Cancel action"); m_wallpaperService->stopAll(); emit cancelCompleted(); } - bool isProcessing() const { return m_isProcessing; } - - bool hasSelected() const { return m_hasSelected; } - - public slots: - - void previewWallpaper() { + void previewWallpaper(const QString& id) { Logger::debug("ServiceManager", "Preview wallpaper"); - const auto* data = m_imageModel.focusedImage(); + const auto* data = m_imageManager.imageAt(id); if (data) { m_wallpaperService->preview(*data); } else { - Logger::warn("ServiceManager", "No focused image data. Skipping preview action."); + Logger::warn("ServiceManager", "No image data at id " + id + ". Skipping preview action."); emit previewCompleted(); } } + void restoreOnQuit() { + if (m_hasSelected) { + Logger::debug("ServiceManager", "Quit with selected wallpaper, no need to restore"); + return; + } + Logger::debug("ServiceManager", "Restore on quit"); + m_wallpaperService->stopAll(); + QEventLoop loop; + connect(m_wallpaperService, &WallpaperService::restoreCompleted, &loop, &QEventLoop::quit); + m_wallpaperService->restore(); + loop.exec(); + } + private slots: void _onSelectCompleted() { @@ -121,7 +129,7 @@ class Manager : public QObject { private: WallpaperService* m_wallpaperService; const Config::ActionConfigItems& m_actionConfig; - Image::Model& m_imageModel; + Image::Manager& m_imageManager; Palette::Manager& m_paletteManager; bool m_isProcessing = false; diff --git a/WallReel/Core/Service/wallpaper.cpp b/WallReel/Core/Service/wallpaper.cpp index 1a3de1f..3a50cee 100644 --- a/WallReel/Core/Service/wallpaper.cpp +++ b/WallReel/Core/Service/wallpaper.cpp @@ -108,9 +108,9 @@ QHash WallReel::Core::Service::WallpaperService::_generateVari {"width", QString::number(imageData.getTargetSize().width())}, {"height", QString::number(imageData.getTargetSize().height())}, {"palette", palette}, - {"color", color}, + {"colorName", color}, {"colorHex", hex}, - {"domColor", m_paletteManager.color().name()}, + {"domColorHex", imageData.getDominantColor().name()}, }; } diff --git a/WallReel/UI/CMakeLists.txt b/WallReel/UI/CMakeLists.txt index 3542a85..93d347a 100644 --- a/WallReel/UI/CMakeLists.txt +++ b/WallReel/UI/CMakeLists.txt @@ -6,7 +6,6 @@ qt_add_qml_module(${UILIB_NAME} Main.qml Pages/LoadingScreen.qml Pages/CarouselScreen.qml - Providers/CarouselProvider.qml Modules/Carousel.qml Modules/TitleBar.qml Modules/SortControl.qml diff --git a/WallReel/UI/Main.qml b/WallReel/UI/Main.qml index 6934ff1..7b26cbf 100644 --- a/WallReel/UI/Main.qml +++ b/WallReel/UI/Main.qml @@ -4,8 +4,8 @@ import WallReel.Core import WallReel.UI.Pages ApplicationWindow { - width: Config.windowWidth - height: Config.windowHeight + width: CarouselProvider.windowWidth + height: CarouselProvider.windowHeight // minimumWidth: width // maximumWidth: width // minimumHeight: height @@ -14,15 +14,15 @@ ApplicationWindow { title: qsTr("WallReel") LoadingScreen { - visible: ImageModel.isLoading + visible: CarouselProvider.isLoading anchors.fill: parent - currentValue: ImageModel.processedCount - totalValue: ImageModel.totalCount + currentValue: CarouselProvider.processedCount + totalValue: CarouselProvider.totalCount } Loader { anchors.fill: parent - active: !ImageModel.isLoading + active: !CarouselProvider.isLoading sourceComponent: CarouselScreen { } diff --git a/WallReel/UI/Modules/Carousel.qml b/WallReel/UI/Modules/Carousel.qml index 67dc92b..d249743 100644 --- a/WallReel/UI/Modules/Carousel.qml +++ b/WallReel/UI/Modules/Carousel.qml @@ -12,11 +12,12 @@ Item { property int spacing: 0 property int animDuration: 200 property int count: view.count + property string currentImageId: view.currentItem ? view.currentItem.imgId : "" + property string currentImageName: view.currentItem ? view.currentItem.imgName : "" ListView { id: view - model: root.model anchors.fill: parent orientation: ListView.Horizontal spacing: root.spacing @@ -36,6 +37,7 @@ Item { view.currentIndex = root.currentIndex; view.forceActiveFocus(); } + model: root.model Connections { function onCurrentIndexChanged() { @@ -51,6 +53,8 @@ Item { id: delegateItem property bool isFocused: ListView.isCurrentItem + property string imgName: model.imgName + property string imgId: model.imgId width: isFocused ? root.focusedItemWidth : root.itemWidth height: view.height diff --git a/WallReel/UI/Modules/ColorControl.qml b/WallReel/UI/Modules/ColorControl.qml index 114548f..b649d6c 100644 --- a/WallReel/UI/Modules/ColorControl.qml +++ b/WallReel/UI/Modules/ColorControl.qml @@ -38,11 +38,11 @@ Item { implicitWidth: 200 displayText: currentIndex < 0 ? "— palette —" : currentText - model: root.availablePalettes.map((p) => { + model: ["(None)"].concat(root.availablePalettes.map((p) => { return p.name; - }) + })) onActivated: (idx) => { - root.paletteSelected(idx >= 0 ? root.availablePalettes[idx] : null); + root.paletteSelected(idx >= 0 ? root.availablePalettes[idx - 1] : null); } Binding { @@ -50,7 +50,7 @@ Item { property: "currentIndex" value: root.selectedPalette ? root.availablePalettes.findIndex((p) => { return p.name === root.selectedPalette.name; - }) : -1 + }) + 1 : 0 } } diff --git a/WallReel/UI/Modules/SortControl.qml b/WallReel/UI/Modules/SortControl.qml index 369e490..817efab 100644 --- a/WallReel/UI/Modules/SortControl.qml +++ b/WallReel/UI/Modules/SortControl.qml @@ -7,10 +7,10 @@ Item { property var availableSortTypes: [] property string selectedSortType: "" - property bool isReverse: false + property bool isDescending: false signal sortTypeSelected(string sortType) - signal isReverseToggled(bool reverse) + signal isDescendingToggled(bool descending) implicitWidth: row.implicitWidth implicitHeight: row.implicitHeight @@ -44,14 +44,14 @@ Item { } ToolButton { - icon.name: root.isReverse ? "view-sort-descending" : "view-sort-ascending" + icon.name: root.isDescending ? "view-sort-descending" : "view-sort-ascending" icon.width: 16 icon.height: 16 focusPolicy: Qt.NoFocus - onClicked: root.isReverseToggled(!root.isReverse) + onClicked: root.isDescendingToggled(!root.isDescending) ToolTip.visible: hovered ToolTip.delay: 600 - ToolTip.text: root.isReverse ? "Descending order" : "Ascending order" + ToolTip.text: root.isDescending ? "Descending order" : "Ascending order" } } diff --git a/WallReel/UI/Modules/TopBar.qml b/WallReel/UI/Modules/TopBar.qml index aba0849..0c37441 100644 --- a/WallReel/UI/Modules/TopBar.qml +++ b/WallReel/UI/Modules/TopBar.qml @@ -13,10 +13,10 @@ Item { readonly property string searchText: searchBar.typedText property alias availableSortTypes: sortCtrl.availableSortTypes property alias selectedSortType: sortCtrl.selectedSortType - property alias isSortReverse: sortCtrl.isReverse + property alias isSortDescending: sortCtrl.isDescending signal sortTypeSelected(string sortType) - signal sortReverseToggled(bool reverse) + signal sortDescendingToggled(bool descending) signal searchDismissed() function requestSearchFocus() { @@ -58,8 +58,8 @@ Item { onSortTypeSelected: (t) => { return root.sortTypeSelected(t); } - onIsReverseToggled: (r) => { - return root.sortReverseToggled(r); + onIsDescendingToggled: (r) => { + return root.sortDescendingToggled(r); } } diff --git a/WallReel/UI/Pages/CarouselScreen.qml b/WallReel/UI/Pages/CarouselScreen.qml index 3f82293..80ab29e 100644 --- a/WallReel/UI/Pages/CarouselScreen.qml +++ b/WallReel/UI/Pages/CarouselScreen.qml @@ -1,8 +1,8 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import WallReel.Core import WallReel.UI.Modules -import WallReel.UI.Providers Item { id: root @@ -20,9 +20,9 @@ Item { carousel.currentIndex++; } else if (e.key === Qt.Key_Return || e.key === Qt.Key_Enter) - provider.confirm(); + CarouselProvider.confirm(); else if (e.key === Qt.Key_Escape) - provider.cancel(); + CarouselProvider.cancel(); else e.accepted = false; } @@ -35,21 +35,6 @@ Item { 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: 12 @@ -60,29 +45,29 @@ Item { Layout.fillWidth: true totalCount: carousel.count - title: provider.focusedName - availableSortTypes: provider.availableSortTypes - isSortReverse: provider.isSortReverse + title: carousel.currentImageName + availableSortTypes: CarouselProvider.availableSortTypes + isSortDescending: CarouselProvider.sortDescending onSortTypeSelected: (t) => { - return provider.setSortType(t); + return CarouselProvider.setSortType(t); } - onSortReverseToggled: (r) => { - return provider.setSortReverse(r); + onSortDescendingToggled: (r) => { + return CarouselProvider.setSortDescending(r); } onSearchTextChanged: () => { - return provider.setSearchText(topBar.searchText); + return CarouselProvider.setSearchText(searchText); } Binding { target: topBar property: "currentIndex" - value: provider.currentIndex + value: carousel.currentIndex } Binding { target: topBar property: "selectedSortType" - value: provider.selectedSortType + value: CarouselProvider.sortType } } @@ -92,33 +77,22 @@ Item { Layout.fillWidth: true Layout.fillHeight: true - 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.setCurrentIndex(currentIndex); - - } - Component.onCompleted: { - // Sync initial index to provider from Carousel - if (provider.currentIndex !== currentIndex) - provider.setCurrentIndex(currentIndex); - - } + model: CarouselProvider.imageModel + itemWidth: CarouselProvider.imageWidth + itemHeight: CarouselProvider.imageHeight + focusedItemWidth: CarouselProvider.imageWidth * CarouselProvider.imageFocusScale + focusedItemHeight: CarouselProvider.imageHeight * CarouselProvider.imageFocusScale MouseArea { anchors.fill: parent onWheel: (e) => { if (e.angleDelta.y > 0) { - if (provider.currentIndex > 0) - provider.currentIndex--; + if (carousel.currentIndex > 0) + carousel.currentIndex--; } else if (e.angleDelta.y < 0) { - if (provider.currentIndex < carousel.count - 1) - provider.currentIndex++; + if (carousel.currentIndex < carousel.count - 1) + carousel.currentIndex++; } } @@ -141,64 +115,76 @@ Item { Layout.fillWidth: true from: 0 to: Math.max(0, carousel.count - 1) - value: provider.currentIndex - onMoved: provider.currentIndex = Math.round(value) + value: carousel.currentIndex + onMoved: carousel.currentIndex = Math.round(value) } BottomBar { id: bottomBar Layout.fillWidth: true - availablePalettes: provider.availablePalettes - availableColors: provider.availableColors + availablePalettes: CarouselProvider.availablePalettes + availableColors: CarouselProvider.selectedPalette ? CarouselProvider.selectedPalette.colors : [] onPaletteSelected: (p) => { - return provider.selectPalette(p); + return CarouselProvider.requestSelectPalette(p); } onColorSelected: (c) => { - return provider.selectColor(c); + return CarouselProvider.requestSelectColor(c); } - onRestoreClicked: provider.restore() - onConfirmClicked: provider.confirm() - onCancelClicked: provider.cancel() + onRestoreClicked: CarouselProvider.restore() + onConfirmClicked: CarouselProvider.confirm() + onCancelClicked: CarouselProvider.cancel() Binding { target: bottomBar property: "selectedPalette" - value: provider.selectedPalette + value: CarouselProvider.selectedPalette } Binding { target: bottomBar property: "selectedColor" - value: provider.selectedColor + value: CarouselProvider.selectedColor } Binding { target: bottomBar property: "colorName" - value: provider.colorName + value: CarouselProvider.colorName } Binding { target: bottomBar property: "colorHex" - value: provider.colorHex + value: CarouselProvider.color } Binding { target: bottomBar property: "colorValue" - value: provider.colorValue + value: CarouselProvider.color } Binding { target: bottomBar property: "actionsEnabled" - value: !provider.isProcessing + value: !CarouselProvider.isProcessing } } } + Connections { + function onCurrentImageIdChanged() { + CarouselProvider.setCurrentImageId(carousel.currentImageId); + } + + function onCurrentIndexChanged() { + CarouselProvider.setCurrentIndex(carousel.currentIndex); + } + + target: carousel + } + } diff --git a/WallReel/UI/Providers/CarouselProvider.qml b/WallReel/UI/Providers/CarouselProvider.qml deleted file mode 100644 index 118db0f..0000000 --- a/WallReel/UI/Providers/CarouselProvider.qml +++ /dev/null @@ -1,79 +0,0 @@ -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 - // Carousel selection state - readonly property int currentIndex: ImageModel.currentIndex - // Image name - readonly property string focusedName: ImageModel.focusedName - //// Sort - readonly property var availableSortTypes: ["None", "Name", "Date", "Size"] - readonly property string selectedSortType: ImageModel.currentSortType - readonly property bool isSortReverse: ImageModel.currentSortReverse - //// Palette / Color - readonly property var availablePalettes: PaletteManager.availablePalettes - readonly property var selectedPalette: PaletteManager.selectedPalette // PaletteItem | null - readonly property var availableColors: selectedPalette ? selectedPalette.colors : [] - readonly property var selectedColor: PaletteManager.selectedColor // ColorItem | null (null means "auto") - readonly property string colorName: PaletteManager.colorName - readonly property string colorHex: PaletteManager.color - readonly property color colorValue: PaletteManager.color - //// Actions state - readonly property bool isProcessing: ServiceManager.isProcessing - - // Actions - function confirm() { - ServiceManager.selectWallpaper(currentIndex); - } - - function restore() { - ServiceManager.restore(); - } - - function cancel() { - ServiceManager.cancel(); - } - - function setCurrentIndex(index) { - ImageModel.currentIndex = index; - } - - function focusSearch() { - searchBar.requestFocus(); - } - - function setSortType(type) { - ImageModel.currentSortType = type; - } - - function setSortReverse(reverse) { - ImageModel.currentSortReverse = reverse; - } - - function selectPalette(palette) { - PaletteManager.selectedPalette = palette; - } - - function selectColor(colorItem) { - PaletteManager.selectedColor = colorItem; - } - - function setSearchText(text) { - ImageModel.setSearchText(text); - if (currentIndex != 0) - currentIndex = 0; - - } - -} diff --git a/WallReel/main.cpp b/WallReel/main.cpp index 5076808..774f3b4 100644 --- a/WallReel/main.cpp +++ b/WallReel/main.cpp @@ -9,13 +9,8 @@ Q_IMPORT_QML_PLUGIN(WallReel_CorePlugin) Q_IMPORT_QML_PLUGIN(WallReel_UIPlugin) -#include "Cache/manager.hpp" -#include "Core/Config/manager.hpp" -#include "Core/Image/model.hpp" -#include "Core/Palette/data.hpp" -#include "Core/Palette/manager.hpp" -#include "Core/Service/manager.hpp" -#include "Core/Utils/misc.hpp" +#include "Core/Provider/bootstrap.hpp" +#include "Core/Provider/carousel.hpp" #include "Core/appoptions.hpp" #include "Core/logger.hpp" #include "version.h" @@ -25,108 +20,49 @@ using namespace WallReel::Core; WALLREEL_DECLARE_SENDER("Main") int main(int argc, char* argv[]) { - AppOptions s_options; QApplication a(argc, argv); a.setApplicationName(APP_NAME); a.setApplicationVersion(APP_VERSION); a.setWindowIcon(QIcon(QString(":/%1.svg").arg(APP_NAME))); - Logger::init(); - s_options.parseArgs(a); + { + Logger::init(); - if (s_options.doReturn) { - return s_options.errorText.isEmpty() ? 0 : 1; + AppOptions options; + options.parseArgs(a); + + if (options.doReturn) { + return options.errorText.isEmpty() ? 0 : 1; + } + + Provider::Bootstrap bootstrap(options); + + if (options.clearCache) { + return 0; + } + + Provider::Carousel provider(&a, bootstrap); + qmlRegisterSingletonInstance( + COREMODULE_URI, + MODULE_VERSION_MAJOR, + MODULE_VERSION_MINOR, + "CarouselProvider", + &provider); + { + QQmlApplicationEngine engine; + + QObject::connect( + &engine, + &QQmlApplicationEngine::objectCreationFailed, + &a, + []() { QCoreApplication::exit(-1); }, + Qt::QueuedConnection); + engine.loadFromModule(UIMODULE_URI, "Main"); + + provider.start(); + + return a.exec(); + } } - - QQmlApplicationEngine engine; - - auto cacheMgr = new Cache::Manager(Utils::getCacheDir()); - - if (s_options.clearCache) { - cacheMgr->clearCache(); - return 0; - } - - auto config = new Config::Manager( - Utils::getConfigDir(), - s_options.appendDirs, - s_options.configPath, - &engine); - qmlRegisterSingletonInstance( - COREMODULE_URI, - MODULE_VERSION_MAJOR, - MODULE_VERSION_MINOR, - "Config", - config); - - auto imageModel = new Image::Model( - config->getSortConfig(), - *cacheMgr, - config->getFocusImageSize(), - config); - qmlRegisterSingletonInstance( - COREMODULE_URI, - MODULE_VERSION_MAJOR, - MODULE_VERSION_MINOR, - "ImageModel", - imageModel); - - auto paletteMgr = new Palette::Manager( - config->getThemeConfig(), - *imageModel, - imageModel); - engine.rootContext()->setContextProperty("PaletteManager", paletteMgr); - qRegisterMetaType("PaletteItem"); - qRegisterMetaType("ColorItem"); - - auto Service = new Service::Manager( - config->getActionConfig(), - *imageModel, - *paletteMgr, - paletteMgr); - qmlRegisterSingletonInstance( - COREMODULE_URI, - MODULE_VERSION_MAJOR, - MODULE_VERSION_MINOR, - "ServiceManager", - Service); - if (config->getActionConfig().quitOnSelected) { - QObject::connect( - Service, - &Service::Manager::selectCompleted, - &a, - &QApplication::quit, - Qt::QueuedConnection); - } - QObject::connect( - Service, - &Service::Manager::cancelCompleted, - &a, - &QApplication::quit, - Qt::QueuedConnection); - if (config->getActionConfig().restoreOnClose) { - QObject::connect( - &a, - &QApplication::aboutToQuit, - Service, - [Service]() { - if (!Service->hasSelected()) { - Service->restore(); - } - }); - } - - QObject::connect( - &engine, - &QQmlApplicationEngine::objectCreationFailed, - &a, - []() { QCoreApplication::exit(-1); }, - Qt::QueuedConnection); - engine.loadFromModule(UIMODULE_URI, "Main"); - - config->captureState(); - imageModel->loadAndProcess(config->getWallpapers()); - - return a.exec(); }