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