[LyX/master] Introduce dark/light mode preference (#12224)

Juergen Spitzmueller spitz at lyx.org
Sun Nov 10 09:43:24 UTC 2024


commit 7bf14813d7bec0d7e970b579552e9cf28d3c40a0
Author: Juergen Spitzmueller <spitz at lyx.org>
Date:   Sun Nov 10 10:42:20 2024 +0100

    Introduce dark/light mode preference (#12224)
    
    This requires Qt 6.8 and only works on Win and Mac.
---
 lib/RELEASE-NOTES                   |  4 +++
 lib/configure.py                    |  2 +-
 lib/scripts/prefs2prefs_prefs.py    |  7 ++++-
 src/LyXRC.cpp                       | 22 +++++++++++++-
 src/LyXRC.h                         |  3 ++
 src/frontends/qt/GuiApplication.cpp | 10 +++++++
 src/frontends/qt/GuiPrefs.cpp       | 35 +++++++++++++++++++++++
 src/frontends/qt/GuiView.cpp        | 10 ++++---
 src/frontends/qt/ui/PrefUi.ui       | 57 ++++++++++++++++++++++++-------------
 9 files changed, 123 insertions(+), 27 deletions(-)

diff --git a/lib/RELEASE-NOTES b/lib/RELEASE-NOTES
index 03364a06fb..0ca44c7805 100644
--- a/lib/RELEASE-NOTES
+++ b/lib/RELEASE-NOTES
@@ -7,6 +7,10 @@
 
 !!!The following pref variables were added in 2.5:
 
+- \color_scheme {system,light,dark} allows to override the system-wide
+  color scheme (i.e., dark or light mode). This requires LyX to be built
+  against Qt >= 6.8.0.
+
 !!!The following pref variables were changed in 2.5:
 
 !!!The following pref variables are obsoleted in 2.5:
diff --git a/lib/configure.py b/lib/configure.py
index 7c944e72b3..50497eca55 100644
--- a/lib/configure.py
+++ b/lib/configure.py
@@ -1982,7 +1982,7 @@ if __name__ == '__main__':
     lyx_check_config = True
     lyx_kpsewhich = True
     outfile = 'lyxrc.defaults'
-    lyxrc_fileformat = 38
+    lyxrc_fileformat = 39
     rc_entries = ''
     lyx_keep_temps = False
     version_suffix = ''
diff --git a/lib/scripts/prefs2prefs_prefs.py b/lib/scripts/prefs2prefs_prefs.py
index fe7e0a1ff4..5544c6aed2 100644
--- a/lib/scripts/prefs2prefs_prefs.py
+++ b/lib/scripts/prefs2prefs_prefs.py
@@ -170,6 +170,10 @@
 #   Add option to configure ui style
 #   No conversion necessary.
 
+# Incremented to format 39, by spitz
+#   Add \color_scheme {system|light|dark}
+#   No conversion necessary.
+
 # NOTE: The format should also be updated in LYXRC.cpp and
 # in configure.py (search for lyxrc_fileformat).
 
@@ -558,5 +562,6 @@ conversions = [
 	[ 35, [add_dark_color]],
 	[ 36, [add_spellcheck_default]],
 	[ 37, [remove_fullscreen_widthlimit]],
-	[ 38, []]
+	[ 38, []],
+	[ 39, []]
 ]
diff --git a/src/LyXRC.cpp b/src/LyXRC.cpp
index 60c7f23814..e130fbf584 100644
--- a/src/LyXRC.cpp
+++ b/src/LyXRC.cpp
@@ -60,7 +60,7 @@ namespace {
 
 // The format should also be updated in configure.py, and conversion code
 // should be added to prefs2prefs_prefs.py.
-static unsigned int const LYXRC_FILEFORMAT = 38; // chillenb: screen_width and screen_limit
+static unsigned int const LYXRC_FILEFORMAT = 39; // spitz: \color_scheme {system|light|dark}
 // when adding something to this array keep it sorted!
 LexerKeyword lyxrcTags[] = {
 	{ "\\accept_compound", LyXRC::RC_ACCEPT_COMPOUND },
@@ -81,6 +81,7 @@ LexerKeyword lyxrcTags[] = {
 	{ "\\citation_search_pattern", LyXRC::RC_CITATION_SEARCH_PATTERN },
 	{ "\\citation_search_view", LyXRC::RC_CITATION_SEARCH_VIEW },
 	{ "\\close_buffer_with_last_view", LyXRC::RC_CLOSE_BUFFER_WITH_LAST_VIEW },
+	{ "\\color_scheme", LyXRC::RC_COLOR_SCHEME },
 	{ "\\completion_cursor_text", LyXRC::RC_COMPLETION_CURSOR_TEXT },
 	{ "\\completion_inline_delay", LyXRC::RC_COMPLETION_INLINE_DELAY },
 	{ "\\completion_inline_dots", LyXRC::RC_COMPLETION_INLINE_DOTS },
@@ -707,6 +708,11 @@ LyXRC::ReturnValues LyXRC::read(Lexer & lexrc, bool check_format)
 				citation_search_view = lexrc.getString();
 			break;
 
+		case RC_COLOR_SCHEME:
+			if (lexrc.next())
+				color_scheme = lexrc.getString();
+			break;
+
 		case RC_CT_ADDITIONS_UNDERLINED:
 			lexrc >> ct_additions_underlined;
 			break;
@@ -1704,6 +1710,15 @@ void LyXRC::write(ostream & os, bool ignore_system_lyxrc, string const & name) c
 		if (tag != RC_LAST)
 			break;
 		// fall through
+	case RC_COLOR_SCHEME:
+		if (ignore_system_lyxrc ||
+			color_scheme != system_lyxrc.color_scheme) {
+			os << "# Set color scheme (system|light|dark)\n";
+			os << "\\color_scheme " << color_scheme << '\n';
+		}
+		if (tag != RC_LAST)
+			break;
+		// fall through
 	case RC_CT_ADDITIONS_UNDERLINED:
 		if (ignore_system_lyxrc ||
 		    ct_additions_underlined
@@ -2926,6 +2941,7 @@ void actOnUpdatedPrefs(LyXRC const & lyxrc_orig, LyXRC const & lyxrc_new)
 	case LyXRC::RC_CITATION_SEARCH_PATTERN:
 	case LyXRC::RC_CITATION_SEARCH_VIEW:
 	case LyXRC::RC_CHECKLASTFILES:
+	case LyXRC::RC_COLOR_SCHEME:
 	case LyXRC::RC_COMPLETION_CURSOR_TEXT:
 	case LyXRC::RC_COMPLETION_INLINE_DELAY:
 	case LyXRC::RC_COMPLETION_INLINE_DOTS:
@@ -3210,6 +3226,10 @@ string const LyXRC::getDescription(LyXRCTags tag)
 		str = _("Show a small box around a Math Macro with the macro name when the cursor is inside.");
 		break;
 
+	case LyXRC::RC_COLOR_SCHEME:
+		str = _("Possibility to enforce a particular color scheme (system|dark|light)");
+		break;
+
 	case RC_DEFFILE:
 		str = _("Command definition file. Can either specify an absolute path, or LyX will look in its global and local commands/ directories.");
 		break;
diff --git a/src/LyXRC.h b/src/LyXRC.h
index 8c34712d90..ed86e97da9 100644
--- a/src/LyXRC.h
+++ b/src/LyXRC.h
@@ -58,6 +58,7 @@ public:
 		RC_CITATION_SEARCH,
 		RC_CITATION_SEARCH_PATTERN,
 		RC_CITATION_SEARCH_VIEW,
+		RC_COLOR_SCHEME,
 		RC_COMPLETION_CURSOR_TEXT,
 		RC_COMPLETION_INLINE_DELAY,
 		RC_COMPLETION_INLINE_MATH,
@@ -550,6 +551,8 @@ public:
 	std::string forward_search_dvi;
 	///
 	std::string forward_search_pdf;
+	/// specifiy color scheme (system|dark|light)
+	std::string color_scheme;
 	///
 	int export_overwrite = NO_FILES;
 	/// Default decimal point when aligning table columns on decimal
diff --git a/src/frontends/qt/GuiApplication.cpp b/src/frontends/qt/GuiApplication.cpp
index 65ed25013e..019088ea71 100644
--- a/src/frontends/qt/GuiApplication.cpp
+++ b/src/frontends/qt/GuiApplication.cpp
@@ -116,6 +116,9 @@
 #include <QSortFilterProxyModel>
 #include <QStandardItemModel>
 #include <QStyle>
+#if (QT_VERSION >= QT_VERSION_CHECK(6, 8, 0))
+#include <QStyleHints>
+#endif
 #include <QSvgRenderer>
 #include <QTimer>
 #include <QTranslator>
@@ -1256,6 +1259,13 @@ void Application::applyPrefs()
 {
 	if (lyxrc.ui_style != "default")
 		lyx::frontend::GuiApplication::setStyle(toqstr(lyxrc.ui_style));
+#if (defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN) || defined(Q_OS_MAC)) && QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
+	// Set color scheme from prefs
+	if (lyxrc.color_scheme == "dark")
+		guiApp->styleHints()->setColorScheme(Qt::ColorScheme::Dark);
+	else if (lyxrc.color_scheme == "light")
+		guiApp->styleHints()->setColorScheme(Qt::ColorScheme::Light);
+#endif
 }
 
 FuncStatus GuiApplication::getStatus(FuncRequest const & cmd) const
diff --git a/src/frontends/qt/GuiPrefs.cpp b/src/frontends/qt/GuiPrefs.cpp
index 8a25e20451..f94eeeea90 100644
--- a/src/frontends/qt/GuiPrefs.cpp
+++ b/src/frontends/qt/GuiPrefs.cpp
@@ -64,6 +64,9 @@
 #include <QSpinBox>
 #include <QString>
 #include <QStyleFactory>
+#if (defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN) || defined(Q_OS_MAC)) && QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
+#include <QStyleHints>
+#endif
 #include <QTreeWidget>
 #include <QTreeWidgetItem>
 #include <QValidator>
@@ -2523,6 +2526,10 @@ PrefUserInterface::PrefUserInterface(GuiPreferences * form)
 		this, SIGNAL(changed()));
 	connect(uiStyleCO, SIGNAL(activated(int)),
 		this, SIGNAL(changed()));
+#if (defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN) || defined(Q_OS_MAC)) && QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
+	connect(colorSchemeCO, SIGNAL(activated(int)),
+		this, SIGNAL(changed()));
+#endif
 	connect(useSystemThemeIconsCB, SIGNAL(clicked()),
 		this, SIGNAL(changed()));
 	connect(lastfilesSB, SIGNAL(valueChanged(int)),
@@ -2545,6 +2552,15 @@ PrefUserInterface::PrefUserInterface(GuiPreferences * form)
 	iconSetCO->addItem(qt_("Classic"), "classic");
 	iconSetCO->addItem(qt_("Oxygen"), "oxygen");
 
+#if (defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN) || defined(Q_OS_MAC)) && QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
+	colorSchemeCO->addItem(qt_("System Default"), "system");
+	colorSchemeCO->addItem(qt_("Light Mode"), "light");
+	colorSchemeCO->addItem(qt_("Dark Mode"), "dark");
+#else
+	colorSchemeCO->setVisible(false);
+	colorSchemeLA->setVisible(false);
+#endif
+
 	uiStyleCO->addItem(qt_("Default"), toqstr("default"));
 	for (auto const & style : QStyleFactory::keys())
 		uiStyleCO->addItem(style, style.toLower());
@@ -2575,6 +2591,19 @@ void PrefUserInterface::applyRC(LyXRC & rc) const
 		else
 			frontend::GuiApplication::setStyle(uistyle);
 	}
+#if (defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN) || defined(Q_OS_MAC)) && QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
+	QString const color_scheme = colorSchemeCO->itemData(
+		colorSchemeCO->currentIndex()).toString();
+	if (rc.color_scheme != fromqstr(color_scheme)) {
+		if (lyxrc.color_scheme == "dark")
+			guiApp->styleHints()->setColorScheme(Qt::ColorScheme::Dark);
+		else if (lyxrc.color_scheme == "light")
+			guiApp->styleHints()->setColorScheme(Qt::ColorScheme::Light);
+		else
+			guiApp->styleHints()->unsetColorScheme();
+	}
+	rc.color_scheme = fromqstr(color_scheme);
+#endif
 
 	rc.ui_file = internal_path(fromqstr(uiFileED->text()));
 	rc.use_system_theme_icons = useSystemThemeIconsCB->isChecked();
@@ -2607,6 +2636,12 @@ void PrefUserInterface::updateRC(LyXRC const & rc)
 	toggleToolbarsCB->setChecked(rc.full_screen_toolbars);
 	toggleTabbarCB->setChecked(rc.full_screen_tabbar);
 	toggleMenubarCB->setChecked(rc.full_screen_menubar);
+#if (defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN) || defined(Q_OS_MAC)) && QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
+	int colorscheme = colorSchemeCO->findData(toqstr(rc.color_scheme));
+	if (colorscheme < 0)
+		colorscheme = 0;
+	colorSchemeCO->setCurrentIndex(colorscheme);
+#endif
 }
 
 
diff --git a/src/frontends/qt/GuiView.cpp b/src/frontends/qt/GuiView.cpp
index 4163affb52..29a450a8b4 100644
--- a/src/frontends/qt/GuiView.cpp
+++ b/src/frontends/qt/GuiView.cpp
@@ -1792,10 +1792,12 @@ bool GuiView::event(QEvent * e)
 	// dark/light mode runtime switch support
 #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
 	case QEvent::ThemeChange: {
-		guiApp->setPalette(guiApp->style()->standardPalette());
-		// We need to update metrics here to avoid a crash (#12786)
-		theBufferList().changed(true);
-		refillToolbars();
+		if (lyxrc.color_scheme != "dark" && lyxrc.color_scheme != "light") {
+			guiApp->setPalette(guiApp->style()->standardPalette());
+			// We need to update metrics here to avoid a crash (#12786)
+			theBufferList().changed(true);
+			refillToolbars();
+		}
 		return QMainWindow::event(e);
 	}
 #else
diff --git a/src/frontends/qt/ui/PrefUi.ui b/src/frontends/qt/ui/PrefUi.ui
index 877d5de4ca..59e40edba8 100644
--- a/src/frontends/qt/ui/PrefUi.ui
+++ b/src/frontends/qt/ui/PrefUi.ui
@@ -42,23 +42,22 @@
       </item>
       <item row="0" column="0">
        <layout class="QGridLayout" name="gridLayout" columnstretch="0,1,0">
-        <item row="1" column="2">
-         <layout class="QHBoxLayout" name="horizontalLayout"/>
-        </item>
-        <item row="0" column="2">
-         <widget class="QPushButton" name="uiFilePB">
-          <property name="text">
-           <string>Bro&wse...</string>
-          </property>
-         </widget>
-        </item>
-        <item row="1" column="1">
-         <widget class="QComboBox" name="iconSetCO">
+        <item row="2" column="1">
+         <widget class="QComboBox" name="uiStyleCO">
           <property name="toolTip">
-           <string>The icon set to use. Warning: normal size of icons may be wrong until you save the preferences and restart LyX.</string>
+           <string>You can set a custom style here. Note that only certain styles may support dark mode, e.g. fusion on Windows.</string>
           </property>
          </widget>
         </item>
+        <item row="2" column="2">
+         <layout class="QHBoxLayout" name="horizontalLayout_2"/>
+        </item>
+        <item row="0" column="1">
+         <widget class="QLineEdit" name="uiFileED"/>
+        </item>
+        <item row="1" column="2">
+         <layout class="QHBoxLayout" name="horizontalLayout"/>
+        </item>
         <item row="1" column="0">
          <widget class="QLabel" name="iconSetLA">
           <property name="text">
@@ -79,8 +78,12 @@
           </property>
          </widget>
         </item>
-        <item row="0" column="1">
-         <widget class="QLineEdit" name="uiFileED"/>
+        <item row="1" column="1">
+         <widget class="QComboBox" name="iconSetCO">
+          <property name="toolTip">
+           <string>The icon set to use. Warning: normal size of icons may be wrong until you save the preferences and restart LyX.</string>
+          </property>
+         </widget>
         </item>
         <item row="2" column="0">
          <widget class="QLabel" name="uiStyleLA">
@@ -92,15 +95,29 @@
           </property>
          </widget>
         </item>
-        <item row="2" column="1">
-         <widget class="QComboBox" name="uiStyleCO">
+        <item row="0" column="2">
+         <widget class="QPushButton" name="uiFilePB">
+          <property name="text">
+           <string>Bro&wse...</string>
+          </property>
+         </widget>
+        </item>
+        <item row="3" column="1">
+         <widget class="QComboBox" name="colorSchemeCO">
           <property name="toolTip">
-           <string>You can set a custom style here. Note that only certain styles may support dark mode, e.g. fusion on Windows.</string>
+           <string>You can override the system's color scheme here if the selected style supports multiple schemes.</string>
           </property>
          </widget>
         </item>
-        <item row="2" column="2">
-         <layout class="QHBoxLayout" name="horizontalLayout_2"/>
+        <item row="3" column="0">
+         <widget class="QLabel" name="colorSchemeLA">
+          <property name="text">
+           <string>C&olor scheme:</string>
+          </property>
+          <property name="buddy">
+           <cstring>colorSchemeCO</cstring>
+          </property>
+         </widget>
         </item>
        </layout>
       </item>


More information about the lyx-cvs mailing list