♻️ refactor
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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()},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user