diff --git a/liteidex/src/liteapp/liteapp.cpp b/liteidex/src/liteapp/liteapp.cpp index aed788a0..6c1b93cb 100644 --- a/liteidex/src/liteapp/liteapp.cpp +++ b/liteidex/src/liteapp/liteapp.cpp @@ -62,6 +62,9 @@ #include #include #include + +#include "thememanager.h" + //lite_memory_check_begin #if defined(WIN32) && defined(_MSC_VER) && defined(_DEBUG) #define _CRTDBG_MAP_ALLOC @@ -136,6 +139,10 @@ IApplication* LiteApp::NewApplication(const QString &sessionName, IApplication * { LiteApp *app = new LiteApp; app->load(sessionName,baseApp); + + //chen: improve for new app instance + ThemeManager::apply_to_liteApp(app); + return app; } @@ -154,6 +161,8 @@ QMap LiteApp::s_cookie; QList LiteApp::s_appList; +bool LiteApp::s_darkMode; + LiteApp::LiteApp() : m_rootPath(LiteApp::getRootPath()), m_applicationPath(QApplication::applicationDirPath()), diff --git a/liteidex/src/liteapp/liteapp.h b/liteidex/src/liteapp/liteapp.h index a36bca8e..c2d60e1a 100644 --- a/liteidex/src/liteapp/liteapp.h +++ b/liteidex/src/liteapp/liteapp.h @@ -159,6 +159,7 @@ protected slots: public: static QMap s_cookie; static QList s_appList; + static bool s_darkMode; //app dark or light protected: QAction *m_newAct; QAction *m_openFileAct; diff --git a/liteidex/src/liteapp/liteapp.pro b/liteidex/src/liteapp/liteapp.pro index 6eb9d257..13cae7ad 100644 --- a/liteidex/src/liteapp/liteapp.pro +++ b/liteidex/src/liteapp/liteapp.pro @@ -75,6 +75,7 @@ SOURCES += main.cpp\ folderprojectfactory.cpp \ goproxy.cpp \ htmlwidgetmanager.cpp \ + thememanager.cpp \ textbrowserhtmlwidget.cpp \ splitwindowstyle.cpp \ sidewindowstyle.cpp \ @@ -110,6 +111,7 @@ HEADERS += mainwindow.h \ goproxy.h \ cdrv.h \ htmlwidgetmanager.h \ + thememanager.h \ textbrowserhtmlwidget.h \ windowstyle.h \ splitwindowstyle.h \ diff --git a/liteidex/src/liteapp/liteapp_global.h b/liteidex/src/liteapp/liteapp_global.h index e10cdd7a..b7369158 100644 --- a/liteidex/src/liteapp/liteapp_global.h +++ b/liteidex/src/liteapp/liteapp_global.h @@ -53,6 +53,7 @@ #define LITEAPP_EDITTABSENABLEWHELL "LiteApp/EditTabEnableWhell" #define LITEAPP_SHOWEDITTOOLBAR "LiteApp/ShowEditToolbar" #define LITEAPP_QSS "LiteApp/Qss" +#define LITEAPP_QSS_DARK "LiteApp/Qss_dark" #define LITEAPP_FULLSCREEN "LiteApp/FullScreen" #define LITEAPP_WINSTATE "LiteApp/WinState" #define LITEAPP_SHORTCUTS "keybord_shortcuts/" diff --git a/liteidex/src/liteapp/liteappoption.cpp b/liteidex/src/liteapp/liteappoption.cpp index 0bb6b292..8cb65322 100644 --- a/liteidex/src/liteapp/liteappoption.cpp +++ b/liteidex/src/liteapp/liteappoption.cpp @@ -34,6 +34,8 @@ #include #include +#include "thememanager.h" + //lite_memory_check_begin #if defined(WIN32) && defined(_MSC_VER) && defined(_DEBUG) #define _CRTDBG_MAP_ALLOC @@ -102,6 +104,12 @@ LiteAppOption::LiteAppOption(LiteApi::IApplication *app,QObject *parent) : foreach (QFileInfo info, qssDir.entryInfoList(QStringList() << "*.qss")) { ui->qssComboBox->addItem(info.fileName()); } + + // qss dark: init + foreach (QFileInfo info, qssDir.entryInfoList(QStringList() << "*.qss")) { + ui->qssComboBox_dark->addItem(info.fileName()); + } + ui->qssComboBox_dark->addItem(QString("NOT SET")); } // if (libgopher.isValid()) { @@ -225,8 +233,21 @@ void LiteAppOption::save() m_liteApp->settings()->setValue(LITEAPP_QSS,qss); QString styleSheet = QLatin1String(f.readAll()); qApp->setStyleSheet(styleSheet); + + //chen: auto editor theme + ThemeManager::app_theme_changed(qss); } } + // qss dark: save + QString qss_dark = ui->qssComboBox_dark->currentText(); + if (!qss_dark.isEmpty()) { + m_liteApp->settings()->setValue(LITEAPP_QSS_DARK,qss_dark); + + //chen: auto editor theme + // auto_editor_theme(qss, m_liteApp); + } + qDebug() << "=== save qss, qss_dark:" << qss << qss_dark; + // TODO: improve the dark/light settings UI bool customelIcon = ui->customIconCheckBox->isChecked(); m_liteApp->settings()->setValue(LITEIDE_CUSTOMEICON,customelIcon); @@ -291,6 +312,13 @@ void LiteAppOption::load() if (index >= 0 && index < ui->qssComboBox->count()) { ui->qssComboBox->setCurrentIndex(index); } + //qss dark: load + QString qss_dark = m_liteApp->settings()->value(LITEAPP_QSS_DARK,"NOT SET").toString(); + int index2 = ui->qssComboBox_dark->findText(qss_dark,Qt::MatchFixedString); + if (index2 >= 0 && index < ui->qssComboBox_dark->count()) { + ui->qssComboBox_dark->setCurrentIndex(index2); + } + qDebug() << "=== load qss, qss_dark:" << qss << qss_dark << index2; int max = m_liteApp->settings()->value(LITEAPP_MAXRECENTFILES,32).toInt(); //ui->maxRecentLineEdit->setText(QString("%1").arg(max)); diff --git a/liteidex/src/liteapp/liteappoption.ui b/liteidex/src/liteapp/liteappoption.ui index 9d61dc5d..0775b6c2 100644 --- a/liteidex/src/liteapp/liteappoption.ui +++ b/liteidex/src/liteapp/liteappoption.ui @@ -178,6 +178,45 @@ + + + + + + + + Theme Dark + + + + + + Theme: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + diff --git a/liteidex/src/liteapp/main.cpp b/liteidex/src/liteapp/main.cpp index cfd5d154..982f8d28 100644 --- a/liteidex/src/liteapp/main.cpp +++ b/liteidex/src/liteapp/main.cpp @@ -41,6 +41,9 @@ #include "liteapp.h" #include "goproxy.h" #include "cdrv.h" + +#include "thememanager.h" + //lite_memory_check_begin #if defined(WIN32) && defined(_MSC_VER) && defined(_DEBUG) #define _CRTDBG_MAP_ALLOC @@ -224,6 +227,10 @@ int main(int argc, char *argv[]) app.liteApp = liteApp; #endif + //chen: auto editor theme + ThemeManager themeManager(&app); + ThemeManager::monit_system_theme(&app); + foreach(QString file, fileList) { QFileInfo f(file); if (f.isFile()) { diff --git a/liteidex/src/liteapp/thememanager.cpp b/liteidex/src/liteapp/thememanager.cpp new file mode 100644 index 00000000..253004b5 --- /dev/null +++ b/liteidex/src/liteapp/thememanager.cpp @@ -0,0 +1,256 @@ + +// Module: thememanager.cpp +// Creator: yurenchen + +#include +#include +#include +#include + +#include "liteapp.h" + +#ifndef Q_OS_LINUX + #define Q_OS_LINUX 11 +#endif + +class ThemeManager : public QObject { + Q_OBJECT + +public: + //single instance + static ThemeManager* instance(QObject *parent = nullptr) { + static ThemeManager* _instance = nullptr; + if (!_instance) { + _instance = new ThemeManager(parent); + } + return _instance; + } + + explicit ThemeManager(QObject *parent = nullptr); + + static bool system_dark_mode; + static bool app_dark_mode; + static QApplication *app; + + static void monit_system_theme(QApplication *app); + + static void app_theme_changed(QString qss); + static void editor_theme_changed(); + static bool guess_app_theme_dark(QString qss); + + static void apply_to_liteApp(LiteApp *liteApp); + +signals: + void system_theme_change(bool dark_mode); + void app_theme_change(bool dark_mode); + +public slots: + void setting_changed(); + void system_theme_onchange(bool dark_mode); + + +}; + +// Static member initialization +bool ThemeManager::system_dark_mode = false; +bool ThemeManager::app_dark_mode = false; +QApplication *ThemeManager::app; + +void auto_editor_theme(bool is_dark , LiteApi::IApplication *m_liteApp); +void auto_app_theme(bool is_dark, QApplication &app, LiteApi::IApplication *m_liteApp); + +ThemeManager::ThemeManager(QObject *parent) : QObject(parent) { + +} + +void ThemeManager::apply_to_liteApp(LiteApp *liteApp) { + if (!liteApp) return; + + auto_editor_theme(LiteApp::s_darkMode, liteApp); +} + +void ThemeManager::system_theme_onchange(bool dark_mode) { + foreach(LiteApi::IApplication *liteApp, LiteApp::appList()) { + // auto_app_theme(qss, *app, liteApp); + // auto_app_theme(dark_mode, *app, liteApp); + auto_app_theme(dark_mode, *ThemeManager::app, liteApp); + } +} + +void ThemeManager::app_theme_changed(QString qss){ + bool is_dark = guess_app_theme_dark(qss); + if (is_dark != app_dark_mode) { + app_dark_mode = is_dark; + emit instance()->app_theme_change(app_dark_mode); + } +} +void ThemeManager::editor_theme_changed(){ + qDebug()<< "=== editor_thene_changed: need reload!"; +} + +bool ThemeManager::guess_app_theme_dark(QString qss){ + // printf("--- guess_dark: %s\n", qss.toStdString().c_str()); + QString dark_keywords[] = { "black", "dark", "night" }; + QString dark_names[] = { "carbon", "detroit-future", "gray", "sublime" }; + + QString qss_lower = qss.toLower(); + + for (const QString &keyword : dark_keywords) { + if (qss_lower.contains(keyword)) { + // qDebug() << "= guess dark by keyword:" << keyword; + return true; + } + } + if (qss_lower.endsWith(".qss")) { + qss_lower.chop(4); + } + for (const QString &theme : dark_names) { + if (qss_lower == theme) { + // qDebug() << "= guess dark by name:" << theme; + return true; + } + } + + return false; +} + +void ThemeManager::monit_system_theme(QApplication *app) { +// void ThemeManager::monit_system_theme(QObject *app) { + // monitor system theme and emit signal if changed + + ThemeManager::app = app; + +#if defined(Q_OS_LINUX) + // TODO: implement it for other desktop environment (currently only gnome is supported) + // TODO: improve it without run a gsettings process (currently need gsettings cmd) + qDebug()<< "=== monit_system_theme_change..."; + + // 启动 gsettings monitor 进程 + // gsettings monitor org.gnome.desktop.interface color-scheme + QProcess *proc = new QProcess(app); + proc->setProcessChannelMode(QProcess::MergedChannels); + proc->start("gsettings", {"monitor", "org.gnome.desktop.interface", "color-scheme"}); + + // 初始读取一次 + { + QProcess getProc; + getProc.start("gsettings", {"get", "org.gnome.desktop.interface", "color-scheme"}); + getProc.waitForFinished(); + QString theme = getProc.readAllStandardOutput().trimmed(); + theme = theme.remove("'"); + qDebug() << "== system theme current:" << theme; + + //----- 0. init theme + bool is_dark = theme.contains("dark", Qt::CaseInsensitive); + system_dark_mode = is_dark; + // emit instance()->system_theme_change(system_dark_mode); + instance()->system_theme_onchange(system_dark_mode); + } + + // 监听变化 + QObject::connect(proc, &QProcess::readyReadStandardOutput, app, [proc, app]() { + while (proc->canReadLine()) { + QString line = QString::fromUtf8(proc->readLine()).trimmed(); + if (line.contains("color-scheme")) { + QString theme = line.section(' ', -1); // 取最后一个单词 + theme = theme.remove("'"); + qDebug() << "== system theme changed:" << theme; + + bool is_dark = theme.contains("dark", Qt::CaseInsensitive); + + bool new_mode = is_dark; + if (new_mode != system_dark_mode) { + system_dark_mode = new_mode; + // emit instance()->system_theme_change(system_dark_mode); + + instance()->system_theme_onchange(system_dark_mode); + } + } + } + }); + +#elif defined(Q_OS_WIN) + // TODO: implement it for windows11 +#elif defined(Q_OS_MAC) + // TODO: implement it for macos +#else + +#endif +} + + +bool guess_dark(QString qss){ + // printf("--- guess_dark: %s\n", qss.toStdString().c_str()); + QString dark_keywords[] = { "black", "dark", "night" }; + QString dark_names[] = { "carbon", "detroit-future", "gray", "sublime" }; + + QString qss_lower = qss.toLower(); + + for (const QString &keyword : dark_keywords) { + if (qss_lower.contains(keyword)) { + // qDebug() << "= guess dark by keyword:" << keyword; + return true; + } + } + if (qss_lower.endsWith(".qss")) { + qss_lower.chop(4); + } + for (const QString &theme : dark_names) { + if (qss_lower == theme) { + // qDebug() << "= guess dark by name:" << theme; + return true; + } + } + + return false; +} + +void auto_editor_theme(bool is_dark , LiteApi::IApplication *m_liteApp){ + //----1.check if dark + // bool is_dark = guess_dark(qss); + // qDebug() << "--- auto_editor_theme guess_dark:" << (is_dark ? "dark" : "light") << " //app:" << m_liteApp; + + //----2.get editor settings + #define EDITOR_STYLE "editor/style" + #define EDITOR_STYLE_DARK "editor/style_dark" + QString styleName = m_liteApp->settings()->value(EDITOR_STYLE,"default.xml").toString(); + QString styleName_dark = m_liteApp->settings()->value(EDITOR_STYLE_DARK,"NOT SET").toString(); + + //----3.apply editor theme + if (is_dark && styleName_dark != "NOT SET") { + // styleName = styleName_dark; + qDebug() << "=== auto_editor_theme dark:" << styleName_dark; + QString style = styleName_dark; + QString styleFile = m_liteApp->resourcePath()+"/liteeditor/color/"+style; + m_liteApp->editorManager()->loadColorStyleScheme(styleFile); + }else{ + qDebug() << "=== auto_editor_theme light:" << styleName; + QString style = styleName; + QString styleFile = m_liteApp->resourcePath()+"/liteeditor/color/"+style; + m_liteApp->editorManager()->loadColorStyleScheme(styleFile); + } +} + +void auto_app_theme(QString qss, QApplication &app, LiteApi::IApplication *m_liteApp){ + // QFile f(resPath+"/liteapp/qss/"+qss); + QFile f(m_liteApp->resourcePath()+"/liteapp/qss/"+qss); + if (f.open(QFile::ReadOnly)) { + QString styleSheet = QLatin1String(f.readAll()); + app.setStyleSheet(styleSheet); + } + + LiteApp::s_darkMode = guess_dark(qss); + auto_editor_theme(LiteApp::s_darkMode, m_liteApp); + + // auto_editor_theme(qss, m_liteApp); + // foreach(LiteApi::IApplication *liteApp, m_liteApp->appList()) { + // foreach(LiteApi::IApplication *liteApp, m_liteApp->instanceList()) { + // qDebug() << " >>> auto_app_theme: - instance:" << liteApp; + // auto_editor_theme(qss, liteApp); + // } +} + +void auto_app_theme(bool is_dark, QApplication &app, LiteApi::IApplication *m_liteApp){ + QString qss = is_dark ? "gray.qss" : "default.qss"; + auto_app_theme(qss, app, m_liteApp); +} diff --git a/liteidex/src/liteapp/thememanager.h b/liteidex/src/liteapp/thememanager.h new file mode 100644 index 00000000..3bf47fcf --- /dev/null +++ b/liteidex/src/liteapp/thememanager.h @@ -0,0 +1,42 @@ + +// Module: thememanager.cpp +// Creator: yurenchen + +#pragma once + +#include +#include +#include + +#include "liteapp.h" + +class ThemeManager : public QObject { + Q_OBJECT + +public: + static ThemeManager* instance(QObject *parent = nullptr); + + explicit ThemeManager(QObject *parent = nullptr); + + static bool system_dark_mode; + static bool app_dark_mode; + + //init + static void monit_system_theme(QApplication *app); + + //call when change + static void app_theme_changed(QString qss); + static void editor_theme_changed(); + + //call when liteApp created + static void apply_to_liteApp(LiteApp *liteApp); +private: + static bool guess_app_theme_dark(QString qss); + +signals: + void system_theme_change(bool dark_mode); + void app_theme_change(bool dark_mode); + +public slots: + void setting_changed(); +}; \ No newline at end of file diff --git a/liteidex/src/plugins/liteeditor/liteeditor_global.h b/liteidex/src/plugins/liteeditor/liteeditor_global.h index a9c401cc..599ae4a9 100644 --- a/liteidex/src/plugins/liteeditor/liteeditor_global.h +++ b/liteidex/src/plugins/liteeditor/liteeditor_global.h @@ -41,6 +41,7 @@ #define OPTION_LITEEDITOR "option/liteeditor" #define EDITOR_STYLE "editor/style" +#define EDITOR_STYLE_DARK "editor/style_dark" #define EDITOR_FAMILY "editor/family" #define EDITOR_FONTSIZE "editor/fontsize" #define EDITOR_FONTZOOM "editor/fontzoom" diff --git a/liteidex/src/plugins/liteeditor/liteeditoroption.cpp b/liteidex/src/plugins/liteeditor/liteeditoroption.cpp index 082e20ec..8c36d748 100755 --- a/liteidex/src/plugins/liteeditor/liteeditoroption.cpp +++ b/liteidex/src/plugins/liteeditor/liteeditoroption.cpp @@ -30,6 +30,9 @@ #include #include #include +#include + +#include "../liteapp/thememanager.h" //lite_memory_check_begin #if defined(WIN32) && defined(_MSC_VER) && defined(_DEBUG) @@ -123,11 +126,24 @@ void LiteEditorOption::save() m_liteApp->settings()->setValue(EDITOR_FONTZOOM,fontZoom); QString style = ui->styleComboBox->currentText(); + bool theme_changed = false; if (style != m_liteApp->settings()->value(EDITOR_STYLE,"default.xml").toString()) { m_liteApp->settings()->setValue(EDITOR_STYLE,style); QString styleFile = m_liteApp->resourcePath()+"/liteeditor/color/"+style; m_liteApp->editorManager()->loadColorStyleScheme(styleFile); + theme_changed = true; + } + // CHEN: save style_dark + QString style_dark = ui->styleComboBox_dark->currentText(); + if (style_dark != m_liteApp->settings()->value(EDITOR_STYLE_DARK,"NOT SET").toString()) { + m_liteApp->settings()->setValue(EDITOR_STYLE_DARK,style_dark); + qDebug() << "=== editor_dark_theme save:" << style_dark; + theme_changed = true; } + if(theme_changed){ + ThemeManager::editor_theme_changed(); + } + // TODO: improve the dark/light settings UI bool noprintCheck = ui->noprintCheckBox->isChecked(); bool autoIndent = ui->autoIndentCheckBox->isChecked(); @@ -229,23 +245,39 @@ void LiteEditorOption::load() ui->fontZoomSpinBox->setValue(fontZoom); + //CHEN: load style_dark + QString styleName_dark = m_liteApp->settings()->value(EDITOR_STYLE_DARK,"NOT SET").toString(); QString styleName = m_liteApp->settings()->value(EDITOR_STYLE,"default.xml").toString(); QString stylePath = m_liteApp->resourcePath()+"/liteeditor/color"; QDir dir(stylePath); + int index_dark = -1; int index = -1; if (!QFileInfo(stylePath,styleName).exists()) { styleName = "default.xml"; } + + //----- CHEN: 加载可选 editor theme ui->styleComboBox->clear(); + ui->styleComboBox_dark->clear(); foreach(QFileInfo info, dir.entryInfoList(QStringList() << "*.xml")) { ui->styleComboBox->addItem(info.fileName()); + ui->styleComboBox_dark->addItem(info.fileName()); if (info.fileName() == styleName) { index = ui->styleComboBox->count()-1; } + if (info.fileName() == styleName_dark) { + index_dark = ui->styleComboBox_dark->count()-1; + } } if (index >= 0 && index < ui->styleComboBox->count()) { ui->styleComboBox->setCurrentIndex(index); } + if (index_dark >= 0 && index_dark < ui->styleComboBox_dark->count()) { + ui->styleComboBox_dark->setCurrentIndex(index_dark); + } + ui->styleComboBox_dark->addItem("NOT SET"); + qDebug() << "=== editor_dark_theme load:" << styleName_dark; + bool noprintCheck = m_liteApp->settings()->value(EDITOR_NOPRINTCHECK,true).toBool(); bool autoIndent = m_liteApp->settings()->value(EDITOR_AUTOINDENT,true).toBool(); bool autoBraces0 = m_liteApp->settings()->value(EDITOR_AUTOBRACE0,true).toBool(); diff --git a/liteidex/src/plugins/liteeditor/liteeditoroption.ui b/liteidex/src/plugins/liteeditor/liteeditoroption.ui index 3deb9c9a..3f355715 100644 --- a/liteidex/src/plugins/liteeditor/liteeditoroption.ui +++ b/liteidex/src/plugins/liteeditor/liteeditoroption.ui @@ -191,6 +191,47 @@ + + + + + + Dark: + + + + + + + + 0 + 0 + + + + + + + + Edit + + + + + + + Qt::Horizontal + + + + 37 + 17 + + + + + +