Files
pineapple-notepad/mainwindow.cpp

381 lines
13 KiB
C++
Raw Normal View History

2026-02-01 15:56:33 +08:00
#include "mainwindow.h"
#include "sciedit.h"
#include "tabwidget.h"
#include "documentmanager.h"
#include "appsettings.h"
#include "editorviewhelper.h"
#include <QActionGroup>
#include <QApplication>
#include <QMenu>
#include <QStringBuilder>
#include <QStatusBar>
#include <QFileDialog>
#include <QMessageBox>
#include <KActionCollection>
#include <KStandardAction>
#include <KActionMenu>
#include <KConfigDialog>
#include <KColorSchemeManager>
#include <KColorSchemeMenu>
#include <QSpinBox>
#include <ILexer.h>
#include <Lexilla.h>
MainWindow::MainWindow(QWidget *parent)
: KXmlGuiWindow(parent)
, m_documentManager(new DocumentManager(this))
, m_tabWidget(new TabWidget(m_documentManager, this))
, m_cursorPosStatusLabel(new QLabel(QString("Ln: ? Col: ?"), this))
, m_encodingStatusLabel(new QLabel(QString("UTF-8"), this))
, m_languageStatusLabel(new QLabel(QString("Plain Text"), this))
{
setCentralWidget(m_tabWidget);
// 设置状态栏
statusBar()->addPermanentWidget(m_cursorPosStatusLabel);
statusBar()->addPermanentWidget(m_encodingStatusLabel);
statusBar()->addPermanentWidget(m_languageStatusLabel);
// 连接标签页信号
connect(m_tabWidget, &TabWidget::currentEditorChanged, this, &MainWindow::onCurrentEditorChanged);
// 连接文档管理器信号
connect(m_documentManager, &DocumentManager::documentModified, this, &MainWindow::onDocumentModified);
setupActions();
// 创建第一个标签页
newFile();
}
MainWindow::~MainWindow()
{
}
void MainWindow::setupActions()
{
using namespace Qt::Literals::StringLiterals;
// "File" menu
KStandardAction::openNew(this, &MainWindow::newFile, actionCollection());
KStandardAction::open(this, &MainWindow::openFile, actionCollection());
KStandardAction::save(this, &MainWindow::saveFile, actionCollection());
KStandardAction::close(this, &MainWindow::closeFile, actionCollection());
KStandardAction::quit(qApp, &QApplication::quit, actionCollection());
// 添加另存为动作
QAction *saveAsAction = KStandardAction::saveAs(this, &MainWindow::saveAsFile, actionCollection());
// "Edit" menu
KStandardAction::undo(this, [this](){
if (SciEdit *editor = m_tabWidget->currentEditor()) {
editor->undo();
}
}, actionCollection());
KStandardAction::redo(this, [this](){
if (SciEdit *editor = m_tabWidget->currentEditor()) {
editor->redo();
}
}, actionCollection());
KStandardAction::cut(this, [this](){
if (SciEdit *editor = m_tabWidget->currentEditor()) {
editor->cut();
}
}, actionCollection());
KStandardAction::copy(this, [this](){
if (SciEdit *editor = m_tabWidget->currentEditor()) {
editor->copy();
}
}, actionCollection());
KStandardAction::paste(this, [this](){
if (SciEdit *editor = m_tabWidget->currentEditor()) {
editor->paste();
}
}, actionCollection());
// "Search" menu
KStandardAction::find(this, [](){}, actionCollection());
KStandardAction::findNext(this, [](){}, actionCollection());
KStandardAction::findPrev(this, [](){}, actionCollection());
KStandardAction::replace(this, [](){}, actionCollection());
qDebug() << KStandardAction::name(KStandardAction::Replace);
// "Language" menu
QAction *lexerNoneAction = new QAction(this);
lexerNoneAction->setText("None (Normal Text)");
actionCollection()->addAction(u"lexer_none"_s, lexerNoneAction);
connect(lexerNoneAction, &QAction::triggered, this, [this](){
if (SciEdit *editor = m_tabWidget->currentEditor()) {
editor->setLexer(nullptr);
2026-02-01 16:23:33 +08:00
const int docId = m_tabWidget->currentDocumentId();
if (docId >= 0) {
m_documentManager->setDocumentLanguage(docId, QString());
}
updateStatusBar();
2026-02-01 15:56:33 +08:00
}
});
QMetaObject::invokeMethod(this, [this](){
auto lexerGroupListActions = QList<QAction*>();
for (const QChar &group : LexerGroupActionMenu::groups()) {
auto *lexerGroupMenu = new LexerGroupActionMenu(group.toUpper(), group, this);
connect(lexerGroupMenu, &LexerGroupActionMenu::lexerSelected, this, &MainWindow::applyLexer);
lexerGroupListActions.append(lexerGroupMenu);
}
unplugActionList("lexer_group_list");
plugActionList(QStringLiteral("lexer_group_list"), lexerGroupListActions);
}, Qt::QueuedConnection);
// Toolbar actions
KStandardAction::zoomIn(this, [this](){
if (SciEdit *editor = m_tabWidget->currentEditor()) {
editor->zoomIn();
}
}, actionCollection());
KStandardAction::zoomOut(this, [this](){
if (SciEdit *editor = m_tabWidget->currentEditor()) {
editor->zoomOut();
}
}, actionCollection());
KStandardAction::preferences(this, &MainWindow::showSettings, actionCollection());
QAction *toggleWrapModeAction = new QAction(this);
toggleWrapModeAction->setText("Toggle Wrap Mode");
toggleWrapModeAction->setIcon(QIcon::fromTheme(u"text-wrap"_s));
toggleWrapModeAction->setCheckable(true);
actionCollection()->addAction(u"toggle_wrap_mode"_s, toggleWrapModeAction);
connect(toggleWrapModeAction, &QAction::triggered, this, [this, toggleWrapModeAction](){
if (SciEdit *editor = m_tabWidget->currentEditor()) {
bool switchToWrapNone = editor->wrapMode() == SC_WRAP_WORD;
editor->setWrapMode(switchToWrapNone ? SC_WRAP_NONE : SC_WRAP_WORD);
toggleWrapModeAction->setChecked(!switchToWrapNone);
}
});
QAction *toggleWhitespaceVisibilityAction = new QAction(this);
toggleWhitespaceVisibilityAction->setText("Show All Characters");
toggleWhitespaceVisibilityAction->setCheckable(true);
// toggleWhitespaceVisibilityAction->setIcon(QIcon::fromTheme(u"text-wrap"_s));
actionCollection()->addAction(u"toggle_show_all_characters"_s, toggleWhitespaceVisibilityAction);
connect(toggleWhitespaceVisibilityAction, &QAction::triggered, this, [this, toggleWhitespaceVisibilityAction](){
if (SciEdit *editor = m_tabWidget->currentEditor()) {
bool switchToVisible = editor->viewWS() == SCWS_INVISIBLE;
editor->setViewWS(switchToVisible ? SCWS_VISIBLEALWAYS : SCWS_INVISIBLE);
editor->setViewEOL(switchToVisible);
toggleWhitespaceVisibilityAction->setChecked(switchToVisible);
}
});
QAction *toggleIndentGuideAction = new QAction(this);
toggleIndentGuideAction->setText("Toggle Indent Guide");
toggleIndentGuideAction->setIcon(QIcon::fromTheme(u"show-guides"_s));
2026-02-01 23:06:41 +08:00
toggleIndentGuideAction->setCheckable(true);
toggleIndentGuideAction->setChecked(m_indentGuidesEnabled);
2026-02-01 15:56:33 +08:00
actionCollection()->addAction(u"toggle_indent_guide"_s, toggleIndentGuideAction);
2026-02-01 23:06:41 +08:00
connect(toggleIndentGuideAction, &QAction::toggled, this, [this](bool checked){
m_indentGuidesEnabled = checked;
applySettingsToAllEditors();
2026-02-01 15:56:33 +08:00
});
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);
colorSelectionMenu->menu()->setTitle("&Window Color Scheme");
actionCollection()->addAction(QStringLiteral("colorscheme_menu"), colorSelectionMenu);
setupGUI();
}
void MainWindow::showSettings()
{
if (KConfigDialog::showDialog(QStringLiteral("settings"))) {
return;
}
KConfigDialog *dialog = new KConfigDialog(this, QStringLiteral("settings"), AppSettings::self());
dialog->setFaceType(KPageDialog::FlatList);
dialog->addPage(new SettingsPage<Ui::GeneralSettings>(dialog), "Appearance", "preferences-desktop-theme-global");
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();
}
void MainWindow::applyLexer(const QString &lexerName)
{
if (SciEdit *editor = m_tabWidget->currentEditor()) {
Scintilla::ILexer5 * lexer = CreateLexer(lexerName.toStdString().c_str());
editor->setLexer(lexer);
2026-02-01 16:23:33 +08:00
const int docId = m_tabWidget->currentDocumentId();
if (docId >= 0) {
m_documentManager->setDocumentLanguage(docId, lexerName);
}
updateStatusBar();
2026-02-01 15:56:33 +08:00
}
}
void MainWindow::newFile()
{
m_tabWidget->newDocument();
2026-02-01 23:06:41 +08:00
updateActions();
2026-02-01 15:56:33 +08:00
}
void MainWindow::openFile()
{
QString fileName = QFileDialog::getOpenFileName(this,
tr("Open File"),
QString(),
tr("All Files (*.*)"));
if (!fileName.isEmpty()) {
m_tabWidget->openDocument(fileName);
}
}
void MainWindow::saveFile()
{
m_tabWidget->saveCurrentDocument();
}
void MainWindow::saveAsFile()
{
m_tabWidget->saveCurrentDocumentAs();
}
void MainWindow::closeFile()
{
m_tabWidget->closeCurrentTab();
}
void MainWindow::onCurrentTabChanged()
{
updateWindowTitle();
updateStatusBar();
updateActions();
}
void MainWindow::onCurrentEditorChanged(SciEdit *editor)
{
Q_UNUSED(editor)
updateWindowTitle();
updateStatusBar();
updateActions();
// 连接当前编辑器的光标位置变化信号
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());
2026-02-01 23:06:41 +08:00
editor->setIndentationGuides(m_indentGuidesEnabled ? SC_IV_LOOKBOTH : SC_IV_NONE);
2026-02-01 15:56:33 +08:00
}
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)
Q_UNUSED(modified)
updateWindowTitle();
}
void MainWindow::updateWindowTitle()
{
QString title = "Pineapple Notepad";
if (SciEdit *editor = m_tabWidget->currentEditor()) {
int docId = m_tabWidget->currentDocumentId();
if (docId >= 0) {
QString fileName = m_documentManager->getDocumentTitle(docId);
bool isModified = m_documentManager->isDocumentModified(docId);
title = QString("%1%2")
.arg(fileName)
.arg(isModified ? "*" : "");
}
}
setWindowTitle(title);
}
void MainWindow::updateStatusBar()
{
if (SciEdit *editor = m_tabWidget->currentEditor()) {
int docId = m_tabWidget->currentDocumentId();
if (docId >= 0) {
QString encoding = m_documentManager->getDocumentEncoding(docId);
2026-02-01 16:23:33 +08:00
const QString lexerName = m_documentManager->getDocumentLanguage(docId);
const QString language = lexerName.isEmpty() ? QStringLiteral("Plain Text") : LexerGroupActionMenu::displayName(lexerName);
2026-02-01 15:56:33 +08:00
m_encodingStatusLabel->setText(encoding);
m_languageStatusLabel->setText(language);
}
} else {
m_encodingStatusLabel->setText("UTF-8");
m_languageStatusLabel->setText("Plain Text");
}
}
void MainWindow::updateActions()
{
bool hasEditor = (m_tabWidget->currentEditor() != nullptr);
// 更新编辑相关的动作状态
if (QAction *undoAction = actionCollection()->action(KStandardAction::name(KStandardAction::Undo))) {
undoAction->setEnabled(hasEditor);
}
if (QAction *redoAction = actionCollection()->action(KStandardAction::name(KStandardAction::Redo))) {
redoAction->setEnabled(hasEditor);
}
if (QAction *cutAction = actionCollection()->action(KStandardAction::name(KStandardAction::Cut))) {
cutAction->setEnabled(hasEditor);
}
if (QAction *copyAction = actionCollection()->action(KStandardAction::name(KStandardAction::Copy))) {
copyAction->setEnabled(hasEditor);
}
if (QAction *pasteAction = actionCollection()->action(KStandardAction::name(KStandardAction::Paste))) {
2026-02-01 23:06:41 +08:00
pasteAction->setEnabled(hasEditor && m_tabWidget->currentEditor()->canPaste());
}
if (QAction *indentGuideAction = actionCollection()->action(QStringLiteral("toggle_indent_guide"))) {
indentGuideAction->setEnabled(hasEditor);
indentGuideAction->setChecked(m_indentGuidesEnabled);
2026-02-01 15:56:33 +08:00
}
}