diff --git a/src/autotests/test_project.cpp b/src/autotests/test_project.cpp index f154d9dd..ed85a0f9 100644 --- a/src/autotests/test_project.cpp +++ b/src/autotests/test_project.cpp @@ -1,174 +1,174 @@ /* * Copyright 2014 Andreas Cord-Landwehr * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "test_project.h" #include "libgraphtheory/typenames.h" #include "libgraphtheory/editor.h" #include "libgraphtheory/graphdocument.h" #include "libgraphtheory/nodetype.h" #include "libgraphtheory/edgetype.h" #include "libgraphtheory/node.h" #include "libgraphtheory/edge.h" #include "project/project.h" #include #include #include #include #include using namespace GraphTheory; void TestProject::projectOperations() { GraphTheory::Editor *graphEditor = new GraphTheory::Editor; Project project(graphEditor); // test import of codefile QTemporaryFile codeFile1; QTemporaryFile codeFile2; codeFile1.setFileTemplate("XXXXXXX.js"); codeFile2.setFileTemplate("XXXXXXX.js"); codeFile1.open(); codeFile2.open(); KTextEditor::Document *codeDoc1 = project.importCodeDocument(QUrl::fromLocalFile(codeFile1.fileName())); KTextEditor::Document *codeDoc2 = project.importCodeDocument(QUrl::fromLocalFile(codeFile2.fileName())); project.setDocumentName(codeDoc1, "Test"); QCOMPARE(project.codeDocuments().length(), 2); // check correct adding QVERIFY(project.codeDocuments().first()->url().toLocalFile().startsWith(project.workingDir())); // check working dir QVERIFY(codeDoc1->url().toLocalFile() != codeDoc2->url().toLocalFile()); // check for distinct names - project.removeCodeDocument(codeDoc1); // check removal + project.tryToRemoveCodeDocument(codeDoc1); // check removal QCOMPARE(project.codeDocuments().length(), 1); QCOMPARE(project.documentName(codeDoc1), QString("Test")); // test import of graph QTemporaryFile graphFile; graphFile.setFileTemplate("XXXXXXX.graph2"); graphFile.open(); GraphTheory::GraphDocumentPtr graphDoc = project.importGraphDocument(QUrl::fromLocalFile(graphFile.fileName())); QCOMPARE(project.graphDocuments().length(), 1); QVERIFY(project.graphDocuments().first()->documentUrl().toLocalFile().startsWith(project.workingDir())); project.removeGraphDocument(graphDoc); QCOMPARE(project.graphDocuments().length(), 0); //cleanup graphEditor->deleteLater(); } void TestProject::loadSave() { GraphTheory::Editor *graphEditor = new GraphTheory::Editor; Project project(graphEditor); QTemporaryFile projectFile; projectFile.open(); project.setProjectUrl(QUrl::fromLocalFile(projectFile.fileName())); // add files QTemporaryFile codeFile; codeFile.setFileTemplate("XXXXXXX.js"); codeFile.open(); project.importCodeDocument(QUrl::fromLocalFile(codeFile.fileName())); project.addGraphDocument(GraphTheory::GraphDocument::create()); QVERIFY(project.projectSave()); Project loadedProject(QUrl::fromLocalFile(projectFile.fileName()), graphEditor); QCOMPARE(loadedProject.codeDocuments().count(), project.codeDocuments().count()); QCOMPARE(loadedProject.graphDocuments().count(), project.graphDocuments().count()); graphEditor->deleteLater(); } void TestProject::loadSaveMultipleGraphDocuments() { GraphTheory::Editor *graphEditor = new GraphTheory::Editor; Project project(graphEditor); GraphDocumentPtr docA = graphEditor->createDocument(); GraphDocumentPtr docB = graphEditor->createDocument(); docA->setDocumentName("docA"); project.addGraphDocument(docA); project.addGraphDocument(docB); // add nodes to identify documents later NodePtr nodeA = Node::create(docA); NodePtr nodeB1 = Node::create(docB); NodePtr nodeB2 = Node::create(docB); QCOMPARE(docA->nodes().count(), 1); QCOMPARE(docB->nodes().count(), 2); // save & load this project QTemporaryFile projectFile; projectFile.open(); project.setProjectUrl(QUrl::fromLocalFile(projectFile.fileName())); QVERIFY(project.projectSave()); Project loadedProject(QUrl::fromLocalFile(projectFile.fileName()), graphEditor); QCOMPARE(loadedProject.graphDocuments().count(), 2); QCOMPARE(loadedProject.graphDocuments().at(0)->nodes().count(), 1); QCOMPARE(loadedProject.graphDocuments().at(0)->documentName(), QString("docA")); QCOMPARE(loadedProject.graphDocuments().at(1)->nodes().count(), 2); graphEditor->deleteLater(); } void TestProject::loadSaveMultipleScriptDocuments() { GraphTheory::Editor *graphEditor = new GraphTheory::Editor; Project project(graphEditor); GraphDocumentPtr graphDoc = graphEditor->createDocument(); project.addGraphDocument(graphDoc); // add files QTemporaryFile codeFileA, codeFileB; codeFileA.setFileTemplate("XXXXXXX.js"); codeFileA.open(); KTextEditor::Document* docA = project.importCodeDocument(QUrl::fromLocalFile(codeFileA.fileName())); project.setDocumentName(docA, "A"); docA->setText("1"); codeFileB.setFileTemplate("XXXXXXX.js"); codeFileB.open(); KTextEditor::Document* docB = project.importCodeDocument(QUrl::fromLocalFile(codeFileB.fileName())); project.setDocumentName(docB, "B"); docB->setText("2"); // save & load this project QTemporaryFile projectFile; projectFile.open(); project.setProjectUrl(QUrl::fromLocalFile(projectFile.fileName())); QVERIFY(project.projectSave()); Project loadedProject(QUrl::fromLocalFile(projectFile.fileName()), graphEditor); QCOMPARE(loadedProject.codeDocuments().count(), 2); QCOMPARE(loadedProject.documentName(loadedProject.codeDocuments().at(0)), QString("A")); QCOMPARE(loadedProject.codeDocuments().at(0)->text().trimmed(), QString("1")); QCOMPARE(loadedProject.codeDocuments().at(1)->text().trimmed(), QString("2")); graphEditor->deleteLater(); } void TestProject::loadBrokenFilesWithoutCrashing01() { GraphTheory::Editor *graphEditor = new GraphTheory::Editor; Project project(QUrl::fromLocalFile("testfiles/broken_01.rocs"), graphEditor); QCOMPARE(project.graphDocuments().count(), 0); } QTEST_MAIN(TestProject) diff --git a/src/project/project.cpp b/src/project/project.cpp index ce82fb98..5624d86f 100644 --- a/src/project/project.cpp +++ b/src/project/project.cpp @@ -1,468 +1,469 @@ /* * Copyright 2012-2015 Andreas Cord-Landwehr * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "project.h" #include "libgraphtheory/typenames.h" #include "libgraphtheory/graphdocument.h" #include "libgraphtheory/editor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace GraphTheory; class ProjectPrivate { public: ProjectPrivate() : m_journal(Q_NULLPTR) , m_graphEditor(Q_NULLPTR) , m_modified(false) , m_activeGraphDocumentIndex(-1) , m_activeCodeDocumentIndex(-1) { } QUrl m_projectUrl; //!< the project's archive file QTemporaryDir m_workingDirectory; //!< temporary directory where all project files are organized QList m_codeDocuments; QList m_graphDocuments; QHash m_documentNames; KTextEditor::Document *m_journal; GraphTheory::Editor *m_graphEditor; bool m_modified; int m_activeGraphDocumentIndex; int m_activeCodeDocumentIndex; /** * Set project from project archive file. */ bool loadProject(const QUrl &path); /** * Write project meta info file */ bool writeProjectMetaInfo(); }; bool ProjectPrivate::loadProject(const QUrl &url) { // extract archive file to temporary working directory KTar tar = KTar(url.toLocalFile(), QString("application/x-gzip")); if (!tar.open(QIODevice::ReadOnly)) { qCritical() << "Could not open project archive file for reading, aborting."; return false; } tar.directory()->copyTo(m_workingDirectory.path(), true); QFile metaInfoFile(m_workingDirectory.path() + QChar('/') + "project.json"); if (!metaInfoFile.open(QIODevice::ReadOnly)) { qWarning("Could not open project.json file for reading, aborting."); return false; } QJsonDocument metaInfoDoc = QJsonDocument::fromJson(metaInfoFile.readAll()); // set project QJsonObject metaInfo = metaInfoDoc.object(); QJsonArray codeDocs = metaInfo["scripts"].toArray(); QJsonArray codeDocNames = metaInfo["scriptNames"].toArray(); for (int index = 0; index < codeDocs.count(); ++index) { QJsonObject docInfo = codeDocs.at(index).toObject(); QString filename = docInfo["file"].toString(); KTextEditor::Document *document = KTextEditor::Editor::instance()->createDocument(nullptr); document->openUrl(QUrl::fromLocalFile(m_workingDirectory.path() + QChar('/') + filename)); m_codeDocuments.append(document); m_documentNames.insert(document, codeDocNames.at(index).toString()); } QJsonArray graphDocs = metaInfo["graphs"].toArray(); for (int index = 0; index < graphDocs.count(); ++index) { QJsonObject docInfo = graphDocs.at(index).toObject(); QString fileName = docInfo["file"].toString(); QUrl fileUrl = QUrl::fromLocalFile(m_workingDirectory.path() + QChar('/') + fileName); GraphDocumentPtr document = m_graphEditor->openDocument(fileUrl); // add only to project, if document was loaded correctly if (document) { document->setDocumentName(docInfo["name"].toString()); m_graphDocuments.append(document); } } m_journal = KTextEditor::Editor::instance()->createDocument(nullptr); m_journal->openUrl(QUrl::fromLocalFile(m_workingDirectory.path() + QChar('/') + metaInfo["journal.txt"].toString())); Q_ASSERT(m_journal != nullptr); //TODO save & load open document index return true; } bool ProjectPrivate::writeProjectMetaInfo() { QJsonObject metaInfo; QJsonArray codeDocs, codeDocNames, graphDocs; foreach (KTextEditor::Document *document, m_codeDocuments) { QJsonObject docInfo; docInfo.insert("file", document->url().fileName()); codeDocs.append(docInfo); codeDocNames.append(m_documentNames.value(document)); } foreach (GraphTheory::GraphDocumentPtr document, m_graphDocuments) { QJsonObject docInfo; docInfo.insert("file", document->documentUrl().fileName()); docInfo.insert("name", document->documentName()); graphDocs.append(docInfo); } metaInfo.insert("scripts", codeDocs); metaInfo.insert("scriptNames", codeDocNames); metaInfo.insert("graphs", graphDocs); metaInfo.insert("journal", m_journal->url().fileName()); // write to file QFile metaInfoFile(m_workingDirectory.path() + QChar('/') + "project.json"); if (!metaInfoFile.open(QIODevice::WriteOnly)) { qWarning("Couldn't open project.json file for writing, abort."); return false; } QJsonDocument metaInfoDoc(metaInfo); metaInfoFile.write(metaInfoDoc.toJson()); return true; } //TODO make graphEditor singleton Project::Project(GraphTheory::Editor *graphEditor) : d(new ProjectPrivate) { d->m_graphEditor = graphEditor; d->m_journal = KTextEditor::Editor::instance()->createDocument(nullptr); d->m_journal->saveAs(QUrl::fromLocalFile(workingDir() + QChar('/') + QString("journal.txt"))); } //TODO make graphEditor singleton Project::Project(const QUrl &projectFile, GraphTheory::Editor *graphEditor) : d(new ProjectPrivate) { d->m_graphEditor = graphEditor; d->m_projectUrl = projectFile; if (!d->loadProject(projectFile)) { addCodeDocument(KTextEditor::Editor::instance()->createDocument(nullptr)); addGraphDocument(graphEditor->createDocument()); setModified(false); d->m_journal = KTextEditor::Editor::instance()->createDocument(nullptr); KMessageBox::error(nullptr, i18nc("@info", "The Rocs project could not be imported because the project file could not be parsed.")); } for (const auto &document : d->m_codeDocuments) { connect(document, &KTextEditor::Document::modifiedChanged, this, &Project::modifiedChanged); } for (const auto &document : d->m_graphDocuments) { connect(document.data(), &GraphDocument::modifiedChanged, this, &Project::modifiedChanged); } } Project::~Project() { } QUrl Project::projectUrl() const { return d->m_projectUrl; } void Project::setProjectUrl(const QUrl &url) { d->m_projectUrl = url; } QString Project::workingDir() const { return d->m_workingDirectory.path(); } bool Project::addCodeDocument(KTextEditor::Document *document) { // compute first unused document path QStringList usedPaths; foreach (KTextEditor::Document *document, d->m_codeDocuments) { usedPaths.append(document->url().toLocalFile()); } QString path; for (int i = 0; i <= d->m_codeDocuments.count(); ++i) { path = d->m_workingDirectory.path() + QChar('/') + "codefile" + QString::number(i) + QString(".js"); if (!usedPaths.contains(path)) { break; } } // put document into working directory if (!document->saveAs(QUrl::fromLocalFile(path))) { qCritical() << "Error when saving code file to working directory, aborting."; return false; } emit codeDocumentAboutToBeAdded(document, d->m_codeDocuments.count()); connect(document, &KTextEditor::Document::modifiedChanged, this, &Project::modifiedChanged); d->m_codeDocuments.append(document); emit codeDocumentAdded(); setModified(true); return true; } KTextEditor::Document * Project::importCodeDocument(const QUrl &url) { KTextEditor::Document *document = KTextEditor::Editor::instance()->createDocument(nullptr); document->openUrl(url); addCodeDocument(document); return document; } -void Project::removeCodeDocument(KTextEditor::Document *document) +void Project::tryToRemoveCodeDocument(KTextEditor::Document *document) { QString path = document->url().toString(); - document->closeUrl(); + if(!document->closeUrl()) + return; int index = d->m_codeDocuments.indexOf(document); emit codeDocumentAboutToBeRemoved(index, index); disconnect(document, &KTextEditor::Document::modifiedChanged, this, &Project::modifiedChanged); d->m_codeDocuments.removeAt(index); emit codeDocumentRemoved(); if (!path.startsWith(d->m_workingDirectory.path())) { qCritical() << "Aborting removal of document: not in temporary working directory"; return; } if (!QFile::remove(path)) { qCritical() << "Could not remove code file" << path; } d->m_documentNames.remove(document); setModified(true); } QString Project::documentName(KTextEditor::Document *document) const { if (!d->m_documentNames.value(document).isEmpty()) { return d->m_documentNames.value(document); } else { // fall back to internal document name return document->documentName(); } } void Project::setDocumentName(KTextEditor::Document *document, const QString &name) { d->m_documentNames.insert(document, name); setModified(true); } QList Project::codeDocuments() const { return d->m_codeDocuments; } void Project::setActiveCodeDocument(int index) { if (index < 0 || index >= d->m_codeDocuments.count()) { qCritical() << "Code document index invalid, aborting change of current document."; return; } d->m_activeCodeDocumentIndex = index; emit activeCodeDocumentChanged(index); } QList Project::graphDocuments() const { return d->m_graphDocuments; } bool Project::addGraphDocument(GraphDocumentPtr document) { // compute first unused document path QStringList usedFileNames; foreach (GraphTheory::GraphDocumentPtr document, d->m_graphDocuments) { usedFileNames.append(document->documentUrl().fileName()); } QString fileName; for (int i = 0; i <= d->m_graphDocuments.count(); ++i) { fileName = "graphfile" + QString::number(i) + QString(".graph2"); if (!usedFileNames.contains(fileName)) { break; } } QString path = d->m_workingDirectory.path() + QChar('/') + fileName; // put document into working directory document->documentSaveAs(QUrl::fromLocalFile(path)); int index = d->m_graphDocuments.length(); emit graphDocumentAboutToBeAdded(document, index); connect(document.data(), &GraphDocument::modifiedChanged, this, &Project::modifiedChanged); d->m_graphDocuments.append(document); emit graphDocumentAdded(); setModified(true); if (d->m_activeGraphDocumentIndex < 0) { setActiveGraphDocument(index); } return true; } GraphTheory::GraphDocumentPtr Project::importGraphDocument(const QUrl &documentUrl) { Q_ASSERT(d->m_graphEditor); GraphTheory::GraphDocumentPtr document = d->m_graphEditor->openDocument(documentUrl); Q_ASSERT(document); addGraphDocument(document); return document; } void Project::removeGraphDocument(GraphDocumentPtr document) { QString path = document->documentUrl().toLocalFile(); d->m_graphDocuments.removeAll(document); if (!path.startsWith(d->m_workingDirectory.path())) { qCritical() << "Aborting removal of graph document with path " << path << ", not in temporary working directory" << d->m_workingDirectory.path(); return; } if (!QFile::remove(path)) { qCritical() << "Could not remove graph file" << path; } int index = d->m_graphDocuments.indexOf(document); emit graphDocumentAboutToBeRemoved(index, index); d->m_graphDocuments.removeAt(index); emit graphDocumentRemoved(); setModified(true); } void Project::setActiveGraphDocument(int index) { if (index < 0 || index >= d->m_graphDocuments.count()) { qCritical() << "Graph document index invalid, aborting change of current document."; return; } d->m_activeGraphDocumentIndex = index; emit activeGraphDocumentChanged(index); emit activeGraphDocumentChanged(d->m_graphDocuments.at(index)); } GraphDocumentPtr Project::activeGraphDocument() const { if (d->m_activeGraphDocumentIndex < 0 || d->m_graphDocuments.count() <= d->m_activeGraphDocumentIndex) { return GraphDocumentPtr(); } return d->m_graphDocuments.at(d->m_activeGraphDocumentIndex); } KTextEditor::Document * Project::journalDocument() const { return d->m_journal; } bool Project::projectSave() { if (d->m_projectUrl.isEmpty()) { qCritical() << "No project file specified, abort saving."; return false; } KTar tar = KTar(d->m_projectUrl.toLocalFile(), QString("application/x-gzip")); tar.open(QIODevice::WriteOnly); foreach (KTextEditor::Document *document, d->m_codeDocuments) { document->save(); tar.addLocalFile(document->url().toLocalFile(), document->url().fileName()); } foreach (GraphTheory::GraphDocumentPtr document, d->m_graphDocuments) { document->documentSave(); tar.addLocalFile(document->documentUrl().toLocalFile(), document->documentUrl().fileName()); } tar.addLocalFile(d->m_journal->url().toLocalFile(), "journal.txt"); d->writeProjectMetaInfo(); tar.addLocalFile(d->m_workingDirectory.path() + QChar('/') + "project.json", "project.json"); tar.close(); // update modified state setModified(false); return true; } bool Project::projectSaveAs(const QUrl &url) { d->m_projectUrl = url; return projectSave(); } void Project::setModified(bool modified) { if (modified == d->m_modified) { return; } d->m_modified = modified; emit modifiedChanged(); } bool Project::isModified() const { foreach (GraphDocumentPtr document, d->m_graphDocuments) { if (document->isModified()) { return true; } } foreach (KTextEditor::Document *document, d->m_codeDocuments) { if (document->isModified()) { return true; } } if (d->m_journal && d->m_journal->isModified()) { return true; } return d->m_modified; } diff --git a/src/project/project.h b/src/project/project.h index 03393a5b..3e95341c 100644 --- a/src/project/project.h +++ b/src/project/project.h @@ -1,227 +1,227 @@ /* * Copyright 2012-2014 Andreas Cord-Landwehr * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #ifndef PROJECT_H #define PROJECT_H #include "libgraphtheory/editor.h" #include "libgraphtheory/typenames.h" #include #include #include class QUrl; class ProjectPrivate; namespace KTextEditor { class Document; class Editor; } /** * \class Project * \brief A project represents the compilation of graphs, scripts and further meta information. * * Topics: * - \ref project_intro * - \ref project_usage * * \section project_intro Introduction * * A project consists of a zipped archive file that contains all of its files and, when opened, * a working directory that contains (temporary) copies of all of these files. Only on writing back, * the archive file gets updated. * * \section project_usage Using Projects * * A project can be created by creating an empty project or by using the overloaded constructor * to open an existing project file. */ class Project : public QObject { Q_OBJECT public: /** * Constructs an empty project. */ explicit Project(GraphTheory::Editor *graphEditor); /** * Opens the project @p url as well as all contained files.chive */ explicit Project(const QUrl &url, GraphTheory::Editor *graphEditor); virtual ~Project(); /** * @return the project's journal document * Note that the pointer may be 0. */ KTextEditor::Document * journalDocument() const; /* * Handling of script documents. */ public: /** * @return list of all scripts contained in this project */ QList codeDocuments() const; /** * Add code document @p document to project. This updates the document's url. */ bool addCodeDocument(KTextEditor::Document *document); /** * Import the script given in file @p url to the project. This creates a copy * of the original document. */ KTextEditor::Document * importCodeDocument(const QUrl &url); /** * Remove the script file @p document from the project */ - void removeCodeDocument(KTextEditor::Document *document); + void tryToRemoveCodeDocument(KTextEditor::Document *document); /** * @return project dependent document name */ QString documentName(KTextEditor::Document *document) const; /** * Set a project dependent document name @p name for @p document , i.e., the name is only * provided by the project but not by the text document file. */ void setDocumentName(KTextEditor::Document *document, const QString &name); public Q_SLOTS: /** * Set the currently active graph document index to @p index. * If the index does not exist, it will not be changed. */ void setActiveCodeDocument(int index); Q_SIGNALS: void codeDocumentAboutToBeAdded(KTextEditor::Document*,int); void codeDocumentAdded(); void codeDocumentAboutToBeRemoved(int,int); void codeDocumentRemoved(); void activeCodeDocumentChanged(int index); void modifiedChanged(); /* * Handling of graph documents. */ public: /** * @return list of all graph documents contained in this project */ QList graphDocuments() const; GraphTheory::GraphDocumentPtr activeGraphDocument() const; /** * Add the graph document @p document to project. This updates the document's url. */ bool addGraphDocument(GraphTheory::GraphDocumentPtr document); /** * Import the graph document given in file @p documentUrl to the project. This creates a copy * of the original graph document. */ GraphTheory::GraphDocumentPtr importGraphDocument(const QUrl &documentUrl); /** * Remove the graph file @p document from the project */ void removeGraphDocument(GraphTheory::GraphDocumentPtr document); public Q_SLOTS: /** * Set the currently active graph document index to @p index. * If the index does not exist, it will not be changed. */ void setActiveGraphDocument(int index); Q_SIGNALS: void graphDocumentAboutToBeAdded(GraphTheory::GraphDocumentPtr, int); void graphDocumentAdded(); void graphDocumentAboutToBeRemoved(int,int); void graphDocumentRemoved(); void activeGraphDocumentChanged(int index); void activeGraphDocumentChanged(GraphTheory::GraphDocumentPtr document); /* * General file related actions. * None of these actions cause user interaction. */ public: /** * Save project to path as given by projectUrl(). * @return @e true on success, i.e. the save has been done, otherwise * @e false */ bool projectSave(); /** * Save project to path @p url. This also changes the projectUrl() path. * @return @e true on success, i.e. the save has been done, otherwise * @e false */ bool projectSaveAs(const QUrl &url); /** * @return project file path */ QUrl projectUrl() const; /** * Set the project file to @p url. This is the path where the project is saved. */ void setProjectUrl(const QUrl &url); /** * @internal * @return working directory that contains the extraced project archive while open */ QString workingDir() const; /** * @internal * set modified state of project container to @c modified * @note this does not change the modified state of any associated document */ void setModified(bool modified = true); /** * @return @e true if project (including any file of this project) was modified after last saving/loading, otherwise * @e false */ bool isModified() const; private: const QScopedPointer d; }; #endif diff --git a/src/ui/codeeditorwidget.cpp b/src/ui/codeeditorwidget.cpp index cd3253c0..2aa01883 100644 --- a/src/ui/codeeditorwidget.cpp +++ b/src/ui/codeeditorwidget.cpp @@ -1,106 +1,111 @@ /* * Copyright 2014-2015 Andreas Cord-Landwehr * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "codeeditorwidget.h" #include "project/project.h" #include #include #include #include #include #include #include #include #include CodeEditorWidget::CodeEditorWidget(QWidget *parent) : QWidget(parent) , m_project(Q_NULLPTR) { QLayout *layout = new QVBoxLayout(); m_editor = KTextEditor::Editor::instance(); if (!m_editor) { qCritical() << "KTextEditor could not be found, please check your installation"; } m_viewWidgets = new QTabWidget(this); - m_viewWidgets->setTabsClosable(false); + m_viewWidgets->setTabsClosable(true); layout->addWidget(m_viewWidgets); layout->setSpacing(0); setLayout(layout); - connect(m_viewWidgets, &QTabWidget::tabBarDoubleClicked, this, &CodeEditorWidget::showDocumentNameDialog); + connect(m_viewWidgets, &QTabWidget::tabCloseRequested, this, &CodeEditorWidget::closeTab); } void CodeEditorWidget::setProject(Project *project) { if (m_project) { m_project->disconnect(this); } // remove all data while (m_viewWidgets->count() > 0) { m_viewWidgets->removeTab(0); } m_project = project; connect(project, &Project::codeDocumentAboutToBeAdded, this, &CodeEditorWidget::onCodeDocumentAboutToBeAdded); connect(project, &Project::codeDocumentAboutToBeRemoved, this, &CodeEditorWidget::onCodeDocumentAboutToBeRemoved); // initialize views for (int index = 0; index < m_project->codeDocuments().count(); ++index) { KTextEditor::Document *document = m_project->codeDocuments().at(index); m_viewWidgets->insertTab(index, document->createView(this), project->documentName(document)); } } KTextEditor::Document * CodeEditorWidget::activeDocument() const { return m_project->codeDocuments().at(m_viewWidgets->currentIndex()); } void CodeEditorWidget::onCodeDocumentAboutToBeAdded(KTextEditor::Document* document, int index) { m_viewWidgets->insertTab(index, document->createView(this), m_project->documentName(document)); } void CodeEditorWidget::onCodeDocumentAboutToBeRemoved(int start, int end) { for (int i = end; i >= start; --i) { m_viewWidgets->removeTab(i); } } void CodeEditorWidget::showDocumentNameDialog(int index) { if (!m_project || m_project->codeDocuments().count() < index) { return; } auto document = m_project->codeDocuments().at(index); bool ok; QString name = QInputDialog::getText(this, i18nc("@title", "Code Document Name"), i18n("Enter the name of your code document"), QLineEdit::Normal, m_project->documentName(document), &ok); if (ok) { m_project->setDocumentName(document, name); m_viewWidgets->setTabText(index, name); } } + +void CodeEditorWidget::closeTab(int index) +{ + m_project->tryToRemoveCodeDocument(m_project->codeDocuments().at(index)); +} diff --git a/src/ui/codeeditorwidget.h b/src/ui/codeeditorwidget.h index e25ef774..bccf093b 100644 --- a/src/ui/codeeditorwidget.h +++ b/src/ui/codeeditorwidget.h @@ -1,57 +1,58 @@ /* * Copyright 2014 Andreas Cord-Landwehr * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef CODEEDITORWIDGET_H #define CODEEDITORWIDGET_H #include #include "project/project.h" namespace KTextEditor { class Document; class View; class Editor; } class QTabWidget; class CodeEditorWidget : public QWidget { Q_OBJECT public: explicit CodeEditorWidget(QWidget *parent = 0); void setProject(Project *project); KTextEditor::Document *activeDocument() const; Q_SIGNALS: void activeDocumentChanged(int index); private Q_SLOTS: void onCodeDocumentAboutToBeAdded(KTextEditor::Document *document, int index); void onCodeDocumentAboutToBeRemoved(int start, int end); void showDocumentNameDialog(int index); + void closeTab(int index); private: QTabWidget *m_viewWidgets; KTextEditor::Editor *m_editor; Project *m_project; //!< current project }; #endif