Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/ApplicationSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#include <QApplication>
#include <QFont>
#include <QStyleHints>

#define CREATE_SETTING(group, name, lname, type, default) \
ApplicationSetting<type> name{#group "/" #name, default};\
Expand All @@ -38,6 +39,28 @@ ApplicationSetting<type> name{#group "/" #name, default};\
ApplicationSettings::ApplicationSettings(QObject *parent)
: QSettings{parent}
{
// Translate user-facing theme changes and OS-level color-scheme changes
// into a single effectiveDarkModeChanged signal that consumers listen for.
connect(this, &ApplicationSettings::themeChanged, this, [this](ThemeMode) {
emit effectiveDarkModeChanged(effectiveDarkMode());
});
if (auto *hints = QGuiApplication::styleHints()) {
connect(hints, &QStyleHints::colorSchemeChanged, this, [this](Qt::ColorScheme) {
if (theme() == SystemTheme)
emit effectiveDarkModeChanged(effectiveDarkMode());
});
}
}

bool ApplicationSettings::effectiveDarkMode() const
{
switch (theme()) {
case LightTheme: return false;
case DarkTheme: return true;
case SystemTheme:
default:
return QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark;
}
}

CREATE_SETTING(Gui, ShowMenuBar, showMenuBar, bool, true)
Expand Down Expand Up @@ -71,3 +94,5 @@ CREATE_SETTING(Editor, AdditionalWordChars, additionalWordChars, QString, QStrin
CREATE_SETTING(Editor, DefaultEOLMode, defaultEOLMode, QString, QStringLiteral(""))
CREATE_SETTING(Editor, URLHighlighting, urlHighlighting, bool, true)
CREATE_SETTING(Editor, ShowLineNumbers, showLineNumbers, bool, true)

CREATE_SETTING(Gui, Theme, theme, ApplicationSettings::ThemeMode, ApplicationSettings::SystemTheme)
13 changes: 13 additions & 0 deletions src/ApplicationSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@ class ApplicationSettings : public QSettings
};
Q_ENUM(DefaultDirectoryBehaviorEnum)

enum ThemeMode {
SystemTheme,
LightTheme,
DarkTheme
};
Q_ENUM(ThemeMode)

// Resolves Theme=System against the current QStyleHints color scheme.
bool effectiveDarkMode() const;
Q_SIGNAL void effectiveDarkModeChanged(bool dark);

template <typename T>
T get(const char *key, const T &defaultValue) const
{ return value(QLatin1String(key), defaultValue).template value<T>(); }
Expand Down Expand Up @@ -119,4 +130,6 @@ class ApplicationSettings : public QSettings
DEFINE_SETTING(DefaultEOLMode, defaultEOLMode, QString)
DEFINE_SETTING(URLHighlighting, urlHighlighting, bool)
DEFINE_SETTING(ShowLineNumbers, showLineNumbers, bool)

DEFINE_SETTING(Theme, theme, ThemeMode)
};
92 changes: 55 additions & 37 deletions src/EditorManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ EditorManager::EditorManager(ApplicationSettings *settings, QObject *parent)
}
}
});

connect(settings, &ApplicationSettings::effectiveDarkModeChanged, this, [=](bool) {
for (auto &editor : getEditors())
applyEditorTheme(editor);
});
}

ScintillaNext *EditorManager::createEditor(const QString &name)
Expand Down Expand Up @@ -225,54 +230,18 @@ void EditorManager::setupEditor(ScintillaNext *editor)

editor->setEdgeColour(0x80FFFF);

// https://www.scintilla.org/ScintillaDoc.html#ElementColours
// SC_ELEMENT_SELECTION_TEXT
// SC_ELEMENT_SELECTION_BACK
// SC_ELEMENT_SELECTION_ADDITIONAL_TEXT
// SC_ELEMENT_SELECTION_ADDITIONAL_BACK
// SC_ELEMENT_SELECTION_SECONDARY_TEXT
// SC_ELEMENT_SELECTION_SECONDARY_BACK
// SC_ELEMENT_SELECTION_INACTIVE_TEXT
editor->setElementColour(SC_ELEMENT_SELECTION_INACTIVE_BACK, 0xFFE0E0E0);
// SC_ELEMENT_CARET
// SC_ELEMENT_CARET_ADDITIONAL
editor->setElementColour(SC_ELEMENT_CARET_LINE_BACK, 0xFFFFE8E8);
editor->setElementColour(SC_ELEMENT_WHITE_SPACE, 0xFFD0D0D0);
// SC_ELEMENT_WHITE_SPACE_BACK
// SC_ELEMENT_HOT_SPOT_ACTIVE
// SC_ELEMENT_HOT_SPOT_ACTIVE_BACK
editor->setElementColour(SC_ELEMENT_FOLD_LINE, 0xFFA0A0A0);
// SC_ELEMENT_HIDDEN_LINE

editor->setWhitespaceSize(2);

editor->setFoldMarginColour(true, 0xFFFFFF);
editor->setFoldMarginHiColour(true, 0xE9E9E9);

editor->setAutomaticFold(SC_AUTOMATICFOLD_SHOW | SC_AUTOMATICFOLD_CLICK | SC_AUTOMATICFOLD_CHANGE);
editor->markerEnableHighlight(true);

editor->setCharsDefault();
editor->setWordChars(editor->wordChars() + settings->additionalWordChars().toLatin1());

editor->styleSetFore(STYLE_DEFAULT, 0x000000);
editor->styleSetBack(STYLE_DEFAULT, 0xFFFFFF);
editor->styleSetSize(STYLE_DEFAULT, settings->fontSize());
editor->styleSetFont(STYLE_DEFAULT, settings->fontName().toUtf8().data());
editor->styleClearAll();

editor->styleSetFore(STYLE_LINENUMBER, 0x808080);
editor->styleSetBack(STYLE_LINENUMBER, 0xE4E4E4);
editor->styleSetBold(STYLE_LINENUMBER, false);

editor->styleSetFore(STYLE_BRACELIGHT, 0x0000FF);
editor->styleSetBack(STYLE_BRACELIGHT, 0xFFFFFF);

editor->styleSetFore(STYLE_BRACEBAD, 0x000080);
editor->styleSetBack(STYLE_BRACEBAD, 0xFFFFFF);

editor->styleSetFore(STYLE_INDENTGUIDE, 0xC0C0C0);
editor->styleSetBack(STYLE_INDENTGUIDE, 0xFFFFFF);
applyEditorTheme(editor);

// STYLE_CONTROLCHAR
// STYLE_CALLTIP
Expand Down Expand Up @@ -334,6 +303,55 @@ void EditorManager::setupEditor(ScintillaNext *editor)
new HTMLAutoCompleteDecorator(editor);
}

void EditorManager::applyEditorTheme(ScintillaNext *editor)
{
const bool dark = settings->effectiveDarkMode();

// Fold markers (not affected by styleClearAll)
for (int i = SC_MARKNUM_FOLDEREND; i <= SC_MARKNUM_FOLDEROPEN; ++i) {
editor->markerSetFore(i, dark ? 0x3C3C3C : 0xF3F3F3);
editor->markerSetBack(i, 0x808080);
editor->markerSetBackSelected(i, dark ? 0xCC7A00 : 0x0000FF);
}

// Element colors (ARGB 0xAARRGGBB; not affected by styleClearAll)
editor->setElementColour(SC_ELEMENT_SELECTION_INACTIVE_BACK, dark ? 0xFF3A3D41 : 0xFFE0E0E0);
editor->setElementColour(SC_ELEMENT_CARET_LINE_BACK, dark ? 0xFF2A2D2E : 0xFFFFE8E8);
editor->setElementColour(SC_ELEMENT_WHITE_SPACE, dark ? 0xFF505050 : 0xFFD0D0D0);
editor->setElementColour(SC_ELEMENT_FOLD_LINE, dark ? 0xFF505050 : 0xFFA0A0A0);

// Fold margin (not affected by styleClearAll)
editor->setFoldMarginColour(true, dark ? 0x1E1E1E : 0xFFFFFF);
editor->setFoldMarginHiColour(true, dark ? 0x252526 : 0xE9E9E9);

// STYLE_DEFAULT sets the base for styleClearAll()
editor->styleSetFore(STYLE_DEFAULT, dark ? 0xD4D4D4 : 0x000000);
editor->styleSetBack(STYLE_DEFAULT, dark ? 0x1E1E1E : 0xFFFFFF);
editor->styleClearAll();

// Named styles must be re-applied after any subsequent styleClearAll
// (Lua SetStyle does one when a language is loaded)
applyEditorNamedStyles(editor);
}

void EditorManager::applyEditorNamedStyles(ScintillaNext *editor)
{
const bool dark = settings->effectiveDarkMode();

editor->styleSetFore(STYLE_LINENUMBER, dark ? 0x858585 : 0x808080);
editor->styleSetBack(STYLE_LINENUMBER, dark ? 0x252526 : 0xE4E4E4);
editor->styleSetBold(STYLE_LINENUMBER, false);

editor->styleSetFore(STYLE_BRACELIGHT, dark ? 0xD4D4D4 : 0x0000FF);
editor->styleSetBack(STYLE_BRACELIGHT, dark ? 0x1E1E1E : 0xFFFFFF);

editor->styleSetFore(STYLE_BRACEBAD, dark ? 0x0000FF : 0x000080);
editor->styleSetBack(STYLE_BRACEBAD, dark ? 0x1E1E1E : 0xFFFFFF);

editor->styleSetFore(STYLE_INDENTGUIDE, dark ? 0x404040 : 0xC0C0C0);
editor->styleSetBack(STYLE_INDENTGUIDE, dark ? 0x1E1E1E : 0xFFFFFF);
}

void EditorManager::purgeOldEditorPointers()
{
QMutableListIterator<QPointer<ScintillaNext>> it(editors);
Expand Down
6 changes: 5 additions & 1 deletion src/EditorManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,18 @@ class EditorManager : public QObject

void manageEditor(ScintillaNext *editor);

void applyEditorTheme(ScintillaNext *editor);
void applyEditorNamedStyles(ScintillaNext *editor);

QList<QPointer<ScintillaNext>> getEditors();

signals:
void editorCreated(ScintillaNext *editor);
void editorClosed(ScintillaNext *editor);

private:
void setupEditor(ScintillaNext *editor);
void purgeOldEditorPointers();
QList<QPointer<ScintillaNext>> getEditors();
int detectEOLMode(ScintillaNext *editor) const;

QList<QPointer<ScintillaNext>> editors;
Expand Down
18 changes: 18 additions & 0 deletions src/NotepadNextApplication.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,12 @@ bool NotepadNextApplication::init()
MarkerAppDecorator *mad = new MarkerAppDecorator(this);
mad->setEnabled(true);

luaState->setVariable("dark_mode", settings->effectiveDarkMode());
luaState->executeFile(":/scripts/init.lua");
LuaExtension::Instance().Initialise(luaState->L, Q_NULLPTR);

connect(settings, &ApplicationSettings::effectiveDarkModeChanged, this, &NotepadNextApplication::refreshEditorTheme);

createNewWindow();
connect(editorManager, &EditorManager::editorCreated, window, &MainWindow::addEditor);

Expand Down Expand Up @@ -295,6 +298,10 @@ void NotepadNextApplication::setEditorLanguage(ScintillaNext *editor, const QStr
getLuaState()->setVariable("skip_tabwidth", skipTabWidth);

getLuaState()->execute("SetLanguage(languageName)");

// Restore line number, brace match, and indent guide colors that the
// Lua-side styleClearAll wiped.
editorManager->applyEditorNamedStyles(editor);
}

QString NotepadNextApplication::detectLanguage(ScintillaNext *editor) const
Expand Down Expand Up @@ -508,3 +515,14 @@ QStringList NotepadNextApplication::debugInfo() const

return info;
}

void NotepadNextApplication::refreshEditorTheme()
{
getLuaState()->setVariable("dark_mode", settings->effectiveDarkMode());
getLuaState()->execute("UpdateTheme()");

for (auto &editor : editorManager->getEditors()) {
if (!editor->languageName.isEmpty())
setEditorLanguage(editor, editor->languageName);
}
}
3 changes: 3 additions & 0 deletions src/NotepadNextApplication.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ class NotepadNextApplication : public SingleApplication
protected:
bool event(QEvent *event) override;

public slots:
void refreshEditorTheme();

private slots:
void saveSettings();
void receiveInfoFromSecondaryInstance(quint32 instanceId, QByteArray message);
Expand Down
54 changes: 51 additions & 3 deletions src/dialogs/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include <QProcess>
#include <QScreen>
#include <QFontDatabase>
#include <QPalette>


#ifdef Q_OS_WIN
Expand Down Expand Up @@ -882,7 +883,7 @@ MainWindow::MainWindow(NotepadNextApplication *app) :
languageInspectorDock->hide();
addDockWidget(Qt::RightDockWidgetArea, languageInspectorDock);

LuaConsoleDock *luaConsoleDock = new LuaConsoleDock(app->getLuaState(), this);
LuaConsoleDock *luaConsoleDock = new LuaConsoleDock(app->getLuaState(), app->getSettings(), this);
luaConsoleDock->hide();
addDockWidget(Qt::BottomDockWidgetArea, luaConsoleDock);

Expand Down Expand Up @@ -914,6 +915,7 @@ MainWindow::MainWindow(NotepadNextApplication *app) :
});
connect(app->getSettings(), &ApplicationSettings::showToolBarChanged, ui->mainToolBar, &QToolBar::setVisible);
connect(app->getSettings(), &ApplicationSettings::showStatusBarChanged, ui->statusBar, &QStatusBar::setVisible);
connect(app->getSettings(), &ApplicationSettings::effectiveDarkModeChanged, this, &MainWindow::applyStyleSheet);
connect(ui->statusBar, &EditorInfoStatusBar::customContextMenuRequestedForEOLLabel, this, [=](const QPoint &pos){
ui->menuEOLConversion->popup(pos);
});
Expand Down Expand Up @@ -1799,10 +1801,56 @@ void MainWindow::applyStyleSheet()
{
qInfo(Q_FUNC_INFO);

auto *settings = app->getSettings();
const bool dark = settings->effectiveDarkMode();

if (settings->theme() == ApplicationSettings::SystemTheme) {
// Follow the desktop environment - leave the Qt-default palette alone.
// Qt 6.5+ already syncs the palette with QStyleHints::colorScheme().
} else {
// Explicit Light or Dark override - hardcoded palette so the app
// theme is independent of the system color scheme.
QPalette p;
if (dark) {
p.setColor(QPalette::Window, QColor(0x1E, 0x1E, 0x1E));
p.setColor(QPalette::WindowText, QColor(0xD4, 0xD4, 0xD4));
p.setColor(QPalette::Base, QColor(0x25, 0x25, 0x26));
p.setColor(QPalette::AlternateBase, QColor(0x2D, 0x2D, 0x2D));
p.setColor(QPalette::Text, QColor(0xD4, 0xD4, 0xD4));
p.setColor(QPalette::Button, QColor(0x3C, 0x3C, 0x3C));
p.setColor(QPalette::ButtonText, QColor(0xD4, 0xD4, 0xD4));
p.setColor(QPalette::Highlight, QColor(0x00, 0x7A, 0xCC));
p.setColor(QPalette::HighlightedText, QColor(0xFF, 0xFF, 0xFF));
p.setColor(QPalette::ToolTipBase, QColor(0x25, 0x25, 0x26));
p.setColor(QPalette::ToolTipText, QColor(0xD4, 0xD4, 0xD4));
p.setColor(QPalette::Link, QColor(0x40, 0xA0, 0xFF));
p.setColor(QPalette::Disabled, QPalette::Text, QColor(0x66, 0x66, 0x66));
p.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(0x66, 0x66, 0x66));
} else {
p.setColor(QPalette::Window, QColor(0xF0, 0xF0, 0xF0));
p.setColor(QPalette::WindowText, QColor(0x00, 0x00, 0x00));
p.setColor(QPalette::Base, QColor(0xFF, 0xFF, 0xFF));
p.setColor(QPalette::AlternateBase, QColor(0xF7, 0xF7, 0xF7));
p.setColor(QPalette::Text, QColor(0x00, 0x00, 0x00));
p.setColor(QPalette::Button, QColor(0xE6, 0xE6, 0xE6));
p.setColor(QPalette::ButtonText, QColor(0x00, 0x00, 0x00));
p.setColor(QPalette::Highlight, QColor(0x33, 0x99, 0xFF));
p.setColor(QPalette::HighlightedText, QColor(0xFF, 0xFF, 0xFF));
p.setColor(QPalette::ToolTipBase, QColor(0xFF, 0xFF, 0xDC));
p.setColor(QPalette::ToolTipText, QColor(0x00, 0x00, 0x00));
p.setColor(QPalette::Link, QColor(0x00, 0x00, 0xEE));
p.setColor(QPalette::Disabled, QPalette::Text, QColor(0x99, 0x99, 0x99));
p.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(0x99, 0x99, 0x99));
}
QApplication::setPalette(p);
}

// Targeted CSS for custom widgets (ADS dock tabs, QuickFindWidget, status bar)
const QString cssResource = dark ? QStringLiteral(":/stylesheets/npp-dark.css")
: QStringLiteral(":/stylesheets/npp.css");
QString sheet;
QFile f(":/stylesheets/npp.css");
QFile f(cssResource);
qInfo() << "Loading stylesheet:" << f.fileName();

f.open(QFile::ReadOnly);
sheet = f.readAll();
f.close();
Expand Down
7 changes: 7 additions & 0 deletions src/dialogs/PreferencesDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ PreferencesDialog::PreferencesDialog(ApplicationSettings *settings, QWidget *par
MapSettingToCheckBox(ui->checkBoxMenuBar, &ApplicationSettings::showMenuBar, &ApplicationSettings::setShowMenuBar, &ApplicationSettings::showMenuBarChanged);
MapSettingToCheckBox(ui->checkBoxToolBar, &ApplicationSettings::showToolBar, &ApplicationSettings::setShowToolBar, &ApplicationSettings::showToolBarChanged);
MapSettingToCheckBox(ui->checkBoxStatusBar, &ApplicationSettings::showStatusBar, &ApplicationSettings::setShowStatusBar, &ApplicationSettings::showStatusBarChanged);
ui->comboBoxTheme->setCurrentIndex(static_cast<int>(settings->theme()));
connect(ui->comboBoxTheme, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [=](int index) {
settings->setTheme(static_cast<ApplicationSettings::ThemeMode>(index));
});
connect(settings, &ApplicationSettings::themeChanged, this, [=](ApplicationSettings::ThemeMode mode) {
ui->comboBoxTheme->setCurrentIndex(static_cast<int>(mode));
});
MapSettingToCheckBox(ui->checkBoxRecenterSearchDialog, &ApplicationSettings::centerSearchDialog, &ApplicationSettings::setCenterSearchDialog, &ApplicationSettings::centerSearchDialogChanged);

MapSettingToGroupBox(ui->gbxRestorePreviousSession, &ApplicationSettings::restorePreviousSession, &ApplicationSettings::setRestorePreviousSession, &ApplicationSettings::restorePreviousSessionChanged);
Expand Down
Loading