♻️ refactor

This commit is contained in:
2026-02-28 02:12:20 +01:00
parent 922ecac3b0
commit f03bdf4e6a
25 changed files with 876 additions and 806 deletions
+3 -1
View File
@@ -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
-6
View File
@@ -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);
}
}
+34 -8
View File
@@ -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
+4 -6
View File
@@ -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();
}
}
}
-23
View File
@@ -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;
}
+113
View File
@@ -0,0 +1,113 @@
#include "manager.hpp"
#include <QFuture>
#include <QtConcurrent>
#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<Data*>::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<Data*> 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<Data*> 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();
}
+91
View File
@@ -0,0 +1,91 @@
#ifndef WALLREEL_IMAGEMANAGER_HPP
#define WALLREEL_IMAGEMANAGER_HPP
#include <QAbstractListModel>
#include <QDir>
#include <QFutureWatcher>
#include <QTimer>
#include <atomic>
#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<QString, Data*> m_dataMap;
Cache::Manager& m_cacheMgr;
QSize m_thumbnailSize;
QFutureWatcher<Data*> m_watcher;
bool m_isLoading = false;
std::atomic<int> m_processedCount{0};
QTimer m_progressUpdateTimer;
static constexpr int s_ProgressUpdateIntervalMs = 30;
};
} // namespace WallReel::Core::Image
#endif // WALLREEL_IMAGEMANAGER_HPP
+32 -306
View File
@@ -1,72 +1,32 @@
#include "model.hpp"
#include <QFuture>
#include <QtConcurrent>
#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<Data*>::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<Data*>& 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<Data*> 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<int>& indices = m_sortIndices[static_cast<int>(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<int>(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<Config::SortType>(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
+35 -146
View File
@@ -2,75 +2,25 @@
#define WALLREEL_IMAGEMODEL_HPP
#include <QAbstractListModel>
#include <QDir>
#include <QFutureWatcher>
#include <QTimer>
#include <atomic>
#include <QSortFilterProxyModel>
#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<int, QByteArray> 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<Data*>& 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<Data*> m_data;
};
QList<QList<int>> 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<int> m_filteredIndices;
QString m_searchText{};
// QTimer m_searchDebounceTimer;
// static constexpr int s_SearchDebounceIntervalMs = 300;
void setSearchText(const QString& text);
QFutureWatcher<Data*> m_watcher;
bool m_isLoading = false;
QString getSearchText() const { return m_searchText; }
int m_focusedIndex = -1;
void setSortType(Config::SortType type);
std::atomic<int> 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
+67
View File
@@ -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<int>(m_sortType);
return false;
}
}
} // namespace WallReel::Core::Image
+6 -6
View File
@@ -1,5 +1,7 @@
#include "manager.hpp"
#include <qcontainerfwd.h>
#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;
+6 -13
View File
@@ -4,22 +4,17 @@
#include <qcolor.h>
#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<PaletteItem> 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<ColorItem>();
}
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<PaletteItem> m_palettes;
// Null means auto
+66
View File
@@ -0,0 +1,66 @@
#ifndef WALLREEL_PROVIDER_BOOTSTRAP_HPP
#define WALLREEL_PROVIDER_BOOTSTRAP_HPP
#include <QQmlEngine>
#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<Palette::PaletteItem>("PaletteItem");
qRegisterMetaType<Palette::ColorItem>("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
+274
View File
@@ -0,0 +1,274 @@
#ifndef WALLREEL_PROVIDER_CAROUSEL_HPP
#define WALLREEL_PROVIDER_CAROUSEL_HPP
#include <qapplication.h>
#include <QApplication>
#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<Palette::PaletteItem> 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<Palette::PaletteItem> 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
+32 -24
View File
@@ -5,7 +5,7 @@
#include <QTimer>
#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;
+2 -2
View File
@@ -108,9 +108,9 @@ QHash<QString, QString> 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()},
};
}
-1
View File
@@ -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
+6 -6
View File
@@ -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 {
}
+5 -1
View File
@@ -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
+4 -4
View File
@@ -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
}
}
+5 -5
View File
@@ -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"
}
}
+4 -4
View File
@@ -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);
}
}
+48 -62
View File
@@ -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
}
}
@@ -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;
}
}
+39 -103
View File
@@ -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<Palette::PaletteItem>("PaletteItem");
qRegisterMetaType<Palette::ColorItem>("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();
}