diff --git a/src/application.cpp b/src/application.cpp index 02e4e86d..6585accf 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -2,6 +2,11 @@ #include #include +#include +#include +#include +#include +#include using namespace vnotex; @@ -10,6 +15,53 @@ Application::Application(int &p_argc, char **p_argv) { } +void Application::watchThemeFolder(const QString &p_themeFolderPath) +{ + if (p_themeFolderPath.isEmpty()) { + return; + } + + // Initialize watchers only when needed + if (!m_styleWatcher) { + m_styleWatcher = new QFileSystemWatcher(this); + } + if (!m_reloadTimer) { + m_reloadTimer = new QTimer(this); + m_reloadTimer->setSingleShot(true); + m_reloadTimer->setInterval(500); // 500ms debounce delay + connect(m_reloadTimer, &QTimer::timeout, + this, &Application::reloadThemeResources); + + // Connect file watcher to timer + connect(m_styleWatcher, &QFileSystemWatcher::directoryChanged, + m_reloadTimer, qOverload<>(&QTimer::start)); + connect(m_styleWatcher, &QFileSystemWatcher::fileChanged, + m_reloadTimer, qOverload<>(&QTimer::start)); + } + + // Watch the theme folder and its files + m_styleWatcher->addPath(p_themeFolderPath); + + // Also watch individual files in the theme folder + QDir themeDir(p_themeFolderPath); + QStringList files = themeDir.entryList(QDir::Files); + for (const QString &file : files) { + m_styleWatcher->addPath(themeDir.filePath(file)); + } +} + +void Application::reloadThemeResources() +{ + VNoteX::getInst().getThemeMgr().refreshCurrentTheme(); + + auto stylesheet = VNoteX::getInst().getThemeMgr().fetchQtStyleSheet(); + if (!stylesheet.isEmpty()) { + setStyleSheet(stylesheet); + style()->unpolish(this); + style()->polish(this); + } +} + bool Application::event(QEvent *p_event) { // On macOS, we need this to open file from Finder. diff --git a/src/application.h b/src/application.h index 4e08903e..74060bf2 100644 --- a/src/application.h +++ b/src/application.h @@ -1,8 +1,10 @@ #ifndef APPLICATION_H #define APPLICATION_H - #include +class QFileSystemWatcher; +class QTimer; + namespace vnotex { class Application : public QApplication @@ -11,11 +13,21 @@ namespace vnotex public: Application(int &p_argc, char **p_argv); + // Set up theme folder watcher for hot-reload + void watchThemeFolder(const QString &p_themeFolderPath); + + // Reload the theme resources (stylesheet, icons, etc) + void reloadThemeResources(); + signals: void openFileRequested(const QString &p_filePath); protected: bool event(QEvent *p_event) Q_DECL_OVERRIDE; + + private: + QFileSystemWatcher *m_styleWatcher = nullptr; + QTimer *m_reloadTimer = nullptr; }; } diff --git a/src/commandlineoptions.cpp b/src/commandlineoptions.cpp index aa03f8b0..e0449ed0 100644 --- a/src/commandlineoptions.cpp +++ b/src/commandlineoptions.cpp @@ -25,6 +25,9 @@ CommandLineOptions::ParseResult CommandLineOptions::parse(const QStringList &p_a const QCommandLineOption logStderrOpt("log-stderr", MainWindow::tr("Log to stderr.")); parser.addOption(logStderrOpt); + const QCommandLineOption watchThemesOpt("watch-themes", MainWindow::tr("Watch theme folder for changes.")); + parser.addOption(watchThemesOpt); + // WebEngine options. // No need to handle them. Just add them to the parser to avoid parse error. { @@ -70,5 +73,9 @@ CommandLineOptions::ParseResult CommandLineOptions::parse(const QStringList &p_a m_logToStderr = true; } + if (parser.isSet(watchThemesOpt)) { + m_watchThemes = true; + } + return ParseResult::Ok; } diff --git a/src/commandlineoptions.h b/src/commandlineoptions.h index 88d6f340..3ec4b419 100644 --- a/src/commandlineoptions.h +++ b/src/commandlineoptions.h @@ -27,6 +27,9 @@ public: bool m_verbose = false; bool m_logToStderr = false; + + // Whether to watch theme folder for changes + bool m_watchThemes = false; }; #endif // COMMANDLINEOPTIONS_H diff --git a/src/core/theme.cpp b/src/core/theme.cpp index bb4cc75d..c149e5f0 100644 --- a/src/core/theme.cpp +++ b/src/core/theme.cpp @@ -24,6 +24,11 @@ Theme::Theme(const QString &p_themeFolderPath, { } +QString vnotex::Theme::getThemeFolder() const +{ + return m_themeFolderPath; +} + bool Theme::isValidThemeFolder(const QString &p_folder) { QDir dir(p_folder); diff --git a/src/core/theme.h b/src/core/theme.h index 589e0f76..42d2588c 100644 --- a/src/core/theme.h +++ b/src/core/theme.h @@ -47,6 +47,8 @@ namespace vnotex QString name() const; + QString getThemeFolder() const; + static bool isValidThemeFolder(const QString &p_folder); static Theme *fromFolder(const QString &p_folder); diff --git a/src/core/thememgr.cpp b/src/core/thememgr.cpp index 2b8e24c1..705227cc 100644 --- a/src/core/thememgr.cpp +++ b/src/core/thememgr.cpp @@ -24,8 +24,6 @@ ThemeMgr::ThemeMgr(const QString &p_currentThemeName, QObject *p_parent) loadAvailableThemes(); loadCurrentTheme(p_currentThemeName); - - IconUtils::setDefaultIconForeground(paletteColor("base#icon#fg"), paletteColor("base#icon#disabled#fg")); } QString ThemeMgr::getIconFile(const QString &p_icon) const @@ -91,6 +89,7 @@ const Theme &ThemeMgr::getCurrentTheme() const void ThemeMgr::loadCurrentTheme(const QString &p_themeName) { + m_currentTheme.reset(); auto themeFolder = findThemeFolder(p_themeName); if (themeFolder.isNull()) { qWarning() << "failed to locate theme" << p_themeName; @@ -104,6 +103,8 @@ void ThemeMgr::loadCurrentTheme(const QString &p_themeName) qWarning() << "fall back to default theme" << defaultTheme; m_currentTheme.reset(loadTheme(findThemeFolder(defaultTheme))); } + + IconUtils::setDefaultIconForeground(paletteColor("base#icon#fg"), paletteColor("base#icon#disabled#fg")); } Theme *ThemeMgr::loadTheme(const QString &p_themeFolder) @@ -211,6 +212,14 @@ QPixmap ThemeMgr::getThemePreview(const QString &p_name) const void ThemeMgr::refresh() { loadAvailableThemes(); + refreshCurrentTheme(); +} + +void vnotex::ThemeMgr::refreshCurrentTheme() +{ + if (m_currentTheme) { + loadCurrentTheme(m_currentTheme->name()); + } } void ThemeMgr::addWebStylesSearchPath(const QString &p_path) diff --git a/src/core/thememgr.h b/src/core/thememgr.h index 2ad2d7ec..399fde43 100644 --- a/src/core/thememgr.h +++ b/src/core/thememgr.h @@ -60,10 +60,11 @@ namespace vnotex const ThemeInfo *findTheme(const QString &p_name) const; - // Refresh the themes list. - // Won't affect current theme since we do not support changing theme real time for now. + // Refresh the themes list and reload current theme. void refresh(); + void refreshCurrentTheme(); + // Return all web stylesheets available, including those from themes and web styles search paths. // . QVector> getWebStyles() const; diff --git a/src/main.cpp b/src/main.cpp index f605d7fd..425f0e73 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -161,6 +161,11 @@ int main(int argc, char *argv[]) auto style = VNoteX::getInst().getThemeMgr().fetchQtStyleSheet(); if (!style.isEmpty()) { app.setStyleSheet(style); + // Set up hot-reload for the theme folder if enabled via command line + if (cmdOptions.m_watchThemes) { + const auto themeFolderPath = VNoteX::getInst().getThemeMgr().getCurrentTheme().getThemeFolder(); + app.watchThemeFolder(themeFolderPath); + } } }