diff --git a/autotests/courseresource/resourcerepositorystub.h b/autotests/courseresource/resourcerepositorystub.h index 3cf2a10..f1e6472 100644 --- a/autotests/courseresource/resourcerepositorystub.h +++ b/autotests/courseresource/resourcerepositorystub.h @@ -1,81 +1,83 @@ /* * Copyright 2019 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 RESOURCEREPOSITORYSTUB_H #define RESOURCEREPOSITORYSTUB_H #include "core/iresourcerepository.h" +#include "core/language.h" #include #include -class Language; class ICourse; /** * @brief The ResourceRepositoryStub that only provides languages and a storage location, but nothing else */ class ResourceRepositoryStub : public IResourceRepository { Q_OBJECT public: - ResourceRepositoryStub(QVector languages) + ResourceRepositoryStub(std::vector> languages) { - m_languages = languages; + for (auto &language : languages) { + m_languages.append(std::move(language)); + } } ~ResourceRepositoryStub() override; QString storageLocation() const override { return m_storageLocation; } - QVector courses() const override + QVector> courses() const override { - return QVector(); // do not return any courses: stub shall only provide languages + return QVector>(); // do not return any courses: stub shall only provide languages } - QVector courses(Language *language) const override + QVector> courses(const QString &languageId) const override { - Q_UNUSED(language); - return QVector(); // do not return any courses: stub shall only provide languages + Q_UNUSED(languageId); + return QVector>(); // do not return any courses: stub shall only provide languages } void reloadCourses() override { ; // do nothing, stub shall only provide languages } - QVector languages() const override + QVector> languages() const override { return m_languages; } Q_SIGNALS: void courseAboutToBeAdded(ICourse*,int) override; void courseAdded() override; void courseAboutToBeRemoved(int) override; void courseRemoved() override; private: QString m_storageLocation; - QVector m_languages; + QVector> m_languages; }; #endif diff --git a/autotests/courseresource/test_courseresource.cpp b/autotests/courseresource/test_courseresource.cpp index 76fba2d..c4a0171 100644 --- a/autotests/courseresource/test_courseresource.cpp +++ b/autotests/courseresource/test_courseresource.cpp @@ -1,192 +1,201 @@ /* * Copyright 2013 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 "test_courseresource.h" #include "resourcerepositorystub.h" #include "core/language.h" #include "core/unit.h" #include "core/phrase.h" #include "core/resources/languageresource.h" #include "core/resources/courseresource.h" #include #include #include #include #include #include #include #include #include TestCourseResource::TestCourseResource() { } void TestCourseResource::init() { } void TestCourseResource::cleanup() { } void TestCourseResource::courseSchemeValidationTest() { QUrl schemeFile = QUrl::fromLocalFile(":/artikulate/schemes/course.xsd"); QXmlSchema courseSchema; QVERIFY(courseSchema.load(schemeFile)); QVERIFY(courseSchema.isValid()); } void TestCourseResource::loadCourseResource() { - Language language; - language.setId("de"); - ResourceRepositoryStub repository({&language}); + std::unique_ptr language(new Language); + language->setId("de"); + std::vector> languages; + languages.push_back(std::move(language)); + ResourceRepositoryStub repository(std::move(languages)); + const QString courseDirectory = "data/courses/de/"; const QString courseFile = courseDirectory + "de.xml"; CourseResource course(QUrl::fromLocalFile(courseFile), &repository); QCOMPARE(course.file().toLocalFile(), courseFile); QCOMPARE(course.id(), "de"); QCOMPARE(course.foreignId(), "artikulate-basic"); QCOMPARE(course.title(), "Artikulate Deutsch"); QCOMPARE(course.description(), "Ein Kurs in (hoch-)deutscher Aussprache."); QVERIFY(course.language() != nullptr); QCOMPARE(course.language()->id(), "de"); QCOMPARE(course.unitList().count(), 1); const auto unit = course.unitList().first(); QVERIFY(unit != nullptr); QCOMPARE(unit->id(), "1"); QCOMPARE(unit->title(), QStringLiteral("Auf der Straße")); QCOMPARE(unit->foreignId(), "{dd60f04a-eb37-44b7-9787-67aaf7d3578d}"); QCOMPARE(unit->phraseList().count(), 3); // note: this test takes the silent assumption that phrases are added to the list in same // order as they are defined in the file. This assumption should be made explicit or dropped const auto firstPhrase = unit->phraseList().first(); QVERIFY(firstPhrase != nullptr); QCOMPARE(firstPhrase->id(), "1"); QCOMPARE(firstPhrase->foreignId(), "{3a4c1926-60d7-44c6-80d1-03165a641c75}"); QCOMPARE(firstPhrase->text(), "Guten Tag."); QCOMPARE(firstPhrase->soundFileUrl(), courseDirectory + "de_01.ogg"); QCOMPARE(firstPhrase->type(), Phrase::Type::Sentence); QVERIFY(firstPhrase->phonemes().isEmpty()); } void TestCourseResource::unitAddAndRemoveHandling() { // boilerplate - Language language; - language.setId("de"); - ResourceRepositoryStub repository({&language}); + std::unique_ptr language(new Language); + language->setId("de"); + std::vector> languages; + languages.push_back(std::move(language)); + ResourceRepositoryStub repository(std::move(languages)); + const QString courseDirectory = "data/courses/de/"; const QString courseFile = courseDirectory + "de.xml"; CourseResource course(QUrl::fromLocalFile(courseFile), &repository); // begin of test - Unit *unit = new Unit; + std::unique_ptr unit(new Unit); unit->setId("testunit"); const int initialUnitNumber = course.unitList().count(); QCOMPARE(initialUnitNumber, 1); QSignalSpy spyAboutToBeAdded(&course, SIGNAL(unitAboutToBeAdded(Unit*, int))); QSignalSpy spyAdded(&course, SIGNAL(unitAdded())); QCOMPARE(spyAboutToBeAdded.count(), 0); QCOMPARE(spyAdded.count(), 0); - course.addUnit(unit); + course.addUnit(std::move(unit)); QCOMPARE(course.unitList().count(), initialUnitNumber + 1); QCOMPARE(spyAboutToBeAdded.count(), 1); QCOMPARE(spyAdded.count(), 1); } void TestCourseResource::coursePropertyChanges() { // boilerplate - Language language; - language.setId("de"); - ResourceRepositoryStub repository({&language}); + std::unique_ptr language(new Language); + language->setId("de"); + std::vector> languages; + languages.push_back(std::move(language)); + ResourceRepositoryStub repository(std::move(languages)); + const QString courseDirectory = "data/courses/de/"; const QString courseFile = courseDirectory + "de.xml"; CourseResource course(QUrl::fromLocalFile(courseFile), &repository); // id { const QString value = "newId"; QSignalSpy spy(&course, SIGNAL(idChanged())); QCOMPARE(spy.count(), 0); course.setId(value); QCOMPARE(course.id(), value); QCOMPARE(spy.count(), 1); } // foreign id { const QString value = "newForeignId"; QSignalSpy spy(&course, SIGNAL(foreignIdChanged())); QCOMPARE(spy.count(), 0); course.setForeignId(value); QCOMPARE(course.foreignId(), value); QCOMPARE(spy.count(), 1); } // title { const QString value = "newTitle"; QSignalSpy spy(&course, SIGNAL(titleChanged())); QCOMPARE(spy.count(), 0); course.setTitle(value); QCOMPARE(course.title(), value); QCOMPARE(spy.count(), 1); } // title { const QString value = "newI18nTitle"; QSignalSpy spy(&course, SIGNAL(i18nTitleChanged())); QCOMPARE(spy.count(), 0); course.setI18nTitle(value); QCOMPARE(course.i18nTitle(), value); QCOMPARE(spy.count(), 1); } // description { const QString value = "newDescription"; QSignalSpy spy(&course, SIGNAL(descriptionChanged())); QCOMPARE(spy.count(), 0); course.setDescription(value); QCOMPARE(course.description(), value); QCOMPARE(spy.count(), 1); } // language { - Language testLanguage; + std::shared_ptr testLanguage; QSignalSpy spy(&course, SIGNAL(languageChanged())); QCOMPARE(spy.count(), 0); - course.setLanguage(&testLanguage); - QCOMPARE(course.language(), &testLanguage); + course.setLanguage(testLanguage); + QCOMPARE(course.language(), testLanguage); QCOMPARE(spy.count(), 1); } } QTEST_GUILESS_MAIN(TestCourseResource) diff --git a/autotests/editablecourseresource/resourcerepositorystub.h b/autotests/editablecourseresource/resourcerepositorystub.h index 3cf2a10..eaae9f7 100644 --- a/autotests/editablecourseresource/resourcerepositorystub.h +++ b/autotests/editablecourseresource/resourcerepositorystub.h @@ -1,81 +1,84 @@ /* * Copyright 2019 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 RESOURCEREPOSITORYSTUB_H #define RESOURCEREPOSITORYSTUB_H #include "core/iresourcerepository.h" +#include "core/language.h" #include #include class Language; class ICourse; /** * @brief The ResourceRepositoryStub that only provides languages and a storage location, but nothing else */ class ResourceRepositoryStub : public IResourceRepository { Q_OBJECT public: - ResourceRepositoryStub(QVector languages) + ResourceRepositoryStub(std::vector> languages) { - m_languages = languages; + for (auto &language : languages) { + m_languages.append(std::move(language)); + } } ~ResourceRepositoryStub() override; QString storageLocation() const override { return m_storageLocation; } - QVector courses() const override + QVector> courses() const override { - return QVector(); // do not return any courses: stub shall only provide languages + return QVector>(); // do not return any courses: stub shall only provide languages } - QVector courses(Language *language) const override + QVector> courses(const QString &languageId) const override { - Q_UNUSED(language); - return QVector(); // do not return any courses: stub shall only provide languages + Q_UNUSED(languageId); + return QVector>(); // do not return any courses: stub shall only provide languages } void reloadCourses() override { ; // do nothing, stub shall only provide languages } - QVector languages() const override + QVector> languages() const override { return m_languages; } Q_SIGNALS: void courseAboutToBeAdded(ICourse*,int) override; void courseAdded() override; void courseAboutToBeRemoved(int) override; void courseRemoved() override; private: QString m_storageLocation; - QVector m_languages; + QVector> m_languages; }; #endif diff --git a/autotests/editablecourseresource/test_editablecourseresource.cpp b/autotests/editablecourseresource/test_editablecourseresource.cpp index d625e6d..28d1ebe 100644 --- a/autotests/editablecourseresource/test_editablecourseresource.cpp +++ b/autotests/editablecourseresource/test_editablecourseresource.cpp @@ -1,241 +1,250 @@ /* * Copyright 2013-2019 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 "test_editablecourseresource.h" #include "resourcerepositorystub.h" #include "core/language.h" #include "core/unit.h" #include "core/phrase.h" #include "core/resources/courseparser.h" #include "core/resources/languageresource.h" #include "core/resources/editablecourseresource.h" +#include #include #include #include #include #include #include #include #include #include TestEditableCourseResource::TestEditableCourseResource() { } void TestEditableCourseResource::init() { } void TestEditableCourseResource::cleanup() { } void TestEditableCourseResource::courseSchemeValidationTest() { QUrl schemeFile = QUrl::fromLocalFile(":/artikulate/schemes/course.xsd"); QXmlSchema courseSchema; QVERIFY(courseSchema.load(schemeFile)); QVERIFY(courseSchema.isValid()); //TODO shall be used in skeleton specific test QUrl skeletonFile = QUrl::fromLocalFile(":/artikulate/schemes/skeleton.xsd"); QXmlSchema skeletonScheme; QVERIFY(skeletonScheme.load(skeletonFile)); QVERIFY(skeletonScheme.isValid()); } void TestEditableCourseResource::loadCourseResource() { - Language language; - language.setId("de"); - ResourceRepositoryStub repository({&language}); + std::unique_ptr language(new Language); + language->setId("de"); + std::vector> languages; + languages.push_back(std::move(language)); + ResourceRepositoryStub repository(std::move(languages)); EditableCourseResource course(QUrl::fromLocalFile(":/courses/de.xml"), &repository); QCOMPARE(course.file().toLocalFile(), ":/courses/de.xml"); QCOMPARE(course.id(), "de"); QCOMPARE(course.foreignId(), "artikulate-basic"); QCOMPARE(course.title(), "Artikulate Deutsch"); QCOMPARE(course.description(), "Ein Kurs in (hoch-)deutscher Aussprache."); QVERIFY(course.language() != nullptr); QCOMPARE(course.language()->id(), "de"); QCOMPARE(course.unitList().count(), 1); const auto unit = course.unitList().first(); QVERIFY(unit != nullptr); QCOMPARE(unit->id(), "1"); QCOMPARE(unit->title(), QStringLiteral("Auf der Straße")); QCOMPARE(unit->foreignId(), "{dd60f04a-eb37-44b7-9787-67aaf7d3578d}"); QCOMPARE(unit->phraseList().count(), 3); // note: this test takes the silent assumption that phrases are added to the list in same // order as they are defined in the file. This assumption should be made explicit or dropped const auto firstPhrase = unit->phraseList().first(); QVERIFY(firstPhrase != nullptr); QCOMPARE(firstPhrase->id(), "1"); QCOMPARE(firstPhrase->foreignId(), "{3a4c1926-60d7-44c6-80d1-03165a641c75}"); QCOMPARE(firstPhrase->text(), "Guten Tag."); QCOMPARE(firstPhrase->soundFileUrl(), ":/courses/de_01.ogg"); QCOMPARE(firstPhrase->type(), Phrase::Type::Sentence); QVERIFY(firstPhrase->phonemes().isEmpty()); } void TestEditableCourseResource::unitAddAndRemoveHandling() { // boilerplate - Language language; - language.setId("de"); - ResourceRepositoryStub repository({&language}); + std::unique_ptr language(new Language); + language->setId("de"); + std::vector> languages; + languages.push_back(std::move(language)); + ResourceRepositoryStub repository(std::move(languages)); EditableCourseResource course(QUrl::fromLocalFile(":/courses/de.xml"), &repository); // begin of test - Unit *unit = new Unit; // TODO: change to unique pointer when interface is changed to scoped pointers + std::unique_ptr unit(new Unit); unit->setId("testunit"); const int initialUnitNumber = course.unitList().count(); QCOMPARE(initialUnitNumber, 1); QSignalSpy spyAboutToBeAdded(&course, SIGNAL(unitAboutToBeAdded(Unit*, int))); QSignalSpy spyAdded(&course, SIGNAL(unitAdded())); QCOMPARE(spyAboutToBeAdded.count(), 0); QCOMPARE(spyAdded.count(), 0); - course.addUnit(unit); + course.addUnit(std::move(unit)); QCOMPARE(course.unitList().count(), initialUnitNumber + 1); QCOMPARE(spyAboutToBeAdded.count(), 1); QCOMPARE(spyAdded.count(), 1); } void TestEditableCourseResource::coursePropertyChanges() { // boilerplate - Language language; - language.setId("de"); - ResourceRepositoryStub repository({&language}); + std::unique_ptr language(new Language); + language->setId("de"); + std::vector> languages; + languages.push_back(std::move(language)); + ResourceRepositoryStub repository(std::move(languages)); CourseResource course(QUrl::fromLocalFile(":/courses/de.xml"), &repository); // id { const QString value = "newId"; QSignalSpy spy(&course, SIGNAL(idChanged())); QCOMPARE(spy.count(), 0); course.setId(value); QCOMPARE(course.id(), value); QCOMPARE(spy.count(), 1); } // foreign id { const QString value = "newForeignId"; QSignalSpy spy(&course, SIGNAL(foreignIdChanged())); QCOMPARE(spy.count(), 0); course.setForeignId(value); QCOMPARE(course.foreignId(), value); QCOMPARE(spy.count(), 1); } // title { const QString value = "newTitle"; QSignalSpy spy(&course, SIGNAL(titleChanged())); QCOMPARE(spy.count(), 0); course.setTitle(value); QCOMPARE(course.title(), value); QCOMPARE(spy.count(), 1); } // title { const QString value = "newI18nTitle"; QSignalSpy spy(&course, SIGNAL(i18nTitleChanged())); QCOMPARE(spy.count(), 0); course.setI18nTitle(value); QCOMPARE(course.i18nTitle(), value); QCOMPARE(spy.count(), 1); } // description { const QString value = "newDescription"; QSignalSpy spy(&course, SIGNAL(descriptionChanged())); QCOMPARE(spy.count(), 0); course.setDescription(value); QCOMPARE(course.description(), value); QCOMPARE(spy.count(), 1); } // language { - Language testLanguage; + std::shared_ptr testLanguage; QSignalSpy spy(&course, SIGNAL(languageChanged())); QCOMPARE(spy.count(), 0); - course.setLanguage(&testLanguage); - QCOMPARE(course.language(), &testLanguage); + course.setLanguage(testLanguage); + QCOMPARE(course.language(), testLanguage); QCOMPARE(spy.count(), 1); } } void TestEditableCourseResource::fileLoadSaveCompleteness() { // boilerplate - Language language; - language.setId("de"); - ResourceRepositoryStub repository({&language}); + std::unique_ptr language(new Language); + language->setId("de"); + std::vector> languages; + languages.push_back(std::move(language)); + ResourceRepositoryStub repository(std::move(languages)); EditableCourseResource course(QUrl::fromLocalFile(":/courses/de.xml"), &repository); QTemporaryFile outputFile; outputFile.open(); course.exportCourse(QUrl::fromLocalFile(outputFile.fileName())); // note: this only works, since the resource manager not checks uniqueness of course ids! EditableCourseResource loadedCourse(QUrl::fromLocalFile(outputFile.fileName()), &repository); // test that we actually call the different files QVERIFY(course.file().toLocalFile() != loadedCourse.file().toLocalFile()); QVERIFY(course.id() == loadedCourse.id()); QVERIFY(course.foreignId() == loadedCourse.foreignId()); QVERIFY(course.title() == loadedCourse.title()); QVERIFY(course.description() == loadedCourse.description()); QVERIFY(course.language()->id() == loadedCourse.language()->id()); QVERIFY(course.unitList().count() == loadedCourse.unitList().count()); Unit *testUnit = course.unitList().constFirst(); Unit *compareUnit = loadedCourse.unitList().constFirst(); QVERIFY(testUnit->id() == compareUnit->id()); QVERIFY(testUnit->foreignId() == compareUnit->foreignId()); QVERIFY(testUnit->title() == compareUnit->title()); QVERIFY(testUnit->phraseList().count() == compareUnit->phraseList().count()); Phrase *testPhrase = testUnit->phraseList().constFirst(); Phrase *comparePhrase = new Phrase(this); // note that this actually means that we DO NOT respect phrase orders by list order for (Phrase *phrase : compareUnit->phraseList()) { if (testPhrase->id() == phrase->id()) { comparePhrase = phrase; break; } } QVERIFY(testPhrase->id() == comparePhrase->id()); QVERIFY(testPhrase->foreignId() == comparePhrase->foreignId()); QVERIFY(testPhrase->text() == comparePhrase->text()); QVERIFY(testPhrase->type() == comparePhrase->type()); QVERIFY(testPhrase->sound().fileName() == comparePhrase->sound().fileName()); QVERIFY(testPhrase->phonemes().count() == comparePhrase->phonemes().count()); } QTEST_GUILESS_MAIN(TestEditableCourseResource) diff --git a/autotests/editorsession/editablerepositorystub.h b/autotests/editorsession/editablerepositorystub.h index f928d05..088dbf5 100644 --- a/autotests/editorsession/editablerepositorystub.h +++ b/autotests/editorsession/editablerepositorystub.h @@ -1,106 +1,112 @@ /* * Copyright 2019 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 EDITABLEREPOSITORYSTUB_H #define EDITABLEREPOSITORYSTUB_H #include "core/ieditablerepository.h" #include "core/ieditablecourse.h" #include #include class Language; class SkeletonResource; /** * @brief The EditableRepositoryStub is simple sub class only for testing */ class EditableRepositoryStub : public IEditableRepository { Q_OBJECT public: EditableRepositoryStub( - QVector languages, - QVector skeletons, - QVector courses) - : m_languages{ languages } - , m_skeletons{ skeletons } - , m_courses{ courses } + std::vector> languages, + std::vector> skeletons, + std::vector> courses) { + for (auto &language : languages) { + m_languages.append(std::move(language)); + } + for (auto &skeleton : skeletons) { + m_skeletons.append(std::move(skeleton)); + } + for (auto &course : courses) { + m_courses.append(course); + } } ~EditableRepositoryStub() override; QString storageLocation() const override { return QString(); } - QVector skeletons() const override + QVector> skeletons() const override { return m_skeletons; } - QVector editableCourses() const override + QVector> editableCourses() const override { return m_courses; } - QVector courses() const override + QVector> courses() const override { - QVector courses; + QVector> courses; for (auto course : m_courses) { courses.push_back(course); } return courses; } - QVector courses(Language *language) const override + QVector> courses(const QString &languageId) const override { - Q_UNUSED(language); - return QVector(); + Q_UNUSED(languageId); + return QVector>(); } - IEditableCourse * editableCourse(Language *language, int index) const override + std::shared_ptr editableCourse(std::shared_ptr language, int index) const override { Q_UNUSED(language); Q_UNUSED(index); - return nullptr; + return std::shared_ptr(); } void reloadCourses() override { // do nothing } - QVector languages() const override + QVector> languages() const override { return m_languages; } - void updateCourseFromSkeleton(IEditableCourse *course) override + void updateCourseFromSkeleton(std::shared_ptr course) override { Q_UNUSED(course); // do nothing } Q_SIGNALS: void courseAboutToBeAdded(ICourse*,int) override; void courseAdded() override; void courseAboutToBeRemoved(int) override; void courseRemoved() override; private: - QVector m_languages; - QVector m_skeletons; - QVector m_courses; + QVector> m_languages; + QVector> m_skeletons; + QVector> m_courses; }; #endif diff --git a/autotests/editorsession/test_editorsession.cpp b/autotests/editorsession/test_editorsession.cpp index 5caec9e..a3e64a6 100644 --- a/autotests/editorsession/test_editorsession.cpp +++ b/autotests/editorsession/test_editorsession.cpp @@ -1,235 +1,242 @@ /* * Copyright 2019 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 "test_editorsession.h" #include "editablerepositorystub.h" #include "src/core/editorsession.h" #include "src/core/icourse.h" #include "src/core/ieditablecourse.h" #include "src/core/ieditablerepository.h" #include "src/core/language.h" #include "src/core/resources/skeletonresource.h" #include "src/core/unit.h" #include #include class EditableCourseStub : public IEditableCourse { public: - EditableCourseStub(Language *language, QVector units) + EditableCourseStub(std::shared_ptr language, QVector> units) : IEditableCourse() , m_language(language) , m_units(units) { } ~EditableCourseStub() override; QString id() const override { return m_id; } void setId(QString id) override { m_id = id; emit idChanged(); } QString foreignId() const override { return m_foreignId; } void setForeignId(QString id) override { m_foreignId = id; } QString title() const override { return m_title; } void setTitle(QString title) override { m_title = title; emit titleChanged(); } QString i18nTitle() const override { return m_i18nTitle; } void setI18nTitle(QString title) override { m_i18nTitle = title; } QString description() const override { return m_description; } void setDescription(QString description) override { m_description = description; emit descriptionChanged(); } - Language * language() const override + std::shared_ptr language() const override { return m_language; } - void setLanguage(Language *language) override + void setLanguage(std::shared_ptr language) override { m_language = language; emit languageChanged(); } QList unitList() override { - return m_units.toList(); + QList rawList; + for (auto unit : m_units) { + rawList.append(unit.get()); + } + return rawList; } - void addUnit(Unit *unit) override + std::shared_ptr addUnit(std::unique_ptr unit) override { - m_units.append(unit); + m_units.append(std::move(unit)); + return m_units.last(); } QUrl file() const override { return QUrl(); } private: QString m_id{ "courseid" }; QString m_foreignId{ "foreigncourseid" }; QString m_title{ "title" }; QString m_i18nTitle{ "i18n title" }; QString m_description{ "description of the course" }; - Language *m_language{nullptr}; - QVector m_units; + std::shared_ptr m_language; + QVector> m_units; }; // define one virtual method out of line to pin CourseStub to this translation unit EditableCourseStub::~EditableCourseStub() = default; void TestEditorSession::init() { // TODO initialization of test case } void TestEditorSession::cleanup() { // TODO cleanup after test run } void TestEditorSession::createEditorSession() { - Language languageGerman; - languageGerman.setId("de"); - Language languageEnglish; - languageEnglish.setId("en"); - EditableCourseStub course(&languageGerman, QVector()); - course.setLanguage(&languageGerman); SkeletonResource skeleton(QUrl(), nullptr); + std::shared_ptr languageGerman(new Language); + languageGerman->setId("de"); + std::shared_ptr languageEnglish(new Language); + languageEnglish->setId("en"); + + std::shared_ptr course(new EditableCourseStub(languageGerman, QVector>())); + course->setLanguage(languageGerman); + std::shared_ptr skeleton(new SkeletonResource(QUrl(), nullptr)); EditableRepositoryStub repository{ - {&languageGerman, &languageEnglish}, // languages - {&skeleton}, - {&course} // courses + {languageGerman, languageEnglish}, // languages + {skeleton}, // skeletons + {course} // courses }; EditorSession session; session.setRepository(&repository); QVERIFY(session.course() == nullptr); QVERIFY(session.language() == nullptr); QVERIFY(session.skeleton() == nullptr); } void TestEditorSession::nonSkeletonSwitchingBehavior() { - Language languageGerman; - languageGerman.setId("de"); - Language languageEnglish; - languageEnglish.setId("en"); - EditableCourseStub courseGerman(&languageGerman, QVector()); - courseGerman.setId("course-german"); - EditableCourseStub courseEnglish(&languageEnglish, QVector()); - courseEnglish.setId("course-english"); + std::shared_ptr languageGerman; + languageGerman->setId("de"); + std::shared_ptr languageEnglish; + languageEnglish->setId("en"); + std::shared_ptr courseGerman(new EditableCourseStub(languageGerman, QVector>())); + courseGerman->setId("course-german"); + std::shared_ptr courseEnglish(new EditableCourseStub(languageEnglish, QVector>())); + courseEnglish->setId("course-english"); EditableRepositoryStub repository{ - {&languageGerman, &languageEnglish}, // languages + {languageGerman, languageEnglish}, // languages {}, // skeletons - {&courseGerman, &courseEnglish} // courses + {courseGerman, courseEnglish} // courses }; EditorSession session; session.setRepository(&repository); QVERIFY(session.course() == nullptr); - session.setCourse(&courseGerman); - QCOMPARE(session.course()->id(), courseGerman.id()); + session.setCourse(courseGerman.get()); + QCOMPARE(session.course()->id(), courseGerman->id()); QVERIFY(session.language() != nullptr); - QCOMPARE(session.language()->id(), languageGerman.id()); + QCOMPARE(session.language()->id(), languageGerman->id()); QVERIFY(session.language() != nullptr); - QCOMPARE(session.language()->id(), languageGerman.id()); - session.setCourse(&courseEnglish); + QCOMPARE(session.language()->id(), languageGerman->id()); + session.setCourse(courseEnglish.get()); QVERIFY(session.course() != nullptr); - QCOMPARE(session.course()->id(), courseEnglish.id()); + QCOMPARE(session.course()->id(), courseEnglish->id()); QVERIFY(session.language() != nullptr); - QCOMPARE(session.language()->id(), languageEnglish.id()); + QCOMPARE(session.language()->id(), languageEnglish->id()); } void TestEditorSession::skeletonSwitchingBehavior() { - Language languageGerman; - languageGerman.setId("de"); - Language languageEnglish; - languageEnglish.setId("en"); - EditableCourseStub courseGermanA(&languageGerman, QVector()); - courseGermanA.setId("course-german"); - courseGermanA.setForeignId("testskeletonA"); - EditableCourseStub courseGermanB(&languageGerman, QVector()); - courseGermanB.setId("course-german"); - courseGermanB.setForeignId("testskeletonB"); - EditableCourseStub courseEnglishA(&languageEnglish, QVector()); - courseEnglishA.setId("course-english"); - courseEnglishA.setForeignId("testskeletonA"); - SkeletonResource skeletonA(QUrl(), nullptr); - skeletonA.setId("testskeletonA"); - SkeletonResource skeletonB(QUrl(), nullptr); - skeletonB.setId("testskeletonB"); + std::shared_ptr languageGerman; + languageGerman->setId("de"); + std::shared_ptr languageEnglish; + languageEnglish->setId("en"); + std::shared_ptr courseGermanA(new EditableCourseStub(languageGerman, QVector>())); + courseGermanA->setId("course-german"); + courseGermanA->setForeignId("testskeletonA"); + std::shared_ptr courseGermanB(new EditableCourseStub(languageGerman, QVector>())); + courseGermanB->setId("course-german"); + courseGermanB->setForeignId("testskeletonB"); + std::shared_ptr courseEnglishA(new EditableCourseStub(languageEnglish, QVector>())); + courseEnglishA->setId("course-english"); + courseEnglishA->setForeignId("testskeletonA"); + std::shared_ptr skeletonA(new SkeletonResource(QUrl(), nullptr)); + skeletonA->setId("testskeletonA"); + std::shared_ptr skeletonB(new SkeletonResource(QUrl(), nullptr)); + skeletonB->setId("testskeletonB"); EditableRepositoryStub repository{ - {&languageGerman, &languageEnglish}, // languages - {&skeletonA, &skeletonB}, // skeletons - {&courseGermanA, &courseEnglishA, &courseGermanB} // courses + {languageGerman, languageEnglish}, // languages + {skeletonA, skeletonB}, // skeletons + {courseGermanA, courseEnglishA, courseGermanB} // courses }; EditorSession session; session.setRepository(&repository); - session.setSkeleton(&skeletonA); + session.setSkeleton(skeletonA.get()); Q_ASSERT(session.skeleton() != nullptr); - QCOMPARE(session.skeleton()->id(), skeletonA.id()); + QCOMPARE(session.skeleton()->id(), skeletonA->id()); Q_ASSERT(session.course() != nullptr); - QCOMPARE(session.course()->id(), courseGermanA.id()); - session.setCourse(&courseEnglishA); + QCOMPARE(session.course()->id(), courseGermanA->id()); + session.setCourse(courseEnglishA.get()); Q_ASSERT(session.course() != nullptr); - QCOMPARE(session.course()->id(), courseEnglishA.id()); + QCOMPARE(session.course()->id(), courseEnglishA->id()); - session.setCourse(&courseGermanB); + session.setCourse(courseGermanB.get()); QVERIFY(session.skeleton() != nullptr); - QCOMPARE(session.skeleton()->id(), skeletonB.id()); + QCOMPARE(session.skeleton()->id(), skeletonB->id()); QVERIFY(session.course() != nullptr); - QCOMPARE(session.course()->id(), courseGermanB.id()); + QCOMPARE(session.course()->id(), courseGermanB->id()); QVERIFY(session.language() != nullptr); - QCOMPARE(session.language()->id(), languageGerman.id()); + QCOMPARE(session.language()->id(), languageGerman->id()); } QTEST_GUILESS_MAIN(TestEditorSession) diff --git a/autotests/iresourcerepository_integration/test_iresourcerepository.cpp b/autotests/iresourcerepository_integration/test_iresourcerepository.cpp index 829cbfb..6d8c3bd 100644 --- a/autotests/iresourcerepository_integration/test_iresourcerepository.cpp +++ b/autotests/iresourcerepository_integration/test_iresourcerepository.cpp @@ -1,89 +1,89 @@ /* * Copyright 2019 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 "test_iresourcerepository.h" #include #include #include #include "src/core/resourcerepository.h" #include "src/core/contributorrepository.h" #include "src/core/language.h" #include "../src/settings.h" void TestIResourceRepository::init() { // check that test data is deployed at the expected location QVERIFY(QFile::exists("data/courses/de/de.xml")); QVERIFY(QFile::exists("data/courses/fr/fr.xml")); } void TestIResourceRepository::resourceRepository() { ResourceRepository repository(QUrl::fromLocalFile("data/courses/")); QCOMPARE(repository.storageLocation(), "data/courses/"); performInterfaceTests(&repository); } void TestIResourceRepository::contributorRepository() { ContributorRepository repository; repository.setStorageLocation("data/contributorrepository/"); // contributor repository requires subdirectory "courses" QCOMPARE(repository.storageLocation(), "data/contributorrepository/"); performInterfaceTests(&repository); } void TestIResourceRepository::performInterfaceTests(IResourceRepository *interface) { QVERIFY(interface->languages().count() > 0); // automatically load languages QCOMPARE(interface->courses().count(), 0); // load courses only on demand // test adding QSignalSpy spyAboutToBeAdded(dynamic_cast(interface), SIGNAL(courseAboutToBeAdded(ICourse*, int))); QSignalSpy spyAdded(dynamic_cast(interface), SIGNAL(courseAdded())); QCOMPARE(spyAboutToBeAdded.count(), 0); QCOMPARE(spyAdded.count(), 0); interface->reloadCourses(); // initial loading of courses QCOMPARE(interface->courses().count(), 2); QCOMPARE(spyAboutToBeAdded.count(), 2); QCOMPARE(spyAdded.count(), 2); // test reloading of courses interface->reloadCourses(); // initial loading of courses QCOMPARE(interface->courses().count(), 2); // test removal // note: repository does not provide removal of courses, yet // test access of courses grouped by language auto languages = interface->languages(); - Language *german = nullptr; + std::shared_ptr german; for (auto language : interface->languages()) { if (language->id() == "de") { german = language; break; } } QVERIFY(german != nullptr); // ensure that German language was found - QCOMPARE(interface->courses(german).count(), 1); // there is exactly one German course + QCOMPARE(interface->courses(german->id()).count(), 1); // there is exactly one German course QCOMPARE(interface->courses(nullptr).count(), 2); // all courses in total are 2 } QTEST_GUILESS_MAIN(TestIResourceRepository) diff --git a/autotests/languageresources/testlanguagefiles.cpp b/autotests/languageresources/testlanguagefiles.cpp index cf971bd..9f71770 100644 --- a/autotests/languageresources/testlanguagefiles.cpp +++ b/autotests/languageresources/testlanguagefiles.cpp @@ -1,101 +1,101 @@ /* * Copyright 2013 Oindrila Gupta * * 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 "testlanguagefiles.h" #include "core/language.h" #include "core/unit.h" #include "core/phrase.h" #include "core/resources/courseparser.h" #include #include #include #include #include #include #include #include #include #include #include TestLanguageFiles::TestLanguageFiles() { } void TestLanguageFiles::init() { // TODO initialization of test case } void TestLanguageFiles::cleanup() { // TODO cleanup after test run } void TestLanguageFiles::languageSchemeValidationTest() { QUrl languageFile = QUrl::fromLocalFile(":/artikulate/schemes/language.xsd"); QXmlSchema languageSchema; QVERIFY(languageSchema.load(languageFile)); QVERIFY(languageSchema.isValid()); } void TestLanguageFiles::checkIdUniqueness() { QDirIterator iter(QDir(":/artikulate/languages/")); while (iter.hasNext()) { const QString &file = iter.next(); qDebug() << "File being parsed: " << file; QStringList idList; const QUrl &languageFile = QUrl::fromLocalFile(file); QVERIFY(languageFile.isLocalFile()); QXmlSchema schema = CourseParser::loadXmlSchema(QStringLiteral("language")); QVERIFY(schema.isValid()); QDomDocument document = CourseParser::loadDomDocument(languageFile, schema); QVERIFY(!document.isNull()); QDomElement root(document.documentElement()); - Language *language = new Language(this); + std::unique_ptr language(new Language); language->setFile(languageFile); language->setId(root.firstChildElement(QStringLiteral("id")).text()); language->setTitle(root.firstChildElement(QStringLiteral("title")).text()); // create phoneme groups for (QDomElement groupNode = root.firstChildElement(QStringLiteral("phonemeGroups")).firstChildElement(); !groupNode.isNull(); groupNode = groupNode.nextSiblingElement()) { for (QDomElement phonemeNode = groupNode.firstChildElement(QStringLiteral("phonemes")).firstChildElement(); !phonemeNode.isNull(); phonemeNode = phonemeNode.nextSiblingElement()) { QString id = phonemeNode.firstChildElement(QStringLiteral("id")).text(); qDebug() << "ID: " << id; QVERIFY2(!idList.contains(id),"Phoneme ID used more than once in the tested file"); idList.append(id); } } } } QTEST_GUILESS_MAIN(TestLanguageFiles) diff --git a/autotests/resourcerepository/test_resourcerepository.cpp b/autotests/resourcerepository/test_resourcerepository.cpp index b872a24..45c29d7 100644 --- a/autotests/resourcerepository/test_resourcerepository.cpp +++ b/autotests/resourcerepository/test_resourcerepository.cpp @@ -1,84 +1,84 @@ /* * Copyright 2019 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 "test_resourcerepository.h" #include #include #include #include "src/core/resourcerepository.h" #include "src/core/language.h" void TestResourceRepository::init() { // check that test data is deployed at the expected location QVERIFY(QFile::exists("data/courses/de/de.xml")); QVERIFY(QFile::exists("data/courses/fr/fr.xml")); } void TestResourceRepository::cleanup() { // TODO cleanup after test run } void TestResourceRepository::createRepository() { ResourceRepository repository(QUrl::fromLocalFile("data/courses/")); QCOMPARE(repository.storageLocation(), "data/courses/"); repository.reloadCourses(); QCOMPARE(repository.courses().count(), 2); } void TestResourceRepository::iResourceRepositoryCompatability() { ResourceRepository repository(QUrl::fromLocalFile("data/courses/")); IResourceRepository *interface = &repository; QCOMPARE(interface->storageLocation(), "data/courses/"); QVERIFY(interface->languages().count() > 0); QCOMPARE(interface->courses().count(), 0); // test adding QSignalSpy spyAboutToBeAdded(dynamic_cast(interface), SIGNAL(courseAboutToBeAdded(ICourse*, int))); QSignalSpy spyAdded(dynamic_cast(interface), SIGNAL(courseAdded())); QCOMPARE(spyAboutToBeAdded.count(), 0); QCOMPARE(spyAdded.count(), 0); repository.reloadCourses(); QCOMPARE(interface->courses().count(), 2); QCOMPARE(spyAboutToBeAdded.count(), 2); QCOMPARE(spyAdded.count(), 2); // test removal // note: repository does not provide removal of courses, yet // test access of courses grouped by language auto languages = interface->languages(); - Language *german = nullptr; + std::shared_ptr german; for (auto language : interface->languages()) { if (language->id() == "de") { german = language; break; } } QVERIFY(german != nullptr); // ensure that German language was found - QCOMPARE(interface->courses(german).count(), 1); // there is exactly one German course + QCOMPARE(interface->courses(german->id()).count(), 1); // there is exactly one German course QCOMPARE(interface->courses(nullptr).count(), 2); // all courses in total are 2 } QTEST_GUILESS_MAIN(TestResourceRepository) diff --git a/autotests/skeletonresource/resourcerepositorystub.h b/autotests/skeletonresource/resourcerepositorystub.h index 3cf2a10..7ce190a 100644 --- a/autotests/skeletonresource/resourcerepositorystub.h +++ b/autotests/skeletonresource/resourcerepositorystub.h @@ -1,81 +1,81 @@ /* * Copyright 2019 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 RESOURCEREPOSITORYSTUB_H #define RESOURCEREPOSITORYSTUB_H #include "core/iresourcerepository.h" #include #include class Language; class ICourse; /** * @brief The ResourceRepositoryStub that only provides languages and a storage location, but nothing else */ class ResourceRepositoryStub : public IResourceRepository { Q_OBJECT public: - ResourceRepositoryStub(QVector languages) + ResourceRepositoryStub(QVector> languages) + : m_languages(languages) { - m_languages = languages; } ~ResourceRepositoryStub() override; QString storageLocation() const override { return m_storageLocation; } - QVector courses() const override + QVector> courses() const override { - return QVector(); // do not return any courses: stub shall only provide languages + return QVector>(); // do not return any courses: stub shall only provide languages } - QVector courses(Language *language) const override + QVector> courses(const QString &languageId) const override { - Q_UNUSED(language); - return QVector(); // do not return any courses: stub shall only provide languages + Q_UNUSED(languageId); + return QVector>(); // do not return any courses: stub shall only provide languages } void reloadCourses() override { ; // do nothing, stub shall only provide languages } - QVector languages() const override + QVector> languages() const override { return m_languages; } Q_SIGNALS: void courseAboutToBeAdded(ICourse*,int) override; void courseAdded() override; void courseAboutToBeRemoved(int) override; void courseRemoved() override; private: QString m_storageLocation; - QVector m_languages; + QVector> m_languages; }; #endif diff --git a/autotests/skeletonresource/test_skeletonresource.cpp b/autotests/skeletonresource/test_skeletonresource.cpp index 4095bd3..1e624df 100644 --- a/autotests/skeletonresource/test_skeletonresource.cpp +++ b/autotests/skeletonresource/test_skeletonresource.cpp @@ -1,212 +1,212 @@ /* * Copyright 2019 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 "test_skeletonresource.h" #include "resourcerepositorystub.h" #include "core/language.h" #include "core/unit.h" #include "core/phrase.h" #include "core/resources/languageresource.h" #include "core/resources/skeletonresource.h" #include #include #include #include #include #include #include #include #include TestSkeletonResource::TestSkeletonResource() { } void TestSkeletonResource::init() { } void TestSkeletonResource::cleanup() { } void TestSkeletonResource::schemeValidationTest() { QUrl skeletonFile = QUrl::fromLocalFile(":/artikulate/schemes/skeleton.xsd"); QXmlSchema skeletonScheme; QVERIFY(skeletonScheme.load(skeletonFile)); QVERIFY(skeletonScheme.isValid()); } void TestSkeletonResource::loadSkeletonResource() { - Language language; - language.setId("de"); - ResourceRepositoryStub repository({&language}); + std::shared_ptr language(new Language); + language->setId("de"); + ResourceRepositoryStub repository({language}); const QString courseDirectory = "data/contributorrepository/skeletons/"; const QString courseFile = courseDirectory + "skeleton.xml"; SkeletonResource course(QUrl::fromLocalFile(courseFile), &repository); QCOMPARE(course.file().toLocalFile(), courseFile); QCOMPARE(course.id(), "skeleton-testdata"); QCOMPARE(course.foreignId(), "skeleton-testdata"); // always same as ID QCOMPARE(course.title(), "Artikulate Test Course Title"); QCOMPARE(course.description(), "Artikulate Test Course Description"); QVERIFY(course.language() == nullptr); // a skeleton must not have a language QCOMPARE(course.unitList().count(), 2); const auto unit = course.unitList().first(); QVERIFY(unit != nullptr); QCOMPARE(unit->id(), "{11111111-b885-4833-97ff-27cb1ca2f543}"); QCOMPARE(unit->title(), QStringLiteral("Numbers")); QCOMPARE(unit->phraseList().count(), 2); // note: this test takes the silent assumption that phrases are added to the list in same // order as they are defined in the file. This assumption should be made explicit or dropped const auto firstPhrase = unit->phraseList().first(); QVERIFY(firstPhrase != nullptr); QCOMPARE(firstPhrase->id(), "{22222222-9234-4da5-a6fe-dbd5104f57d5}"); QCOMPARE(firstPhrase->text(), "0"); QCOMPARE(firstPhrase->type(), Phrase::Type::Word); const auto secondPhrase = unit->phraseList().at(1); QVERIFY(secondPhrase != nullptr); QCOMPARE(secondPhrase->id(), "{333333333-b4a9-4264-9a26-75a55aa5d302}"); QCOMPARE(secondPhrase->text(), "1"); QCOMPARE(secondPhrase->type(), Phrase::Type::Word); } void TestSkeletonResource::unitAddAndRemoveHandling() { // boilerplate - Language language; - language.setId("de"); - ResourceRepositoryStub repository({&language}); + std::shared_ptr language(new Language); + language->setId("de"); + ResourceRepositoryStub repository({language}); const QString courseDirectory = "data/contributorrepository/skeletons/"; const QString courseFile = courseDirectory + "skeleton.xml"; SkeletonResource course(QUrl::fromLocalFile(courseFile), &repository); // begin of test - Unit unit; - unit.setId("testunit"); + std::unique_ptr unit(new Unit); + unit->setId("testunit"); const int initialUnitNumber = course.unitList().count(); QCOMPARE(initialUnitNumber, 2); QSignalSpy spyAboutToBeAdded(&course, SIGNAL(unitAboutToBeAdded(Unit*, int))); QSignalSpy spyAdded(&course, SIGNAL(unitAdded())); QCOMPARE(spyAboutToBeAdded.count(), 0); QCOMPARE(spyAdded.count(), 0); - course.addUnit(&unit); + course.addUnit(std::move(unit)); QCOMPARE(course.unitList().count(), initialUnitNumber + 1); QCOMPARE(spyAboutToBeAdded.count(), 1); QCOMPARE(spyAdded.count(), 1); } void TestSkeletonResource::coursePropertyChanges() { // boilerplate - Language language; - language.setId("de"); - ResourceRepositoryStub repository({&language}); + std::shared_ptr language(new Language); + language->setId("de"); + ResourceRepositoryStub repository({language}); const QString courseDirectory = "data/contributorrepository/skeletons/"; const QString courseFile = courseDirectory + "skeleton.xml"; SkeletonResource course(QUrl::fromLocalFile(courseFile), &repository); // id { const QString value = "newId"; QSignalSpy spy(&course, SIGNAL(idChanged())); QCOMPARE(spy.count(), 0); course.setId(value); QCOMPARE(course.id(), value); QCOMPARE(spy.count(), 1); } // title { const QString value = "newTitle"; QSignalSpy spy(&course, SIGNAL(titleChanged())); QCOMPARE(spy.count(), 0); course.setTitle(value); QCOMPARE(course.title(), value); QCOMPARE(spy.count(), 1); } // description { const QString value = "newDescription"; QSignalSpy spy(&course, SIGNAL(descriptionChanged())); QCOMPARE(spy.count(), 0); course.setDescription(value); QCOMPARE(course.description(), value); QCOMPARE(spy.count(), 1); } } void TestSkeletonResource::fileLoadSaveCompleteness() { // boilerplate - Language language; - language.setId("de"); - ResourceRepositoryStub repository({&language}); + std::shared_ptr language(new Language); + language->setId("de"); + ResourceRepositoryStub repository({language}); const QString courseDirectory = "data/contributorrepository/skeletons/"; const QString courseFile = courseDirectory + "skeleton.xml"; SkeletonResource course(QUrl::fromLocalFile(courseFile), &repository); QTemporaryFile outputFile; outputFile.open(); course.exportCourse(QUrl::fromLocalFile(outputFile.fileName())); // note: this only works, since the resource manager not checks uniqueness of course ids! SkeletonResource loadedCourse(QUrl::fromLocalFile(outputFile.fileName()), &repository); // test that we actually call the different files QVERIFY(course.file().toLocalFile() != loadedCourse.file().toLocalFile()); QCOMPARE(loadedCourse.id(), course.id()); QCOMPARE(loadedCourse.foreignId(), course.foreignId()); QCOMPARE(loadedCourse.title(), course.title()); QCOMPARE(loadedCourse.description(), course.description()); QCOMPARE(loadedCourse.language(), course.language()); QCOMPARE(loadedCourse.unitList().count(), course.unitList().count()); Unit *testUnit = course.unitList().constFirst(); Unit *compareUnit = loadedCourse.unitList().constFirst(); QCOMPARE(testUnit->id(), compareUnit->id()); QCOMPARE(testUnit->foreignId(), compareUnit->foreignId()); QCOMPARE(testUnit->title(), compareUnit->title()); QCOMPARE(testUnit->phraseList().count(), compareUnit->phraseList().count()); Phrase *testPhrase = testUnit->phraseList().constFirst(); Phrase *comparePhrase = new Phrase(this); // note that this actually means that we DO NOT respect phrase orders by list order for (Phrase *phrase : compareUnit->phraseList()) { if (testPhrase->id() == phrase->id()) { comparePhrase = phrase; break; } } QVERIFY(testPhrase->id() == comparePhrase->id()); QVERIFY(testPhrase->foreignId() == comparePhrase->foreignId()); QVERIFY(testPhrase->text() == comparePhrase->text()); QVERIFY(testPhrase->type() == comparePhrase->type()); QVERIFY(testPhrase->sound().fileName() == comparePhrase->sound().fileName()); QVERIFY(testPhrase->phonemes().count() == comparePhrase->phonemes().count()); } QTEST_GUILESS_MAIN(TestSkeletonResource) diff --git a/autotests/trainingsession/test_trainingsession.cpp b/autotests/trainingsession/test_trainingsession.cpp index 9925fed..c4999ec 100644 --- a/autotests/trainingsession/test_trainingsession.cpp +++ b/autotests/trainingsession/test_trainingsession.cpp @@ -1,257 +1,257 @@ /* * Copyright 2019 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 "test_trainingsession.h" #include "src/core/trainingsession.h" #include "src/core/icourse.h" #include "src/core/language.h" #include "src/core/unit.h" #include "src/core/trainingaction.h" #include "liblearnerprofile/src/profilemanager.h" #include #include // assumption: during a training session the units and phrases of a course do not change // any change of such a course shall result in a reload of a training session class CourseStub : public ICourse { public: CourseStub(Language *language, QVector units) : m_language(language) , m_units(units) { } ~CourseStub() override; QString id() const override { return "courseid"; } QString foreignId() const override { return "foreigncourseid"; } QString title() const override { return "title"; } QString i18nTitle() const override { return "i18n title"; } QString description() const override { return "description of the course"; } - Language * language() const override + std::shared_ptr language() const override { return m_language; } QList unitList() override { return m_units.toList(); } QUrl file() const override { return QUrl(); } private: - Language *m_language{nullptr}; + std::shared_ptr m_language; QVector m_units; }; // define one virtual method out of line to pin CourseStub to this translation unit CourseStub::~CourseStub() = default; void TestTrainingSession::init() { // TODO initialization of test case } void TestTrainingSession::cleanup() { // TODO cleanup after test run } void TestTrainingSession::createTrainingSessionWithoutUnits() { Language language; CourseStub course(&language, QVector()); LearnerProfile::ProfileManager manager; TrainingSession session(&manager); session.setCourse(&course); QVERIFY(&course == session.course()); } void TestTrainingSession::createTrainingSessionWithEmptySounds() { Language language; Unit unitA; Unit unitB; Phrase *phraseA1 = new Phrase; Phrase *phraseA2 = new Phrase; Phrase *phraseB1 = new Phrase; Phrase *phraseB2 = new Phrase; // note: phrases without soundfiles are skipped in session generation phraseA1->setId("A1"); phraseA2->setId("A2"); phraseB1->setId("B1"); phraseB2->setId("B2"); phraseA1->setSound(QUrl::fromLocalFile("/tmp/a1.ogg")); unitA.addPhrase(phraseA1); unitA.addPhrase(phraseA2); unitB.addPhrase(phraseB1); unitB.addPhrase(phraseB2); CourseStub course(&language, QVector({&unitA, &unitB})); LearnerProfile::ProfileManager manager; TrainingSession session(&manager); session.setCourse(&course); // test number of actions auto actions = session.trainingActions(); QCOMPARE(actions.count(), 1); QCOMPARE(actions.at(0)->actions().count(), 1); } void TestTrainingSession::createTrainingSessionWithEmptyUnits() { Language language; Unit firstUnit; Unit secondUnit; CourseStub course(&language, QVector({&firstUnit, &secondUnit})); LearnerProfile::ProfileManager manager; TrainingSession session(&manager); session.setCourse(&course); QVERIFY(&course == session.course()); } void TestTrainingSession::createTrainingSessionWithUnitsAndPhrases() { Language language; Unit unit; Phrase *firstPhrase = new Phrase; Phrase *secondPhrase = new Phrase; unit.addPhrase(firstPhrase); unit.addPhrase(secondPhrase); CourseStub course(&language, QVector({&unit})); LearnerProfile::ProfileManager manager; TrainingSession session(&manager); session.setCourse(&course); QVERIFY(&course == session.course()); } void TestTrainingSession::iterateCourse() { Language language; Unit unitA; Unit unitB; Phrase *phraseA1 = new Phrase; Phrase *phraseA2 = new Phrase; Phrase *phraseB1 = new Phrase; Phrase *phraseB2 = new Phrase; // note: phrases without soundfiles are skipped in session generation phraseA1->setId("A1"); phraseA2->setId("A2"); phraseB1->setId("B1"); phraseB2->setId("B2"); phraseA1->setSound(QUrl::fromLocalFile("/tmp/a1.ogg")); phraseA2->setSound(QUrl::fromLocalFile("/tmp/a1.ogg")); phraseB1->setSound(QUrl::fromLocalFile("/tmp/b1.ogg")); phraseB2->setSound(QUrl::fromLocalFile("/tmp/b2.ogg")); unitA.addPhrase(phraseA1); unitA.addPhrase(phraseA2); unitB.addPhrase(phraseB1); unitB.addPhrase(phraseB2); CourseStub course(&language, QVector({&unitA, &unitB})); LearnerProfile::ProfileManager manager; TrainingSession session(&manager); session.setCourse(&course); // session assumed to initialize with first units's first phrase QCOMPARE(session.activeUnit(), &unitA); QCOMPARE(session.activePhrase(), phraseA1); QVERIFY(&course == session.course()); // test direct unit setters session.setUnit(&unitA); QCOMPARE(session.activeUnit(), &unitA); session.setUnit(&unitB); QCOMPARE(session.activeUnit(), &unitB); // test direct phrase setters session.setPhrase(phraseA1); QCOMPARE(session.activePhrase(), phraseA1); QCOMPARE(session.activeUnit(), &unitA); session.setPhrase(phraseB1); QCOMPARE(session.activePhrase(), phraseB1); QCOMPARE(session.activeUnit(), &unitB); // test number of actions auto actions = session.trainingActions(); QCOMPARE(actions.count(), 2); QCOMPARE(actions.at(0)->actions().count(), 2); QCOMPARE(actions.at(1)->actions().count(), 2); // test phrase iterators: accept iterator session.setPhrase(phraseA1); QCOMPARE(session.activeUnit(), &unitA); QCOMPARE(session.activePhrase(), phraseA1); QVERIFY(session.hasNext()); session.accept(); QCOMPARE(session.activeUnit(), &unitA); QCOMPARE(session.activePhrase(), phraseA2); session.accept(); QCOMPARE(session.activePhrase(), phraseB1); session.accept(); QCOMPARE(session.activePhrase(), phraseB2); QVERIFY(!session.hasNext()); // test phrase iterators: skip iterator session.setPhrase(phraseA1); QCOMPARE(session.activeUnit(), &unitA); QCOMPARE(session.activePhrase(), phraseA1); QVERIFY(!session.hasPrevious()); QVERIFY(session.hasNext()); session.skip(); QCOMPARE(session.activeUnit(), &unitA); QCOMPARE(session.activePhrase(), phraseA2); session.skip(); QCOMPARE(session.activePhrase(), phraseB1); session.skip(); QCOMPARE(session.activePhrase(), phraseB2); QVERIFY(session.hasPrevious()); QVERIFY(!session.hasNext()); // test completed signal QSignalSpy spy(&session, SIGNAL(completed())); session.setPhrase(phraseB1); session.accept(); QCOMPARE(spy.count(), 0); session.accept(); QCOMPARE(spy.count(), 1); } QTEST_GUILESS_MAIN(TestTrainingSession) diff --git a/src/core/contributorrepository.cpp b/src/core/contributorrepository.cpp index 8b6788f..c57aae2 100644 --- a/src/core/contributorrepository.cpp +++ b/src/core/contributorrepository.cpp @@ -1,489 +1,486 @@ /* * Copyright 2013-2019 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 "contributorrepository.h" #include "artikulate_debug.h" #include "language.h" #include "unit.h" #include "phrase.h" #include "phoneme.h" #include "phonemegroup.h" #include "resources/languageresource.h" #include "resources/editablecourseresource.h" #include "resources/skeletonresource.h" #include "liblearnerprofile/src/profilemanager.h" #include "liblearnerprofile/src/learninggoal.h" #include #include #include #include #include #include ContributorRepository::ContributorRepository() : IEditableRepository() { loadLanguageResources(); } ContributorRepository::~ContributorRepository() { for (auto skeleton : m_skeletonResources) { skeleton->deleteLater(); } m_skeletonResources.clear(); for (auto language : m_languageResources) { language->deleteLater(); } } void ContributorRepository::loadLanguageResources() { // load language resources // all other resources are only loaded on demand QDir dir(":/artikulate/languages/"); dir.setFilter(QDir::Files | QDir::NoSymLinks); QFileInfoList list = dir.entryInfoList(); for (int i = 0; i < list.size(); ++i) { QFileInfo fileInfo = list.at(i); if (fileInfo.completeSuffix() != QLatin1String("xml")) { continue; } addLanguage(QUrl::fromLocalFile(fileInfo.absoluteFilePath())); } } void ContributorRepository::sync() { // QMap< QString, QList< CourseResource* > >::iterator iter; // for (iter = m_courseResources.begin(); iter != m_courseResources.end(); ++iter) { // foreach (auto const &courseRes, iter.value()) { // courseRes->sync(); // } // } // foreach (auto const &courseRes, m_skeletonResources) { // courseRes->sync(); // } } bool ContributorRepository::modified() const { for (auto iter = m_courses.constBegin(); iter != m_courses.constEnd(); ++iter) { - for (auto *course : iter.value()) { + for (auto course : iter.value()) { if (course->isModified()) { return true; } } } - foreach (auto const &courseRes, m_skeletonResources) { + for (auto const &courseRes : m_skeletonResources) { if (courseRes->isModified()) { return true; } } return false; } void ContributorRepository::addLanguage(const QUrl &languageFile) { if (m_loadedResources.contains(languageFile.toLocalFile())) { return; } - LanguageResource *resource = new LanguageResource(languageFile); + std::shared_ptr resource(new LanguageResource(languageFile)); - emit languageResourceAboutToBeAdded(resource, m_languageResources.count()); + emit languageResourceAboutToBeAdded(resource.get(), m_languageResources.count()); m_languageResources.append(resource); m_loadedResources.append(languageFile.toLocalFile()); - m_courses.insert(resource->identifier(), QList()); + m_courses.insert(resource->identifier(), QVector>()); emit languageResourceAdded(); } QString ContributorRepository::storageLocation() const { return m_storageLocation; } void ContributorRepository::setStorageLocation(const QString &path) { m_storageLocation = path; } -QList< LanguageResource* > ContributorRepository::languageResources() const +QVector> ContributorRepository::languages() const { - return m_languageResources; -} - -QVector ContributorRepository::languages() const -{ - QVector languages; + QVector> languages; for (auto resourse : m_languageResources) { languages.append(resourse->language()); } return languages; } -Language * ContributorRepository::language(int index) const +std::shared_ptr ContributorRepository::language(int index) const { Q_ASSERT(index >= 0 && index < m_languageResources.count()); return m_languageResources.at(index)->language(); } Language * ContributorRepository::language(LearnerProfile::LearningGoal *learningGoal) const { if (!learningGoal) { return nullptr; } if (learningGoal->category() != LearnerProfile::LearningGoal::Language) { qCritical() << "Cannot translate non-language learning goal to language"; return nullptr; } - foreach (LanguageResource *resource, m_languageResources) { + for (auto resource : m_languageResources) { if (resource->identifier() == learningGoal->identifier()) { - return resource->language(); + return resource->language().get(); } } qCritical() << "No language registered with identifier " << learningGoal->identifier() << ": aborting"; return nullptr; } -QList ContributorRepository::courseResources(Language *language) +QVector> ContributorRepository::courseResources(std::shared_ptr language) { if (!language) { - QList courses; + QVector> courses; for (auto iter = m_courses.constBegin(); iter != m_courses.constEnd(); ++iter) { courses.append(iter.value()); } return courses; } // return empty list if no course available for language if (!m_courses.contains(language->id())) { - return QList< EditableCourseResource* >(); + return QVector>(); } return m_courses[language->id()]; } -QVector ContributorRepository::courses() const +QVector> ContributorRepository::courses() const { - QVector courses; + QVector> courses; for (const auto &courseList : m_courses) { for (const auto &course : courseList) { courses.append(course); } } return courses; } -QVector ContributorRepository::editableCourses() const +QVector> ContributorRepository::editableCourses() const { - QVector courses; + QVector> courses; for (const auto &courseList : m_courses) { for (const auto &course : courseList) { courses.append(course); } } return courses; } -QVector ContributorRepository::courses(Language *language) const +QVector> ContributorRepository::courses(const QString &languageId) const { - if (language == nullptr) { + if (languageId.isEmpty()) { return courses(); } - QVector courses; - if (m_courses.contains(language->id())) { - for (const auto &course : m_courses[language->id()]) { + QVector> courses; + if (m_courses.contains(languageId)) { + for (const auto &course : m_courses[languageId]) { courses.append(course); } } return courses; } -IEditableCourse * ContributorRepository::editableCourse(Language *language, int index) const +std::shared_ptr ContributorRepository::editableCourse(std::shared_ptr language, int index) const { Q_ASSERT(m_courses.contains(language->id())); Q_ASSERT(index >= 0 && index < m_courses[language->id()].count()); return m_courses[language->id()].at(index); } -void ContributorRepository::reloadCourseOrSkeleton(ICourse *courseOrSkeleton) +void ContributorRepository::reloadCourseOrSkeleton(std::shared_ptr courseOrSkeleton) { if (!courseOrSkeleton) { qCritical() << "Cannot reload non-existing course"; return; } if (!courseOrSkeleton->file().isValid()) { qCritical() << "Cannot reload temporary file, aborting."; return; } // figure out if this is a course or a skeleton if (courseOrSkeleton->language()) { // only course files have a language //TODO better add a check if this is contained in the course list // to catch possible errors QUrl file = courseOrSkeleton->file(); m_loadedResources.removeOne(courseOrSkeleton->file().toLocalFile()); removeCourse(courseOrSkeleton); addCourse(file); } else { - for (SkeletonResource *resource : m_skeletonResources) { + for (auto resource : m_skeletonResources) { if (resource->id() == courseOrSkeleton->id()) { // TODO no reload available return; } } } } void ContributorRepository::reloadCourses() { // register skeleton resources QDir skeletonDirectory = QDir(storageLocation()); skeletonDirectory.setFilter(QDir::Files | QDir::Hidden); if (!skeletonDirectory.cd(QStringLiteral("skeletons"))) { qCritical() << "There is no subdirectory \"skeletons\" in directory " << skeletonDirectory.path() << " cannot load skeletons."; } else { // read skeletons QFileInfoList list = skeletonDirectory.entryInfoList(); for (int i = 0; i < list.size(); ++i) { QFileInfo fileInfo = list.at(i); addSkeleton(QUrl::fromLocalFile(fileInfo.absoluteFilePath())); } } // register contributor course files QDir courseDirectory(storageLocation()); if (!courseDirectory.cd(QStringLiteral("courses"))) { qCritical() << "There is no subdirectory \"courses\" in directory " << courseDirectory.path() << " cannot load courses."; } else { // find courses courseDirectory.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); QFileInfoList courseDirList = courseDirectory.entryInfoList(); // traverse all course directories for (const QFileInfo &info : courseDirList) { QDir courseDir = QDir(info.absoluteFilePath()); courseDir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); QFileInfoList courseLangDirList = courseDir.entryInfoList(); // traverse all language directories for each course for (const QFileInfo &langInfo : courseLangDirList) { QDir courseLangDir = QDir(langInfo.absoluteFilePath()); courseLangDir.setFilter(QDir::Files); QStringList nameFilters; nameFilters.append(QStringLiteral("*.xml")); QFileInfoList courses = courseLangDir.entryInfoList(nameFilters); // find and add course files for (const QFileInfo &courseInfo : courses) { addCourse(QUrl::fromLocalFile(courseInfo.filePath())); } } } } //TODO this signal should only be emitted when repository was added/removed // yet the call to this method is very seldom and emitting it too often is not that harmful emit repositoryChanged(); } -void ContributorRepository::updateCourseFromSkeleton(IEditableCourse *course) +void ContributorRepository::updateCourseFromSkeleton(std::shared_ptr course) { //TODO implement status information that are shown at mainwindow if (course->foreignId().isEmpty()) { qCritical() << "No skeleton ID specified, aborting update."; return; } - ICourse *skeleton = nullptr; + std::shared_ptr skeleton; for (const auto &iter : m_skeletonResources) { if (iter->id() == course->foreignId()) { skeleton = iter; break; } } if (!skeleton) { qCritical() << "Could not find skeleton with id " << course->foreignId() << ", aborting update."; return; } - // update now - for (Unit *unitSkeleton : skeleton->unitList()) { - // import unit if not exists - Unit *currentUnit = nullptr; - bool found = false; - for (Unit *unit : course->unitList()) { - if (unit->foreignId() == unitSkeleton->id()) { - found = true; - currentUnit = unit; - break; - } - } - if (found == false) { - currentUnit = new Unit(course); - currentUnit->setId(QUuid::createUuid().toString()); - currentUnit->setTitle(unitSkeleton->title()); - currentUnit->setForeignId(unitSkeleton->id()); - currentUnit->setCourse(course); - course->addUnit(currentUnit); - } +// FIXME memory handling logic is broken +// // update now +// for (Unit *unitSkeleton : skeleton->unitList()) { +// // import unit if not exists +// std::unique_ptr currentUnit(new Unit); +// bool found = false; +// for (Unit *unit : course->unitList()) { +// if (unit->foreignId() == unitSkeleton->id()) { +// found = true; +// currentUnit = unit; +// break; +// } +// } +// if (found == false) { +// currentUnit = new Unit(course); +// currentUnit->setId(QUuid::createUuid().toString()); +// currentUnit->setTitle(unitSkeleton->title()); +// currentUnit->setForeignId(unitSkeleton->id()); +// currentUnit->setCourse(course); +// course->addUnit(std::move(currentUnit)); +// } - // update phrases - for (Phrase *phraseSkeleton : unitSkeleton->phraseList()) { - bool found = false; - for (Phrase *phrase : currentUnit->phraseList()) { - if (phrase->foreignId() == phraseSkeleton->id()) { - if (phrase->i18nText() != phraseSkeleton->text()) { - phrase->setEditState(Phrase::Unknown); - phrase->seti18nText(phraseSkeleton->text()); - } - found = true; - break; - } - } - if (found == false) { - Phrase *newPhrase = new Phrase(course); - newPhrase->setForeignId(phraseSkeleton->id()); - newPhrase->setId(QUuid::createUuid().toString()); - newPhrase->setText(phraseSkeleton->text()); - newPhrase->seti18nText(phraseSkeleton->text()); - newPhrase->setType(phraseSkeleton->type()); - newPhrase->setUnit(currentUnit); - currentUnit->addPhrase(newPhrase); - } - } - } +// // update phrases +// for (Phrase *phraseSkeleton : unitSkeleton->phraseList()) { +// bool found = false; +// for (Phrase *phrase : currentUnit->phraseList()) { +// if (phrase->foreignId() == phraseSkeleton->id()) { +// if (phrase->i18nText() != phraseSkeleton->text()) { +// phrase->setEditState(Phrase::Unknown); +// phrase->seti18nText(phraseSkeleton->text()); +// } +// found = true; +// break; +// } +// } +// if (found == false) { +// Phrase *newPhrase = new Phrase(course); +// newPhrase->setForeignId(phraseSkeleton->id()); +// newPhrase->setId(QUuid::createUuid().toString()); +// newPhrase->setText(phraseSkeleton->text()); +// newPhrase->seti18nText(phraseSkeleton->text()); +// newPhrase->setType(phraseSkeleton->type()); +// newPhrase->setUnit(currentUnit.get()); +// currentUnit->addPhrase(newPhrase); +// } +// } +// } // FIXME deassociate removed phrases qCDebug(ARTIKULATE_LOG) << "Update performed!"; } -EditableCourseResource * ContributorRepository::addCourse(const QUrl &courseFile) +std::shared_ptr ContributorRepository::addCourse(const QUrl &courseFile) { - EditableCourseResource *resource = new EditableCourseResource(courseFile, this); + std::unique_ptr resource(new EditableCourseResource(courseFile, this)); if (resource->language() == nullptr) { - delete resource; qCritical() << "Could not load course, language unknown:" << courseFile.toLocalFile(); - return nullptr; + return std::shared_ptr(); } // skip already loaded resources if (m_loadedResources.contains(courseFile.toLocalFile())) { - delete resource; - return nullptr; + //TODO return the already loaded course + return std::shared_ptr(); } m_loadedResources.append(courseFile.toLocalFile()); - addCourseResource(resource); + auto course = addCourseResource(std::move(resource)); emit languageCoursesChanged(); - return resource; + return course; } -void ContributorRepository::addCourseResource(EditableCourseResource *resource) +std::shared_ptr ContributorRepository::addCourseResource(std::unique_ptr resource) { - Q_ASSERT(m_courses.contains(resource->language()->id())); - - if (!m_courses.contains(resource->language()->id())) { - m_courses.insert(resource->language()->id(), QList()); + const QString languageId = resource->language()->id(); + Q_ASSERT(!languageId.isEmpty()); + if (!m_courses.contains(languageId)) { + m_courses.insert(languageId, QVector>()); } - emit courseAboutToBeAdded(resource, m_courses[resource->language()->id()].count()); - m_courses[resource->language()->id()].append(resource); + emit courseAboutToBeAdded(resource.get(), m_courses[resource->language()->id()].count()); + m_courses[languageId].append(std::move(resource)); emit courseAdded(); + Q_ASSERT(m_courses[languageId].size() > 0); + return m_courses[languageId].last(); } -void ContributorRepository::removeCourse(ICourse *course) +void ContributorRepository::removeCourse(std::shared_ptr course) { for (int index = 0; index < m_courses[course->language()->id()].length(); ++index) { if (m_courses[course->language()->id()].at(index) == course) { emit courseAboutToBeRemoved(index); m_courses[course->language()->id()].removeAt(index); emit courseRemoved(); course->deleteLater(); return; } } } -EditableCourseResource * ContributorRepository::createCourse(Language *language, SkeletonResource *skeleton) +IEditableCourse * ContributorRepository::createCourse(std::shared_ptr language, std::shared_ptr skeleton) { // set path QString path = QStringLiteral("%1/%2/%3/%4/%4.xml") .arg(storageLocation(), QStringLiteral("courses"), skeleton->id(), language->id()); - EditableCourseResource * course = new EditableCourseResource(QUrl::fromLocalFile(path), this); + std::unique_ptr course(new EditableCourseResource(QUrl::fromLocalFile(path), this)); Q_ASSERT(course); course->setId(QUuid::createUuid().toString()); course->setTitle(skeleton->title()); course->setDescription(skeleton->description()); course->setFile(QUrl::fromLocalFile(path)); course->setLanguage(language); // set skeleton course->setForeignId(skeleton->id()); - addCourseResource(course); - - return course; + return addCourseResource(std::move(course)).get(); } -void ContributorRepository::addSkeleton(const QUrl &file) +std::shared_ptr ContributorRepository::addSkeleton(const QUrl &file) { - SkeletonResource *resource = new SkeletonResource(file, this); - addSkeletonResource(resource); + std::unique_ptr resource(new SkeletonResource(file, this)); + return addSkeletonResource(std::move(resource)); } -void ContributorRepository::addSkeletonResource(SkeletonResource *resource) +std::shared_ptr ContributorRepository::addSkeletonResource(std::unique_ptr resource) { // skip already loaded resources if (m_loadedResources.contains(resource->file().toLocalFile())) { - return; + // TODO return existing skeleton + return std::shared_ptr(); } m_loadedResources.append(resource->file().toLocalFile()); - emit skeletonAboutToBeAdded(resource, m_skeletonResources.count()); - m_skeletonResources.append(resource); + emit skeletonAboutToBeAdded(resource.get(), m_skeletonResources.count()); + m_skeletonResources.append(std::move(resource)); emit skeletonAdded(); + return m_skeletonResources.last(); } void ContributorRepository::removeSkeleton(SkeletonResource *skeleton) { for (int index = 0; index < m_skeletonResources.length(); ++index) { if (m_skeletonResources.at(index)->id() == skeleton->id()) { emit skeletonAboutToBeRemoved(index, index); m_skeletonResources.removeAt(index); emit skeletonRemoved(); skeleton->deleteLater(); return; } } } -QVector ContributorRepository::skeletons() const +QVector> ContributorRepository::skeletons() const { - QVector skeletonList; + QVector> skeletonList; for (const auto &skeleton : m_skeletonResources) { skeletonList.append(skeleton); } return skeletonList; } diff --git a/src/core/contributorrepository.h b/src/core/contributorrepository.h index 43c55e0..3b891e9 100644 --- a/src/core/contributorrepository.h +++ b/src/core/contributorrepository.h @@ -1,211 +1,207 @@ /* * Copyright 2013-2019 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 CONTRIBUTORREPOSITORY_H #define CONTRIBUTORREPOSITORY_H #include "artikulatecore_export.h" #include "ieditablerepository.h" +#include #include #include #include #include #include #include "liblearnerprofile/src/learninggoal.h" class SkeletonResource; class EditableCourseResource; class LanguageResource; class Language; class ICourse; class QUrl; /** * @class ContributorRepository * This class handles the resources of a contributor. */ class ARTIKULATECORE_EXPORT ContributorRepository : public IEditableRepository { Q_OBJECT Q_INTERFACES(IResourceRepository) Q_INTERFACES(IEditableRepository) Q_PROPERTY(QString repositoryUrl READ storageLocation NOTIFY repositoryChanged) public: explicit ContributorRepository(); ~ContributorRepository() override; /** * save all changes to course resources */ void sync(); /** * \return \c true if any course or skeleton is modified, otherwise \c false */ bool modified() const; /** * \return path to working repository, if one is set */ QString storageLocation() const override; /** * Set path to central storage location * \param path the path to the storage location directory */ void setStorageLocation(const QString &path); - /** - * \return list of all available language specifications - */ - Q_DECL_DEPRECATED QList languageResources() const; - - QVector languages() const override; + QVector> languages() const override; /** * \return language by \p index */ - Q_INVOKABLE Language * language(int index) const; + std::shared_ptr language(int index) const; /** * \return language by \p learningGoal */ Q_INVOKABLE Language * language(LearnerProfile::LearningGoal* learningGoal) const; - QVector courses() const override; - QVector editableCourses() const override; - QVector courses(Language *language) const override; + QVector> courses() const override; + QVector> courses(const QString &languageId) const override; + QVector> editableCourses() const override; /** * \return list of all loaded courses for language \p language */ - QList courseResources(Language *language); + QVector> courseResources(std::shared_ptr language); - Q_INVOKABLE IEditableCourse * editableCourse(Language *language, int index) const override; + std::shared_ptr editableCourse(std::shared_ptr language, int index) const override; /** * Reset the file for this course or skeleton. * * \param course the course to be reloaded */ - Q_INVOKABLE void reloadCourseOrSkeleton(ICourse *course); + void reloadCourseOrSkeleton(std::shared_ptr course); /** * @brief Implementation of course resource reloading */ void reloadCourses() override; - void updateCourseFromSkeleton(IEditableCourse *course) override; + void updateCourseFromSkeleton(std::shared_ptr course) override; /** * Add language to resource manager by parsing the given language specification file. * * \param languageFile is the local XML file containing the language */ void addLanguage(const QUrl &languageFile); /** * Adds course to resource manager by parsing the given course specification file. * * \param courseFile is the local XML file containing the course * \return true if loaded successfully, otherwise false */ - EditableCourseResource * addCourse(const QUrl &courseFile); + std::shared_ptr addCourse(const QUrl &courseFile); /** * Adds course to resource manager. If the course's language is not registered, the language * is registered by this method. * * \param resource the course resource to add to resource manager */ - void addCourseResource(EditableCourseResource *resource); + std::shared_ptr addCourseResource(std::unique_ptr resource); /** * Remove course from resource manager. If the course is modified its changes are NOT * written. For writing changes, the Course::sync() method must be called directly. * * \param course is the course to be removed */ - void removeCourse(ICourse *course); + void removeCourse(std::shared_ptr course); /** * Create new course for \p language and derived from \p skeleton. * * \return created course */ - Q_INVOKABLE EditableCourseResource * createCourse(Language *language, SkeletonResource *skeleton); + Q_INVOKABLE IEditableCourse * createCourse(std::shared_ptr language, std::shared_ptr skeleton); /** * Adds skeleton resource to resource manager * * \param resource the skeleton resource to add to resource manager */ - void addSkeleton(const QUrl &skeletonFile); + std::shared_ptr addSkeleton(const QUrl &skeletonFile); /** * Adds skeleton resource to resource manager * * \param resource the skeleton resource to add to resource manager */ - void addSkeletonResource(SkeletonResource *resource); + std::shared_ptr addSkeletonResource(std::unique_ptr resource); /** * Remove skeleton from resource manager. If the skeleton is modified its changes are NOT * written. For writing changes, the Skeleton::sync() method must be called directly. * * \param skeleton is the skeleton to be removed */ void removeSkeleton(SkeletonResource *skeleton); - QVector skeletons() const override; + QVector> skeletons() const override; Q_SIGNALS: void languageResourceAdded(); void languageResourceAboutToBeAdded(LanguageResource*,int); void languageResourceRemoved(); void languageResourceAboutToBeRemoved(int); void repositoryChanged(); void courseAdded() override; void courseAboutToBeAdded(ICourse*,int) override; void courseAboutToBeRemoved(int) override; void courseRemoved() override; void skeletonAdded(); void skeletonAboutToBeAdded(ICourse*,int); void skeletonRemoved(); void skeletonAboutToBeRemoved(int,int); void languageCoursesChanged(); private: /** * This method loads all language files that are provided in the standard directories * for this application. */ void loadLanguageResources(); QString m_storageLocation; - QList m_languageResources; - QMap > m_courses; //!> (language-id, course-resource) - QVector m_skeletonResources; + QVector> m_languageResources; + QMap> > m_courses; //!> (language-id, course-resource) + QVector> m_skeletonResources; QStringList m_loadedResources; }; #endif diff --git a/src/core/editorsession.cpp b/src/core/editorsession.cpp index 734ae92..f4a3c72 100644 --- a/src/core/editorsession.cpp +++ b/src/core/editorsession.cpp @@ -1,273 +1,278 @@ /* * Copyright 2013-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 "editorsession.h" #include "core/language.h" #include "core/resources/editablecourseresource.h" #include "core/resources/skeletonresource.h" #include "core/resources/languageresource.h" #include "core/unit.h" #include "core/phrase.h" #include "core/contributorrepository.h" #include "artikulate_debug.h" EditorSession::EditorSession(QObject *parent) : QObject(parent) { connect(this, &EditorSession::skeletonChanged, this, &EditorSession::displayedCourseChanged); connect(this, &EditorSession::courseChanged, this, &EditorSession::displayedCourseChanged); connect(this, &EditorSession::editSkeletonChanged, this, &EditorSession::displayedCourseChanged); connect(this, &EditorSession::displayedCourseChanged, this, &EditorSession::updateDisplayedUnit); connect(this, &EditorSession::courseChanged, this, &EditorSession::skeletonModeChanged); } void EditorSession::setRepository(IEditableRepository *repository) { m_repository = repository; } bool EditorSession::skeletonMode() const { return m_skeleton != nullptr; } void EditorSession::setEditSkeleton(bool enabled) { if (m_editSkeleton == enabled) { return; } m_editSkeleton = enabled; emit editSkeletonChanged(); } bool EditorSession::isEditSkeleton() const { return m_editSkeleton; } IEditableCourse * EditorSession::skeleton() const { return m_skeleton; } void EditorSession::setSkeleton(IEditableCourse *skeleton) { if (m_skeleton == skeleton) { return; } m_skeleton = skeleton; IEditableCourse *newCourse{ nullptr }; if (m_skeleton && m_repository) { for (const auto &course : m_repository->editableCourses()) { if (course->foreignId() == m_skeleton->id()) { - newCourse = course; + newCourse = course.get(); break; } } } setCourse(newCourse); emit skeletonChanged(); } Language * EditorSession::language() const { return m_language; } IEditableCourse * EditorSession::course() const { return m_course; } void EditorSession::setCourse(IEditableCourse *course) { if (m_course == course) { return; } m_course = course; if (m_course != nullptr) { // update skeleton IEditableCourse * newSkeleton{ nullptr }; if (m_skeleton == nullptr || m_skeleton->id() != course->foreignId()) { for (const auto &skeleton : m_repository->skeletons()) { if (skeleton->id() == course->foreignId()) { - newSkeleton = skeleton; + newSkeleton = skeleton.get(); break; } } m_skeleton = newSkeleton; emit skeletonChanged(); } // update language - m_language = m_course->language(); + m_language = m_course->language().get(); } else { m_language = nullptr; } emit languageChanged(); emit courseChanged(); } void EditorSession::setCourseByLanguage(Language *language) { if (!skeletonMode() || m_skeleton == nullptr) { qDebug() << "Course selection by language is only available in skeleton mode"; return; } if (language == nullptr || m_repository == nullptr) { return; } IEditableCourse *newCourse{ nullptr }; + QString languageId; + if (language) { + languageId = language->id(); + } for (auto course : m_repository->editableCourses()) { if (course->foreignId() == m_skeleton->id() && course->language()->id() == language->id()) { - newCourse = course; + newCourse = course.get(); break; } } setCourse(newCourse); } IEditableCourse * EditorSession::displayedCourse() const { IEditableCourse * course{ nullptr }; if (m_editSkeleton) { course = m_skeleton; } else { course = m_course; } return course; } void EditorSession::updateDisplayedUnit() { auto course = displayedCourse(); Unit * unit{ nullptr }; if (course != nullptr) { auto units = course->unitList(); if (!units.isEmpty()) { unit = units.constFirst(); } } setUnit(unit); } Unit * EditorSession::unit() const { return m_unit; } void EditorSession::setUnit(Unit *unit) { if (m_unit == unit) { return; } m_unit = unit; // different than above, do not directly enter phrases // but first show editing information for units setPhrase(nullptr); emit unitChanged(); } void EditorSession::setPhrase(Phrase *phrase) { if (m_phrase == phrase) { return; } if (phrase) { setUnit(phrase->unit()); } m_phrase = phrase; emit phraseChanged(); } Phrase * EditorSession::phrase() const { return m_phrase; } Phrase * EditorSession::previousPhrase() const { if (!m_phrase) { return nullptr; } const int index = m_phrase->unit()->phraseList().indexOf(m_phrase); if (index > 0) { return m_phrase->unit()->phraseList().at(index - 1); } else { Unit *unit = m_phrase->unit(); int uIndex = unit->course()->unitList().indexOf(unit); if (uIndex > 0) { return unit->course()->unitList().at(uIndex - 1)->phraseList().last(); } } return nullptr; } Phrase * EditorSession::nextPhrase() const { if (!m_phrase) { return nullptr; } const int index = m_phrase->unit()->phraseList().indexOf(m_phrase); if (index < m_phrase->unit()->phraseList().length() - 1) { return m_phrase->unit()->phraseList().at(index + 1); } else { Unit *unit = m_phrase->unit(); int uIndex = unit->course()->unitList().indexOf(unit); if (uIndex < unit->course()->unitList().length() - 1) { Unit *nextUnit = unit->course()->unitList().at(uIndex + 1); if (nextUnit->phraseList().isEmpty()) { return nullptr; } return nextUnit->phraseList().constFirst(); } } return nullptr; } void EditorSession::switchToPreviousPhrase() { setPhrase(previousPhrase()); } void EditorSession::switchToNextPhrase() { setPhrase(nextPhrase()); } bool EditorSession::hasPreviousPhrase() const { return previousPhrase() != nullptr; } bool EditorSession::hasNextPhrase() const { return nextPhrase() != nullptr; } void EditorSession::updateCourseFromSkeleton() { if (!m_course) { qCritical() << "Not updating course from skeleton, no one set."; return; } - m_repository->updateCourseFromSkeleton(m_course); + //FIXME convert to interface +// m_repository->updateCourseFromSkeleton(m_course); } diff --git a/src/core/icourse.h b/src/core/icourse.h index 5c90803..933b607 100644 --- a/src/core/icourse.h +++ b/src/core/icourse.h @@ -1,74 +1,74 @@ /* * Copyright 2019 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 ICOURSE_H #define ICOURSE_H #include "artikulatecore_export.h" #include #include #include +#include class QString; class Language; class Unit; class ARTIKULATECORE_EXPORT ICourse : public QObject { Q_OBJECT Q_PROPERTY(QString id READ id NOTIFY idChanged) Q_PROPERTY(QString title READ title NOTIFY titleChanged) Q_PROPERTY(QString i18nTitle READ i18nTitle NOTIFY titleChanged) Q_PROPERTY(QString description READ description NOTIFY descriptionChanged) - Q_PROPERTY(Language * language READ language NOTIFY languageChanged) public: ICourse(QObject *parent = nullptr) : QObject(parent) { } virtual ~ICourse() = default; virtual QString id() const = 0; virtual QString foreignId() const = 0; virtual QString title() const = 0; virtual QString i18nTitle() const = 0; virtual QString description() const = 0; - virtual Language * language() const = 0; + virtual std::shared_ptr language() const = 0; /** * @brief Lazy loading unit list * @return list of units in course */ virtual QList unitList() = 0; virtual QUrl file() const = 0; Q_SIGNALS: void idChanged(); void titleChanged(); void descriptionChanged(); void languageChanged(); void unitAdded(); void unitAboutToBeAdded(Unit*,int); void unitsRemoved(); void unitsAboutToBeRemoved(int,int); }; Q_DECLARE_INTERFACE(ICourse, "com.kde.artikulate.ICourse/1.0") #endif // COURSE_H diff --git a/src/core/ieditablecourse.h b/src/core/ieditablecourse.h index 1d35c2c..9790b92 100644 --- a/src/core/ieditablecourse.h +++ b/src/core/ieditablecourse.h @@ -1,50 +1,51 @@ /* * Copyright 2019 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 IEDITABLECOURSE_H #define IEDITABLECOURSE_H #include "artikulatecore_export.h" #include "icourse.h" +#include #include class QString; class Language; class ARTIKULATECORE_EXPORT IEditableCourse : public ICourse { public: IEditableCourse(QObject *parent = nullptr) : ICourse(parent) { } virtual ~IEditableCourse() = default; virtual void setId(QString id) = 0; virtual void setForeignId(QString foreignId) = 0; virtual void setTitle(QString title) = 0; virtual void setI18nTitle(QString title) = 0; virtual void setDescription(QString description) = 0; - virtual void setLanguage(Language *language) = 0; - virtual void addUnit(Unit *unit) = 0; + virtual void setLanguage(std::shared_ptr language) = 0; + virtual std::shared_ptr addUnit(std::unique_ptr unit) = 0; }; Q_DECLARE_INTERFACE(IEditableCourse, "com.kde.artikulate.IEditableCourse/1.0") #endif // EDITABLECOURSE_H diff --git a/src/core/ieditablerepository.h b/src/core/ieditablerepository.h index 6adf2fb..9cf7b7a 100644 --- a/src/core/ieditablerepository.h +++ b/src/core/ieditablerepository.h @@ -1,52 +1,53 @@ /* * Copyright 2019 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 IEDITABLEREPOSITORY_H #define IEDITABLEREPOSITORY_H #include "artikulatecore_export.h" #include "iresourcerepository.h" +#include class IEditableCourse; class Language; /** * \class IEditableRepository * This interface provides a generic interface that provides just the methods and signals needed * to integrade a repository into the editing part of Artikulate. */ class ARTIKULATECORE_EXPORT IEditableRepository : public IResourceRepository { Q_OBJECT public: virtual ~IEditableRepository() = default; - virtual QVector editableCourses() const = 0; - virtual IEditableCourse * editableCourse(Language *language, int index) const = 0; - virtual QVector skeletons() const = 0; + virtual QVector> editableCourses() const = 0; + virtual std::shared_ptr editableCourse(std::shared_ptr language, int index) const = 0; + virtual QVector> skeletons() const = 0; /** * Imports units and phrases from skeleton, deassociates removed ones. * * \param course the course to be updated */ - virtual void updateCourseFromSkeleton(IEditableCourse *course) = 0; + virtual void updateCourseFromSkeleton(std::shared_ptr course) = 0; }; Q_DECLARE_INTERFACE(IEditableRepository, "IEditableRepository") #endif diff --git a/src/core/iresourcerepository.h b/src/core/iresourcerepository.h index e65ffe6..24874ca 100644 --- a/src/core/iresourcerepository.h +++ b/src/core/iresourcerepository.h @@ -1,77 +1,78 @@ /* * Copyright 2019 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 IRESOURCEREPOSITORY_H #define IRESOURCEREPOSITORY_H #include "artikulatecore_export.h" +#include #include class ICourse; class Language; /** * \class IResourceRepository * This interface provides a generic interface that provides just the methods and signals needed * to integrade a repository into the training part of Artikulate. */ class ARTIKULATECORE_EXPORT IResourceRepository : public QObject { Q_OBJECT public: virtual ~IResourceRepository() = default; /** * \return path to working repository, if one is set */ virtual QString storageLocation() const = 0; /** * @return list of all loaded courses */ - virtual QVector courses() const = 0; + virtual QVector> courses() const = 0; /** * @param language to use for filtering * @return list of all loaded courses filtered by the named language */ - virtual QVector courses(Language *language) const = 0; + virtual QVector> courses(const QString &languageId) const = 0; /** * @brief Requests a refresh of all resources * * Typical reasons to call this are GHNS signals */ virtual void reloadCourses() = 0; /** * \return list of all available languages */ - virtual QVector languages() const = 0; + virtual QVector> languages() const = 0; Q_SIGNALS: virtual void courseAboutToBeAdded(ICourse*,int) = 0; virtual void courseAdded() = 0; virtual void courseAboutToBeRemoved(int) = 0; virtual void courseRemoved() = 0; }; Q_DECLARE_INTERFACE(IResourceRepository, "IResourceRepository") #endif diff --git a/src/core/language.cpp b/src/core/language.cpp index be814fe..cc91462 100644 --- a/src/core/language.cpp +++ b/src/core/language.cpp @@ -1,124 +1,124 @@ /* * Copyright 2013 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 "language.h" #include "models/languagemodel.h" #include "phoneme.h" #include "phonemegroup.h" #include "artikulate_debug.h" #include -Language::Language(QObject *parent) - : QObject(parent) +Language::Language() + : QObject() { } Language::~Language() { qDeleteAll(m_phonemeGroups); } QString Language::id() const { return m_id; } void Language::setId(const QString &id) { if (id != m_id) { m_id = id; emit idChanged(); } } QString Language::title() const { return m_title; } void Language::setTitle(const QString &title) { if (QString::compare(title, m_title) != 0) { m_title = title; emit titleChanged(); } } QString Language::i18nTitle() const { return i18n(m_i18nTitle.toUtf8()); } void Language::seti18nTitle(const QString &title) { if (m_i18nTitle == title) { return; } m_i18nTitle = title; emit i18nTitleChanged(); } QUrl Language::file() const { return m_file; } void Language::setFile(const QUrl &file) { m_file = file; } QList Language::phonemes() const { QList list; foreach (PhonemeGroup *group, m_phonemeGroups) { list.append(group->phonemes()); } return list; } QList Language::phonemeGroups() const { return m_phonemeGroups; } PhonemeGroup * Language::addPhonemeGroup(const QString &identifier, const QString &title) { QList::ConstIterator iter = m_phonemeGroups.constBegin(); while (iter != m_phonemeGroups.constEnd()) { if (QString::compare((*iter)->id(), identifier) == 0) { qCWarning(ARTIKULATE_LOG) << "Pronunciation Group identifier already registered, aborting"; return nullptr; } ++iter; } PhonemeGroup *newGroup = new PhonemeGroup(); newGroup->setId(identifier); newGroup->setTitle(title); m_phonemeGroups.append(newGroup); connect(newGroup, &PhonemeGroup::phonemeAdded, this, &Language::phonemesChanged); connect(newGroup, &PhonemeGroup::phonemeRemoved, this, &Language::phonemesChanged); emit phonemeGroupsChanged(); return newGroup; } diff --git a/src/core/language.h b/src/core/language.h index ce6fb55..fd69a74 100644 --- a/src/core/language.h +++ b/src/core/language.h @@ -1,74 +1,73 @@ /* * Copyright 2013 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 LANGUAGE_H #define LANGUAGE_H #include "artikulatecore_export.h" #include #include #include class QString; class Phoneme; class PhonemeGroup; class ARTIKULATECORE_EXPORT Language : public QObject { Q_OBJECT Q_PROPERTY(QString id READ id WRITE setId NOTIFY idChanged) Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged) Q_PROPERTY(QString i18nTitle READ i18nTitle WRITE seti18nTitle NOTIFY i18nTitleChanged) public: - explicit Language(QObject *parent = nullptr); + explicit Language(); ~Language(); QString id() const; void setId(const QString &id); QString title() const; void seti18nTitle(const QString &title); QString i18nTitle() const; void setTitle(const QString &title); QUrl file() const; void setFile(const QUrl &file); QList phonemes() const; Phoneme * addPhoneme(const QString &identifier, const QString &title); QList phonemeGroups() const; PhonemeGroup * addPhonemeGroup(const QString &identifier, const QString &title); Q_SIGNALS: void idChanged(); void associatedLanguageItemChanged(); void titleChanged(); void i18nTitleChanged(); void phonemesChanged(); void phonemeGroupsChanged(); private: - Q_DISABLE_COPY(Language) QString m_id; QString m_title; QString m_i18nTitle; QUrl m_file; QList m_phonemeGroups; }; #endif // LANGUAGE_H diff --git a/src/core/resourcerepository.cpp b/src/core/resourcerepository.cpp index 848ec42..8bb515a 100644 --- a/src/core/resourcerepository.cpp +++ b/src/core/resourcerepository.cpp @@ -1,168 +1,163 @@ /* * Copyright 2019 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 "resourcerepository.h" #include "artikulate_debug.h" #include "resources/courseresource.h" #include "resources/languageresource.h" +#include "core/language.h" #include #include #include #include ResourceRepository::ResourceRepository(QObject *parent) : ResourceRepository(QUrl::fromLocalFile(QStandardPaths::standardLocations(QStandardPaths::DataLocation).constFirst() + QStringLiteral("/courses/")), parent) { } ResourceRepository::ResourceRepository(const QUrl &storageLocation, QObject *parent) : IResourceRepository() , m_storageLocation(storageLocation.toLocalFile()) { // load language resources // all other resources are only loaded on demand QDir dir(":/artikulate/languages/"); dir.setFilter(QDir::Files | QDir::NoSymLinks); QFileInfoList list = dir.entryInfoList(); for (int i = 0; i < list.size(); ++i) { QFileInfo fileInfo = list.at(i); if (fileInfo.completeSuffix() != QLatin1String("xml")) { continue; } loadLanguage(fileInfo.absoluteFilePath()); } } -ResourceRepository::~ResourceRepository() -{ - for (auto language : m_languages) { - language->deleteLater(); - } - m_languages.clear(); -} +ResourceRepository::~ResourceRepository() = default; QString ResourceRepository::storageLocation() const { return m_storageLocation; } -QVector ResourceRepository::courses() const +QVector> ResourceRepository::courses() const { - QVector courses; + QVector> courses; for (const auto &course : m_courses) { courses.append(course); } return courses; } -QVector ResourceRepository::courses(Language *language) const +QVector> ResourceRepository::courses(const QString &languageId) const { - QVector courses; + QVector> courses; for (const auto &course : m_courses) { - if (language != nullptr && course->language() != language) { + if (course->language() && course->language()->id() == languageId) { continue; } courses.append(course); } return courses; } -QVector ResourceRepository::languages() const +QVector> ResourceRepository::languages() const { - QVector languages; + QVector> languages; for (const auto &language : m_languages) { if (language == nullptr) { continue; } languages.append(language->language()); } return languages; } Language * ResourceRepository::language(const QString &id) const { if (m_languages.contains(id)) { - return m_languages.value(id)->language(); + return m_languages.value(id)->language().get(); } return nullptr; } void ResourceRepository::reloadCourses() { std::function scanDirectoryForXmlCourseFiles = [this](QDir dir) { dir.setFilter(QDir::Files | QDir::NoSymLinks); QFileInfoList list = dir.entryInfoList(); for (int i = 0; i < list.size(); ++i) { QFileInfo fileInfo = list.at(i); if (fileInfo.completeSuffix() != QLatin1String("xml")) { continue; } loadCourse(fileInfo.absoluteFilePath()); } }; QDir rootDirectory = QDir(m_storageLocation); QDirIterator it(rootDirectory, QDirIterator::Subdirectories); qCInfo(ARTIKULATE_CORE()) << "Loading courses from" << rootDirectory.absolutePath(); while (it.hasNext()) { scanDirectoryForXmlCourseFiles(it.next()); } } bool ResourceRepository::loadCourse(const QString &resourceFile) { qCDebug(ARTIKULATE_CORE()) << "Loading resource" << resourceFile; // skip already loaded resources if (m_loadedCourses.contains(resourceFile)) { qCWarning(ARTIKULATE_CORE()) << "Reloading of resources not yet supported, skippen course"; return false; } - CourseResource *resource = new CourseResource(QUrl::fromLocalFile(resourceFile), this); + std::shared_ptr resource(new CourseResource(QUrl::fromLocalFile(resourceFile), this)); if (resource->language() == nullptr) { resource->deleteLater(); qCCritical(ARTIKULATE_CORE()) << "Could not load course, language unknown:" << resourceFile; return false; } - emit courseAboutToBeAdded(resource, m_courses.count() - 1); - m_courses.append(resource); + emit courseAboutToBeAdded(resource.get(), m_courses.count() - 1); + m_courses.append(std::move(resource)); emit courseAdded(); m_loadedCourses.append(resourceFile); return true; } bool ResourceRepository::loadLanguage(const QString &resourceFile) { - LanguageResource *resource = new LanguageResource(QUrl::fromLocalFile(resourceFile)); + std::shared_ptr resource(new LanguageResource(QUrl::fromLocalFile(resourceFile))); if (!resource) { qCWarning(ARTIKULATE_CORE()) << "Could not load language" << resourceFile; resource->deleteLater(); return false; } if (m_languages.contains(resource->identifier())) { qCWarning(ARTIKULATE_CORE()) << "Could not load language" << resourceFile; resource->deleteLater(); return false; } m_languages.insert(resource->identifier(), resource); return true; } diff --git a/src/core/resourcerepository.h b/src/core/resourcerepository.h index ab1e559..2b74052 100644 --- a/src/core/resourcerepository.h +++ b/src/core/resourcerepository.h @@ -1,104 +1,104 @@ /* * Copyright 2013-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 . */ #ifndef RESOURCEREPOSITORY_H #define RESOURCEREPOSITORY_H #include "artikulatecore_export.h" #include "iresourcerepository.h" #include #include #include #include #include class QUrl; class CourseResource; class ICourse; class Language; class LanguageResource; /** * @class ResourceRepository * * This class provides data handling of all downloaded trainingdata of a user. It dervies from the repository interface * to provide a generalized access to the data. */ class ARTIKULATECORE_EXPORT ResourceRepository : public IResourceRepository { Q_OBJECT Q_INTERFACES(IResourceRepository) public: explicit ResourceRepository(QObject *parent = nullptr); ~ResourceRepository() override; /** * @brief Construtor for ResourceRepository object with explicitly set course folder * * @param storageLocation relative or absolute path to courses/ folder (including that directory) * @param parent the parent object in the QObject hierarchy */ explicit ResourceRepository(const QUrl &storageLocation, QObject *parent = nullptr); /** * @return path to repository location */ QString storageLocation() const override; /** * @return list of available courses */ - QVector courses() const override; + QVector> courses() const override; /** * @return list of available courses */ - QVector courses(Language *language) const override; + QVector> courses(const QString &languageId) const override; /** * @return list of all available language specifications */ - QVector languages() const override; + QVector> languages() const override; Language * language(const QString &id) const; public Q_SLOTS: /** * \brief updates available resources */ void reloadCourses() override; Q_SIGNALS: void courseAboutToBeAdded(ICourse*, int) override; void courseAdded() override; void courseAboutToBeRemoved(int) override; void courseRemoved() override; private: bool loadCourse(const QString &resourceFile); bool loadLanguage(const QString &resourceFile); - QVector m_courses; - QHash m_languages; ///>! (language-identifier, language resource) + QVector> m_courses; + QHash> m_languages; ///>! (language-identifier, language resource) QStringList m_loadedCourses; const QString m_storageLocation; }; #endif // RESOURCEREPOSITORY_H diff --git a/src/core/resources/courseparser.cpp b/src/core/resources/courseparser.cpp index da8d6ce..18cedcb 100644 --- a/src/core/resources/courseparser.cpp +++ b/src/core/resources/courseparser.cpp @@ -1,414 +1,414 @@ /* * Copyright 2013-2019 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 "courseparser.h" #include "core/icourse.h" #include "core/language.h" #include "core/unit.h" #include "core/phrase.h" #include "core/phoneme.h" #include "artikulate_debug.h" #include #include #include #include #include #include #include #include QXmlSchema CourseParser::loadXmlSchema(const QString &schemeName) { QString relPath = QStringLiteral(":/artikulate/schemes/%1.xsd").arg(schemeName); QUrl file = QUrl::fromLocalFile(relPath); QXmlSchema schema; if (file.isEmpty() || schema.load(file) == false) { qCWarning(ARTIKULATE_PARSER()) << "Schema at file " << file.toLocalFile() << " is invalid."; } return schema; } QDomDocument CourseParser::loadDomDocument(const QUrl &path, const QXmlSchema &schema) { QDomDocument document; QXmlSchemaValidator validator(schema); if (!validator.validate(path)) { qCWarning(ARTIKULATE_PARSER()) << "Schema is not valid, aborting loading of XML document:" << path.toLocalFile(); return document; } QString errorMsg; QFile file(path.toLocalFile()); if (file.open(QIODevice::ReadOnly)) { if (!document.setContent(&file, &errorMsg)) { qCWarning(ARTIKULATE_PARSER()) << errorMsg; } } else { qCWarning(ARTIKULATE_PARSER()) << "Could not open XML document " << path.toLocalFile() << " for reading, aborting."; } return document; } -QVector CourseParser::parseUnits(const QUrl &path) +std::vector> CourseParser::parseUnits(const QUrl &path) { - QVector units; + std::vector> units; QFileInfo info(path.toLocalFile()); if (!info.exists()) { qCCritical(ARTIKULATE_PARSER()()) << "No course file available at location" << path.toLocalFile(); return units; } QXmlStreamReader xml; QFile file(path.toLocalFile()); if (file.open(QIODevice::ReadOnly)) { xml.setDevice(&file); xml.readNextStartElement(); while (!xml.atEnd() && !xml.hasError()) { bool elementOk{ false }; QXmlStreamReader::TokenType token = xml.readNext(); if (token == QXmlStreamReader::StartDocument) { continue; } if (token == QXmlStreamReader::StartElement) { if (xml.name() == "units") { continue; } else if (xml.name() == "unit") { auto unit = parseUnit(xml, elementOk); if (elementOk) { - units.append(unit); + units.push_back(std::move(unit)); } } } } if (xml.hasError()) { qCCritical(ARTIKULATE_PARSER()) << "Error occurred when reading Course XML file:" << path.toLocalFile(); } } else { qCCritical(ARTIKULATE_PARSER()) << "Could not open course file" << path.toLocalFile(); } xml.clear(); file.close(); return units; } -Unit * CourseParser::parseUnit(QXmlStreamReader &xml, bool &ok) +std::unique_ptr CourseParser::parseUnit(QXmlStreamReader &xml, bool &ok) { - Unit * unit = new Unit(nullptr); + std::unique_ptr unit(new Unit); ok = true; if (xml.tokenType() != QXmlStreamReader::StartElement && xml.name() == "unit") { qCWarning(ARTIKULATE_PARSER()) << "Expected to parse 'unit' element, aborting here"; return unit; } xml.readNext(); while (!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == "unit")) { if (xml.tokenType() == QXmlStreamReader::StartElement) { bool elementOk{ false }; if (xml.name() == "id") { unit->setId(parseElement(xml, elementOk)); ok &= elementOk; } else if (xml.name() == "title") { unit->setTitle(parseElement(xml, elementOk)); ok &= elementOk; } else if (xml.name() == "phrases") { // nothing to do } else if (xml.name() == "phrase") { auto phrase = parsePhrase(xml, elementOk); if (elementOk) { unit->addPhrase(phrase); } ok &= elementOk; } else { qCWarning(ARTIKULATE_PARSER()) << "Skipping unknown token" << xml.name(); } } xml.readNext(); } if (!ok) { qCWarning(ARTIKULATE_PARSER()) << "Errors occured while parsing unit" << unit->title() << unit->id(); } return unit; } Phrase * CourseParser::parsePhrase(QXmlStreamReader &xml, bool &ok) { Phrase * phrase = new Phrase(nullptr); ok = true; if (xml.tokenType() != QXmlStreamReader::StartElement && xml.name() == "phrase") { qCWarning(ARTIKULATE_PARSER()) << "Expected to parse 'phrase' element, aborting here"; ok = false; return phrase; } xml.readNext(); while (!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == "phrase")) { if (xml.tokenType() == QXmlStreamReader::StartElement) { bool elementOk{ false }; if (xml.name() == "id") { phrase->setId(parseElement(xml, elementOk)); ok &= elementOk; } else if (xml.name() == "text") { phrase->setText(parseElement(xml, elementOk)); ok &= elementOk; } else if (xml.name() == "type") { const QString type = parseElement(xml, elementOk); if (type == "word") { phrase->setType(Phrase::Word); } else if (type == "expression") { phrase->setType(Phrase::Expression); } else if (type == "sentence") { phrase->setType(Phrase::Sentence); } else if (type == "paragraph") { phrase->setType(Phrase::Paragraph); } ok &= elementOk; } else { qCWarning(ARTIKULATE_PARSER()) << "Skipping unknown token" << xml.name(); } } xml.readNext(); } if (!ok) { qCWarning(ARTIKULATE_PARSER()) << "Errors occured while parsing phrase" << phrase->text() << phrase->id(); } return phrase; } QString CourseParser::parseElement(QXmlStreamReader& xml, bool &ok) { ok = true; if (xml.tokenType() != QXmlStreamReader::StartElement) { qCCritical(ARTIKULATE_PARSER()) << "Parsing element that does not start with a start element"; ok = false; return QString(); } QString elementName = xml.name().toString(); xml.readNext(); // qCDebug(ARTIKULATE_PARSER()) << "parsed: " << elementName << " / " << xml.text().toString(); return xml.text().toString(); } Phrase * CourseParser::parsePhrase(QDomElement phraseNode, Unit* parentUnit) { const ICourse *course = parentUnit->course(); Q_ASSERT(course != nullptr); Phrase *phrase = new Phrase(parentUnit); phrase->setId(phraseNode.firstChildElement(QStringLiteral("id")).text()); phrase->setText(phraseNode.firstChildElement(QStringLiteral("text")).text()); phrase->seti18nText(phraseNode.firstChildElement(QStringLiteral("i18nText")).text()); phrase->setUnit(parentUnit); if (!phraseNode.firstChildElement(QStringLiteral("soundFile")).text().isEmpty()) { phrase->setSound(QUrl::fromLocalFile( course->file().adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() + '/' + phraseNode.firstChildElement(QStringLiteral("soundFile")).text()) ); } phrase->setType(phraseNode.firstChildElement(QStringLiteral("type")).text()); phrase->setEditState(phraseNode.firstChildElement(QStringLiteral("editState")).text()); if (!phraseNode.firstChildElement(QStringLiteral("foreignId")).isNull()) { phrase->setForeignId(phraseNode.firstChildElement(QStringLiteral("foreignId")).text()); } // add phonemes QList phonemes = course->language()->phonemes(); for (QDomElement phonemeID = phraseNode.firstChildElement(QStringLiteral("phonemes")).firstChildElement(); !phonemeID.isNull(); phonemeID = phonemeID.nextSiblingElement()) { QString id = phonemeID.text(); if (id.isEmpty()) { qCritical() << "Phoneme ID string is empty for phrase "<< phrase->id() <<", aborting."; continue; } for (Phoneme *phoneme : phonemes) { if (phoneme->id() == id) { phrase->addPhoneme(phoneme); break; } } } if (!phraseNode.firstChildElement(QStringLiteral("excluded")).isNull() && phraseNode.firstChildElement(QStringLiteral("excluded")).text() == QLatin1String("true")) { phrase->setExcluded(true); } return phrase; } QDomDocument CourseParser::serializedDocument(ICourse *course, bool trainingExport) { QDomDocument document; // prepare xml header QDomProcessingInstruction header = document.createProcessingInstruction(QStringLiteral("xml"), QStringLiteral("version=\"1.0\"")); document.appendChild(header); // create main element QDomElement root = document.createElement(QStringLiteral("course")); document.appendChild(root); QDomElement idElement = document.createElement(QStringLiteral("id")); QDomElement titleElement = document.createElement(QStringLiteral("title")); QDomElement descriptionElement = document.createElement(QStringLiteral("description")); QDomElement languageElement = document.createElement(QStringLiteral("language")); idElement.appendChild(document.createTextNode(course->id())); titleElement.appendChild(document.createTextNode(course->title())); descriptionElement.appendChild(document.createTextNode(course->description())); languageElement.appendChild(document.createTextNode(course->id())); QDomElement unitListElement = document.createElement(QStringLiteral("units")); // create units for (Unit *unit : course->unitList()) { QDomElement unitElement = document.createElement(QStringLiteral("unit")); QDomElement unitIdElement = document.createElement(QStringLiteral("id")); QDomElement unitTitleElement = document.createElement(QStringLiteral("title")); QDomElement unitPhraseListElement = document.createElement(QStringLiteral("phrases")); unitIdElement.appendChild(document.createTextNode(unit->id())); unitTitleElement.appendChild(document.createTextNode(unit->title())); // construct phrases for (Phrase *phrase : unit->phraseList()) { if (trainingExport && phrase->soundFileUrl().isEmpty()) { continue; } unitPhraseListElement.appendChild(serializedPhrase(phrase, document)); } if (trainingExport && unitPhraseListElement.childNodes().count() == 0) { continue; } // construct the unit element unitElement.appendChild(unitIdElement); if (!unit->foreignId().isEmpty()) { QDomElement unitForeignIdElement = document.createElement(QStringLiteral("foreignId")); unitForeignIdElement.appendChild(document.createTextNode(unit->foreignId())); unitElement.appendChild(unitForeignIdElement); } unitElement.appendChild(unitTitleElement); unitElement.appendChild(unitPhraseListElement); unitListElement.appendChild(unitElement); } root.appendChild(idElement); if (!course->foreignId().isEmpty()) { QDomElement courseForeignIdElement = document.createElement(QStringLiteral("foreignId")); courseForeignIdElement.appendChild(document.createTextNode(course->foreignId())); root.appendChild(courseForeignIdElement); } root.appendChild(titleElement); root.appendChild(descriptionElement); root.appendChild(languageElement); root.appendChild(unitListElement); return document; } QDomElement CourseParser::serializedPhrase(Phrase *phrase, QDomDocument &document) { QDomElement phraseElement = document.createElement(QStringLiteral("phrase")); QDomElement phraseIdElement = document.createElement(QStringLiteral("id")); QDomElement phraseTextElement = document.createElement(QStringLiteral("text")); QDomElement phrasei18nTextElement = document.createElement(QStringLiteral("i18nText")); QDomElement phraseSoundFileElement = document.createElement(QStringLiteral("soundFile")); QDomElement phraseTypeElement = document.createElement(QStringLiteral("type")); QDomElement phraseEditStateElement = document.createElement(QStringLiteral("editState")); QDomElement phrasePhonemeListElement = document.createElement(QStringLiteral("phonemes")); phraseIdElement.appendChild(document.createTextNode(phrase->id())); phraseTextElement.appendChild(document.createTextNode(phrase->text())); phrasei18nTextElement.appendChild(document.createTextNode(phrase->i18nText())); phraseSoundFileElement.appendChild(document.createTextNode(phrase->sound().fileName())); phraseTypeElement.appendChild(document.createTextNode(phrase->typeString())); phraseEditStateElement.appendChild(document.createTextNode(phrase->editStateString())); // add phonemes foreach (Phoneme *phoneme, phrase->phonemes()) { QDomElement phonemeElement = document.createElement(QStringLiteral("phonemeID")); phonemeElement.appendChild(document.createTextNode(phoneme->id())); phrasePhonemeListElement.appendChild(phonemeElement); } phraseElement.appendChild(phraseIdElement); if (!phrase->foreignId().isEmpty()) { QDomElement phraseForeignIdElement = document.createElement(QStringLiteral("foreignId")); phraseForeignIdElement.appendChild(document.createTextNode(phrase->foreignId())); phraseElement.appendChild(phraseForeignIdElement); } phraseElement.appendChild(phraseTextElement); phraseElement.appendChild(phrasei18nTextElement); phraseElement.appendChild(phraseSoundFileElement); phraseElement.appendChild(phraseTypeElement); phraseElement.appendChild(phraseEditStateElement); phraseElement.appendChild(phrasePhonemeListElement); if (phrase->isExcluded()) { QDomElement phraseIsExcludedElement = document.createElement(QStringLiteral("excluded")); phraseIsExcludedElement.appendChild(document.createTextNode(QStringLiteral("true"))); phraseElement.appendChild(phraseIsExcludedElement); } return phraseElement; } bool CourseParser::exportCourseToGhnsPackage(ICourse *course, const QString &exportPath) { // filename const QString fileName = course->id() + ".tar.bz2"; KTar tar = KTar(exportPath + '/' + fileName, QStringLiteral("application/x-bzip")); if (!tar.open(QIODevice::WriteOnly)) { qCWarning(ARTIKULATE_CORE()) << "Unable to open tar file" << exportPath + '/' + fileName << "in write mode, aborting."; return false; } for (auto *unit : course->unitList()) { for (auto *phrase : unit->phraseList()) { if (QFile::exists(phrase->soundFileUrl())) { tar.addLocalFile(phrase->soundFileUrl(), phrase->id() + ".ogg"); } } } tar.writeFile(course->id() + ".xml", CourseParser::serializedDocument(course, true).toByteArray()); tar.close(); return true; } diff --git a/src/core/resources/courseparser.h b/src/core/resources/courseparser.h index e5c587f..53eb598 100644 --- a/src/core/resources/courseparser.h +++ b/src/core/resources/courseparser.h @@ -1,74 +1,75 @@ /* * Copyright 2013-2019 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 COURSEPARSER_H #define COURSEPARSER_H #include "artikulatecore_export.h" +#include #include class ICourse; class Unit; class Phrase; class QXmlSchema; class QJSonDocument; class QDomDocument; class QDomElement; class QXmlStreamReader; class QString; class QUrl; class ARTIKULATECORE_EXPORT CourseParser { public: /** * Load XSD file given by its file name (without ".xsd" suffix). The method searches exclusively * the standard install dir for XSD files in subdirectory "schemes/". * * \param schemeName name of the Xml schema without suffix * \return loaded XML Schema */ static QXmlSchema loadXmlSchema(const QString &schemeName); /** * Load XML file given by \p file that confirms with XML schema \p scheme. * * \param path is the path to the XML file to be loaded * \param scheme is the XML schema describing the DOM * \return the loaded DOM document */ static QDomDocument loadDomDocument(const QUrl &path, const QXmlSchema &schema); - static QVector parseUnits(const QUrl &path); + static std::vector> parseUnits(const QUrl &path); static Phrase * parsePhrase(QDomElement phraseNode, Unit* parentUnit); static QDomDocument serializedDocument(ICourse *course, bool trainingExport); static QDomElement serializedPhrase(Phrase *phrase, QDomDocument &document); static bool exportCourseToGhnsPackage(ICourse *course, const QString &exportPath); private: - static Unit * parseUnit(QXmlStreamReader &xml, bool &ok); + static std::unique_ptr parseUnit(QXmlStreamReader &xml, bool &ok); static Phrase * parsePhrase(QXmlStreamReader &xml, bool &ok); static QString parseElement(QXmlStreamReader &xml, bool &ok); }; #endif diff --git a/src/core/resources/courseresource.cpp b/src/core/resources/courseresource.cpp index 0ae456d..0d6f167 100644 --- a/src/core/resources/courseresource.cpp +++ b/src/core/resources/courseresource.cpp @@ -1,316 +1,316 @@ /* * Copyright 2013-2015 Andreas Cord-Landwehr * Copyright 2013 Oindrila Gupta * * 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 "courseresource.h" #include "courseparser.h" #include "core/language.h" #include "core/unit.h" #include "core/phoneme.h" #include "core/phonemegroup.h" #include "core/language.h" #include "core/iresourcerepository.h" +#include #include #include #include #include #include #include #include #include "artikulate_debug.h" class CourseResourcePrivate { public: CourseResourcePrivate() = default; ~CourseResourcePrivate(); void loadCourse(CourseResource *parent); IResourceRepository *m_repository{ nullptr }; QUrl m_file; QString m_identifier; QString m_foreignId; QString m_title; QString m_languageId; - Language *m_language{ nullptr }; + std::shared_ptr m_language; QString m_i18nTitle; QString m_description; - QVector m_units; + QVector> m_units; bool m_courseLoaded{ false }; ///deleteLater(); - } - m_units.clear(); -} +CourseResourcePrivate::~CourseResourcePrivate() = default; void CourseResourcePrivate::loadCourse(CourseResource *parent) { if (m_courseLoaded == true) { qCWarning(ARTIKULATE_CORE()) << "Skipping loading of course, no reloading implemented yet"; return; } m_courseLoaded = true; QFileInfo info(m_file.toLocalFile()); if (!info.exists()) { qCCritical(ARTIKULATE_CORE()) << "No course file available at location" << m_file.toLocalFile(); return; } // load existing file QXmlSchema schema = CourseParser::loadXmlSchema(QStringLiteral("course")); if (!schema.isValid()) { qCWarning(ARTIKULATE_CORE()) << "Scheme not valid, aborting"; return; } QDomDocument document = CourseParser::loadDomDocument(m_file, schema); if (document.isNull()) { qCWarning(ARTIKULATE_CORE()) << "Could not parse document " << m_file.toLocalFile() << ", aborting."; return; } // load missing elements of course // TODO usage of QDomElement is quite slow and should be exchanged with the QXmlParser in the future QDomElement root(document.documentElement()); if (!root.firstChildElement(QStringLiteral("foreignId")).isNull()) { m_foreignId = root.firstChildElement(QStringLiteral("foreignId")).text(); } // create units for (QDomElement unitNode = root.firstChildElement(QStringLiteral("units")).firstChildElement(); !unitNode.isNull(); unitNode = unitNode.nextSiblingElement()) { - Unit *unit = new Unit(nullptr); + std::unique_ptr unit(new Unit); unit->setId(unitNode.firstChildElement(QStringLiteral("id")).text()); unit->setCourse(parent); unit->setTitle(unitNode.firstChildElement(QStringLiteral("title")).text()); if (!unitNode.firstChildElement(QStringLiteral("foreignId")).isNull()) { unit->setForeignId(unitNode.firstChildElement(QStringLiteral("foreignId")).text()); } - parent->addUnit(unit); // create phrases for (QDomElement phraseNode = unitNode.firstChildElement(QStringLiteral("phrases")).firstChildElement(); !phraseNode.isNull(); phraseNode = phraseNode.nextSiblingElement()) { - unit->addPhrase(CourseParser::parsePhrase(phraseNode, unit)); // add to unit at last step to produce only one signal + unit->addPhrase(CourseParser::parsePhrase(phraseNode, unit.get())); // add to unit at last step to produce only one signal //FIXME phrase does not cause unit signals that phonemes list is changed } + parent->addUnit(std::move(unit)); } } CourseResource::CourseResource(const QUrl &path, IResourceRepository *repository) : ICourse(repository) , d(new CourseResourcePrivate()) { QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); d->m_file = path; d->m_repository = repository; // load basic information from language file, but does not parse everything QXmlStreamReader xml; QFile file(path.toLocalFile()); if (file.open(QIODevice::ReadOnly)) { xml.setDevice(&file); xml.readNextStartElement(); while (xml.readNext() && !xml.atEnd()) { if (xml.name() == "id") { d->m_identifier = xml.readElementText(); continue; } if (xml.name() == "foreignId") { d->m_foreignId = xml.readElementText(); continue; } //TODO i18nTitle must be implemented, currently missing and hence not parsed if (xml.name() == "title") { d->m_title = xml.readElementText(); d->m_i18nTitle = d->m_title; continue; } if (xml.name() == "description") { d->m_description = xml.readElementText(); continue; } if (xml.name() == "language") { d->m_languageId = xml.readElementText(); continue; } // quit reading when basic elements are read if (!d->m_identifier.isEmpty() && !d->m_title.isEmpty() && !d->m_i18nTitle.isEmpty() && !d->m_description.isEmpty() && !d->m_languageId.isEmpty() && !d->m_foreignId.isEmpty() ) { break; } } if (xml.hasError()) { qCritical() << "Error occurred when reading Course XML file:" << path.toLocalFile(); } } else { qCCritical(ARTIKULATE_CORE()) << "Could not open course file" << path.toLocalFile(); } xml.clear(); file.close(); // find correct language if (repository != nullptr) { for (const auto &language : repository->languages()) { if (language == nullptr) { continue; } if (language->id() == d->m_languageId) { d->m_language = language; } } } if (d->m_language == nullptr) { qCCritical(ARTIKULATE_CORE()) << "A course with an unknown language was loaded"; } } CourseResource::~CourseResource() = default; QString CourseResource::id() const { return d->m_identifier; } void CourseResource::setId(const QString &id) { if (d->m_identifier == id) { return; } d->m_identifier = id; emit idChanged(); } QString CourseResource::foreignId() const { return d->m_foreignId; } void CourseResource::setForeignId(const QString &foreignId) { if (d->m_foreignId == foreignId) { return; } d->m_foreignId = foreignId; emit foreignIdChanged(); } QString CourseResource::title() const { return d->m_title; } void CourseResource::setTitle(const QString &title) { if (d->m_title == title) { return; } d->m_title = title; emit titleChanged(); } QString CourseResource::i18nTitle() const { return d->m_i18nTitle; } void CourseResource::setI18nTitle(const QString &i18nTitle) { if (d->m_i18nTitle == i18nTitle) { return; } d->m_i18nTitle = i18nTitle; emit i18nTitleChanged(); } QString CourseResource::description() const { return d->m_description; } void CourseResource::setDescription(const QString &description) { if (d->m_description == description) { return; } d->m_description = description; emit descriptionChanged(); } -Language * CourseResource::language() const +std::shared_ptr CourseResource::language() const { return d->m_language; } -void CourseResource::setLanguage(Language *language) +void CourseResource::setLanguage(std::shared_ptr language) { if (d->m_language == language) { return; } d->m_language = language; emit languageChanged(); } -void CourseResource::addUnit(Unit *unit) +std::shared_ptr CourseResource::addUnit(std::unique_ptr unit) { - emit unitAboutToBeAdded(unit, d->m_units.count() - 1); - d->m_units.append(unit); + emit unitAboutToBeAdded(unit.get(), d->m_units.count() - 1); + d->m_units.append(std::move(unit)); emit unitAdded(); + return d->m_units.last(); } QList CourseResource::unitList() { if (d->m_courseLoaded == false) { d->loadCourse(this); } - return d->m_units.toList(); + QList rawList; + for (auto unit : d->m_units) { + rawList.append(unit.get()); + } + return rawList; } -QVector CourseResource::units() +QVector> CourseResource::units() { if (d->m_courseLoaded == false) { d->loadCourse(this); } return d->m_units; } QUrl CourseResource::file() const { return d->m_file; } diff --git a/src/core/resources/courseresource.h b/src/core/resources/courseresource.h index 39e942b..acd2b5f 100644 --- a/src/core/resources/courseresource.h +++ b/src/core/resources/courseresource.h @@ -1,118 +1,118 @@ /* * Copyright 2013 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 COURSERESOURCE_H #define COURSERESOURCE_H #include "artikulatecore_export.h" #include "core/icourse.h" - +#include #include #include class QString; class CourseResourcePrivate; class Unit; class Phrase; class IResourceRepository; class ARTIKULATECORE_EXPORT CourseResource : public ICourse { Q_OBJECT Q_INTERFACES(ICourse) public: /** * Create course resource from file. */ explicit CourseResource(const QUrl &path, IResourceRepository *repository); ~CourseResource() override; /** * \return unique identifier */ QString id() const override; void setId(const QString &id); /** * \return global ID for this course */ QString foreignId() const override; void setForeignId(const QString &foreignId); /** * \return human readable localized title */ QString title() const override; void setTitle(const QString &title); /** * \return human readable title in English */ QString i18nTitle() const override; void setI18nTitle(const QString &i18nTitle); /** * \return description text for course */ QString description() const override; void setDescription(const QString &description); /** * \return language identifier of this course */ - Language * language() const override; + std::shared_ptr language() const override; - void setLanguage(Language *language); + void setLanguage(std::shared_ptr language); - void addUnit(Unit *unit); + std::shared_ptr addUnit(std::unique_ptr unit); /** * \return true if resource is loaded, otherwise false */ bool isOpen() const; void sync(); QUrl file() const override; QList unitList() override; - QVector units(); + QVector> units(); Q_SIGNALS: void idChanged(); void foreignIdChanged(); void titleChanged(); void i18nTitleChanged(); void descriptionChanged(); void languageChanged(); private: const QScopedPointer d; }; #endif diff --git a/src/core/resources/editablecourseresource.cpp b/src/core/resources/editablecourseresource.cpp index 221e06f..824af10 100644 --- a/src/core/resources/editablecourseresource.cpp +++ b/src/core/resources/editablecourseresource.cpp @@ -1,221 +1,221 @@ /* * Copyright 2019 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 "editablecourseresource.h" #include "courseparser.h" #include "artikulate_debug.h" #include "core/unit.h" #include "core/phrase.h" #include "core/phoneme.h" #include #include #include #include #include #include #include #include #include EditableCourseResource::EditableCourseResource(const QUrl &path, IResourceRepository *repository) : IEditableCourse() , m_course(new CourseResource(path, repository)) { QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); connect(m_course.get(), &ICourse::unitAboutToBeAdded, this, &ICourse::unitAboutToBeAdded); connect(m_course.get(), &ICourse::unitAdded, this, &ICourse::unitAdded); connect(m_course.get(), &CourseResource::idChanged, this, &EditableCourseResource::idChanged); connect(m_course.get(), &CourseResource::foreignIdChanged, this, &EditableCourseResource::foreignIdChanged); connect(m_course.get(), &CourseResource::titleChanged, this, &EditableCourseResource::titleChanged); connect(m_course.get(), &CourseResource::descriptionChanged, this, &EditableCourseResource::descriptionChanged); connect(m_course.get(), &CourseResource::languageChanged, this, &EditableCourseResource::languageChanged); } QString EditableCourseResource::id() const { return m_course->id(); } void EditableCourseResource::setId(QString id) { m_course->setId(id); } QString EditableCourseResource::foreignId() const { return m_course->foreignId(); } void EditableCourseResource::setForeignId(QString foreignId) { m_course->setForeignId(std::move(foreignId)); } QString EditableCourseResource::title() const { return m_course->title(); } void EditableCourseResource::setTitle(QString title) { m_course->setTitle(title); } QString EditableCourseResource::i18nTitle() const { return m_course->i18nTitle(); } void EditableCourseResource::setI18nTitle(QString i18nTitle) { m_course->setI18nTitle(i18nTitle); } QString EditableCourseResource::description() const { return m_course->description(); } void EditableCourseResource::setDescription(QString description) { m_course->setDescription(description); } -Language * EditableCourseResource::language() const +std::shared_ptr EditableCourseResource::language() const { return m_course->language(); } -void EditableCourseResource::setLanguage(Language *language) +void EditableCourseResource::setLanguage(std::shared_ptr language) { m_course->setLanguage(language); } QList EditableCourseResource::unitList() { return m_course->unitList(); } QUrl EditableCourseResource::file() const { return m_course->file(); } void EditableCourseResource::sync() { Q_ASSERT(file().isValid()); Q_ASSERT(file().isLocalFile()); Q_ASSERT(!file().isEmpty()); // not writing back if not modified if (!m_modified) { qCDebug(ARTIKULATE_LOG()) << "Aborting sync, course was not modified."; return; } exportCourse(file()); } bool EditableCourseResource::exportCourse(const QUrl &filePath) { // write back to file // create directories if necessary QFileInfo info(filePath.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path()); if (!info.exists()) { qCDebug(ARTIKULATE_LOG()) << "create xml output file directory, not existing"; QDir dir; dir.mkpath(filePath.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path()); } //TODO port to KSaveFile QFile file(filePath.toLocalFile()); if (!file.open(QIODevice::WriteOnly)) { qCWarning(ARTIKULATE_LOG()) << "Unable to open file " << file.fileName() << " in write mode, aborting."; return false; } file.write(CourseParser::serializedDocument(m_course.get(), false).toByteArray()); return true; } -void EditableCourseResource::addUnit(Unit *unit) +std::shared_ptr EditableCourseResource::addUnit(std::unique_ptr unit) { - m_course->addUnit(unit); setModified(true); + return m_course->addUnit(std::move(unit)); } bool EditableCourseResource::isModified() const { return m_modified; } void EditableCourseResource::setModified(bool modified) { m_modified = modified; } Unit * EditableCourseResource::createUnit() { // find first unused id QStringList unitIds; - for (auto *unit : m_course->units()) { + for (auto unit : m_course->units()) { unitIds.append(unit->id()); } QString id = QUuid::createUuid().toString(); while (unitIds.contains(id)) { id = QUuid::createUuid().toString(); qCWarning(ARTIKULATE_LOG) << "Unit id generator has found a collision, recreating id."; } // create unit - Unit *unit = new Unit(this); + std::unique_ptr unit(new Unit(this)); unit->setCourse(this); unit->setId(id); unit->setTitle(i18n("New Unit")); - addUnit(unit); + auto sharedUnit = addUnit(std::move(unit)); - return unit; + return sharedUnit.get(); } Phrase * EditableCourseResource::createPhrase(Unit *unit) { // find globally unique phrase id inside course QStringList phraseIds; - for (auto *unit : m_course->units()) { + for (auto unit : m_course->units()) { for (auto *phrase : unit->phraseList()) { phraseIds.append(phrase->id()); } } QString id = QUuid::createUuid().toString(); while (phraseIds.contains(id)) { id = QUuid::createUuid().toString(); qCWarning(ARTIKULATE_LOG) << "Phrase id generator has found a collision, recreating id."; } // create unit Phrase *phrase = new Phrase(this); phrase->setId(id); phrase->setText(QLatin1String("")); phrase->setType(Phrase::Word); unit->addPhrase(phrase); return phrase; } diff --git a/src/core/resources/editablecourseresource.h b/src/core/resources/editablecourseresource.h index 240a86d..538765a 100644 --- a/src/core/resources/editablecourseresource.h +++ b/src/core/resources/editablecourseresource.h @@ -1,139 +1,139 @@ /* * Copyright 2019 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 EDITABLECOURSERESOURCE_H #define EDITABLECOURSERESOURCE_H #include "artikulatecore_export.h" #include "courseresource.h" #include "core/icourse.h" #include "core/ieditablecourse.h" #include #include #include class IResourceRepository; class Course; class Unit; class Phrase; class QString; class QDomDocument; /** * @brief Decorator for CourseResource * * This decorator adds functionality to modify and write back changes of a course. */ class ARTIKULATECORE_EXPORT EditableCourseResource : public IEditableCourse { Q_OBJECT Q_INTERFACES(ICourse) Q_INTERFACES(IEditableCourse) public: /** * Create course resource from file. */ explicit EditableCourseResource(const QUrl &path, IResourceRepository *repository); ~EditableCourseResource() override = default; /** * \return unique identifier */ QString id() const override; void setId(QString id) override; /** * \return unique identifier */ QString foreignId() const override; void setForeignId(QString foreignId) override; /** * \return human readable localized title */ QString title() const override; void setTitle(QString title) override; /** * \return human readable title in English */ QString i18nTitle() const override; void setI18nTitle(QString i18nTitle) override; /** * \return description text for course */ QString description() const override; void setDescription(QString description) override; /** * \return language identifier of this course */ - Language * language() const override; + std::shared_ptr language() const override; - void setLanguage(Language *language) override; + void setLanguage(std::shared_ptr language) override; void sync(); /** * @brief Export course to specified file. * @param filePath the absolute path to the export file * @return true of export finished without errors */ bool exportCourse(const QUrl &filePath); - void addUnit(Unit *unit) override; + std::shared_ptr addUnit(std::unique_ptr unit) override; bool isModified() const; QUrl file() const override; void setFile(const QUrl &) {} QList unitList() override; Q_INVOKABLE Unit * createUnit(); Q_INVOKABLE Phrase * createPhrase(Unit *unit); Q_SIGNALS: void idChanged(); void foreignIdChanged(); void titleChanged(); void i18nTitleChanged(); void descriptionChanged(); void languageChanged(); private: Q_DISABLE_COPY(EditableCourseResource) void setModified(bool modified); bool m_modified{ false }; const std::unique_ptr m_course; }; #endif diff --git a/src/core/resources/languageresource.cpp b/src/core/resources/languageresource.cpp index d39c9e0..c77644c 100644 --- a/src/core/resources/languageresource.cpp +++ b/src/core/resources/languageresource.cpp @@ -1,180 +1,169 @@ /* * Copyright 2013 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 "languageresource.h" #include "courseparser.h" #include "core/language.h" #include "core/phoneme.h" #include "core/phonemegroup.h" #include #include #include #include #include #include "artikulate_debug.h" class LanguageResourcePrivate { public: - LanguageResourcePrivate() - : m_languageResource(nullptr) - { - } - - ~LanguageResourcePrivate() - { - } + LanguageResourcePrivate() = default; + ~LanguageResourcePrivate() = default; - Language *m_languageResource; + std::shared_ptr m_language; QString m_identifier; QUrl m_path; QString m_title; QString m_i18nTitle; }; LanguageResource::LanguageResource(const QUrl &path) : d(new LanguageResourcePrivate) { d->m_path = path; // load basic information from language file, but does not parse everything QXmlStreamReader xml; QFile file(path.toLocalFile()); if (file.open(QIODevice::ReadOnly)) { xml.setDevice(&file); xml.readNextStartElement(); while (xml.readNext() && !xml.atEnd()) { if (xml.name() == "id") { d->m_identifier = xml.readElementText(); } if (xml.name() == "title") { d->m_title = xml.readElementText(); } if (xml.name() == "i18nTitle") { d->m_i18nTitle = xml.readElementText(); } // quit reading when basic elements are read if (!d->m_identifier.isEmpty() && !d->m_title.isEmpty() && !d->m_i18nTitle.isEmpty() ) { break; } } if (xml.hasError()) { qCritical() << "Error occurred when reading Language XML file:" << path.toLocalFile(); } } xml.clear(); file.close(); } LanguageResource::~LanguageResource() { } QString LanguageResource::identifier() { return d->m_identifier; } QString LanguageResource::title() { return d->m_title; } QString LanguageResource::i18nTitle() { return d->m_i18nTitle; } void LanguageResource::close() { // do nothing // language files are never closed } bool LanguageResource::isOpen() const { - return (d->m_languageResource != nullptr); + return (d->m_language != nullptr); } QUrl LanguageResource::path() const { return d->m_path; } -QObject * LanguageResource::resource() +std::shared_ptr LanguageResource::language() { - if (d->m_languageResource != nullptr) { - return d->m_languageResource; + if (d->m_language) { + return d->m_language; } if (!d->m_path.isLocalFile()) { qCWarning(ARTIKULATE_LOG) << "Cannot open language file at " << d->m_path.toLocalFile() << ", aborting."; return nullptr; } QXmlSchema schema = CourseParser::loadXmlSchema(QStringLiteral("language")); if (!schema.isValid()) { return nullptr; } QDomDocument document = CourseParser::loadDomDocument(d->m_path, schema); if (document.isNull()) { qCWarning(ARTIKULATE_LOG) << "Could not parse document " << d->m_path.toLocalFile() << ", aborting."; return nullptr; } QDomElement root(document.documentElement()); - d->m_languageResource = new Language(this); - d->m_languageResource->setFile(d->m_path); - d->m_languageResource->setId(root.firstChildElement(QStringLiteral("id")).text()); - d->m_languageResource->setTitle(root.firstChildElement(QStringLiteral("title")).text()); - d->m_languageResource->seti18nTitle(root.firstChildElement(QStringLiteral("i18nTitle")).text()); + d->m_language = std::shared_ptr(new Language()); + d->m_language->setFile(d->m_path); + d->m_language->setId(root.firstChildElement(QStringLiteral("id")).text()); + d->m_language->setTitle(root.firstChildElement(QStringLiteral("title")).text()); + d->m_language->seti18nTitle(root.firstChildElement(QStringLiteral("i18nTitle")).text()); // create phoneme groups for (QDomElement groupNode = root.firstChildElement(QStringLiteral("phonemeGroups")).firstChildElement(); !groupNode.isNull(); groupNode = groupNode.nextSiblingElement()) { - PhonemeGroup *group = d->m_languageResource->addPhonemeGroup( + PhonemeGroup *group = d->m_language->addPhonemeGroup( groupNode.firstChildElement(QStringLiteral("id")).text(), groupNode.firstChildElement(QStringLiteral("title")).text()); group->setDescription(groupNode.attribute(QStringLiteral("description"))); // register phonemes for (QDomElement phonemeNode = groupNode.firstChildElement(QStringLiteral("phonemes")).firstChildElement(); !phonemeNode.isNull(); phonemeNode = phonemeNode.nextSiblingElement()) { group->addPhoneme(phonemeNode.firstChildElement(QStringLiteral("id")).text(), phonemeNode.firstChildElement(QStringLiteral("title")).text()); } } - return d->m_languageResource; -} - -Language * LanguageResource::language() -{ - return qobject_cast(resource()); + return d->m_language; } diff --git a/src/core/resources/languageresource.h b/src/core/resources/languageresource.h index 816ae12..19c9b6c 100644 --- a/src/core/resources/languageresource.h +++ b/src/core/resources/languageresource.h @@ -1,86 +1,81 @@ /* * Copyright 2013 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 LANGUAGERESOURCE_H #define LANGUAGERESOURCE_H #include "artikulatecore_export.h" +#include #include class LanguageResourcePrivate; class Language; class ARTIKULATECORE_EXPORT LanguageResource : public QObject { Q_OBJECT public: explicit LanguageResource(const QUrl &path); virtual ~LanguageResource(); /** * \return unique identifier */ QString identifier(); /** * \return human readable localized title */ QString title(); /** * \return human readable title in English */ QString i18nTitle(); /** * \return true if resource is loaded, otherwise false */ bool isOpen() const; /** * close resource without writing changes back to file */ void close(); /** * \return path to resource file */ QUrl path() const; /** * \return reference to the loaded resource * if resource is not open yet, it will be loaded */ - QObject * resource(); - - /** - * \return reference to the loaded language resource - * Same behavior as \see resource() but casted to Language - */ - Language * language(); + std::shared_ptr language(); private: const QScopedPointer d; }; #endif diff --git a/src/core/resources/skeletonresource.cpp b/src/core/resources/skeletonresource.cpp index 79ee09b..0d4cfe8 100644 --- a/src/core/resources/skeletonresource.cpp +++ b/src/core/resources/skeletonresource.cpp @@ -1,307 +1,316 @@ /* * Copyright 2013 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 "skeletonresource.h" #include "courseparser.h" #include "core/language.h" #include "core/unit.h" #include "editablecourseresource.h" #include "core/phoneme.h" #include "core/phonemegroup.h" #include "core/resources/languageresource.h" #include #include #include #include #include #include #include #include "artikulate_debug.h" class SkeletonResourcePrivate { public: SkeletonResourcePrivate(const QUrl &path) : m_path(path) { // load basic information from language file, but does not parse everything QXmlStreamReader xml; QFile file(path.toLocalFile()); if (file.open(QIODevice::ReadOnly)) { xml.setDevice(&file); xml.readNextStartElement(); while (xml.readNext() && !xml.atEnd()) { if (xml.name() == "id") { m_identifier = xml.readElementText(); continue; } if (xml.name() == "title") { m_title = xml.readElementText(); continue; } if (xml.name() == "description") { m_description = xml.readElementText(); continue; } // quit reading when basic elements are read if (!m_identifier.isEmpty() && !m_title.isEmpty() && !m_description.isEmpty() ) { break; } } if (xml.hasError()) { qCritical() << "Error occurred when reading Course XML file:" << path.toLocalFile(); } } else { qCCritical(ARTIKULATE_CORE()) << "Could not open course file" << path.toLocalFile(); } xml.clear(); file.close(); } - QVector units(); + QVector> units(); - void appendUnit(Unit *unit); + std::shared_ptr appendUnit(std::unique_ptr unit); /** * @return the skeleton resource as serialized byte array */ QDomDocument serializedSkeleton(); QUrl m_path; QString m_identifier; QString m_title; QString m_description; bool m_unitsParsed{ false }; protected: - QVector m_units; ///!< the units variable is loaded lazily and shall never be access directly + QVector> m_units; ///!< the units variable is loaded lazily and shall never be access directly }; -QVector SkeletonResourcePrivate::units() +QVector> SkeletonResourcePrivate::units() { if (m_unitsParsed) { return m_units; } - m_units = CourseParser::parseUnits(m_path); + auto units = CourseParser::parseUnits(m_path); + for (auto &unit : units) { + m_units.append(std::move(unit)); + } m_unitsParsed = true; return m_units; } -void SkeletonResourcePrivate::appendUnit(Unit *unit) { +std::shared_ptr SkeletonResourcePrivate::appendUnit(std::unique_ptr unit) { units(); // ensure that units are parsed - m_units.append(unit); + m_units.append(std::move(unit)); + return m_units.last(); } QDomDocument SkeletonResourcePrivate::serializedSkeleton() { QDomDocument document; // prepare xml header QDomProcessingInstruction header = document.createProcessingInstruction(QStringLiteral("xml"), QStringLiteral("version=\"1.0\"")); document.appendChild(header); // create main element QDomElement root = document.createElement(QStringLiteral("skeleton")); document.appendChild(root); QDomElement idElement = document.createElement(QStringLiteral("id")); QDomElement titleElement = document.createElement(QStringLiteral("title")); QDomElement descriptionElement = document.createElement(QStringLiteral("description")); idElement.appendChild(document.createTextNode(m_identifier)); titleElement.appendChild(document.createTextNode(m_title)); descriptionElement.appendChild(document.createTextNode(m_description)); QDomElement unitListElement = document.createElement(QStringLiteral("units")); // create units for (auto unit : units()) { QDomElement unitElement = document.createElement(QStringLiteral("unit")); QDomElement unitIdElement = document.createElement(QStringLiteral("id")); QDomElement unitTitleElement = document.createElement(QStringLiteral("title")); QDomElement unitPhraseListElement = document.createElement(QStringLiteral("phrases")); unitIdElement.appendChild(document.createTextNode(unit->id())); unitTitleElement.appendChild(document.createTextNode(unit->title())); // construct phrases for (Phrase *phrase : unit->phraseList()) { QDomElement phraseElement = document.createElement(QStringLiteral("phrase")); QDomElement phraseIdElement = document.createElement(QStringLiteral("id")); QDomElement phraseTextElement = document.createElement(QStringLiteral("text")); QDomElement phraseTypeElement = document.createElement(QStringLiteral("type")); phraseIdElement.appendChild(document.createTextNode(phrase->id())); phraseTextElement.appendChild(document.createTextNode(phrase->text())); phraseTypeElement.appendChild(document.createTextNode(phrase->typeString())); phraseElement.appendChild(phraseIdElement); phraseElement.appendChild(phraseTextElement); phraseElement.appendChild(phraseTypeElement); unitPhraseListElement.appendChild(phraseElement); } // construct the unit element unitElement.appendChild(unitIdElement); unitElement.appendChild(unitTitleElement); unitElement.appendChild(unitPhraseListElement); unitListElement.appendChild(unitElement); } root.appendChild(idElement); root.appendChild(titleElement); root.appendChild(descriptionElement); root.appendChild(unitListElement); return document; } SkeletonResource::SkeletonResource(const QUrl &path, IResourceRepository *repository) : IEditableCourse() , d(new SkeletonResourcePrivate(path)) { QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); Q_UNUSED(repository); } SkeletonResource::~SkeletonResource() = default; QString SkeletonResource::id() const { return d->m_identifier; } void SkeletonResource::setId(QString id) { if (d->m_identifier == id) { return; } d->m_identifier = id; emit idChanged(); } QString SkeletonResource::foreignId() const { return id(); } void SkeletonResource::setForeignId(QString id) { Q_UNUSED(id); Q_UNREACHABLE(); } QString SkeletonResource::title() const { return d->m_title; } void SkeletonResource::setTitle(QString title) { if (d->m_title == title) { return; } d->m_title = title; emit titleChanged(); } QString SkeletonResource::i18nTitle() const { // there are no localized titles available return title(); } void SkeletonResource::setI18nTitle(QString title) { Q_UNUSED(title); Q_UNREACHABLE(); } QString SkeletonResource::description() const { return d->m_description; } void SkeletonResource::setDescription(QString description) { if (d->m_description == description) { return; } d->m_description = description; emit descriptionChanged(); } bool SkeletonResource::exportCourse(const QUrl &filePath) { // write back to file // create directories if necessary QFileInfo info(filePath.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path()); if (!info.exists()) { qCDebug(ARTIKULATE_LOG()) << "create xml output file directory, not existing"; QDir dir; dir.mkpath(filePath.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path()); } //TODO port to atomic file swap QFile file(filePath.toLocalFile()); if (!file.open(QIODevice::WriteOnly)) { qCWarning(ARTIKULATE_LOG()) << "Unable to open file " << filePath << " in write mode, aborting."; return false; } file.write(d->serializedSkeleton().toByteArray()); return true; } -void SkeletonResource::addUnit(Unit *unit) +std::shared_ptr SkeletonResource::addUnit(std::unique_ptr unit) { - emit unitAboutToBeAdded(unit, d->units().count() - 1); - d->appendUnit(unit); + emit unitAboutToBeAdded(unit.get(), d->units().count() - 1); + auto sharedUnit = d->appendUnit(std::move(unit)); emit unitAdded(); + return sharedUnit; } -Language * SkeletonResource::language() const +std::shared_ptr SkeletonResource::language() const { // skeleton must not have a dedicated language - return nullptr; + return std::shared_ptr(); } -void SkeletonResource::setLanguage(Language *language) +void SkeletonResource::setLanguage(std::shared_ptr language) { Q_UNUSED(language); Q_UNREACHABLE(); } QList SkeletonResource::unitList() { - return d->units().toList(); + QList rawList; + for (auto unit : d->units()) { + rawList.append(unit.get()); + } + return rawList; } QUrl SkeletonResource::file() const { return d->m_path; } diff --git a/src/core/resources/skeletonresource.h b/src/core/resources/skeletonresource.h index 0787755..893b248 100644 --- a/src/core/resources/skeletonresource.h +++ b/src/core/resources/skeletonresource.h @@ -1,75 +1,75 @@ /* * Copyright 2013 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 SKELETONRESOURCE_H #define SKELETONRESOURCE_H #include "artikulatecore_export.h" #include "core/ieditablecourse.h" #include class SkeletonResourcePrivate; class IResourceRepository; /** * @brief The SkeletonResource class is a decorator for EditableCourseResource */ class ARTIKULATECORE_EXPORT SkeletonResource : public IEditableCourse { Q_OBJECT Q_INTERFACES(ICourse) public: /** * Create course resource from file. */ explicit SkeletonResource(const QUrl &path, IResourceRepository *repository); ~SkeletonResource() override; QString id() const override; void setId(QString id) override; QString foreignId() const override; void setForeignId(QString id) override; QString title() const override; void setTitle(QString title) override; QString i18nTitle() const override; void setI18nTitle(QString title) override; QString description() const override; void setDescription(QString description) override; - Language * language() const override; - void setLanguage(Language *language) override; + std::shared_ptr language() const override; + void setLanguage(std::shared_ptr language) override; QList unitList() override; QUrl file() const override; bool exportCourse(const QUrl &filePath); - void addUnit(Unit *unit) override; + std::shared_ptr addUnit(std::unique_ptr unit) override; bool isModified() const { return true;} //FIXME private: const QScopedPointer d; }; #endif diff --git a/src/models/coursemodel.cpp b/src/models/coursemodel.cpp index 1a41d19..ba9fdde 100644 --- a/src/models/coursemodel.cpp +++ b/src/models/coursemodel.cpp @@ -1,219 +1,237 @@ /* * Copyright 2013 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 "coursemodel.h" #include "core/language.h" #include "core/iresourcerepository.h" #include "core/resources/courseresource.h" #include #include "artikulate_debug.h" #include #include #include "application.h" CourseModel::CourseModel(QObject *parent) : QAbstractListModel(parent) , m_resourceRepository(nullptr) , m_language(nullptr) , m_signalMapper(new QSignalMapper(this)) { connect(m_signalMapper, static_cast(&QSignalMapper::mapped), this, &CourseModel::emitCourseChanged); connect(this, &CourseModel::resourceManagerChanged, this, &CourseModel::rowCountChanged); connect(this, &CourseModel::languageChanged, this, &CourseModel::rowCountChanged); setResourceRepository(artikulateApp->resourceRepository()); } QHash< int, QByteArray > CourseModel::roleNames() const { QHash roles; roles[TitleRole] = "title"; roles[DescriptionRole] = "description"; roles[IdRole] = "id"; roles[LanguageRole] = "language"; roles[DataRole] = "dataRole"; return roles; } void CourseModel::setResourceRepository(IResourceRepository *resourceRepository) { if (resourceRepository == nullptr) { qCWarning(ARTIKULATE_CORE()) << "setting resource repository to nullptr, this shall never happen"; } Q_ASSERT(resourceRepository != nullptr); if (m_resourceRepository == resourceRepository) { return; } beginResetModel(); if (m_resourceRepository) { disconnect(m_resourceRepository, &IResourceRepository::courseAboutToBeAdded, this, &CourseModel::onCourseAboutToBeAdded); disconnect(m_resourceRepository, &IResourceRepository::courseAdded, this, &CourseModel::onCourseAdded); disconnect(m_resourceRepository, &IResourceRepository::courseAboutToBeRemoved, this, &CourseModel::onCourseAboutToBeRemoved); } m_resourceRepository = resourceRepository; m_courses.clear(); if (m_resourceRepository) { connect(m_resourceRepository, &IResourceRepository::courseAboutToBeAdded, this, &CourseModel::onCourseAboutToBeAdded); connect(m_resourceRepository, &IResourceRepository::courseAdded, this, &CourseModel::onCourseAdded); connect(m_resourceRepository, &IResourceRepository::courseAboutToBeRemoved, this, &CourseModel::onCourseAboutToBeRemoved); } + m_courses.clear(); + QString languageId; + if (m_language) { + languageId = m_language->id(); + } if (m_resourceRepository) { - m_courses = m_resourceRepository->courses(m_language); + for (auto course : m_resourceRepository->courses(languageId)) { + m_courses.append(course.get()); + } } endResetModel(); emit resourceManagerChanged(); } IResourceRepository * CourseModel::resourceRepository() const { return m_resourceRepository; } Language * CourseModel::language() const { return m_language; } void CourseModel::setLanguage(Language *language) { beginResetModel(); m_language = language; - m_courses = m_resourceRepository->courses(m_language); + m_courses.clear(); + QString languageId; + if (m_language) { + languageId = m_language->id(); + } + for (auto course : m_resourceRepository->courses(languageId)) { + m_courses.append(course.get()); + } emit languageChanged(); endResetModel(); emit rowCountChanged(); } QVariant CourseModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } if (index.row() >= m_courses.count()) { return QVariant(); } ICourse * const course = m_courses.at(index.row()); switch(role) { case Qt::DisplayRole: return !course->title().isEmpty()? QVariant(course->title()): QVariant(i18nc("@item:inlistbox:", "unknown")); case Qt::ToolTipRole: return QVariant(course->title()); case TitleRole: return course->title(); case DescriptionRole: return course->description(); case IdRole: return course->id(); case ContributerResourceRole: return false;// m_resources.at(index.row())->isContributorResource();//FIXME case LanguageRole: - return QVariant::fromValue(course->language()); + return QVariant::fromValue(course->language().get()); case DataRole: return QVariant::fromValue(course); default: return QVariant(); } } int CourseModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { return 0; } return m_courses.count(); } void CourseModel::onCourseAboutToBeAdded(ICourse *course, int index) { Q_UNUSED(index); beginInsertRows(QModelIndex(), m_courses.count(), m_courses.count()); m_courses.append(course); connect(course, SIGNAL(titleChanged()), m_signalMapper, SLOT(map())); //TODO add missing signals } void CourseModel::onCourseAdded() { updateMappings(); endInsertRows(); emit rowCountChanged(); } void CourseModel::onCourseAboutToBeRemoved(int index) { - if (index >= m_resourceRepository->courses(m_language).count()) { + QString languageId; + if (m_language) { + languageId = m_language->id(); + } + if (index >= m_resourceRepository->courses(languageId).count()) { return; } - ICourse *originalCourse = m_resourceRepository->courses(m_language).at(index); + ICourse *originalCourse = m_resourceRepository->courses(languageId).at(index).get(); int modelIndex = m_courses.indexOf(originalCourse); if (modelIndex == -1) { qCWarning(ARTIKULATE_LOG) << "Cannot remove course from model, not registered"; return; } beginRemoveRows(QModelIndex(), modelIndex, modelIndex); m_courses.removeAt(modelIndex); endRemoveRows(); emit rowCountChanged(); } void CourseModel::emitCourseChanged(int row) { emit courseChanged(row); emit dataChanged(index(row, 0), index(row, 0)); } QVariant CourseModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) { return QVariant(); } if (orientation == Qt::Vertical) { return QVariant(section + 1); } return QVariant(i18nc("@title:column", "Course")); } void CourseModel::updateMappings() { int courses = m_courses.count(); for (int i = 0; i < courses; i++) { m_signalMapper->setMapping(m_courses.at(i), i); } } QVariant CourseModel::course(int row) const { return data(index(row, 0), CourseModel::DataRole); } diff --git a/src/models/coursemodel.h b/src/models/coursemodel.h index f7d6f04..2e6a10a 100644 --- a/src/models/coursemodel.h +++ b/src/models/coursemodel.h @@ -1,88 +1,89 @@ /* * Copyright 2013-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 . */ #ifndef COURSEMODEL_H #define COURSEMODEL_H #include +#include class IResourceRepository; class ICourse; class Language; class QSignalMapper; class CourseModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(Language *language READ language WRITE setLanguage NOTIFY languageChanged) Q_PROPERTY(int size READ rowCount NOTIFY rowCountChanged) public: enum courseRoles { TitleRole = Qt::UserRole + 1, DescriptionRole, IdRole, ContributerResourceRole, LanguageRole, DataRole }; explicit CourseModel(QObject *parent = nullptr); ~CourseModel() override = default; /** * Reimplemented from QAbstractListModel::roleNames() */ QHash roleNames() const override; IResourceRepository * resourceRepository() const; void setLanguage(Language *language); Language * language() const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; Q_INVOKABLE QVariant course(int index) const; protected: void setResourceRepository(IResourceRepository *resourceRepository); Q_SIGNALS: void courseChanged(int index); void resourceManagerChanged(); void languageChanged(); void rowCountChanged(); private Q_SLOTS: void onCourseAboutToBeAdded(ICourse *resource, int index); void onCourseAdded(); void onCourseAboutToBeRemoved(int index); void emitCourseChanged(int row); private: /** * Updates internal mappings of course signals. */ void updateMappings(); IResourceRepository *m_resourceRepository; Language *m_language; - QVector m_courses; + QVector m_courses; QSignalMapper *m_signalMapper; }; #endif // COURSEMODEL_H diff --git a/src/models/languageresourcemodel.cpp b/src/models/languageresourcemodel.cpp index 46fc75c..30cc6ff 100644 --- a/src/models/languageresourcemodel.cpp +++ b/src/models/languageresourcemodel.cpp @@ -1,201 +1,204 @@ /* * Copyright 2013 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 "languageresourcemodel.h" #include "application.h" #include "core/language.h" #include "core/iresourcerepository.h" #include #include #include #include "artikulate_debug.h" LanguageResourceModel::LanguageResourceModel(QObject* parent) : QAbstractListModel(parent) , m_repository(nullptr) , m_view(LanguageModel::NonEmptyGhnsOnlyLanguages) , m_signalMapper(new QSignalMapper(this)) { connect(m_signalMapper, SIGNAL(mapped(int)), SLOT(emitLanguageChanged(int))); setResourceRepository(artikulateApp->resourceRepository()); } QHash< int, QByteArray > LanguageResourceModel::roleNames() const { QHash roles; roles[TitleRole] = "title"; roles[I18nTitleRole] = "i18nTitle"; roles[IdRole] = "id"; roles[DataRole] = "dataRole"; roles[CourseNumberRole] = "courseNumberRole"; return roles; } void LanguageResourceModel::setResourceRepository(IResourceRepository *repository) { if (m_repository == repository) { return; } if (m_repository) { m_repository->disconnect(this); } m_repository = repository; updateDisplayedLanguages(); emit resourceRepositoryChanged(); } IResourceRepository * LanguageResourceModel::resourceRepository() const { return m_repository; } QVariant LanguageResourceModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (index.row() >= m_languages.count()) { return QVariant(); } Language * const language = m_languages.at(index.row()); switch(role) { case Qt::DisplayRole: return !language->title().isEmpty() ? QVariant(language->title()): QVariant(i18nc("@item:inlistbox:", "unknown")); case Qt::ToolTipRole: return QVariant(language->title()); case TitleRole: return language->title(); case I18nTitleRole: return language->i18nTitle(); case IdRole: return language->id(); case DataRole: return QVariant::fromValue(language); case CourseNumberRole: return m_languages.count(); default: return QVariant(); } } int LanguageResourceModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return m_languages.count(); } void LanguageResourceModel::onLanguageAboutToBeAdded(Language *language, int index) { beginInsertRows(QModelIndex(), index, index); m_languages.append(language); connect(language, SIGNAL(titleChanged()), m_signalMapper, SLOT(map())); connect(language, SIGNAL(phonemesChanged()), m_signalMapper, SLOT(map())); connect(language, SIGNAL(phonemeGroupsChanged()), m_signalMapper, SLOT(map())); } void LanguageResourceModel::onLanguageAdded() { updateMappings(); endInsertRows(); } void LanguageResourceModel::onLanguageAboutToBeRemoved(int index) { if (!m_repository) { return; } - Language *originalLanguage = m_repository->languages().at(index); + Language *originalLanguage = m_repository->languages().at(index).get(); int modelIndex = m_languages.indexOf(originalLanguage); if (modelIndex == -1) { qCWarning(ARTIKULATE_LOG()) << "Cannot remove language from model, not registered"; return; } beginRemoveRows(QModelIndex(), modelIndex, modelIndex); originalLanguage->disconnect(m_signalMapper); m_languages.removeAt(modelIndex); } void LanguageResourceModel::onLanguageRemoved() { endRemoveRows(); } void LanguageResourceModel::emitLanguageChanged(int row) { emit languageChanged(row); emit dataChanged(index(row, 0), index(row, 0)); } QVariant LanguageResourceModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) { return QVariant(); } if (orientation == Qt::Vertical) { return QVariant(section + 1); } return QVariant(i18nc("@title:column", "Language")); } void LanguageResourceModel::setView(LanguageModel::LanguageResourceView view) { if (m_view == view) { return; } m_view = view; updateDisplayedLanguages(); } void LanguageResourceModel::updateDisplayedLanguages() { beginResetModel(); m_languages.clear(); if (m_repository) { - m_languages = m_repository->languages(); + m_languages.clear(); + for (auto language: m_repository->languages()) { + m_languages.append(language.get()); + } } updateMappings(); endResetModel(); } LanguageModel::LanguageResourceView LanguageResourceModel::view() const { return m_view; } void LanguageResourceModel::updateMappings() { int languages = m_languages.count(); for (int i = 0; i < languages; i++) { m_signalMapper->setMapping(m_languages.at(i), i); } } diff --git a/src/models/skeletonmodel.cpp b/src/models/skeletonmodel.cpp index c06a6e2..d96edfa 100644 --- a/src/models/skeletonmodel.cpp +++ b/src/models/skeletonmodel.cpp @@ -1,185 +1,185 @@ /* * Copyright 2013 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 "skeletonmodel.h" #include "core/icourse.h" #include "core/contributorrepository.h" #include "core/resources/skeletonresource.h" #include #include #include #include "artikulate_debug.h" SkeletonModel::SkeletonModel(QObject *parent) : QAbstractListModel(parent) , m_repository(nullptr) , m_signalMapper(new QSignalMapper(this)) { connect(m_signalMapper, SIGNAL(mapped(int)), SLOT(emitSkeletonChanged(int))); } QHash< int, QByteArray > SkeletonModel::roleNames() const { QHash roles; roles[TitleRole] = "title"; roles[DescriptionRole] = "description"; roles[IdRole] = "id"; roles[DataRole] = "dataRole"; return roles; } void SkeletonModel::setResourceRepository(IEditableRepository *repository) { if (m_repository == repository) { return; } beginResetModel(); if (m_repository) { m_repository->disconnect(this); } m_repository = repository; if (m_repository) { //FIXME // connect(m_resourceManager, &ResourceManager::skeletonAboutToBeAdded, this, &SkeletonModel::onSkeletonAboutToBeAdded); // connect(m_resourceManager, &ResourceManager::skeletonAdded, this, &SkeletonModel::onSkeletonAdded); // connect(m_resourceManager, &ResourceManager::skeletonAboutToBeRemoved, this, &SkeletonModel::onSkeletonsAboutToBeRemoved); // connect(m_resourceManager, &ResourceManager::skeletonRemoved, this, &SkeletonModel::onSkeletonsRemoved); } endResetModel(); emit resourceRepositoryChanged(); } IEditableRepository * SkeletonModel::resourceRepository() const { return m_repository; } QVariant SkeletonModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } if (index.row() >= m_repository->skeletons().count()) { return QVariant(); } - ICourse * const skeleton = m_repository->skeletons().at(index.row()); + std::shared_ptr skeleton = m_repository->skeletons().at(index.row()); switch(role) { case Qt::DisplayRole: return !skeleton->title().isEmpty() ? QVariant(skeleton->title()): QVariant(i18nc("@item:inlistbox:", "unknown")); case Qt::ToolTipRole: return QVariant(skeleton->title()); case TitleRole: return skeleton->title(); case DescriptionRole: return skeleton->description(); case IdRole: return skeleton->id(); case DataRole: - return QVariant::fromValue(skeleton); + return QVariant::fromValue(skeleton.get()); default: return QVariant(); } } int SkeletonModel::rowCount(const QModelIndex &parent) const { if (!m_repository) { return 0; } if (parent.isValid()) { return 0; } return m_repository->skeletons().count(); } void SkeletonModel::onSkeletonAboutToBeAdded(ICourse *skeleton, int index) { connect(skeleton, SIGNAL(titleChanged()), m_signalMapper, SLOT(map())); //TODO add missing signals beginInsertRows(QModelIndex(), index, index); } void SkeletonModel::onSkeletonAdded() { updateMappings(); endInsertRows(); emit countChanged(); } void SkeletonModel::onSkeletonsAboutToBeRemoved(int first, int last) { beginRemoveRows(QModelIndex(), first, last); } void SkeletonModel::onSkeletonsRemoved() { endRemoveRows(); emit countChanged(); } void SkeletonModel::emitSkeletonChanged(int row) { emit skeletonChanged(row); emit dataChanged(index(row, 0), index(row, 0)); } QVariant SkeletonModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) { return QVariant(); } if (orientation == Qt::Vertical) { return QVariant(section + 1); } return QVariant(i18nc("@title:column", "Skeleton")); } int SkeletonModel::count() const { return m_repository->skeletons().count(); } void SkeletonModel::updateMappings() { int skeletons = m_repository->skeletons().count(); for (int i = 0; i < skeletons; ++i) { - m_signalMapper->setMapping(m_repository->skeletons().at(i), i); + m_signalMapper->setMapping(m_repository->skeletons().at(i).get(), i); } } QVariant SkeletonModel::skeleton(int row) const { return data(index(row, 0), SkeletonModel::DataRole); } diff --git a/src/ui/exportghnsdialog.cpp b/src/ui/exportghnsdialog.cpp index c54d9cf..77e1420 100644 --- a/src/ui/exportghnsdialog.cpp +++ b/src/ui/exportghnsdialog.cpp @@ -1,85 +1,86 @@ /* * Copyright 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 "exportghnsdialog.h" #include "core/iresourcerepository.h" #include "core/resources/languageresource.h" #include "core/resources/editablecourseresource.h" #include "core/resources/courseparser.h" +#include "core/language.h" #include "core/icourse.h" #include "artikulate_debug.h" #include #include #include #include #include ExportGhnsDialog::ExportGhnsDialog(IResourceRepository *repository) { ui = new Ui::ExportGhnsDialog; ui->setupUi(this); // require to set a proper directory ui->buttonBox->button(QDialogButtonBox::Apply)->setDisabled(true); ui->buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Export")); connect(ui->exportDirectory, &QLineEdit::textChanged, this, [=](){ const bool directorySet = !ui->exportDirectory->text().isEmpty(); ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(directorySet); }); connect(ui->buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &ExportGhnsDialog::onExportCourse); // directory selection dialog connect(ui->selectDirectoryButton, &QToolButton::clicked, this, [=]() { // TODO save last path in config file const QString dir = QFileDialog::getExistingDirectory( this, i18n("Export Directory"), QString(), QFileDialog::ShowDirsOnly); ui->exportDirectory->setText(dir); }); // add courses to combo box int counter = 0; - for (auto *language : repository->languages()) { - for (auto *course : repository->courses(language)) { + for (auto language : repository->languages()) { + for (auto course : repository->courses(language->id())) { ui->courseListCombo->insertItem(counter, course->i18nTitle(), - QVariant::fromValue(course)); + QVariant::fromValue(course.get())); ++counter; } } connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); } ExportGhnsDialog::~ExportGhnsDialog() { delete ui; } void ExportGhnsDialog::onExportCourse() { ICourse *res = qobject_cast( ui->courseListCombo->currentData().value()); qCDebug(ARTIKULATE_LOG) << res << "export GHNS file for" << res->i18nTitle(); CourseParser::exportCourseToGhnsPackage(res, ui->exportDirectory->text()); }