feat: multitab and basic file saving
This commit is contained in:
@ -33,6 +33,8 @@ PRIVATE
|
||||
main.cpp
|
||||
mainwindow.cpp mainwindow.h
|
||||
sciedit.cpp sciedit.h
|
||||
tabwidget.cpp tabwidget.h
|
||||
documentmanager.cpp documentmanager.h
|
||||
editorviewhelper.cpp editorviewhelper.h
|
||||
generalsettings.ui
|
||||
|
||||
|
275
documentmanager.cpp
Normal file
275
documentmanager.cpp
Normal file
@ -0,0 +1,275 @@
|
||||
#include "documentmanager.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QTextStream>
|
||||
#include <QDebug>
|
||||
|
||||
int DocumentManager::s_untitledCounter = 1;
|
||||
|
||||
DocumentManager::DocumentManager(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_nextDocumentId(1)
|
||||
{
|
||||
}
|
||||
|
||||
DocumentManager::~DocumentManager()
|
||||
{
|
||||
}
|
||||
|
||||
int DocumentManager::createNewDocument()
|
||||
{
|
||||
DocumentInfo doc;
|
||||
doc.id = m_nextDocumentId++;
|
||||
doc.title = generateUntitledName();
|
||||
doc.filePath = QString();
|
||||
doc.content = QString();
|
||||
doc.encoding = "UTF-8";
|
||||
doc.language = "Plain Text";
|
||||
doc.modified = false;
|
||||
doc.untitled = true;
|
||||
|
||||
m_documents[doc.id] = doc;
|
||||
|
||||
emit documentCreated(doc.id);
|
||||
return doc.id;
|
||||
}
|
||||
|
||||
int DocumentManager::openDocument(const QString &filePath)
|
||||
{
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
qWarning() << "Failed to open file:" << filePath;
|
||||
return -1;
|
||||
}
|
||||
|
||||
QTextStream in(&file);
|
||||
in.setEncoding(QStringConverter::Utf8);
|
||||
QString content = in.readAll();
|
||||
file.close();
|
||||
|
||||
DocumentInfo doc;
|
||||
doc.id = m_nextDocumentId++;
|
||||
doc.title = QFileInfo(filePath).fileName();
|
||||
doc.filePath = filePath;
|
||||
doc.content = content;
|
||||
doc.encoding = "UTF-8";
|
||||
doc.language = detectLanguageFromExtension(filePath);
|
||||
doc.modified = false;
|
||||
doc.untitled = false;
|
||||
|
||||
m_documents[doc.id] = doc;
|
||||
|
||||
emit documentOpened(doc.id, filePath);
|
||||
return doc.id;
|
||||
}
|
||||
|
||||
bool DocumentManager::saveDocument(int docId)
|
||||
{
|
||||
if (!m_documents.contains(docId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DocumentInfo &doc = m_documents[docId];
|
||||
if (doc.untitled || doc.filePath.isEmpty()) {
|
||||
return false; // 需要使用 saveAs
|
||||
}
|
||||
|
||||
QFile file(doc.filePath);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
qWarning() << "Failed to save file:" << doc.filePath;
|
||||
return false;
|
||||
}
|
||||
|
||||
QTextStream out(&file);
|
||||
out.setEncoding(QStringConverter::Utf8);
|
||||
out << doc.content;
|
||||
file.close();
|
||||
|
||||
doc.modified = false;
|
||||
emit documentSaved(docId, doc.filePath);
|
||||
emit documentModified(docId, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DocumentManager::saveDocumentAs(int docId, const QString &filePath)
|
||||
{
|
||||
if (!m_documents.contains(docId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DocumentInfo &doc = m_documents[docId];
|
||||
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
qWarning() << "Failed to save file as:" << filePath;
|
||||
return false;
|
||||
}
|
||||
|
||||
QTextStream out(&file);
|
||||
out.setEncoding(QStringConverter::Utf8);
|
||||
out << doc.content;
|
||||
file.close();
|
||||
|
||||
// 更新文档信息
|
||||
doc.filePath = filePath;
|
||||
doc.title = QFileInfo(filePath).fileName();
|
||||
doc.language = detectLanguageFromExtension(filePath);
|
||||
doc.modified = false;
|
||||
doc.untitled = false;
|
||||
|
||||
emit documentSaved(docId, filePath);
|
||||
emit documentModified(docId, false);
|
||||
emit documentTitleChanged(docId, doc.title);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DocumentManager::closeDocument(int docId)
|
||||
{
|
||||
if (m_documents.contains(docId)) {
|
||||
m_documents.remove(docId);
|
||||
emit documentClosed(docId);
|
||||
}
|
||||
}
|
||||
|
||||
QString DocumentManager::getDocumentTitle(int docId) const
|
||||
{
|
||||
if (m_documents.contains(docId)) {
|
||||
return m_documents[docId].title;
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString DocumentManager::getDocumentFilePath(int docId) const
|
||||
{
|
||||
if (m_documents.contains(docId)) {
|
||||
return m_documents[docId].filePath;
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString DocumentManager::getDocumentContent(int docId) const
|
||||
{
|
||||
if (m_documents.contains(docId)) {
|
||||
return m_documents[docId].content;
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString DocumentManager::getDocumentEncoding(int docId) const
|
||||
{
|
||||
if (m_documents.contains(docId)) {
|
||||
return m_documents[docId].encoding;
|
||||
}
|
||||
return "UTF-8";
|
||||
}
|
||||
|
||||
QString DocumentManager::getDocumentLanguage(int docId) const
|
||||
{
|
||||
if (m_documents.contains(docId)) {
|
||||
return m_documents[docId].language;
|
||||
}
|
||||
return "Plain Text";
|
||||
}
|
||||
|
||||
bool DocumentManager::isDocumentModified(int docId) const
|
||||
{
|
||||
if (m_documents.contains(docId)) {
|
||||
return m_documents[docId].modified;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DocumentManager::isDocumentUntitled(int docId) const
|
||||
{
|
||||
if (m_documents.contains(docId)) {
|
||||
return m_documents[docId].untitled;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DocumentManager::setDocumentContent(int docId, const QString &content)
|
||||
{
|
||||
if (m_documents.contains(docId)) {
|
||||
DocumentInfo &doc = m_documents[docId];
|
||||
if (doc.content != content) {
|
||||
doc.content = content;
|
||||
if (!doc.modified) {
|
||||
doc.modified = true;
|
||||
emit documentModified(docId, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DocumentManager::setDocumentModified(int docId, bool modified)
|
||||
{
|
||||
if (m_documents.contains(docId)) {
|
||||
DocumentInfo &doc = m_documents[docId];
|
||||
if (doc.modified != modified) {
|
||||
doc.modified = modified;
|
||||
emit documentModified(docId, modified);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DocumentManager::setDocumentEncoding(int docId, const QString &encoding)
|
||||
{
|
||||
if (m_documents.contains(docId)) {
|
||||
m_documents[docId].encoding = encoding;
|
||||
}
|
||||
}
|
||||
|
||||
void DocumentManager::setDocumentLanguage(int docId, const QString &language)
|
||||
{
|
||||
if (m_documents.contains(docId)) {
|
||||
m_documents[docId].language = language;
|
||||
}
|
||||
}
|
||||
|
||||
QList<int> DocumentManager::getAllDocumentIds() const
|
||||
{
|
||||
return m_documents.keys();
|
||||
}
|
||||
|
||||
int DocumentManager::getDocumentCount() const
|
||||
{
|
||||
return m_documents.size();
|
||||
}
|
||||
|
||||
QString DocumentManager::generateUntitledName()
|
||||
{
|
||||
return QString("Untitled-%1").arg(s_untitledCounter++);
|
||||
}
|
||||
|
||||
QString DocumentManager::detectLanguageFromExtension(const QString &filePath)
|
||||
{
|
||||
QFileInfo fileInfo(filePath);
|
||||
QString extension = fileInfo.suffix().toLower();
|
||||
|
||||
if (extension == "cpp" || extension == "cxx" || extension == "cc" || extension == "c++") {
|
||||
return "C++";
|
||||
} else if (extension == "c") {
|
||||
return "C";
|
||||
} else if (extension == "h" || extension == "hpp" || extension == "hxx") {
|
||||
return "C/C++ Header";
|
||||
} else if (extension == "py") {
|
||||
return "Python";
|
||||
} else if (extension == "js") {
|
||||
return "JavaScript";
|
||||
} else if (extension == "html" || extension == "htm") {
|
||||
return "HTML";
|
||||
} else if (extension == "css") {
|
||||
return "CSS";
|
||||
} else if (extension == "xml") {
|
||||
return "XML";
|
||||
} else if (extension == "json") {
|
||||
return "JSON";
|
||||
} else if (extension == "txt") {
|
||||
return "Plain Text";
|
||||
} else {
|
||||
return "Plain Text";
|
||||
}
|
||||
}
|
69
documentmanager.h
Normal file
69
documentmanager.h
Normal file
@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QTextCodec>
|
||||
|
||||
class DocumentManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DocumentManager(QObject *parent = nullptr);
|
||||
~DocumentManager();
|
||||
|
||||
// 文档管理
|
||||
int createNewDocument();
|
||||
int openDocument(const QString &filePath);
|
||||
bool saveDocument(int docId);
|
||||
bool saveDocumentAs(int docId, const QString &filePath);
|
||||
void closeDocument(int docId);
|
||||
|
||||
// 文档信息
|
||||
QString getDocumentTitle(int docId) const;
|
||||
QString getDocumentFilePath(int docId) const;
|
||||
QString getDocumentContent(int docId) const;
|
||||
QString getDocumentEncoding(int docId) const;
|
||||
QString getDocumentLanguage(int docId) const;
|
||||
bool isDocumentModified(int docId) const;
|
||||
bool isDocumentUntitled(int docId) const;
|
||||
|
||||
// 文档内容操作
|
||||
void setDocumentContent(int docId, const QString &content);
|
||||
void setDocumentModified(int docId, bool modified);
|
||||
void setDocumentEncoding(int docId, const QString &encoding);
|
||||
void setDocumentLanguage(int docId, const QString &language);
|
||||
|
||||
// 获取所有文档
|
||||
QList<int> getAllDocumentIds() const;
|
||||
int getDocumentCount() const;
|
||||
|
||||
signals:
|
||||
void documentCreated(int docId);
|
||||
void documentOpened(int docId, const QString &filePath);
|
||||
void documentSaved(int docId, const QString &filePath);
|
||||
void documentClosed(int docId);
|
||||
void documentModified(int docId, bool modified);
|
||||
void documentTitleChanged(int docId, const QString &title);
|
||||
|
||||
private:
|
||||
struct DocumentInfo {
|
||||
int id;
|
||||
QString title;
|
||||
QString filePath;
|
||||
QString content;
|
||||
QString encoding;
|
||||
QString language;
|
||||
bool modified;
|
||||
bool untitled;
|
||||
};
|
||||
|
||||
QHash<int, DocumentInfo> m_documents;
|
||||
int m_nextDocumentId;
|
||||
static int s_untitledCounter;
|
||||
|
||||
QString generateUntitledName();
|
||||
QString detectLanguageFromExtension(const QString &filePath);
|
||||
};
|
213
mainwindow.cpp
213
mainwindow.cpp
@ -1,6 +1,8 @@
|
||||
#include "mainwindow.h"
|
||||
|
||||
#include "sciedit.h"
|
||||
#include "tabwidget.h"
|
||||
#include "documentmanager.h"
|
||||
#include "appsettings.h"
|
||||
#include "editorviewhelper.h"
|
||||
|
||||
@ -9,6 +11,8 @@
|
||||
#include <QMenu>
|
||||
#include <QStringBuilder>
|
||||
#include <QStatusBar>
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include <KActionCollection>
|
||||
#include <KStandardAction>
|
||||
@ -23,30 +27,29 @@
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent)
|
||||
: KXmlGuiWindow(parent)
|
||||
, m_editor(new SciEdit(this))
|
||||
, 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_editor);
|
||||
setCentralWidget(m_tabWidget);
|
||||
|
||||
// 设置状态栏
|
||||
statusBar()->addPermanentWidget(m_cursorPosStatusLabel);
|
||||
statusBar()->addPermanentWidget(m_encodingStatusLabel);
|
||||
statusBar()->addPermanentWidget(m_languageStatusLabel);
|
||||
|
||||
QFont font(AppSettings::self()->editorFont());
|
||||
m_editor->setStyleFont(font);
|
||||
m_editor->setStyleFont(font, STYLE_LINENUMBER);
|
||||
// 连接标签页信号
|
||||
connect(m_tabWidget, &TabWidget::currentEditorChanged, this, &MainWindow::onCurrentEditorChanged);
|
||||
|
||||
m_editor->setMarginTypeN(1, SC_MARGIN_NUMBER);
|
||||
m_editor->setMarginWidthN(1, m_editor->textWidth(STYLE_LINENUMBER, "_99999"));
|
||||
m_editor->setFolding(SciEdit::BoxFoldType);
|
||||
// m_editor->setBraceMatching(QsciScintilla::SloppyBraceMatch);
|
||||
|
||||
m_editor->setTabWidth(AppSettings::self()->tabWidth());
|
||||
m_editor->setEOLMode(SC_EOL_LF);
|
||||
|
||||
connect(m_editor, &SciEdit::cursorPosChanged, this, [this](int line, int index){
|
||||
m_cursorPosStatusLabel->setText(QString("Ln: %1 Col: %2").arg(line).arg(index));
|
||||
});
|
||||
// 连接文档管理器信号
|
||||
connect(m_documentManager, &DocumentManager::documentModified, this, &MainWindow::onDocumentModified);
|
||||
|
||||
setupActions();
|
||||
|
||||
// 创建第一个标签页
|
||||
newFile();
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
@ -58,27 +61,40 @@ void MainWindow::setupActions()
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
// "File" menu
|
||||
KStandardAction::openNew(this, [](){}, actionCollection());
|
||||
KStandardAction::open(this, [](){}, actionCollection());
|
||||
KStandardAction::save(this, [](){}, actionCollection());
|
||||
KStandardAction::close(this, [](){}, actionCollection());
|
||||
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](){
|
||||
m_editor->undo();
|
||||
if (SciEdit *editor = m_tabWidget->currentEditor()) {
|
||||
editor->undo();
|
||||
}
|
||||
}, actionCollection());
|
||||
KStandardAction::redo(this, [this](){
|
||||
m_editor->redo();
|
||||
if (SciEdit *editor = m_tabWidget->currentEditor()) {
|
||||
editor->redo();
|
||||
}
|
||||
}, actionCollection());
|
||||
KStandardAction::cut(this, [this](){
|
||||
m_editor->cut();
|
||||
if (SciEdit *editor = m_tabWidget->currentEditor()) {
|
||||
editor->cut();
|
||||
}
|
||||
}, actionCollection());
|
||||
KStandardAction::copy(this, [this](){
|
||||
m_editor->copy();
|
||||
if (SciEdit *editor = m_tabWidget->currentEditor()) {
|
||||
editor->copy();
|
||||
}
|
||||
}, actionCollection());
|
||||
KStandardAction::paste(this, [this](){
|
||||
m_editor->paste();
|
||||
if (SciEdit *editor = m_tabWidget->currentEditor()) {
|
||||
editor->paste();
|
||||
}
|
||||
}, actionCollection());
|
||||
|
||||
// "Search" menu
|
||||
@ -104,10 +120,14 @@ void MainWindow::setupActions()
|
||||
|
||||
// Toolbar actions
|
||||
KStandardAction::zoomIn(this, [this](){
|
||||
m_editor->zoomIn();
|
||||
if (SciEdit *editor = m_tabWidget->currentEditor()) {
|
||||
editor->zoomIn();
|
||||
}
|
||||
}, actionCollection());
|
||||
KStandardAction::zoomOut(this, [this](){
|
||||
m_editor->zoomOut();
|
||||
if (SciEdit *editor = m_tabWidget->currentEditor()) {
|
||||
editor->zoomOut();
|
||||
}
|
||||
}, actionCollection());
|
||||
|
||||
KStandardAction::preferences(this, &MainWindow::showSettings, actionCollection());
|
||||
@ -118,9 +138,11 @@ void MainWindow::setupActions()
|
||||
toggleWrapModeAction->setCheckable(true);
|
||||
actionCollection()->addAction(u"toggle_wrap_mode"_s, toggleWrapModeAction);
|
||||
connect(toggleWrapModeAction, &QAction::triggered, this, [this, toggleWrapModeAction](){
|
||||
bool switchToWrapNone = m_editor->wrapMode() == SC_WRAP_WORD;
|
||||
m_editor->setWrapMode(switchToWrapNone ? SC_WRAP_NONE : SC_WRAP_WORD);
|
||||
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);
|
||||
@ -129,10 +151,12 @@ void MainWindow::setupActions()
|
||||
// toggleWhitespaceVisibilityAction->setIcon(QIcon::fromTheme(u"text-wrap"_s));
|
||||
actionCollection()->addAction(u"toggle_show_all_characters"_s, toggleWhitespaceVisibilityAction);
|
||||
connect(toggleWhitespaceVisibilityAction, &QAction::triggered, this, [this, toggleWhitespaceVisibilityAction](){
|
||||
bool switchToVisible = m_editor->viewWS() == SCWS_INVISIBLE;
|
||||
m_editor->setViewWS(switchToVisible ? SCWS_VISIBLEALWAYS : SCWS_INVISIBLE);
|
||||
m_editor->setViewEOL(switchToVisible);
|
||||
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);
|
||||
@ -144,7 +168,7 @@ void MainWindow::setupActions()
|
||||
});
|
||||
|
||||
// Load themes
|
||||
KColorSchemeManager *manager = new KColorSchemeManager(this);
|
||||
KColorSchemeManager *manager = KColorSchemeManager::instance();
|
||||
auto *colorSelectionMenu = KColorSchemeMenu::createMenu(manager, this);
|
||||
colorSelectionMenu->menu()->setTitle("&Window Color Scheme");
|
||||
actionCollection()->addAction(QStringLiteral("colorscheme_menu"), colorSelectionMenu);
|
||||
@ -170,6 +194,127 @@ void MainWindow::showSettings()
|
||||
|
||||
void MainWindow::applyLexer(const QString &lexerName)
|
||||
{
|
||||
if (SciEdit *editor = m_tabWidget->currentEditor()) {
|
||||
Scintilla::ILexer5 * lexer = CreateLexer(lexerName.toStdString().c_str());
|
||||
m_editor->setLexer(lexer);
|
||||
editor->setLexer(lexer);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::newFile()
|
||||
{
|
||||
m_tabWidget->newDocument();
|
||||
}
|
||||
|
||||
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) {
|
||||
connect(editor, &SciEdit::cursorPosChanged, this, [this](int line, int column) {
|
||||
m_cursorPosStatusLabel->setText(QString("Ln: %1 Col: %2").arg(line).arg(column));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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 - Pineapple Notepad")
|
||||
.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);
|
||||
QString language = m_documentManager->getDocumentLanguage(docId);
|
||||
|
||||
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))) {
|
||||
pasteAction->setEnabled(hasEditor);
|
||||
}
|
||||
}
|
||||
|
23
mainwindow.h
23
mainwindow.h
@ -6,6 +6,9 @@
|
||||
|
||||
class QLabel;
|
||||
class SciEdit;
|
||||
class TabWidget;
|
||||
class DocumentManager;
|
||||
|
||||
class MainWindow : public KXmlGuiWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -14,13 +17,29 @@ public:
|
||||
explicit MainWindow(QWidget *parent = nullptr);
|
||||
~MainWindow();
|
||||
|
||||
private slots:
|
||||
void newFile();
|
||||
void openFile();
|
||||
void saveFile();
|
||||
void saveAsFile();
|
||||
void closeFile();
|
||||
void onCurrentTabChanged();
|
||||
void onCurrentEditorChanged(SciEdit *editor);
|
||||
void onDocumentModified(int docIndex, bool modified);
|
||||
|
||||
private:
|
||||
void setupActions();
|
||||
void showSettings();
|
||||
void applyLexer(const QString & lexer);
|
||||
void updateWindowTitle();
|
||||
void updateStatusBar();
|
||||
void updateActions();
|
||||
|
||||
SciEdit * m_editor;
|
||||
QLabel * m_cursorPosStatusLabel;
|
||||
DocumentManager *m_documentManager;
|
||||
TabWidget *m_tabWidget;
|
||||
QLabel *m_cursorPosStatusLabel;
|
||||
QLabel *m_encodingStatusLabel;
|
||||
QLabel *m_languageStatusLabel;
|
||||
};
|
||||
|
||||
// plainly for KConfigDialog
|
||||
|
@ -7,6 +7,10 @@ SciEdit::SciEdit(QWidget * parent)
|
||||
int curPos = currentPos();
|
||||
emit cursorPosChanged(lineFromPosition(curPos) + 1, column(curPos));
|
||||
});
|
||||
|
||||
// 连接文本变化信号
|
||||
connect(this, QOverload<Scintilla::ModificationFlags, Scintilla::Position, Scintilla::Position, Scintilla::Position, const QByteArray &, Scintilla::Position, Scintilla::FoldLevel, Scintilla::FoldLevel>::of(&ScintillaEditBase::modified),
|
||||
this, &SciEdit::textChanged);
|
||||
}
|
||||
|
||||
void SciEdit::setStyleFont(const QFont &font, int style)
|
||||
|
@ -22,6 +22,7 @@ public:
|
||||
|
||||
signals:
|
||||
void cursorPosChanged(int line, int index);
|
||||
void textChanged();
|
||||
|
||||
private:
|
||||
void setMarkerDefine(int markerNumber, int markerSymbol);
|
||||
|
358
tabwidget.cpp
Normal file
358
tabwidget.cpp
Normal file
@ -0,0 +1,358 @@
|
||||
#include "tabwidget.h"
|
||||
#include "sciedit.h"
|
||||
#include "documentmanager.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QDebug>
|
||||
|
||||
TabWidget::TabWidget(DocumentManager *documentManager, QWidget *parent)
|
||||
: QTabWidget(parent)
|
||||
, m_documentManager(documentManager)
|
||||
{
|
||||
setTabsClosable(true);
|
||||
setMovable(true);
|
||||
setUsesScrollButtons(true);
|
||||
|
||||
// 连接信号
|
||||
connect(this, &QTabWidget::currentChanged, this, &TabWidget::onCurrentChanged);
|
||||
connect(this, &QTabWidget::tabCloseRequested, this, &TabWidget::onTabCloseRequested);
|
||||
|
||||
// 连接文档管理器信号
|
||||
connect(m_documentManager, &DocumentManager::documentModified,
|
||||
this, &TabWidget::onDocumentModified);
|
||||
connect(m_documentManager, &DocumentManager::documentTitleChanged,
|
||||
this, &TabWidget::onDocumentTitleChanged);
|
||||
|
||||
// 创建第一个文档
|
||||
newDocument();
|
||||
}
|
||||
|
||||
TabWidget::~TabWidget()
|
||||
{
|
||||
}
|
||||
|
||||
int TabWidget::newDocument()
|
||||
{
|
||||
int docId = m_documentManager->createNewDocument();
|
||||
if (docId == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
SciEdit *editor = createEditor();
|
||||
m_editors[docId] = editor;
|
||||
|
||||
QString title = m_documentManager->getDocumentTitle(docId);
|
||||
int tabIndex = addTab(editor, title);
|
||||
|
||||
m_tabToDocumentId[tabIndex] = docId;
|
||||
m_documentIdToTab[docId] = tabIndex;
|
||||
|
||||
setCurrentIndex(tabIndex);
|
||||
|
||||
return docId;
|
||||
}
|
||||
|
||||
int TabWidget::openDocument(const QString &filePath)
|
||||
{
|
||||
int docId = m_documentManager->openDocument(filePath);
|
||||
if (docId == -1) {
|
||||
QMessageBox::warning(this, "Error", "Failed to open file: " + filePath);
|
||||
return -1;
|
||||
}
|
||||
|
||||
SciEdit *editor = createEditor();
|
||||
m_editors[docId] = editor;
|
||||
|
||||
// 设置编辑器内容
|
||||
QString content = m_documentManager->getDocumentContent(docId);
|
||||
editor->setText(content.toUtf8().constData());
|
||||
|
||||
QString title = m_documentManager->getDocumentTitle(docId);
|
||||
int tabIndex = addTab(editor, title);
|
||||
|
||||
m_tabToDocumentId[tabIndex] = docId;
|
||||
m_documentIdToTab[docId] = tabIndex;
|
||||
|
||||
setCurrentIndex(tabIndex);
|
||||
|
||||
return docId;
|
||||
}
|
||||
|
||||
bool TabWidget::saveCurrentDocument()
|
||||
{
|
||||
SciEdit *editor = currentEditor();
|
||||
if (!editor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int docId = currentDocumentId();
|
||||
if (docId == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 更新文档内容
|
||||
QByteArray content = editor->getText(editor->textLength());
|
||||
m_documentManager->setDocumentContent(docId, QString::fromUtf8(content));
|
||||
|
||||
if (m_documentManager->isDocumentUntitled(docId)) {
|
||||
return saveCurrentDocumentAs();
|
||||
}
|
||||
|
||||
return m_documentManager->saveDocument(docId);
|
||||
}
|
||||
|
||||
bool TabWidget::saveCurrentDocumentAs()
|
||||
{
|
||||
SciEdit *editor = currentEditor();
|
||||
if (!editor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int docId = currentDocumentId();
|
||||
if (docId == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QString fileName = QFileDialog::getSaveFileName(this,
|
||||
"Save File",
|
||||
m_documentManager->getDocumentTitle(docId),
|
||||
"All Files (*.*)");
|
||||
|
||||
if (fileName.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 更新文档内容
|
||||
QByteArray content = editor->getText(editor->textLength());
|
||||
m_documentManager->setDocumentContent(docId, QString::fromUtf8(content));
|
||||
|
||||
return m_documentManager->saveDocumentAs(docId, fileName);
|
||||
}
|
||||
|
||||
void TabWidget::closeCurrentTab()
|
||||
{
|
||||
int index = currentIndex();
|
||||
if (index >= 0) {
|
||||
closeTab(index);
|
||||
}
|
||||
}
|
||||
|
||||
bool TabWidget::closeTab(int index)
|
||||
{
|
||||
if (index < 0 || index >= count()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int docId = m_tabToDocumentId.value(index, -1);
|
||||
if (docId == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否需要保存
|
||||
if (m_documentManager->isDocumentModified(docId)) {
|
||||
QString title = m_documentManager->getDocumentTitle(docId);
|
||||
int ret = QMessageBox::question(this, "Save Changes",
|
||||
QString("The document '%1' has been modified.\nDo you want to save your changes?").arg(title),
|
||||
QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
|
||||
|
||||
if (ret == QMessageBox::Save) {
|
||||
// 更新文档内容
|
||||
SciEdit *editor = editorAt(index);
|
||||
if (editor) {
|
||||
QByteArray content = editor->getText(editor->textLength());
|
||||
m_documentManager->setDocumentContent(docId, QString::fromUtf8(content));
|
||||
}
|
||||
|
||||
bool saved = false;
|
||||
if (m_documentManager->isDocumentUntitled(docId)) {
|
||||
QString fileName = QFileDialog::getSaveFileName(this,
|
||||
"Save File",
|
||||
title,
|
||||
"All Files (*.*)");
|
||||
if (!fileName.isEmpty()) {
|
||||
saved = m_documentManager->saveDocumentAs(docId, fileName);
|
||||
}
|
||||
} else {
|
||||
saved = m_documentManager->saveDocument(docId);
|
||||
}
|
||||
|
||||
if (!saved) {
|
||||
return false; // 保存失败,不关闭标签页
|
||||
}
|
||||
} else if (ret == QMessageBox::Cancel) {
|
||||
return false; // 取消关闭
|
||||
}
|
||||
}
|
||||
|
||||
// 移除标签页
|
||||
QWidget *widget = this->widget(index);
|
||||
removeTab(index);
|
||||
|
||||
// 清理映射关系
|
||||
m_tabToDocumentId.remove(index);
|
||||
m_documentIdToTab.remove(docId);
|
||||
|
||||
// 更新其他标签页的索引映射
|
||||
QHash<int, int> newTabToDocumentId;
|
||||
QHash<int, int> newDocumentIdToTab;
|
||||
|
||||
for (int i = 0; i < count(); ++i) {
|
||||
int oldIndex = (i >= index) ? i + 1 : i;
|
||||
int oldDocId = m_tabToDocumentId.value(oldIndex, -1);
|
||||
if (oldDocId != -1) {
|
||||
newTabToDocumentId[i] = oldDocId;
|
||||
newDocumentIdToTab[oldDocId] = i;
|
||||
}
|
||||
}
|
||||
|
||||
m_tabToDocumentId = newTabToDocumentId;
|
||||
m_documentIdToTab = newDocumentIdToTab;
|
||||
|
||||
// 清理编辑器和文档
|
||||
if (m_editors.contains(docId)) {
|
||||
SciEdit *editor = m_editors[docId];
|
||||
disconnectEditorSignals(editor);
|
||||
m_editors.remove(docId);
|
||||
}
|
||||
|
||||
m_documentManager->closeDocument(docId);
|
||||
|
||||
if (widget) {
|
||||
widget->deleteLater();
|
||||
}
|
||||
|
||||
// 如果没有标签页了,创建一个新的
|
||||
if (count() == 0) {
|
||||
newDocument();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TabWidget::closeAllTabs()
|
||||
{
|
||||
while (count() > 0) {
|
||||
closeTab(0);
|
||||
}
|
||||
}
|
||||
|
||||
SciEdit *TabWidget::currentEditor() const
|
||||
{
|
||||
int docId = currentDocumentId();
|
||||
return m_editors.value(docId, nullptr);
|
||||
}
|
||||
|
||||
SciEdit *TabWidget::editorAt(int index) const
|
||||
{
|
||||
int docId = m_tabToDocumentId.value(index, -1);
|
||||
return m_editors.value(docId, nullptr);
|
||||
}
|
||||
|
||||
int TabWidget::currentDocumentId() const
|
||||
{
|
||||
int index = currentIndex();
|
||||
return m_tabToDocumentId.value(index, -1);
|
||||
}
|
||||
|
||||
int TabWidget::documentIdAt(int index) const
|
||||
{
|
||||
return m_tabToDocumentId.value(index, -1);
|
||||
}
|
||||
|
||||
void TabWidget::setCurrentTab(int index)
|
||||
{
|
||||
if (index >= 0 && index < count()) {
|
||||
setCurrentIndex(index);
|
||||
}
|
||||
}
|
||||
|
||||
int TabWidget::findTabByDocumentId(int docId) const
|
||||
{
|
||||
return m_documentIdToTab.value(docId, -1);
|
||||
}
|
||||
|
||||
void TabWidget::onCurrentChanged(int index)
|
||||
{
|
||||
SciEdit *editor = editorAt(index);
|
||||
emit currentEditorChanged(editor);
|
||||
}
|
||||
|
||||
void TabWidget::onTabCloseRequested(int index)
|
||||
{
|
||||
closeTab(index);
|
||||
}
|
||||
|
||||
void TabWidget::onDocumentModified(int docId, bool modified)
|
||||
{
|
||||
int tabIndex = findTabByDocumentId(docId);
|
||||
if (tabIndex >= 0) {
|
||||
updateTabTitle(tabIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void TabWidget::onDocumentTitleChanged(int docId, const QString &title)
|
||||
{
|
||||
int tabIndex = findTabByDocumentId(docId);
|
||||
if (tabIndex >= 0) {
|
||||
updateTabTitle(tabIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void TabWidget::onEditorTextChanged()
|
||||
{
|
||||
SciEdit *editor = qobject_cast<SciEdit*>(sender());
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 找到对应的文档ID
|
||||
int docId = -1;
|
||||
for (auto it = m_editors.begin(); it != m_editors.end(); ++it) {
|
||||
if (it.value() == editor) {
|
||||
docId = it.key();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (docId != -1) {
|
||||
QByteArray content = editor->getText(editor->textLength());
|
||||
m_documentManager->setDocumentContent(docId, QString::fromUtf8(content));
|
||||
}
|
||||
}
|
||||
|
||||
SciEdit *TabWidget::createEditor()
|
||||
{
|
||||
SciEdit *editor = new SciEdit(this);
|
||||
connectEditorSignals(editor);
|
||||
return editor;
|
||||
}
|
||||
|
||||
void TabWidget::updateTabTitle(int tabIndex)
|
||||
{
|
||||
int docId = m_tabToDocumentId.value(tabIndex, -1);
|
||||
if (docId == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString title = m_documentManager->getDocumentTitle(docId);
|
||||
if (m_documentManager->isDocumentModified(docId)) {
|
||||
title += " *";
|
||||
}
|
||||
|
||||
setTabText(tabIndex, title);
|
||||
}
|
||||
|
||||
void TabWidget::connectEditorSignals(SciEdit *editor)
|
||||
{
|
||||
if (editor) {
|
||||
connect(editor, &SciEdit::textChanged, this, &TabWidget::onEditorTextChanged);
|
||||
}
|
||||
}
|
||||
|
||||
void TabWidget::disconnectEditorSignals(SciEdit *editor)
|
||||
{
|
||||
if (editor) {
|
||||
disconnect(editor, &SciEdit::textChanged, this, &TabWidget::onEditorTextChanged);
|
||||
}
|
||||
}
|
57
tabwidget.h
Normal file
57
tabwidget.h
Normal file
@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <QTabWidget>
|
||||
#include <QHash>
|
||||
|
||||
class SciEdit;
|
||||
class DocumentManager;
|
||||
|
||||
class TabWidget : public QTabWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TabWidget(DocumentManager *documentManager, QWidget *parent = nullptr);
|
||||
~TabWidget();
|
||||
|
||||
// 标签页管理
|
||||
int newDocument();
|
||||
int openDocument(const QString &filePath);
|
||||
bool saveCurrentDocument();
|
||||
bool saveCurrentDocumentAs();
|
||||
void closeCurrentTab();
|
||||
bool closeTab(int index);
|
||||
void closeAllTabs();
|
||||
|
||||
// 获取当前编辑器和文档信息
|
||||
SciEdit *currentEditor() const;
|
||||
SciEdit *editorAt(int index) const;
|
||||
int currentDocumentId() const;
|
||||
int documentIdAt(int index) const;
|
||||
|
||||
// 标签页操作
|
||||
void setCurrentTab(int index);
|
||||
int findTabByDocumentId(int docId) const;
|
||||
|
||||
signals:
|
||||
void currentEditorChanged(SciEdit *editor);
|
||||
void tabCloseRequested(int index);
|
||||
|
||||
private slots:
|
||||
void onCurrentChanged(int index);
|
||||
void onTabCloseRequested(int index);
|
||||
void onDocumentModified(int docId, bool modified);
|
||||
void onDocumentTitleChanged(int docId, const QString &title);
|
||||
void onEditorTextChanged();
|
||||
|
||||
private:
|
||||
DocumentManager *m_documentManager;
|
||||
QHash<int, int> m_tabToDocumentId; // tab index -> document id
|
||||
QHash<int, int> m_documentIdToTab; // document id -> tab index
|
||||
QHash<int, SciEdit*> m_editors; // document id -> editor
|
||||
|
||||
SciEdit *createEditor();
|
||||
void updateTabTitle(int tabIndex);
|
||||
void connectEditorSignals(SciEdit *editor);
|
||||
void disconnectEditorSignals(SciEdit *editor);
|
||||
};
|
Reference in New Issue
Block a user