feat: multitab and basic file saving

This commit is contained in:
2025-10-12 15:42:17 +08:00
parent db20417ce7
commit 7626508462
9 changed files with 970 additions and 40 deletions

View File

@ -33,6 +33,8 @@ PRIVATE
main.cpp main.cpp
mainwindow.cpp mainwindow.h mainwindow.cpp mainwindow.h
sciedit.cpp sciedit.h sciedit.cpp sciedit.h
tabwidget.cpp tabwidget.h
documentmanager.cpp documentmanager.h
editorviewhelper.cpp editorviewhelper.h editorviewhelper.cpp editorviewhelper.h
generalsettings.ui generalsettings.ui

275
documentmanager.cpp Normal file
View 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
View 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);
};

View File

@ -1,6 +1,8 @@
#include "mainwindow.h" #include "mainwindow.h"
#include "sciedit.h" #include "sciedit.h"
#include "tabwidget.h"
#include "documentmanager.h"
#include "appsettings.h" #include "appsettings.h"
#include "editorviewhelper.h" #include "editorviewhelper.h"
@ -9,6 +11,8 @@
#include <QMenu> #include <QMenu>
#include <QStringBuilder> #include <QStringBuilder>
#include <QStatusBar> #include <QStatusBar>
#include <QFileDialog>
#include <QMessageBox>
#include <KActionCollection> #include <KActionCollection>
#include <KStandardAction> #include <KStandardAction>
@ -23,30 +27,29 @@
MainWindow::MainWindow(QWidget *parent) MainWindow::MainWindow(QWidget *parent)
: KXmlGuiWindow(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_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_cursorPosStatusLabel);
statusBar()->addPermanentWidget(m_encodingStatusLabel);
statusBar()->addPermanentWidget(m_languageStatusLabel);
QFont font(AppSettings::self()->editorFont()); // 连接标签页信号
m_editor->setStyleFont(font); connect(m_tabWidget, &TabWidget::currentEditorChanged, this, &MainWindow::onCurrentEditorChanged);
m_editor->setStyleFont(font, STYLE_LINENUMBER);
// 连接文档管理器信号
m_editor->setMarginTypeN(1, SC_MARGIN_NUMBER); connect(m_documentManager, &DocumentManager::documentModified, this, &MainWindow::onDocumentModified);
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));
});
setupActions(); setupActions();
// 创建第一个标签页
newFile();
} }
MainWindow::~MainWindow() MainWindow::~MainWindow()
@ -58,27 +61,40 @@ void MainWindow::setupActions()
using namespace Qt::Literals::StringLiterals; using namespace Qt::Literals::StringLiterals;
// "File" menu // "File" menu
KStandardAction::openNew(this, [](){}, actionCollection()); KStandardAction::openNew(this, &MainWindow::newFile, actionCollection());
KStandardAction::open(this, [](){}, actionCollection()); KStandardAction::open(this, &MainWindow::openFile, actionCollection());
KStandardAction::save(this, [](){}, actionCollection()); KStandardAction::save(this, &MainWindow::saveFile, actionCollection());
KStandardAction::close(this, [](){}, actionCollection()); KStandardAction::close(this, &MainWindow::closeFile, actionCollection());
KStandardAction::quit(qApp, &QApplication::quit, actionCollection()); KStandardAction::quit(qApp, &QApplication::quit, actionCollection());
// 添加另存为动作
QAction *saveAsAction = KStandardAction::saveAs(this, &MainWindow::saveAsFile, actionCollection());
// "Edit" menu // "Edit" menu
KStandardAction::undo(this, [this](){ KStandardAction::undo(this, [this](){
m_editor->undo(); if (SciEdit *editor = m_tabWidget->currentEditor()) {
editor->undo();
}
}, actionCollection()); }, actionCollection());
KStandardAction::redo(this, [this](){ KStandardAction::redo(this, [this](){
m_editor->redo(); if (SciEdit *editor = m_tabWidget->currentEditor()) {
editor->redo();
}
}, actionCollection()); }, actionCollection());
KStandardAction::cut(this, [this](){ KStandardAction::cut(this, [this](){
m_editor->cut(); if (SciEdit *editor = m_tabWidget->currentEditor()) {
editor->cut();
}
}, actionCollection()); }, actionCollection());
KStandardAction::copy(this, [this](){ KStandardAction::copy(this, [this](){
m_editor->copy(); if (SciEdit *editor = m_tabWidget->currentEditor()) {
editor->copy();
}
}, actionCollection()); }, actionCollection());
KStandardAction::paste(this, [this](){ KStandardAction::paste(this, [this](){
m_editor->paste(); if (SciEdit *editor = m_tabWidget->currentEditor()) {
editor->paste();
}
}, actionCollection()); }, actionCollection());
// "Search" menu // "Search" menu
@ -104,10 +120,14 @@ void MainWindow::setupActions()
// Toolbar actions // Toolbar actions
KStandardAction::zoomIn(this, [this](){ KStandardAction::zoomIn(this, [this](){
m_editor->zoomIn(); if (SciEdit *editor = m_tabWidget->currentEditor()) {
editor->zoomIn();
}
}, actionCollection()); }, actionCollection());
KStandardAction::zoomOut(this, [this](){ KStandardAction::zoomOut(this, [this](){
m_editor->zoomOut(); if (SciEdit *editor = m_tabWidget->currentEditor()) {
editor->zoomOut();
}
}, actionCollection()); }, actionCollection());
KStandardAction::preferences(this, &MainWindow::showSettings, actionCollection()); KStandardAction::preferences(this, &MainWindow::showSettings, actionCollection());
@ -118,9 +138,11 @@ void MainWindow::setupActions()
toggleWrapModeAction->setCheckable(true); toggleWrapModeAction->setCheckable(true);
actionCollection()->addAction(u"toggle_wrap_mode"_s, toggleWrapModeAction); actionCollection()->addAction(u"toggle_wrap_mode"_s, toggleWrapModeAction);
connect(toggleWrapModeAction, &QAction::triggered, this, [this, toggleWrapModeAction](){ connect(toggleWrapModeAction, &QAction::triggered, this, [this, toggleWrapModeAction](){
bool switchToWrapNone = m_editor->wrapMode() == SC_WRAP_WORD; if (SciEdit *editor = m_tabWidget->currentEditor()) {
m_editor->setWrapMode(switchToWrapNone ? SC_WRAP_NONE : SC_WRAP_WORD); bool switchToWrapNone = editor->wrapMode() == SC_WRAP_WORD;
toggleWrapModeAction->setChecked(!switchToWrapNone); editor->setWrapMode(switchToWrapNone ? SC_WRAP_NONE : SC_WRAP_WORD);
toggleWrapModeAction->setChecked(!switchToWrapNone);
}
}); });
QAction *toggleWhitespaceVisibilityAction = new QAction(this); QAction *toggleWhitespaceVisibilityAction = new QAction(this);
@ -129,10 +151,12 @@ void MainWindow::setupActions()
// toggleWhitespaceVisibilityAction->setIcon(QIcon::fromTheme(u"text-wrap"_s)); // toggleWhitespaceVisibilityAction->setIcon(QIcon::fromTheme(u"text-wrap"_s));
actionCollection()->addAction(u"toggle_show_all_characters"_s, toggleWhitespaceVisibilityAction); actionCollection()->addAction(u"toggle_show_all_characters"_s, toggleWhitespaceVisibilityAction);
connect(toggleWhitespaceVisibilityAction, &QAction::triggered, this, [this, toggleWhitespaceVisibilityAction](){ connect(toggleWhitespaceVisibilityAction, &QAction::triggered, this, [this, toggleWhitespaceVisibilityAction](){
bool switchToVisible = m_editor->viewWS() == SCWS_INVISIBLE; if (SciEdit *editor = m_tabWidget->currentEditor()) {
m_editor->setViewWS(switchToVisible ? SCWS_VISIBLEALWAYS : SCWS_INVISIBLE); bool switchToVisible = editor->viewWS() == SCWS_INVISIBLE;
m_editor->setViewEOL(switchToVisible); editor->setViewWS(switchToVisible ? SCWS_VISIBLEALWAYS : SCWS_INVISIBLE);
toggleWhitespaceVisibilityAction->setChecked(switchToVisible); editor->setViewEOL(switchToVisible);
toggleWhitespaceVisibilityAction->setChecked(switchToVisible);
}
}); });
QAction *toggleIndentGuideAction = new QAction(this); QAction *toggleIndentGuideAction = new QAction(this);
@ -144,7 +168,7 @@ void MainWindow::setupActions()
}); });
// Load themes // Load themes
KColorSchemeManager *manager = new KColorSchemeManager(this); KColorSchemeManager *manager = KColorSchemeManager::instance();
auto *colorSelectionMenu = KColorSchemeMenu::createMenu(manager, this); auto *colorSelectionMenu = KColorSchemeMenu::createMenu(manager, this);
colorSelectionMenu->menu()->setTitle("&Window Color Scheme"); colorSelectionMenu->menu()->setTitle("&Window Color Scheme");
actionCollection()->addAction(QStringLiteral("colorscheme_menu"), colorSelectionMenu); actionCollection()->addAction(QStringLiteral("colorscheme_menu"), colorSelectionMenu);
@ -170,6 +194,127 @@ void MainWindow::showSettings()
void MainWindow::applyLexer(const QString &lexerName) void MainWindow::applyLexer(const QString &lexerName)
{ {
Scintilla::ILexer5 * lexer = CreateLexer(lexerName.toStdString().c_str()); if (SciEdit *editor = m_tabWidget->currentEditor()) {
m_editor->setLexer(lexer); Scintilla::ILexer5 * lexer = CreateLexer(lexerName.toStdString().c_str());
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);
}
} }

View File

@ -6,6 +6,9 @@
class QLabel; class QLabel;
class SciEdit; class SciEdit;
class TabWidget;
class DocumentManager;
class MainWindow : public KXmlGuiWindow class MainWindow : public KXmlGuiWindow
{ {
Q_OBJECT Q_OBJECT
@ -14,13 +17,29 @@ public:
explicit MainWindow(QWidget *parent = nullptr); explicit MainWindow(QWidget *parent = nullptr);
~MainWindow(); ~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: private:
void setupActions(); void setupActions();
void showSettings(); void showSettings();
void applyLexer(const QString & lexer); void applyLexer(const QString & lexer);
void updateWindowTitle();
void updateStatusBar();
void updateActions();
SciEdit * m_editor; DocumentManager *m_documentManager;
QLabel * m_cursorPosStatusLabel; TabWidget *m_tabWidget;
QLabel *m_cursorPosStatusLabel;
QLabel *m_encodingStatusLabel;
QLabel *m_languageStatusLabel;
}; };
// plainly for KConfigDialog // plainly for KConfigDialog

View File

@ -7,6 +7,10 @@ SciEdit::SciEdit(QWidget * parent)
int curPos = currentPos(); int curPos = currentPos();
emit cursorPosChanged(lineFromPosition(curPos) + 1, column(curPos)); 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) void SciEdit::setStyleFont(const QFont &font, int style)

View File

@ -22,6 +22,7 @@ public:
signals: signals:
void cursorPosChanged(int line, int index); void cursorPosChanged(int line, int index);
void textChanged();
private: private:
void setMarkerDefine(int markerNumber, int markerSymbol); void setMarkerDefine(int markerNumber, int markerSymbol);

358
tabwidget.cpp Normal file
View 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
View 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);
};