diff --git a/appsettings.kcfg b/appsettings.kcfg index bf03ead..7183f39 100644 --- a/appsettings.kcfg +++ b/appsettings.kcfg @@ -13,5 +13,8 @@ QFontDatabase::systemFont(QFontDatabase::FixedFont) + + false + diff --git a/generalsettings.ui b/generalsettings.ui index 594093c..dc93f55 100644 --- a/generalsettings.ui +++ b/generalsettings.ui @@ -44,6 +44,20 @@ + + + + eDITOR dARK tHEME + + + + + + + Enabled + + + diff --git a/mainwindow.cpp b/mainwindow.cpp index 4b63902..4889d67 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -109,7 +109,9 @@ void MainWindow::setupActions() lexerNoneAction->setText("None (Normal Text)"); actionCollection()->addAction(u"lexer_none"_s, lexerNoneAction); connect(lexerNoneAction, &QAction::triggered, this, [this](){ - // m_editor->setLexer(nullptr); + if (SciEdit *editor = m_tabWidget->currentEditor()) { + editor->setLexer(nullptr); + } }); for (const QChar & group : LexerGroupActionMenu::groups()) { @@ -167,6 +169,17 @@ void MainWindow::setupActions() // m_editor->setIndentationGuides(!m_editor->indentationGuides()); }); + QAction *toggleEditorDarkThemeAction = new QAction(this); + toggleEditorDarkThemeAction->setText("Editor Dark Theme"); + toggleEditorDarkThemeAction->setCheckable(true); + toggleEditorDarkThemeAction->setChecked(AppSettings::editorDarkTheme()); + actionCollection()->addAction(u"toggle_editor_dark_theme"_s, toggleEditorDarkThemeAction); + connect(toggleEditorDarkThemeAction, &QAction::toggled, this, [this](bool checked){ + AppSettings::setEditorDarkTheme(checked); + AppSettings::self()->save(); + applySettingsToAllEditors(); + }); + // Load themes KColorSchemeManager *manager = KColorSchemeManager::instance(); auto *colorSelectionMenu = KColorSchemeMenu::createMenu(manager, this); @@ -185,8 +198,12 @@ void MainWindow::showSettings() dialog->setFaceType(KPageDialog::FlatList); dialog->addPage(new SettingsPage(dialog), "Appearance", "preferences-desktop-theme-global"); - connect(dialog, &KConfigDialog::settingsChanged, this, [](const QString &dialogName){ - qDebug() << dialogName << "changed"; + connect(dialog, &KConfigDialog::settingsChanged, this, [this](const QString &dialogName){ + Q_UNUSED(dialogName) + applySettingsToAllEditors(); + if (QAction *action = actionCollection()->action(QStringLiteral("toggle_editor_dark_theme"))) { + action->setChecked(AppSettings::editorDarkTheme()); + } }); dialog->show(); @@ -248,12 +265,30 @@ void MainWindow::onCurrentEditorChanged(SciEdit *editor) // 连接当前编辑器的光标位置变化信号 if (editor) { + applySettingsToEditor(editor); connect(editor, &SciEdit::cursorPosChanged, this, [this](int line, int column) { m_cursorPosStatusLabel->setText(QString("Ln: %1 Col: %2").arg(line).arg(column)); }); } } +void MainWindow::applySettingsToEditor(SciEdit *editor) +{ + if (!editor) { + return; + } + editor->setTabWidth(AppSettings::tabWidth()); + editor->setEditorFont(AppSettings::editorFont()); + editor->applyTheme(AppSettings::editorDarkTheme()); +} + +void MainWindow::applySettingsToAllEditors() +{ + for (int i = 0; i < m_tabWidget->count(); ++i) { + applySettingsToEditor(m_tabWidget->editorAt(i)); + } +} + void MainWindow::onDocumentModified(int docIndex, bool modified) { Q_UNUSED(docIndex) diff --git a/mainwindow.h b/mainwindow.h index 6ab9796..08e1a87 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -31,6 +31,8 @@ private: void setupActions(); void showSettings(); void applyLexer(const QString & lexer); + void applySettingsToEditor(SciEdit *editor); + void applySettingsToAllEditors(); void updateWindowTitle(); void updateStatusBar(); void updateActions(); diff --git a/pineapple-notepadui.rc b/pineapple-notepadui.rc index 401588a..105d443 100644 --- a/pineapple-notepadui.rc +++ b/pineapple-notepadui.rc @@ -26,6 +26,7 @@ Template: https://github.com/KDE/kxmlgui/blob/master/src/ui_standards.rc --> + E&ncoding diff --git a/sciedit.cpp b/sciedit.cpp index c48f62f..2d44f78 100644 --- a/sciedit.cpp +++ b/sciedit.cpp @@ -1,22 +1,34 @@ #include "sciedit.h" +#include + +#include +#include + +static sptr_t toBgr(const QColor &color) { + return (color.blue() << 16) | (color.green() << 8) | color.red(); +} + SciEdit::SciEdit(QWidget * parent) : ScintillaEdit(parent) { connect(this, &ScintillaEditBase::updateUi, this, [this](){ int curPos = currentPos(); emit cursorPosChanged(lineFromPosition(curPos) + 1, column(curPos)); + updateLineNumberMarginWidth(); }); // 连接文本变化信号 connect(this, QOverload::of(&ScintillaEditBase::modified), this, &SciEdit::textChanged); + + setLineNumbersEnabled(true); } void SciEdit::setStyleFont(const QFont &font, int style) { styleSetFont(style, font.family().toUtf8().constData()); - styleSetSizeFractional(0, long(font.pointSizeF() * SC_FONT_SIZE_MULTIPLIER)); + styleSetSizeFractional(style, long(font.pointSizeF() * SC_FONT_SIZE_MULTIPLIER)); } void SciEdit::setFolding(FoldType foldType, int margin) @@ -48,14 +60,64 @@ void SciEdit::setFolding(FoldType foldType, int margin) void SciEdit::setLexer(Scintilla::ILexer5 *lexer) { setILexer((sptr_t)lexer); + m_lexerName = lexer ? QString::fromUtf8(lexer->GetName()) : QString(); + if (lexer == nullptr) { + clearDocumentStyle(); + } + applyLexerStyles(); + colourise(0, -1); +} - qDebug() << lexer->GetName() << "lexer-------------"; +void SciEdit::setLineNumbersEnabled(bool enabled) +{ + m_lineNumbersEnabled = enabled; + setMarginTypeN(0, SC_MARGIN_NUMBER); + updateLineNumberMarginWidth(); +} + +void SciEdit::setEditorFont(const QFont &font) +{ + m_editorFont = font; + applyTheme(m_isDarkTheme); +} + +void SciEdit::applyTheme(bool dark) +{ + m_isDarkTheme = dark; + + const QColor bg = dark ? QColor(0x1e, 0x1e, 0x1e) : QColor(0xff, 0xff, 0xff); + const QColor fg = dark ? QColor(0xd4, 0xd4, 0xd4) : QColor(0x00, 0x00, 0x00); + const QColor caret = dark ? QColor(0xff, 0xff, 0xff) : QColor(0x00, 0x00, 0x00); + const QColor selectionBg = dark ? QColor(0x26, 0x4f, 0x78) : QColor(0xcc, 0xe8, 0xff); + const QColor caretLineBg = dark ? QColor(0x2a, 0x2a, 0x2a) : QColor(0xf5, 0xf5, 0xf5); + const QColor lineNumberFg = dark ? QColor(0x85, 0x85, 0x85) : QColor(0x80, 0x80, 0x80); + const QColor lineNumberBg = dark ? QColor(0x25, 0x25, 0x26) : QColor(0xf0, 0xf0, 0xf0); + + styleResetDefault(); + if (!m_editorFont.family().isEmpty()) { + setStyleFont(m_editorFont, STYLE_DEFAULT); + } + styleSetFore(STYLE_DEFAULT, toBgr(fg)); + styleSetBack(STYLE_DEFAULT, toBgr(bg)); + styleClearAll(); + + setCaretFore(toBgr(caret)); + setSelFore(true, toBgr(fg)); + setSelBack(true, toBgr(selectionBg)); + setCaretLineVisible(true); + setCaretLineBack(toBgr(caretLineBg)); + + styleSetFore(STYLE_LINENUMBER, toBgr(lineNumberFg)); + styleSetBack(STYLE_LINENUMBER, toBgr(lineNumberBg)); + setMarginBackN(0, toBgr(lineNumberBg)); + + updateLineNumberMarginWidth(); + applyLexerStyles(); } void SciEdit::sendColor(unsigned int iMessage, uptr_t wParam, const QColor &color) const { - sptr_t lParam = (color.blue() << 16) | (color.green() << 8) | color.red(); - send(iMessage, wParam, lParam); + send(iMessage, wParam, toBgr(color)); } void SciEdit::setMarkerDefine(int markerNumber, int markerSymbol) @@ -67,3 +129,115 @@ void SciEdit::setMarkerDefine(int markerNumber, int markerSymbol) sendColor(SCI_MARKERSETBACK, markerNumber, QColor(128, 128, 128)); } } + +void SciEdit::updateLineNumberMarginWidth() +{ + if (!m_lineNumbersEnabled) { + setMarginWidthN(0, 0); + return; + } + + const int lines = qMax(1, lineCount()); + const int digits = QString::number(lines).size(); + if (digits == m_lastLineNumberDigits && marginWidthN(0) > 0) { + return; + } + m_lastLineNumberDigits = digits; + + const QString sample = QString(digits, QLatin1Char('9')); + const QByteArray sampleBytes = sample.toUtf8(); + const sptr_t width = textWidth(STYLE_LINENUMBER, sampleBytes.constData()) + 10; + setMarginWidthN(0, width); +} + +void SciEdit::applyLexerStyles() +{ + if (m_lexerName.isEmpty()) { + return; + } + + const bool dark = m_isDarkTheme; + const QColor comment = dark ? QColor(0x6a, 0x99, 0x55) : QColor(0x00, 0x80, 0x00); + const QColor docComment = dark ? QColor(0x60, 0x80, 0x4f) : QColor(0x00, 0x80, 0x00); + const QColor keyword = dark ? QColor(0x56, 0x9c, 0xd6) : QColor(0x00, 0x00, 0xff); + const QColor type = dark ? QColor(0x4e, 0xc9, 0xb0) : QColor(0x2b, 0x91, 0xaf); + const QColor number = dark ? QColor(0xb5, 0xce, 0xa8) : QColor(0x09, 0x86, 0x58); + const QColor string = dark ? QColor(0xce, 0x91, 0x78) : QColor(0xa3, 0x15, 0x15); + const QColor preprocessor = dark ? QColor(0xc5, 0x86, 0xc0) : QColor(0xaf, 0x00, 0xdb); + const QColor tag = dark ? QColor(0x56, 0x9c, 0xd6) : QColor(0x80, 0x00, 0x00); + const QColor attr = dark ? QColor(0x9c, 0xdc, 0xfe) : QColor(0xff, 0x00, 0x00); + + auto setFore = [&](int style, const QColor &c) { + styleSetFore(style, toBgr(c)); + }; + auto setBold = [&](int style, bool b) { + styleSetBold(style, b); + }; + + if (m_lexerName == QStringLiteral("cpp")) { + setFore(SCE_C_COMMENT, comment); + setFore(SCE_C_COMMENTLINE, comment); + setFore(SCE_C_COMMENTDOC, docComment); + setFore(SCE_C_COMMENTLINEDOC, docComment); + setFore(SCE_C_COMMENTDOCKEYWORD, docComment); + setFore(SCE_C_COMMENTDOCKEYWORDERROR, docComment); + setFore(SCE_C_NUMBER, number); + setFore(SCE_C_STRING, string); + setFore(SCE_C_CHARACTER, string); + setFore(SCE_C_PREPROCESSOR, preprocessor); + setFore(SCE_C_WORD, keyword); + setBold(SCE_C_WORD, true); + setFore(SCE_C_WORD2, type); + setBold(SCE_C_WORD2, true); + } else if (m_lexerName == QStringLiteral("python")) { + setFore(SCE_P_COMMENTLINE, comment); + setFore(SCE_P_NUMBER, number); + setFore(SCE_P_STRING, string); + setFore(SCE_P_CHARACTER, string); + setFore(SCE_P_WORD, keyword); + setBold(SCE_P_WORD, true); + setFore(SCE_P_WORD2, type); + setBold(SCE_P_WORD2, true); + setFore(SCE_P_DECORATOR, preprocessor); + setFore(SCE_P_CLASSNAME, type); + setFore(SCE_P_DEFNAME, type); + } else if (m_lexerName == QStringLiteral("hypertext")) { + setFore(SCE_H_COMMENT, comment); + setFore(SCE_H_TAG, tag); + setFore(SCE_H_TAGUNKNOWN, tag); + setFore(SCE_H_ATTRIBUTE, attr); + setFore(SCE_H_ATTRIBUTEUNKNOWN, attr); + setFore(SCE_H_NUMBER, number); + setFore(SCE_H_DOUBLESTRING, string); + setFore(SCE_H_SINGLESTRING, string); + setFore(SCE_H_ENTITY, preprocessor); + } else if (m_lexerName == QStringLiteral("xml")) { + setFore(SCE_H_COMMENT, comment); + setFore(SCE_H_TAG, tag); + setFore(SCE_H_TAGUNKNOWN, tag); + setFore(SCE_H_ATTRIBUTE, attr); + setFore(SCE_H_ATTRIBUTEUNKNOWN, attr); + setFore(SCE_H_NUMBER, number); + setFore(SCE_H_DOUBLESTRING, string); + setFore(SCE_H_SINGLESTRING, string); + setFore(SCE_H_ENTITY, preprocessor); + } else if (m_lexerName == QStringLiteral("json")) { + setFore(SCE_JSON_NUMBER, number); + setFore(SCE_JSON_STRING, string); + setFore(SCE_JSON_PROPERTYNAME, attr); + setFore(SCE_JSON_OPERATOR, preprocessor); + setFore(SCE_JSON_LINECOMMENT, comment); + setFore(SCE_JSON_BLOCKCOMMENT, comment); + } else if (m_lexerName == QStringLiteral("css")) { + setFore(SCE_CSS_COMMENT, comment); + setFore(SCE_CSS_CLASS, attr); + setFore(SCE_CSS_ID, attr); + setFore(SCE_CSS_TAG, tag); + setFore(SCE_CSS_ATTRIBUTE, attr); + setFore(SCE_CSS_DOUBLESTRING, string); + setFore(SCE_CSS_SINGLESTRING, string); + setFore(SCE_CSS_VALUE, string); + setFore(SCE_CSS_IDENTIFIER, keyword); + setBold(SCE_CSS_IDENTIFIER, true); + } +} diff --git a/sciedit.h b/sciedit.h index 456738f..caa25ec 100644 --- a/sciedit.h +++ b/sciedit.h @@ -3,6 +3,9 @@ #include #include +#include +#include + class SciEdit : public ScintillaEdit { Q_OBJECT @@ -18,6 +21,9 @@ public: void setStyleFont(const QFont & font, int style = STYLE_DEFAULT); void setFolding(FoldType foldType, int margin = 2); void setLexer(Scintilla::ILexer5 * lexer); + void setLineNumbersEnabled(bool enabled); + void setEditorFont(const QFont &font); + void applyTheme(bool dark); void sendColor(unsigned int iMessage, uptr_t wParam, const QColor &col) const; signals: @@ -26,4 +32,12 @@ signals: private: void setMarkerDefine(int markerNumber, int markerSymbol); + void updateLineNumberMarginWidth(); + void applyLexerStyles(); + + bool m_lineNumbersEnabled = false; + int m_lastLineNumberDigits = 0; + bool m_isDarkTheme = false; + QFont m_editorFont; + QString m_lexerName; }; diff --git a/tabwidget.cpp b/tabwidget.cpp index 1a36f50..c0353d2 100644 --- a/tabwidget.cpp +++ b/tabwidget.cpp @@ -1,11 +1,41 @@ #include "tabwidget.h" #include "sciedit.h" #include "documentmanager.h" +#include "appsettings.h" #include #include #include +#include +#include + +static QString lexerNameForDocumentLanguage(const QString &language) +{ + if (language == QStringLiteral("C++") || + language == QStringLiteral("C") || + language == QStringLiteral("C/C++ Header")) { + return QStringLiteral("cpp"); + } + if (language == QStringLiteral("Python")) { + return QStringLiteral("python"); + } + if (language == QStringLiteral("HTML")) { + return QStringLiteral("hypertext"); + } + if (language == QStringLiteral("XML")) { + return QStringLiteral("xml"); + } + if (language == QStringLiteral("JSON")) { + return QStringLiteral("json"); + } + if (language == QStringLiteral("CSS")) { + return QStringLiteral("css"); + } + + return QString(); +} + TabWidget::TabWidget(DocumentManager *documentManager, QWidget *parent) : QTabWidget(parent) , m_documentManager(documentManager) @@ -73,6 +103,17 @@ int TabWidget::openDocument(const QString &filePath) // 设置编辑器内容 QString content = m_documentManager->getDocumentContent(docId); editor->setText(content.toUtf8().constData()); + editor->setTabWidth(AppSettings::tabWidth()); + editor->setEditorFont(AppSettings::editorFont()); + editor->applyTheme(AppSettings::editorDarkTheme()); + + const QString lexerName = lexerNameForDocumentLanguage(m_documentManager->getDocumentLanguage(docId)); + if (lexerName.isEmpty()) { + editor->setLexer(nullptr); + } else { + Scintilla::ILexer5 *lexer = CreateLexer(lexerName.toStdString().c_str()); + editor->setLexer(lexer); + } // 重新连接信号 connectEditorSignals(editor); @@ -136,7 +177,17 @@ bool TabWidget::saveCurrentDocumentAs() QByteArray content = editor->getText(editor->textLength()); m_documentManager->setDocumentContent(docId, QString::fromUtf8(content)); - return m_documentManager->saveDocumentAs(docId, fileName); + const bool saved = m_documentManager->saveDocumentAs(docId, fileName); + if (saved) { + const QString lexerName = lexerNameForDocumentLanguage(m_documentManager->getDocumentLanguage(docId)); + if (lexerName.isEmpty()) { + editor->setLexer(nullptr); + } else { + Scintilla::ILexer5 *lexer = CreateLexer(lexerName.toStdString().c_str()); + editor->setLexer(lexer); + } + } + return saved; } void TabWidget::closeCurrentTab() @@ -349,6 +400,9 @@ void TabWidget::onEditorTextChanged() SciEdit *TabWidget::createEditor() { SciEdit *editor = new SciEdit(this); + editor->setTabWidth(AppSettings::tabWidth()); + editor->setEditorFont(AppSettings::editorFont()); + editor->applyTheme(AppSettings::editorDarkTheme()); return editor; } @@ -379,4 +433,4 @@ void TabWidget::disconnectEditorSignals(SciEdit *editor) if (editor) { disconnect(editor, &SciEdit::textChanged, this, &TabWidget::onEditorTextChanged); } -} \ No newline at end of file +}