diff --git a/CMakeLists.txt b/CMakeLists.txt index d2ed4a6..e6107d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/documentmanager.cpp b/documentmanager.cpp new file mode 100644 index 0000000..3e8bb06 --- /dev/null +++ b/documentmanager.cpp @@ -0,0 +1,275 @@ +#include "documentmanager.h" + +#include +#include +#include +#include + +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 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"; + } +} \ No newline at end of file diff --git a/documentmanager.h b/documentmanager.h new file mode 100644 index 0000000..27dac58 --- /dev/null +++ b/documentmanager.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include +#include +#include + +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 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 m_documents; + int m_nextDocumentId; + static int s_untitledCounter; + + QString generateUntitledName(); + QString detectLanguageFromExtension(const QString &filePath); +}; diff --git a/mainwindow.cpp b/mainwindow.cpp index 52d3025..1810d3c 100644 --- a/mainwindow.cpp +++ b/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 #include #include +#include +#include #include #include @@ -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); - - 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_tabWidget, &TabWidget::currentEditorChanged, this, &MainWindow::onCurrentEditorChanged); + + // 连接文档管理器信号 + 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); - toggleWrapModeAction->setChecked(!switchToWrapNone); + 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); - toggleWhitespaceVisibilityAction->setChecked(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) { - Scintilla::ILexer5 * lexer = CreateLexer(lexerName.toStdString().c_str()); - m_editor->setLexer(lexer); + if (SciEdit *editor = m_tabWidget->currentEditor()) { + 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); + } } diff --git a/mainwindow.h b/mainwindow.h index 7796871..ea029ac 100644 --- a/mainwindow.h +++ b/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 diff --git a/sciedit.cpp b/sciedit.cpp index f022755..c48f62f 100644 --- a/sciedit.cpp +++ b/sciedit.cpp @@ -7,6 +7,10 @@ SciEdit::SciEdit(QWidget * parent) int curPos = currentPos(); emit cursorPosChanged(lineFromPosition(curPos) + 1, column(curPos)); }); + + // 连接文本变化信号 + connect(this, QOverload::of(&ScintillaEditBase::modified), + this, &SciEdit::textChanged); } void SciEdit::setStyleFont(const QFont &font, int style) diff --git a/sciedit.h b/sciedit.h index 55c2685..456738f 100644 --- a/sciedit.h +++ b/sciedit.h @@ -22,6 +22,7 @@ public: signals: void cursorPosChanged(int line, int index); + void textChanged(); private: void setMarkerDefine(int markerNumber, int markerSymbol); diff --git a/tabwidget.cpp b/tabwidget.cpp new file mode 100644 index 0000000..97a21ee --- /dev/null +++ b/tabwidget.cpp @@ -0,0 +1,358 @@ +#include "tabwidget.h" +#include "sciedit.h" +#include "documentmanager.h" + +#include +#include +#include + +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 newTabToDocumentId; + QHash 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(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); + } +} \ No newline at end of file diff --git a/tabwidget.h b/tabwidget.h new file mode 100644 index 0000000..398016f --- /dev/null +++ b/tabwidget.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +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 m_tabToDocumentId; // tab index -> document id + QHash m_documentIdToTab; // document id -> tab index + QHash m_editors; // document id -> editor + + SciEdit *createEditor(); + void updateTabTitle(int tabIndex); + void connectEditorSignals(SciEdit *editor); + void disconnectEditorSignals(SciEdit *editor); +}; \ No newline at end of file