diff --git a/README.md b/README.md index c88104d..536039f 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ It might not be that worthy to build a Qt application from ground for such a sma Refer to [config.schema.json](config.schema.json) for a complete reference of the configuration file schema. Below is a summary of the available options. -The configuration file is divided into five main sections: `wallpaper`, `theme`, `action`, `style`, and `sort`. +The configuration file is divided into five main sections: `wallpaper`, `theme`, `action`, `style`, and `cache`. ### Wallpaper (`wallpaper`) @@ -77,26 +77,25 @@ By default, a **dominant color** will be extracted from each wallpaper. If a pal There are a few embeded palettes available in the application, including "Catppuccin Frappe", "Catppuccin Latte", "Catppuccin Macchiato", and "Catppuccin Mocha". You can also define your own palettes or override the embeded ones by providing a custom configuration. -| Property | Type | Default | Description | -| :--------------- | :--------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------ | -| `defaultPalette` | String | `""` | Name of the default palette to use. | -| `palettes` | Array of Objects | `[]` | List of defined palettes. Each contains a `name` (string) and an array of `colors` (each with a `name` and a hex `value` like `"#ff0000"`). | +| Property | Type | Default | Description | +| :--------- | :--------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------ | +| `palettes` | Array of Objects | `[]` | List of defined palettes. Each contains a `name` (string) and an array of `colors` (each with a `name` and a hex `value` like `"#ff0000"`). | ### Action (`action`) Configures system commands to execute on specific events mapping to your window manager or wallpaper utility (e.g., `swaybg`, `feh`). -| Property | Type | Default | Description | -| :-------------------- | :--------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `previewDebounceTime` | Integer | `300` | Debounce time (ms) for triggering the preview action. | -| `printSelected` | Boolean | `true` | Print selected wallpaper path to stdout on confirm. | -| `printPreview` | Boolean | `false` | Print previewed wallpaper path to stdout on preview. | -| `onSelected` | String | `""` | Command to execute when a wallpaper is confirmed. | -| `onPreview` | String | `""` | Command to execute when a wallpaper is previewed. | -| `saveState` | Array of Objects | `[]` | Commands to fetch system states before changing wallpapers. Each object defines: `key`, `default` (fallback value), `command` (stdout mapping), and `timeout` (ms). | -| `onRestore` | String | `""` | Command to execute on restore. Extracted states from `saveState` can be injected using `{{ key }}`. | -| `quitOnSelected` | Boolean | `false` | Quit the application after a selection is made. | -| `restoreOnClose` | Boolean | `true` | Run `onRestore` command if the application is closed without making a final selection. | +| Property | Type | Default | Description | +| :-------------------- | :--------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `previewDebounceTime` | Integer | `300` | Debounce time (ms) for triggering the preview action. | +| `printSelected` | Boolean | `true` | Print selected wallpaper path to stdout on confirm. | +| `printPreview` | Boolean | `false` | Print previewed wallpaper path to stdout on preview. | +| `onSelected` | String | `""` | Command to execute when a wallpaper is confirmed. | +| `onPreview` | String | `""` | Command to execute when a wallpaper is previewed. | +| `saveState` | Array of Objects | `[]` | Commands to fetch system states before changing wallpapers. Each object defines: `key`, `fallback` (fallback value), `command` (stdout mapping), and `timeout` (ms). | +| `onRestore` | String | `""` | Command to execute on restore. Extracted states from `saveState` can be injected using `{{ key }}`. | +| `quitOnSelected` | Boolean | `false` | Quit the application after a selection is made. | +| `restoreOnClose` | Boolean | `true` | Run `onRestore` command if the application is closed without making a final selection. | Available placeholders for `onSelected`, `onPreview` commands: @@ -109,7 +108,7 @@ Available placeholders for `onSelected`, `onPreview` commands: | `{{ colorName }}` | Name of the currently determined primary color. ("null" if none) | | `{{ colorHex }}` | Hex code (starting with "#") of the currently determined primary color. ("null" if none) | | `{{ domColorHex }}` | Hex code (starting with "#") of the dominant color in the selected or previewed wallpaper. | -| `{{ key }}` | Value of the saved state with the specified key. | +| `{{ }}` | Value of the saved state with the specified key. | ### Style (`style`) @@ -123,14 +122,14 @@ Controls the layout and dimensions of the application window and image items. | `window_width` | Integer | `750` | Initial application window width. | | `window_height` | Integer | `500` | Initial application window height. | -### Sort (`sort`) +### Cache (`cache`) -Initial sorting behavior for loaded images. +Controls what UI state is persisted between sessions. -| Property | Type | Default | Description | -| :----------- | :------ | :------- | :------------------------------------------------------------------------------- | -| `type` | String | `"date"` | Defines sorting criteria. Acceptable values: `"name"`, `"date"`, `"size"`. | -| `descending` | Boolean | `true` | If true, sorts in descending order (e.g. newer dates first, larger files first). | +| Property | Type | Default | Description | +| :--------------- | :------ | :------ | :------------------------------------------ | +| `saveSortMethod` | Boolean | `true` | Whether to persist the sort type and order. | +| `savePalette` | Boolean | `true` | Whether to persist the selected palette. | --- @@ -150,7 +149,6 @@ Initial sorting behavior for loaded images. "excludes": ["\\.gif$"] }, "theme": { - "defaultPalette": "Dark", "palettes": [ { "name": "Dark", @@ -169,7 +167,7 @@ Initial sorting behavior for loaded images. "saveState": [ { "key": "current_wp", - "default": "/home/user/Pictures/default.jpg", + "fallback": "/home/user/Pictures/default.jpg", "command": "find ~/.config/wallpaper/current -type f | head -n 1", "timeout": 1000 } @@ -183,9 +181,9 @@ Initial sorting behavior for loaded images. "window_width": 1280, "window_height": 720 }, - "sort": { - "type": "date", - "descending": true + "cache": { + "saveSortMethod": true, + "savePalette": true } } ``` diff --git a/WallReel/Core/Cache/manager.cpp b/WallReel/Core/Cache/manager.cpp index 0e81316..5f93046 100644 --- a/WallReel/Core/Cache/manager.cpp +++ b/WallReel/Core/Cache/manager.cpp @@ -16,6 +16,15 @@ using namespace Qt::StringLiterals; namespace WallReel::Core::Cache { +static QLatin1StringView settingKey(SettingsType type) { + switch (type) { + case SettingsType::LastSelectedPalette: return "last_selected_palette"_L1; + case SettingsType::LastSortType: return "last_sort_type"_L1; + case SettingsType::LastSortDescending: return "last_sort_descending"_L1; + } + Q_UNREACHABLE(); +} + QString Manager::cacheKey(const QFileInfo& fileInfo, const QSize& imageSize) { const QString raw = fileInfo.absoluteFilePath() + QString::number(fileInfo.lastModified().toMSecsSinceEpoch()) + u'x' + QString::number(imageSize.width()) + u'x' + QString::number(imageSize.height()); return QString::fromLatin1( @@ -64,6 +73,11 @@ void Manager::clearCache(Type type) { QSqlQuery(db).exec(QStringLiteral("DELETE FROM color_cache")); WR_INFO(u"Cleared color cache"_s); } + + if ((type & Type::Settings) != Type::None) { + QSqlQuery(db).exec(QStringLiteral("DELETE FROM settings_cache")); + WR_INFO(u"Cleared settings cache"_s); + } } QColor Manager::getColor(const QString& key, const std::function& computeFunc) { @@ -85,6 +99,11 @@ QColor Manager::getColor(const QString& key, const std::function& comp } WR_DEBUG(u"Color cache miss [%1], computing"_s.arg(key)); + if (!computeFunc) { + WR_WARN(u"No compute function provided for color cache miss [%1]"_s.arg(key)); + return QColor(); + } + const QColor color = computeFunc(); if (!color.isValid()) { @@ -138,6 +157,11 @@ QFileInfo Manager::getImage(const QString& key, const std::function& c } WR_DEBUG(u"Image cache miss [%1], computing"_s.arg(key)); + if (!computeFunc) { + WR_WARN(u"No compute function provided for image cache miss [%1]"_s.arg(key)); + return QFileInfo{}; + } + const QImage image = computeFunc(); if (image.isNull()) { WR_WARN(u"ComputeFunc returned null image for key [%1]"_s.arg(key)); @@ -168,6 +192,78 @@ QFileInfo Manager::getImage(const QString& key, const std::function& c return QFileInfo(filePath); } +QString Manager::getSetting(SettingsType key, const std::function& computeFunc) { + QSqlDatabase db = _db(); + const QLatin1StringView keyStr = settingKey(key); + + if (db.isOpen()) { + QSqlQuery query(db); + query.prepare(QStringLiteral( + "SELECT value FROM settings_cache WHERE key = :key")); + query.bindValue(u":key"_s, keyStr); + + if (query.exec() && query.next()) { + WR_DEBUG(u"Settings cache hit [%1]"_s.arg(keyStr)); + return query.value(0).toString(); + } + } + + WR_DEBUG(u"Settings cache miss [%1], computing"_s.arg(keyStr)); + if (!computeFunc) { + WR_WARN(u"No compute function provided for settings cache miss [%1]"_s.arg(keyStr)); + return QString{}; + } + + const QString value = computeFunc(); + + if (db.isOpen() && !value.isNull()) { + QSqlQuery insertQuery(db); + insertQuery.prepare(QStringLiteral( + "INSERT OR REPLACE INTO settings_cache (key, value) " + "VALUES (:key, :value)")); + insertQuery.bindValue(u":key"_s, keyStr); + insertQuery.bindValue(u":value"_s, value); + if (!insertQuery.exec()) + WR_WARN(u"Failed to cache setting [%1]: %2"_s + .arg(keyStr, insertQuery.lastError().text())); + else + WR_DEBUG(u"Setting cached [%1]"_s.arg(keyStr)); + } + + return value; +} + +void Manager::storeSetting(SettingsType key, const QString& value) { + QSqlDatabase db = _db(); + const QLatin1StringView keyStr = settingKey(key); + + if (db.isOpen()) { + if (value.isNull()) { + QSqlQuery deleteQuery(db); + deleteQuery.prepare(QStringLiteral( + "DELETE FROM settings_cache WHERE key = :key")); + deleteQuery.bindValue(u":key"_s, keyStr); + if (!deleteQuery.exec()) + WR_WARN(u"Failed to delete setting [%1]: %2"_s + .arg(keyStr, deleteQuery.lastError().text())); + else + WR_DEBUG(u"Setting deleted [%1]"_s.arg(keyStr)); + } else { + QSqlQuery insertQuery(db); + insertQuery.prepare(QStringLiteral( + "INSERT OR REPLACE INTO settings_cache (key, value) " + "VALUES (:key, :value)")); + insertQuery.bindValue(u":key"_s, keyStr); + insertQuery.bindValue(u":value"_s, value); + if (!insertQuery.exec()) + WR_WARN(u"Failed to store setting [%1]: %2"_s + .arg(keyStr, insertQuery.lastError().text())); + else + WR_DEBUG(u"Setting stored [%1]"_s.arg(keyStr)); + } + } +} + /// Returns an open QSqlDatabase for the calling thread, creating it on first use. QSqlDatabase Manager::_db() const { // thread_local: one slot per OS thread, initialized on first call in that thread. @@ -238,6 +334,11 @@ void Manager::_setupTables(QSqlDatabase& db) const { " key TEXT PRIMARY KEY NOT NULL," " file_name TEXT NOT NULL" ")")); + q.exec(QStringLiteral( + "CREATE TABLE IF NOT EXISTS settings_cache (" + " key TEXT PRIMARY KEY NOT NULL," + " value TEXT NOT NULL" + ");")); } } // namespace WallReel::Core::Cache diff --git a/WallReel/Core/Cache/manager.hpp b/WallReel/Core/Cache/manager.hpp index cfa7f05..f8295fa 100644 --- a/WallReel/Core/Cache/manager.hpp +++ b/WallReel/Core/Cache/manager.hpp @@ -21,9 +21,13 @@ class Manager { void clearCache(Type type = Type::Image | Type::Color); - QColor getColor(const QString& key, const std::function& computeFunc); + QColor getColor(const QString& key, const std::function& computeFunc = nullptr); - QFileInfo getImage(const QString& key, const std::function& computeFunc); + QFileInfo getImage(const QString& key, const std::function& computeFunc = nullptr); + + QString getSetting(SettingsType key, const std::function& computeFunc = nullptr); + + void storeSetting(SettingsType key, const QString& value); private: QDir m_cacheDir; diff --git a/WallReel/Core/Cache/types.hpp b/WallReel/Core/Cache/types.hpp index 133e222..a74ada8 100644 --- a/WallReel/Core/Cache/types.hpp +++ b/WallReel/Core/Cache/types.hpp @@ -10,10 +10,11 @@ namespace WallReel::Core::Cache { enum class Type : uint32_t { - None = 0, - Image = 1, ///< Cache for processed images - Color = 1 << 1, ///< Cache for palette color matching results - All = ~0u + None = 0, + Image = 1, ///< Cache for processed images + Color = 1 << 1, ///< Cache for dominant colors + Settings = 1 << 2, ///< Cache for settings (simple key-value pairs) + All = ~0u }; inline constexpr Type operator|(Type a, Type b) { @@ -28,6 +29,12 @@ inline constexpr Type operator&(Type a, Type b) { using Data = std::variant; +enum class SettingsType : uint32_t { + LastSelectedPalette = 0, + LastSortType, + LastSortDescending, +}; + } // namespace WallReel::Core::Cache #endif // WALLREEL_CACHE_TYPES_HPP diff --git a/WallReel/Core/Config/data.hpp b/WallReel/Core/Config/data.hpp index 228fef3..c44d683 100644 --- a/WallReel/Core/Config/data.hpp +++ b/WallReel/Core/Config/data.hpp @@ -19,7 +19,6 @@ // wallpaper.dirs[].recursive boolean false Whether to search the directory recursively. // wallpaper.excludes array [] Exclude patterns (regex) // -// theme.defaultPalette string "" Name of the default palette to use // theme.palettes array [] // theme.palettes[].name string "" Name of the palette // theme.palettes[].colors array [] List of colors in the palette @@ -33,9 +32,9 @@ // action.onPreview string "" Command to execute on preview // action.saveState array [] Useful for restore command // action.saveState[].key string "" Key of value to save, used as {{ key }} in onRestore command -// action.saveState[].default string "" Value to save, used when "cmd" is not set or command execution fails or output is empty +// action.saveState[].fallback string "" Value to save, used when "command" is not set or command execution fails or output is empty // action.saveState[].command 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 "command" in milliseconds. 0 or negative means no timeout // action.onRestore string "" Command to execute on restore ({{ key }} -> value defined or obtained in saveState) // action.quitOnSelected boolean false Whether to quit the application after confirming a wallpaper // action.restoreOnClose boolean true Whether to run the restore command after closing the application without confirming a wallpaper @@ -46,11 +45,8 @@ // style.window_width number 750 Initial window width // style.window_height number 500 Initial window height // -// sort.type string "date" Initial sorting type: "name", "date", "size" -// sort.descending boolean true Initial sorting order -// Ascending: name: lexicographical, e.g. "a.jpg" before "b.jpg" -// date: older before newer -// size: smaller before larger +// cache.saveSortMethod boolean true Whether to persist the sort type and order +// cache.savePalette bool true Whether to persist the selected palette namespace WallReel::Core::Config { @@ -64,7 +60,7 @@ enum class SortType : int { inline const QStringList s_availableSortTypes = {"Name", "Date", "Size"}; -inline QString sortTypeToString(SortType type) { +inline QString sortTypeToString(const SortType& type) { switch (type) { case SortType::Name: return "Name"; @@ -112,7 +108,6 @@ struct ThemeConfigItems { }; QList palettes; - QString defaultPalette; }; struct ActionConfigItems { @@ -143,9 +138,13 @@ struct StyleConfigItems { int windowHeight = 500; }; -struct SortConfigItems { - SortType type = SortType::Date; - bool descending = true; +struct CacheConfigItems { + bool saveSortMethod = true; + bool savePalette = true; + + static const QString defaultSortType; + static const QString defaultSortDescending; + static const QString defaultSelectedPalette; }; } // namespace WallReel::Core::Config diff --git a/WallReel/Core/Config/manager.cpp b/WallReel/Core/Config/manager.cpp index 7c08912..33fb14a 100644 --- a/WallReel/Core/Config/manager.cpp +++ b/WallReel/Core/Config/manager.cpp @@ -15,7 +15,13 @@ WALLREEL_DECLARE_SENDER("ConfigManager") -WallReel::Core::Config::Manager::Manager( +namespace WallReel::Core::Config { + +const QString CacheConfigItems::defaultSortType = "Date"; +const QString CacheConfigItems::defaultSortDescending = "true"; +const QString CacheConfigItems::defaultSelectedPalette = ""; + +Manager::Manager( const QDir& configDir, const QDir& picturesDir, const QStringList& searchDirs, @@ -47,10 +53,10 @@ WallReel::Core::Config::Manager::Manager( _loadWallpapers(); } -WallReel::Core::Config::Manager::~Manager() { +Manager::~Manager() { } -void WallReel::Core::Config::Manager::_loadConfig(const QString& configPath) { +void Manager::_loadConfig(const QString& configPath) { WR_INFO(QString("Loading configuration from: %1").arg(configPath)); QFile configFile(configPath); if (!configFile.open(QIODevice::ReadOnly)) { @@ -72,10 +78,10 @@ void WallReel::Core::Config::Manager::_loadConfig(const QString& configPath) { _loadThemeConfig(jsonObj); _loadActionConfig(jsonObj); _loadStyleConfig(jsonObj); - _loadSortConfig(jsonObj); + _loadCacheConfig(jsonObj); } -void WallReel::Core::Config::Manager::_loadWallpaperConfig(const QJsonObject& root) { +void Manager::_loadWallpaperConfig(const QJsonObject& root) { if (!root.contains("wallpaper") || !root["wallpaper"].isObject()) { return; } @@ -121,14 +127,11 @@ void WallReel::Core::Config::Manager::_loadWallpaperConfig(const QJsonObject& ro } } -void WallReel::Core::Config::Manager::_loadThemeConfig(const QJsonObject& root) { +void Manager::_loadThemeConfig(const QJsonObject& root) { if (!root.contains("theme") || !root["theme"].isObject()) { return; } const QJsonObject& theme = root["theme"].toObject(); - if (theme.contains("defaultPalette") && theme["defaultPalette"].isString()) { - m_themeConfig.defaultPalette = theme["defaultPalette"].toString(); - } if (!theme.contains("palettes") || !theme["palettes"].isArray()) { return; @@ -176,7 +179,7 @@ void WallReel::Core::Config::Manager::_loadThemeConfig(const QJsonObject& root) } } -void WallReel::Core::Config::Manager::_loadActionConfig(const QJsonObject& root) { +void Manager::_loadActionConfig(const QJsonObject& root) { if (!root.contains("action") || !root["action"].isObject()) { return; } @@ -209,8 +212,8 @@ void WallReel::Core::Config::Manager::_loadActionConfig(const QJsonObject& root) if (obj.contains("key") && obj["key"].isString()) { sItem.key = obj["key"].toString(); } - if (obj.contains("default") && obj["default"].isString()) { - sItem.defaultVal = obj["default"].toString(); + if (obj.contains("fallback") && obj["fallback"].isString()) { + sItem.defaultVal = obj["fallback"].toString(); } if (obj.contains("command") && obj["command"].isString()) { sItem.command = obj["command"].toString(); @@ -257,7 +260,7 @@ void WallReel::Core::Config::Manager::_loadActionConfig(const QJsonObject& root) } } -void WallReel::Core::Config::Manager::_loadStyleConfig(const QJsonObject& root) { +void Manager::_loadStyleConfig(const QJsonObject& root) { if (!root.contains("style") || !root["style"].isObject()) { return; } @@ -295,36 +298,27 @@ void WallReel::Core::Config::Manager::_loadStyleConfig(const QJsonObject& root) } } -void WallReel::Core::Config::Manager::_loadSortConfig(const QJsonObject& root) { - if (!root.contains("sort") || !root["sort"].isObject()) { +void Manager::_loadCacheConfig(const QJsonObject& root) { + if (!root.contains("cache") || !root["cache"].isObject()) { return; } - const QJsonObject& config = root["sort"].toObject(); + const QJsonObject& config = root["cache"].toObject(); - if (config.contains("type")) { - const auto& val = config["type"]; - if (val.isString()) { - QString type = val.toString().toLower(); - if (type == "name") { - m_sortConfig.type = SortType::Name; - } else if (type == "date") { - m_sortConfig.type = SortType::Date; - } else if (type == "size") { - m_sortConfig.type = SortType::Size; - } else { - WR_WARN(QString("Unknown sort type: %1").arg(type)); - } + if (config.contains("saveSortMethod")) { + const auto& val = config["saveSortMethod"]; + if (val.isBool()) { + m_cacheConfig.saveSortMethod = val.toBool(); } } - if (config.contains("descending")) { - const auto& val = config["descending"]; + if (config.contains("savePalette")) { + const auto& val = config["savePalette"]; if (val.isBool()) { - m_sortConfig.descending = val.toBool(); + m_cacheConfig.savePalette = val.toBool(); } } } -void WallReel::Core::Config::Manager::_loadWallpapers() { +void Manager::_loadWallpapers() { m_wallpapers.clear(); // Add paths first using a set to avoid duplicates @@ -389,7 +383,7 @@ void WallReel::Core::Config::Manager::_loadWallpapers() { WR_INFO(QString("Found %1 images").arg(paths.size())); } -void WallReel::Core::Config::Manager::captureState() { +void Manager::captureState() { if (m_pendingCaptures > 0) { WR_WARN("State capture already in progress, ignoring new capture request"); return; @@ -481,7 +475,7 @@ void WallReel::Core::Config::Manager::captureState() { } } -void WallReel::Core::Config::Manager::_onCaptureResult(const QString& key, const QString& value) { +void Manager::_onCaptureResult(const QString& key, const QString& value) { // This is all in main thread, so no lock needed m_actionConfig.savedState[key] = value; m_pendingCaptures--; @@ -489,3 +483,5 @@ void WallReel::Core::Config::Manager::_onCaptureResult(const QString& key, const emit stateCaptured(); } } + +} // namespace WallReel::Core::Config diff --git a/WallReel/Core/Config/manager.hpp b/WallReel/Core/Config/manager.hpp index 46c62e0..df410c0 100644 --- a/WallReel/Core/Config/manager.hpp +++ b/WallReel/Core/Config/manager.hpp @@ -57,7 +57,7 @@ class Manager : public QObject { const StyleConfigItems& getStyleConfig() const { return m_styleConfig; } - const SortConfigItems& getSortConfig() const { return m_sortConfig; } + const CacheConfigItems& getCacheConfig() const { return m_cacheConfig; } QSize getFocusImageSize() const { return QSize{m_styleConfig.imageWidth, m_styleConfig.imageHeight} * m_styleConfig.imageFocusScale; @@ -78,7 +78,7 @@ class Manager : public QObject { void _loadThemeConfig(const QJsonObject& config); void _loadActionConfig(const QJsonObject& config); void _loadStyleConfig(const QJsonObject& config); - void _loadSortConfig(const QJsonObject& config); + void _loadCacheConfig(const QJsonObject& config); // Load wallpapers void _loadWallpapers(); // Callback for state capture results @@ -90,7 +90,7 @@ class Manager : public QObject { ThemeConfigItems m_themeConfig; ActionConfigItems m_actionConfig; StyleConfigItems m_styleConfig; - SortConfigItems m_sortConfig; + CacheConfigItems m_cacheConfig; QStringList m_wallpapers; diff --git a/WallReel/Core/Palette/manager.cpp b/WallReel/Core/Palette/manager.cpp index 768a05d..d842739 100644 --- a/WallReel/Core/Palette/manager.cpp +++ b/WallReel/Core/Palette/manager.cpp @@ -49,19 +49,6 @@ WallReel::Core::Palette::Manager::Manager( m_palettes.append(newP); } } - - // Set default palette if specified - if (!config.defaultPalette.isEmpty()) { - for (const auto& p : m_palettes) { - if (p.name == config.defaultPalette) { - m_selectedColor = std::nullopt; - m_selectedPalette = p; - emit selectedColorChanged(); - emit selectedPaletteChanged(); - break; - } - } - } } void WallReel::Core::Palette::Manager::updateColor(const QString& imageId) { diff --git a/WallReel/Core/Palette/manager.hpp b/WallReel/Core/Palette/manager.hpp index 903ff24..c154fbb 100644 --- a/WallReel/Core/Palette/manager.hpp +++ b/WallReel/Core/Palette/manager.hpp @@ -42,8 +42,23 @@ class Manager : public QObject { void setSelectedPalette(const QVariant& paletteVar) { if (paletteVar.isNull() || !paletteVar.isValid()) { m_selectedPalette = std::nullopt; - } else { + } else if (paletteVar.canConvert()) { m_selectedPalette = paletteVar.value(); + } else if (paletteVar.canConvert()) { + QString paletteName = paletteVar.toString(); + auto it = + std::find_if(m_palettes.begin(), + m_palettes.end(), + [&paletteName](const PaletteItem& item) { + return item.name == paletteName; + }); + if (it != m_palettes.end()) { + m_selectedPalette = *it; + } else { + m_selectedPalette = std::nullopt; + } + } else { + m_selectedPalette = std::nullopt; } m_selectedColor = std::nullopt; emit selectedPaletteChanged(); diff --git a/WallReel/Core/Provider/carousel.hpp b/WallReel/Core/Provider/carousel.hpp index 532741f..12c925c 100644 --- a/WallReel/Core/Provider/carousel.hpp +++ b/WallReel/Core/Provider/carousel.hpp @@ -5,6 +5,8 @@ #include +#include "Cache/manager.hpp" +#include "Cache/types.hpp" #include "Config/data.hpp" #include "Config/manager.hpp" #include "Image/manager.hpp" @@ -190,6 +192,7 @@ class Carousel : public QObject { Bootstrap& bootstrap, QObject* parent = nullptr) : QObject(parent), + m_cacheMgr(bootstrap.cacheMgr), m_configMgr(bootstrap.configMgr), m_imageMgr(bootstrap.imageMgr), m_paletteMgr(bootstrap.paletteMgr), @@ -273,12 +276,41 @@ class Carousel : public QObject { &Service::Manager::restoreOnQuit); } - // Initial value of sort method - setSortType(m_configMgr->getSortConfig().type); - setSortDescending(m_configMgr->getSortConfig().descending); + // Restore last state if configured + // and store state on change if configured + // Note: connect after restoring state to avoid storing the restored state again + if (m_configMgr->getCacheConfig().saveSortMethod) { + setSortType(m_cacheMgr->getSetting( + Cache::SettingsType::LastSortType, + []() { return Config::CacheConfigItems::defaultSortType; })); + setSortDescending(m_cacheMgr->getSetting( + Cache::SettingsType::LastSortDescending, + []() { return Config::CacheConfigItems::defaultSortDescending; }) == "true"); + connect(this, &Carousel::sortTypeChanged, this, [this]() { + m_cacheMgr->storeSetting( + Cache::SettingsType::LastSortType, + Config::sortTypeToString(m_imageMgr->sortType())); + }); + connect(this, &Carousel::sortDescendingChanged, this, [this]() { + m_cacheMgr->storeSetting( + Cache::SettingsType::LastSortDescending, + m_imageMgr->sortDescending() ? "true" : "false"); + }); + } + if (m_configMgr->getCacheConfig().savePalette) { + requestSelectPalette(m_cacheMgr->getSetting( + Cache::SettingsType::LastSelectedPalette, + []() { return Config::CacheConfigItems::defaultSelectedPalette; })); + connect(this, &Carousel::selectedPaletteChanged, this, [this]() { + m_cacheMgr->storeSetting( + Cache::SettingsType::LastSelectedPalette, + m_paletteMgr->getSelectedPaletteName()); + }); + } } private: + Cache::Manager* m_cacheMgr; Config::Manager* m_configMgr; Image::Manager* m_imageMgr; Palette::Manager* m_paletteMgr; diff --git a/WallReel/Core/Service/wallpaper.cpp b/WallReel/Core/Service/wallpaper.cpp index b41a09a..cbe52b6 100644 --- a/WallReel/Core/Service/wallpaper.cpp +++ b/WallReel/Core/Service/wallpaper.cpp @@ -8,7 +8,9 @@ WALLREEL_DECLARE_SENDER("WallpaperService") -WallReel::Core::Service::WallpaperService::WallpaperService( +namespace WallReel::Core::Service { + +WallpaperService::WallpaperService( const Config::ActionConfigItems& actionConfig, const Palette::Manager& paletteManager, QObject* parent) @@ -48,7 +50,7 @@ WallReel::Core::Service::WallpaperService::WallpaperService( }); } -void WallReel::Core::Service::WallpaperService::stopAll() { +void WallpaperService::stopAll() { WR_DEBUG("Stopping all wallpaper service processes"); if (m_previewProcess->state() != QProcess::NotRunning) { m_previewProcess->kill(); @@ -65,12 +67,12 @@ void WallReel::Core::Service::WallpaperService::stopAll() { m_previewDebounceTimer->stop(); } -void WallReel::Core::Service::WallpaperService::preview(const Image::Data& imageData) { +void WallpaperService::preview(const Image::Data& imageData) { m_pendingImageData = &imageData; m_previewDebounceTimer->start(); } -void WallReel::Core::Service::WallpaperService::select(const Image::Data& imageData) { +void WallpaperService::select(const Image::Data& imageData) { if (m_selectProcess->state() != QProcess::NotRunning) { WR_WARN("Previous select command is still running. Ignoring new command."); return; @@ -79,7 +81,7 @@ void WallReel::Core::Service::WallpaperService::select(const Image::Data& imageD _doSelect(imageData); } -void WallReel::Core::Service::WallpaperService::restore() { +void WallpaperService::restore() { if (m_restoreProcess->state() != QProcess::NotRunning) { WR_WARN("Previous restore command is still running. Ignoring new command."); return; @@ -88,7 +90,7 @@ void WallReel::Core::Service::WallpaperService::restore() { _doRestore(); } -QHash WallReel::Core::Service::WallpaperService::_generateVariables(const Image::Data& imageData) { +QHash WallpaperService::_generateVariables(const Image::Data& imageData) { auto palette = m_paletteManager.getSelectedPaletteName(); if (palette.isEmpty()) { palette = "null"; @@ -115,7 +117,7 @@ QHash WallReel::Core::Service::WallpaperService::_generateVari return ret; } -void WallReel::Core::Service::WallpaperService::_doPreview(const Image::Data& imageData) { +void WallpaperService::_doPreview(const Image::Data& imageData) { QString path = imageData.getFullPath(); if (path.isEmpty()) { @@ -144,7 +146,7 @@ void WallReel::Core::Service::WallpaperService::_doPreview(const Image::Data& im m_previewProcess->start("sh", QStringList() << "-c" << command); } -void WallReel::Core::Service::WallpaperService::_doSelect(const Image::Data& imageData) { +void WallpaperService::_doSelect(const Image::Data& imageData) { QString path = imageData.getFullPath(); if (path.isEmpty()) { @@ -168,7 +170,7 @@ void WallReel::Core::Service::WallpaperService::_doSelect(const Image::Data& ima m_selectProcess->start("sh", QStringList() << "-c" << command); } -void WallReel::Core::Service::WallpaperService::_doRestore() { +void WallpaperService::_doRestore() { if (m_actionConfig.onRestore.isEmpty()) { WR_DEBUG("No restore command configured. Skipping restore action."); emit restoreCompleted(); @@ -184,3 +186,5 @@ void WallReel::Core::Service::WallpaperService::_doRestore() { WR_DEBUG(QString("Executing restore command: %1").arg(command)); m_restoreProcess->start("sh", QStringList() << "-c" << command); } + +} // namespace WallReel::Core::Service diff --git a/config.schema.json b/config.schema.json index 7401d02..1fad2d1 100644 --- a/config.schema.json +++ b/config.schema.json @@ -47,11 +47,6 @@ "theme": { "type": "object", "properties": { - "defaultPalette": { - "type": "string", - "default": "", - "description": "Name of the default palette to use" - }, "palettes": { "type": "array", "items": { @@ -126,10 +121,10 @@ "default": "", "description": "Key of value to save, used as {{ key }} in onRestore command" }, - "default": { + "fallback": { "type": "string", "default": "", - "description": "Value to save, used when \"cmd\" is not set or command execution fails or output is empty" + "description": "Value to save, used when \"command\" is not set or command execution fails or output is empty" }, "command": { "type": "string", @@ -179,6 +174,7 @@ "image_focus_scale": { "type": "number", "default": 1.5, + "minimum": 1.0, "description": "Scale of the focused image (relative to unfocused image)" }, "window_width": { @@ -193,23 +189,18 @@ } } }, - "sort": { + "cache": { "type": "object", "properties": { - "type": { - "type": "string", - "enum": [ - "name", - "date", - "size" - ], - "default": "date", - "description": "Initial sorting type" - }, - "descending": { + "saveSortMethod": { "type": "boolean", "default": true, - "description": "Initial sorting order. Ascending: name: lexicographical, date: older before newer, size: smaller before larger" + "description": "Whether to persist the sort type and order" + }, + "savePalette": { + "type": "boolean", + "default": true, + "description": "Whether to persist the selected palette" } } }