diff --git a/autotests/courseresource/test_courseresource.cpp b/autotests/courseresource/test_courseresource.cpp index c4a0171..9a8cdb3 100644 --- a/autotests/courseresource/test_courseresource.cpp +++ b/autotests/courseresource/test_courseresource.cpp @@ -1,201 +1,205 @@ /* * 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/phonemegroup.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() { std::unique_ptr language(new Language); language->setId("de"); + auto group = language->addPhonemeGroup("id", "title"); + group->addPhoneme("g", "G"); + group->addPhoneme("u", "U"); 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()); + QCOMPARE(firstPhrase->phonemes().count(), 2); } void TestCourseResource::unitAddAndRemoveHandling() { // boilerplate 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 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(std::move(unit)); QCOMPARE(course.unitList().count(), initialUnitNumber + 1); QCOMPARE(spyAboutToBeAdded.count(), 1); QCOMPARE(spyAdded.count(), 1); } void TestCourseResource::coursePropertyChanges() { // boilerplate 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 { std::shared_ptr testLanguage; QSignalSpy spy(&course, SIGNAL(languageChanged())); QCOMPARE(spy.count(), 0); course.setLanguage(testLanguage); QCOMPARE(course.language(), testLanguage); QCOMPARE(spy.count(), 1); } } QTEST_GUILESS_MAIN(TestCourseResource) diff --git a/autotests/testdata/courses/de.xml b/autotests/testdata/courses/de.xml index 66f5e2a..2d948cf 100644 --- a/autotests/testdata/courses/de.xml +++ b/autotests/testdata/courses/de.xml @@ -1,41 +1,44 @@ de artikulate-basic Artikulate Deutsch Ein Kurs in (hoch-)deutscher Aussprache. de 1 {dd60f04a-eb37-44b7-9787-67aaf7d3578d} Auf der Straße 1 {3a4c1926-60d7-44c6-80d1-03165a641c75} Guten Tag. de_01.ogg sentence - + + g + u + 2 Auf Wiedersehen. de_02.ogg sentence 3 {56b0d0a2-8505-4a9e-89f7-a933824fac89} Wie geht es dir? sentence diff --git a/src/core/icourse.h b/src/core/icourse.h index 933b607..2e063e8 100644 --- a/src/core/icourse.h +++ b/src/core/icourse.h @@ -1,74 +1,75 @@ /* * 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 Phoneme; 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) 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 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/language.cpp b/src/core/language.cpp index cc91462..90ed602 100644 --- a/src/core/language.cpp +++ b/src/core/language.cpp @@ -1,124 +1,117 @@ /* * 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() { } -Language::~Language() -{ - qDeleteAll(m_phonemeGroups); -} +Language::~Language() = default; 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 +QVector> Language::phonemes() const { - QList list; - foreach (PhonemeGroup *group, m_phonemeGroups) { - list.append(group->phonemes()); + QVector> list; + for (auto group : m_phonemeGroups) { + list << group->phonemes(); } return list; } -QList Language::phonemeGroups() const +QVector> Language::phonemeGroups() const { return m_phonemeGroups; } -PhonemeGroup * Language::addPhonemeGroup(const QString &identifier, const QString &title) +std::shared_ptr 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) { + for (auto group : m_phonemeGroups) { + if (QString::compare(group->id(), identifier) == 0) { qCWarning(ARTIKULATE_LOG) << "Pronunciation Group identifier already registered, aborting"; - return nullptr; + return std::shared_ptr(); } - ++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); + std::shared_ptr phonemeGroup(new PhonemeGroup); + phonemeGroup->setId(identifier); + phonemeGroup->setTitle(title); + m_phonemeGroups.append(phonemeGroup); + connect(phonemeGroup.get(), &PhonemeGroup::phonemeAdded, this, &Language::phonemesChanged); emit phonemeGroupsChanged(); - return newGroup; + return phonemeGroup; } diff --git a/src/core/language.h b/src/core/language.h index fd69a74..7ff7ceb 100644 --- a/src/core/language.h +++ b/src/core/language.h @@ -1,73 +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 +#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(); ~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); + QVector> phonemes() const; + QVector> phonemeGroups() const; + std::shared_ptr addPhonemeGroup(const QString &identifier, const QString &title); Q_SIGNALS: void idChanged(); void associatedLanguageItemChanged(); void titleChanged(); void i18nTitleChanged(); void phonemesChanged(); void phonemeGroupsChanged(); private: QString m_id; QString m_title; QString m_i18nTitle; QUrl m_file; - QList m_phonemeGroups; + QVector> m_phonemeGroups; }; #endif // LANGUAGE_H diff --git a/src/core/phonemegroup.cpp b/src/core/phonemegroup.cpp index a4baa08..98aff00 100644 --- a/src/core/phonemegroup.cpp +++ b/src/core/phonemegroup.cpp @@ -1,133 +1,120 @@ /* * 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 "phonemegroup.h" #include "phoneme.h" #include "artikulate_debug.h" -PhonemeGroup::PhonemeGroup(QObject *parent) - : QObject(parent) +PhonemeGroup::PhonemeGroup() + : QObject(nullptr) { } PhonemeGroup::~PhonemeGroup() { for (auto phoneme : m_phonemes) { phoneme->deleteLater(); } m_phonemes.clear(); } QString PhonemeGroup::id() const { return m_id; } void PhonemeGroup::setId(const QString &id) { if (id != m_id) { m_id = id; emit idChanged(); } } QString PhonemeGroup::title() const { return m_title; } void PhonemeGroup::setTitle(const QString &title) { if (QString::compare(title, m_title) != 0) { m_title = title; emit titleChanged(); } } QString PhonemeGroup::description() const { return m_description; } void PhonemeGroup::setDescription(const QString &description) { m_description = description; emit descriptionChanged(); } -QList< Phoneme* > PhonemeGroup::phonemes() const +QVector> PhonemeGroup::phonemes() const { return m_phonemes; } -bool PhonemeGroup::contains(Phoneme *phoneme) const +bool PhonemeGroup::contains(std::shared_ptr phoneme) const { - QList::ConstIterator iter = m_phonemes.constBegin(); - while (iter != m_phonemes.constEnd()) { - if (QString::compare((*iter)->id(), phoneme->id()) == 0) { + for (auto testPhoneme : m_phonemes) { + if (QString::compare(testPhoneme->id(), phoneme->id()) == 0) { return true; } - ++iter; } return false; } -void PhonemeGroup::addPhoneme(Phoneme *phoneme) +std::shared_ptr PhonemeGroup::addPhoneme(std::unique_ptr phoneme) { - QList::ConstIterator iter = m_phonemes.constBegin(); - while (iter != m_phonemes.constEnd()) { - if (QString::compare((*iter)->id(), phoneme->id()) == 0) { - qCWarning(ARTIKULATE_LOG) << "Phoneme identifier already registered in group "<< m_title <<", aborting"; - return; - } - ++iter; + std::shared_ptr newPhoneme(std::move(phoneme)); + if (!contains(newPhoneme)) { + m_phonemes.append(newPhoneme); + } + else { + qCWarning(ARTIKULATE_LOG) << "Phoneme identifier already registered in group "<< m_title <<", aborting"; } - m_phonemes.append(phoneme); + return std::shared_ptr(); } -Phoneme * PhonemeGroup::addPhoneme(const QString &identifier, const QString &title) +std::shared_ptr PhonemeGroup::addPhoneme(const QString &identifier, const QString &title) { Q_ASSERT(!identifier.isEmpty()); // check that identifier is not used - QList::ConstIterator iter = m_phonemes.constBegin(); - while (iter != m_phonemes.constEnd()) { - if (QString::compare((*iter)->id(), identifier) == 0) { + for (auto phoneme : m_phonemes) { + if (QString::compare(phoneme->id(), identifier) == 0) { qCWarning(ARTIKULATE_LOG) << "Phoneme identifier " << identifier <<" already registered in group " << m_title <<", aborting"; - return nullptr; + return std::shared_ptr(); } - ++iter; } - // create phoneme and add it - Phoneme *newPhoneme = new Phoneme(); + std::unique_ptr newPhoneme(new Phoneme); newPhoneme->setId(identifier); newPhoneme->setTitle(title); - addPhoneme(newPhoneme); - - return newPhoneme; -} - -void PhonemeGroup::removePhoneme(Phoneme *phoneme) -{ - m_phonemes.removeOne(phoneme); + return addPhoneme(std::move(newPhoneme)); } diff --git a/src/core/phonemegroup.h b/src/core/phonemegroup.h index c60a158..7ac75e8 100644 --- a/src/core/phonemegroup.h +++ b/src/core/phonemegroup.h @@ -1,77 +1,76 @@ /* * 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 PHONEMEGROUP_H #define PHONEMEGROUP_H #include "artikulatecore_export.h" +#include +#include #include #include -#include "phoneme.h" class QString; class Phoneme; /** * \class PhonemeGroup */ class ARTIKULATECORE_EXPORT PhonemeGroup : public QObject { Q_OBJECT Q_PROPERTY(QString id READ id WRITE setId NOTIFY idChanged) Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged) public: - explicit PhonemeGroup(QObject *parent = 0); + explicit PhonemeGroup(); ~PhonemeGroup() override; QString id() const; void setId(const QString &id); QString title() const; void setTitle(const QString &title); QString description() const; void setDescription(const QString &description); - void addPhoneme(Phoneme *phoneme); - Phoneme * addPhoneme(const QString &identifier, const QString &title); - void removePhoneme(Phoneme *phoneme); - QList phonemes() const; + std::shared_ptr addPhoneme(std::unique_ptr phoneme); + std::shared_ptr addPhoneme(const QString &identifier, const QString &title); + QVector> phonemes() const; /** * Checks by identifier comparison whether phoneme is registered in this group. * * \param poneme is the phoneme to be checked for if registered * \return true if registered, false otherwise */ - bool contains(Phoneme *phoneme) const; + bool contains(std::shared_ptr phoneme) const; -signals: +Q_SIGNALS: void idChanged(); void titleChanged(); void descriptionChanged(); void phonemeAdded(const Phoneme&); - void phonemeRemoved(const Phoneme&); private: Q_DISABLE_COPY(PhonemeGroup) QString m_id; QString m_title; QString m_description; - QList m_phonemes; + QVector> m_phonemes; }; #endif // PHONEMEGROUP_H diff --git a/src/core/phrase.cpp b/src/core/phrase.cpp index 32c4f4b..f16dc9f 100644 --- a/src/core/phrase.cpp +++ b/src/core/phrase.cpp @@ -1,338 +1,335 @@ /* * 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 "phrase.h" #include "libsound/src/capturedevicecontroller.h" #include "libsound/src/outputdevicecontroller.h" #include "unit.h" #include "icourse.h" #include "settings.h" #include "artikulate_debug.h" #include Phrase::Phrase(QObject *parent) : QObject(parent) , m_type(Phrase::AllTypes) , m_editState(Unknown) , m_unit(nullptr) , m_trainingProgress(0) , m_skipCounter(0) , m_excludedFromUnit(false) { connect(this, &Phrase::idChanged, this, &Phrase::modified); connect(this, &Phrase::typeChanged, this, &Phrase::modified); connect(this, &Phrase::textChanged, this, &Phrase::modified); connect(this, &Phrase::soundChanged, this, &Phrase::modified); connect(this, &Phrase::editStateChanged, this, &Phrase::modified); connect(this, &Phrase::i18nTextChanged, this, &Phrase::modified); connect(this, &Phrase::phonemesChanged, this, &Phrase::modified); connect(this, &Phrase::excludedChanged, this, &Phrase::modified); } -Phrase::~Phrase() -{ - -} +Phrase::~Phrase() = default; QString Phrase::id() const { return m_id; } void Phrase::setId(const QString &id) { if (id != m_id) { m_id = id; emit idChanged(); } } QString Phrase::foreignId() const { return m_foreignId; } void Phrase::setForeignId(const QString &id) { m_foreignId = id; } QString Phrase::text() const { return m_text; } void Phrase::setText(const QString &text) { if (QString::compare(text, m_text) != 0) { m_text = text.trimmed(); emit textChanged(); } } QString Phrase::i18nText() const { return m_i18nText; } void Phrase::seti18nText(const QString &text) { if (QString::compare(text, m_i18nText) != 0) { // copy unmodified original text string m_i18nText = text; emit i18nTextChanged(); } } Phrase::Type Phrase::type() const { return m_type; } QString Phrase::typeString() const { switch(m_type) { case Word: return QStringLiteral("word"); case Expression: return QStringLiteral("expression"); case Sentence: return QStringLiteral("sentence"); case Paragraph: return QStringLiteral("paragraph"); default: return QStringLiteral("ERROR_UNKNOWN_TYPE"); } } void Phrase::setType(Phrase::Type type) { if (m_type == type) { return; } m_type = type; emit typeChanged(); } void Phrase::setType(const QString &typeString) { if (typeString == QLatin1String("word")) { setType(Word); return; } if (typeString == QLatin1String("expression")) { setType(Expression); return; } if (typeString == QLatin1String("sentence")) { setType(Sentence); return; } if (typeString == QLatin1String("paragraph")) { setType(Paragraph); return; } qCWarning(ARTIKULATE_LOG) << "Cannot set type from unknown identifier, aborting"; return; } Phrase::EditState Phrase::editState() const { return m_editState; } QString Phrase::editStateString() const { switch(m_editState) { case Unknown: return QStringLiteral("unknown"); case Translated: return QStringLiteral("translated"); case Completed: return QStringLiteral("completed"); } Q_UNREACHABLE(); } void Phrase::setEditState(Phrase::EditState state) { if (m_editState == state) { return; } m_editState = state; emit editStateChanged(); } void Phrase::setEditState(const QString &stateString) { if (stateString.isEmpty()) { return; } if (stateString == QLatin1String("unknown")) { setEditState(Unknown); return; } if (stateString == QLatin1String("translated")) { setEditState(Translated); return; } if (stateString == QLatin1String("completed")) { setEditState(Completed); return; } qCWarning(ARTIKULATE_LOG) << "Cannot set edit state from unknown identifier " << stateString << ", aborting"; return; } Unit * Phrase::unit() const { return m_unit; } void Phrase::setUnit(Unit *unit) { if (unit == m_unit) { return; } m_unit = unit; emit unitChanged(); } QUrl Phrase::sound() const { return m_nativeSoundFile; } void Phrase::setSound(const QUrl &soundFile) { if (!soundFile.isValid() || soundFile.isEmpty()) { qCWarning(ARTIKULATE_LOG) << "Not setting empty sound file path."; return; } m_nativeSoundFile = soundFile; emit soundChanged(); } QString Phrase::soundFileUrl() const { return m_nativeSoundFile.toLocalFile(); } QString Phrase::soundFileOutputPath() const { if (m_nativeSoundFile.isEmpty()) { QString outputDir = m_unit->course()->file().path() + '/'; //TODO take care that this is proper ASCII return outputDir + id() + ".ogg"; } else { return soundFileUrl(); } } void Phrase::setSoundFileUrl() { if (soundFileOutputPath() != m_nativeSoundFile.toLocalFile()) { m_nativeSoundFile = QUrl::fromLocalFile(soundFileOutputPath()); emit soundChanged(); emit modified(); } } bool Phrase::isExcluded() const { return m_excludedFromUnit; } void Phrase::setExcluded(bool excluded) { if (excluded == m_excludedFromUnit) { return; } m_excludedFromUnit = excluded; emit excludedChanged(); } int Phrase::progress() const { return static_cast(m_trainingProgress); } void Phrase::setProgress(int value) { Q_ASSERT(value >= 0); if (value < 0) { value = 0; } if (m_trainingProgress == static_cast(value)) { return; } m_trainingProgress = static_cast(value); emit progressChanged(); } void Phrase::updateProgress(Phrase::Progress progress) { // logic of progress computation: // a) if skipped 3 times in a row, decrease progress // b) if done and skipped less than two times in a row, increase progress if (progress == Progress::Done) { m_skipCounter = 0; if (m_trainingProgress < 3) { ++m_trainingProgress; emit progressChanged(); } return; } if (progress == Progress::Skip) { ++m_skipCounter; if (m_skipCounter > 2 && m_trainingProgress > 0) { --m_trainingProgress; emit progressChanged(); } return; } } -QList Phrase::phonemes() const +QVector Phrase::phonemes() const { return m_phonemes; } bool Phrase::hasPhoneme(Phoneme* phoneme) { return m_phonemes.contains(phoneme); } void Phrase::addPhoneme(Phoneme *phoneme) { if (!m_phonemes.contains(phoneme)) { m_phonemes.append(phoneme); emit phonemesChanged(); //FIXME tell Unit to also send corresponding signal! } } void Phrase::removePhoneme(Phoneme *phoneme) { if (m_phonemes.removeOne(phoneme)) { emit phonemesChanged(); //FIXME tell Unit to also send corresponding signal! } } diff --git a/src/core/phrase.h b/src/core/phrase.h index def458a..48db14d 100644 --- a/src/core/phrase.h +++ b/src/core/phrase.h @@ -1,139 +1,140 @@ /* * Copyright 2013-2014 Andreas Cord-Landwehr * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef PHRASE_H #define PHRASE_H #include "artikulatecore_export.h" #include #include #include +#include #include class QString; class Phoneme; class Unit; class QUrl; class ARTIKULATECORE_EXPORT Phrase : public QObject { Q_OBJECT Q_PROPERTY(QString id READ id WRITE setId NOTIFY idChanged) Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) Q_PROPERTY(QString i18nText READ i18nText WRITE seti18nText NOTIFY i18nTextChanged) Q_PROPERTY(QString soundFileUrl READ soundFileUrl NOTIFY soundChanged) Q_PROPERTY(Phrase::Type type READ type WRITE setType NOTIFY typeChanged) Q_PROPERTY(Phrase::EditState editState READ editState WRITE setEditState NOTIFY editStateChanged) Q_PROPERTY(Unit *unit READ unit NOTIFY unitChanged) Q_PROPERTY(bool excluded READ isExcluded NOTIFY excludedChanged) Q_PROPERTY(int progress READ progress NOTIFY progressChanged) public: Q_ENUMS(EditState) Q_ENUMS(TrainingState) Q_ENUMS(Type) enum EditState { Unknown, Translated, Completed }; enum TrainingState { //TODO not needed anymore with statistics Trained, Untrained }; enum class Progress { Skip, Done }; enum Type { Word, Expression, Sentence, Paragraph, AllTypes }; explicit Phrase(QObject *parent = nullptr); ~Phrase(); QString id() const; void setId(const QString &id); QString foreignId() const; void setForeignId(const QString &id); QString text() const; void setText(const QString &text); QString i18nText() const; void seti18nText(const QString &text); Unit * unit() const; void setUnit(Unit *unit); Phrase::Type type() const; QString typeString() const; void setType(Phrase::Type type); void setType(const QString &typeString); QString soundFileUrl() const; Q_INVOKABLE QString soundFileOutputPath() const; Q_INVOKABLE void setSoundFileUrl(); Phrase::EditState editState() const; QString editStateString() const; void setEditState(Phrase::EditState state); void setEditState(const QString &stateString); QUrl sound() const; void setSound(const QUrl &soundFile); - QList phonemes() const; + QVector phonemes() const; bool isExcluded() const; void setExcluded(bool excluded = false); int progress() const; void setProgress(int value); void updateProgress(Phrase::Progress progress); Q_INVOKABLE bool hasPhoneme(Phoneme *phoneme); Q_INVOKABLE void addPhoneme(Phoneme *phoneme); Q_INVOKABLE void removePhoneme(Phoneme *phoneme); Q_SIGNALS: void idChanged(); void unitChanged(); void textChanged(); void i18nTextChanged(); void typeChanged(); void editStateChanged(); void soundChanged(); void excludedChanged(); void phonemesChanged(); void modified(); void progressChanged(); private: Q_DISABLE_COPY(Phrase) QString m_id; QString m_foreignId; QString m_text; QString m_i18nText; Type m_type; EditState m_editState; Unit *m_unit; unsigned m_trainingProgress; int m_skipCounter; // count how many skips occurred since last progress update bool m_excludedFromUnit; - QList m_phonemes; + QVector m_phonemes; QUrl m_nativeSoundFile; }; #endif // PHRASE_H diff --git a/src/core/resources/courseparser.cpp b/src/core/resources/courseparser.cpp index 7e69584..4d2665b 100644 --- a/src/core/resources/courseparser.cpp +++ b/src/core/resources/courseparser.cpp @@ -1,425 +1,425 @@ /* * 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; } -std::vector> CourseParser::parseUnits(const QUrl &path) +std::vector> CourseParser::parseUnits(const QUrl &path, QVector> phonemes) { 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, path, elementOk); + auto unit = parseUnit(xml, path, phonemes, elementOk); if (elementOk) { 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; } -std::unique_ptr CourseParser::parseUnit(QXmlStreamReader &xml, const QUrl &path, bool &ok) +std::unique_ptr CourseParser::parseUnit(QXmlStreamReader &xml, const QUrl &path, QVector> phonemes, bool &ok) { 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() == "foreignId") { unit->setForeignId(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, path, elementOk); + auto phrase = parsePhrase(xml, path, phonemes, 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, const QUrl &path, bool &ok) +Phrase * CourseParser::parsePhrase(QXmlStreamReader &xml, const QUrl &path, QVector> phonemes, bool &ok) { - Phrase * phrase = new Phrase(nullptr); + Phrase * phrase = new Phrase; 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() == "foreignId") { phrase->setForeignId(parseElement(xml, elementOk)); ok &= elementOk; } else if (xml.name() == "text") { phrase->setText(parseElement(xml, elementOk)); ok &= elementOk; } else if (xml.name() == "i18nText") { phrase->seti18nText(parseElement(xml, elementOk)); ok &= elementOk; } else if (xml.name() == "soundFile") { phrase->setSound(QUrl::fromLocalFile( path.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() + '/' + parseElement(xml, elementOk))); ok &= elementOk; } else if (xml.name() == "phonemes") { - parsePhonemeIds(xml, elementOk); //TODO register language phonemes at phrase + auto parsedPhonemeIds = parsePhonemeIds(xml, elementOk); + for (auto phoneme : phonemes) { + if (parsedPhonemeIds.contains(phoneme->id())) { + phrase->addPhoneme(phoneme.get()); + } + } 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 if (xml.name() == "editState") { const QString type = parseElement(xml, elementOk); if (type == "translated") { phrase->setEditState(Phrase::EditState::Translated); } else if (type == "completed") { phrase->setEditState(Phrase::EditState::Completed); } else if (type == "unknown") { phrase->setEditState(Phrase::EditState::Completed); } 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; } QStringList CourseParser::parsePhonemeIds(QXmlStreamReader &xml, bool &ok) { QStringList ids; ok = true; if (xml.tokenType() != QXmlStreamReader::StartElement && xml.name() == "phonemes") { qCWarning(ARTIKULATE_PARSER()) << "Expected to parse 'phonemes' element, aborting here"; ok = false; return ids; } xml.readNext(); while (!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == "phonemes")) { xml.readNext(); - if (xml.name() == "phoneme") { - while (!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == "phoneme")) { - if (xml.tokenType() == QXmlStreamReader::StartElement) { - bool elementOk{ false }; - if (xml.name() == "phonemeID") { - ids.append(parseElement(xml, elementOk)); - ok &= elementOk; - } else { - qCWarning(ARTIKULATE_PARSER()) << "Skipping unknown token" << xml.name(); - } - } - xml.readNext(); + if (xml.tokenType() == QXmlStreamReader::StartElement) { + if (xml.name() == "phonemeID") { + bool elementOk{ false }; + ids.append(parseElement(xml, elementOk)); + ok &= elementOk; + } else { + qCWarning(ARTIKULATE_PARSER()) << "Skipping unknown token" << xml.name(); } } } return ids; } 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(); + qCDebug(ARTIKULATE_PARSER()) << "parsed: " << elementName << " / " << xml.text().toString(); return xml.text().toString(); } 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 d54b429..fdcfb81 100644 --- a/src/core/resources/courseparser.h +++ b/src/core/resources/courseparser.h @@ -1,75 +1,76 @@ /* * 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 Phoneme; +class IResourceRepository; 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 std::vector> parseUnits(const QUrl &path); + static std::vector> parseUnits(const QUrl &path, QVector> phonemes = QVector>()); static QDomDocument serializedDocument(ICourse *course, bool trainingExport); static QDomElement serializedPhrase(Phrase *phrase, QDomDocument &document); static bool exportCourseToGhnsPackage(ICourse *course, const QString &exportPath); private: - static std::unique_ptr parseUnit(QXmlStreamReader &xml, const QUrl &path, bool &ok); - static Phrase * parsePhrase(QXmlStreamReader &xml, const QUrl &path, bool &ok); + static std::unique_ptr parseUnit(QXmlStreamReader &xml, const QUrl &path, QVector> phonemes, bool &ok); + static Phrase * parsePhrase(QXmlStreamReader &xml, const QUrl &path, QVector> phonemes, bool &ok); static QStringList parsePhonemeIds(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 ead8ea5..eeb2d31 100644 --- a/src/core/resources/courseresource.cpp +++ b/src/core/resources/courseresource.cpp @@ -1,277 +1,278 @@ /* * 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; std::shared_ptr m_language; QString m_i18nTitle; QString m_description; QVector> m_units; bool m_courseLoaded{ false }; ///> phonemes = m_language->phonemes(); + auto units = CourseParser::parseUnits(m_file, phonemes); for (auto &unit : units) { 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(); } std::shared_ptr CourseResource::language() const { return d->m_language; } void CourseResource::setLanguage(std::shared_ptr language) { if (d->m_language == language) { return; } d->m_language = language; emit languageChanged(); } std::shared_ptr CourseResource::addUnit(std::unique_ptr 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); } QList rawList; for (auto unit : d->m_units) { rawList.append(unit.get()); } return rawList; } 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/languageresource.cpp b/src/core/resources/languageresource.cpp index c77644c..199f0ff 100644 --- a/src/core/resources/languageresource.cpp +++ b/src/core/resources/languageresource.cpp @@ -1,169 +1,163 @@ /* * 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() = default; ~LanguageResourcePrivate() = default; 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_language != nullptr); } QUrl LanguageResource::path() const { return d->m_path; } std::shared_ptr LanguageResource::language() { 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_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_language->addPhonemeGroup( + auto 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_language; } diff --git a/src/core/resources/languageresource.h b/src/core/resources/languageresource.h index 19c9b6c..818268c 100644 --- a/src/core/resources/languageresource.h +++ b/src/core/resources/languageresource.h @@ -1,81 +1,76 @@ /* * 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 */ std::shared_ptr language(); private: const QScopedPointer d; }; #endif diff --git a/src/core/resources/skeletonresource.cpp b/src/core/resources/skeletonresource.cpp index 0d4cfe8..ce22efa 100644 --- a/src/core/resources/skeletonresource.cpp +++ b/src/core/resources/skeletonresource.cpp @@ -1,316 +1,317 @@ /* * 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 "core/phrase.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(); 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> SkeletonResourcePrivate::units() { if (m_unitsParsed) { return m_units; } auto units = CourseParser::parseUnits(m_path); for (auto &unit : units) { m_units.append(std::move(unit)); } m_unitsParsed = true; return m_units; } std::shared_ptr SkeletonResourcePrivate::appendUnit(std::unique_ptr unit) { units(); // ensure that units are parsed 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; } std::shared_ptr SkeletonResource::addUnit(std::unique_ptr unit) { emit unitAboutToBeAdded(unit.get(), d->units().count() - 1); auto sharedUnit = d->appendUnit(std::move(unit)); emit unitAdded(); return sharedUnit; } std::shared_ptr SkeletonResource::language() const { // skeleton must not have a dedicated language return std::shared_ptr(); } void SkeletonResource::setLanguage(std::shared_ptr language) { Q_UNUSED(language); Q_UNREACHABLE(); } QList SkeletonResource::unitList() { 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/unit.cpp b/src/core/unit.cpp index 4a5f68c..e5684c4 100644 --- a/src/core/unit.cpp +++ b/src/core/unit.cpp @@ -1,167 +1,165 @@ /* * 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 "unit.h" #include "phrase.h" #include #include #include #include #include #include "artikulate_debug.h" #include #include Unit::Unit(QObject *parent) : QObject(parent) , m_course(nullptr) , m_phraseSignalMapper(new QSignalMapper(this)) { } Unit::~Unit() { - for (auto phrase : m_phraseList) { + for (auto phrase : m_phrases) { phrase->deleteLater(); } - m_phraseList.clear(); + m_phrases.clear(); m_phraseSignalMapper->deleteLater(); } QString Unit::id() const { return m_id; } void Unit::setId(const QString &id) { if (id != m_id) { m_id = id; emit idChanged(); emit modified(); } } QString Unit::foreignId() const { return m_foreignId; } void Unit::setForeignId(const QString &id) { m_foreignId = id; } ICourse *Unit::course() const { return m_course; } void Unit::setCourse(ICourse *course) { if (course == m_course) { return; } m_course = course; emit courseChanged(); } QString Unit::title() const { return m_title; } void Unit::setTitle(const QString &title) { if (QString::compare(title, m_title) != 0) { m_title = title; emit titleChanged(); emit modified(); } } -QList< Phrase* > Unit::phraseList() const +QList Unit::phraseList() const { - return m_phraseList; + return m_phrases; } void Unit::addPhrase(Phrase *phrase) { - QList::ConstIterator iter = m_phraseList.constBegin(); - while (iter != m_phraseList.constEnd()) { + auto iter = m_phrases.constBegin(); + while (iter != m_phrases.constEnd()) { if (phrase->id() == (*iter)->id()) { qCWarning(ARTIKULATE_LOG()) << "Phrase is already contained in this unit, aborting"; return; } ++iter; } phrase->setUnit(this); - emit phraseAboutToBeAdded(phrase, m_phraseList.length()); - m_phraseList.append(phrase); + emit phraseAboutToBeAdded(phrase, m_phrases.length()); + m_phrases.append(phrase); m_phraseSignalMapper->setMapping(phrase, phrase->id()); emit phraseAdded(phrase); connect(phrase, &Phrase::typeChanged, m_phraseSignalMapper, static_cast(&QSignalMapper::map)); connect(phrase, &Phrase::modified, this, &Unit::modified); emit modified(); } QList Unit::excludedSkeletonPhraseList() const { QList excludedPhraseList; - QList::ConstIterator iter = m_phraseList.constBegin(); - while (iter != m_phraseList.constEnd()) { - if ((*iter)->isExcluded() == true) { - excludedPhraseList.append(*iter); + for (auto phrase : m_phrases) { + if (phrase->isExcluded() == true) { + excludedPhraseList.append(phrase); } - ++iter; } return excludedPhraseList; } void Unit::excludeSkeletonPhrase(const QString &phraseId) { - foreach (Phrase *phrase, m_phraseList) { + for (auto phrase : m_phrases) { if (phrase->id() == phraseId) { phrase->setExcluded(true); emit modified(); return; } } qCWarning(ARTIKULATE_LOG) << "Could not exclude phrase with ID " << phraseId << ", no phrase with this ID."; } void Unit::includeSkeletonPhrase(const QString &phraseId) { - foreach (Phrase *phrase, m_phraseList) { + for (auto phrase : m_phrases) { if (phrase->id() == phraseId) { phrase->setExcluded(false); emit modified(); return; } } qCWarning(ARTIKULATE_LOG) << "Could not include phrase with ID " << phraseId << ", no phrase with this ID."; } diff --git a/src/core/unit.h b/src/core/unit.h index 57d1504..529f4e3 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -1,88 +1,89 @@ /* * 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 UNIT_H #define UNIT_H #include "artikulatecore_export.h" -#include "phrase.h" +#include #include #include +#include #include class QSignalMapper; class QString; class Phrase; class ICourse; class ARTIKULATECORE_EXPORT Unit : 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(ICourse *course READ course WRITE setCourse NOTIFY courseChanged) public: explicit Unit(QObject *parent = nullptr); ~Unit(); QString id() const; void setId(const QString &id); QString foreignId() const; void setForeignId(const QString &id); ICourse * course() const; void setCourse(ICourse* course); QString title() const; void setTitle(const QString &title); - QList phraseList() const; + QList phraseList() const; void addPhrase(Phrase *phrase); QList excludedSkeletonPhraseList() const; /** * Removes phrase with ID \p phraseId from unit and adds ID to set * of excluded IDs. * * \param phraseId is the UID of the to be excluded phrase */ Q_INVOKABLE void excludeSkeletonPhrase(const QString &phraseId); Q_INVOKABLE void includeSkeletonPhrase(const QString &phraseId); Q_SIGNALS: void idChanged(); void titleChanged(); void courseChanged(); void displayPhraseTypeChanged(); void modified(); void phraseAdded(Phrase*); void phraseAboutToBeAdded(Phrase*,int); void phraseRemoved(Phrase*); void phraseAboutToBeRemoved(int,int); private: Q_DISABLE_COPY(Unit) QString m_id; QString m_foreignId; ICourse *m_course; QString m_title; - QList m_phraseList; + QList m_phrases; QSignalMapper *m_phraseSignalMapper; }; #endif // UNIT_H diff --git a/src/models/phonememodel.cpp b/src/models/phonememodel.cpp index 41c7b55..ba887a6 100644 --- a/src/models/phonememodel.cpp +++ b/src/models/phonememodel.cpp @@ -1,153 +1,153 @@ /* * 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 "phonememodel.h" #include "core/language.h" #include "core/phoneme.h" #include #include #include #include "artikulate_debug.h" PhonemeModel::PhonemeModel(QObject *parent) : QAbstractListModel(parent) , m_language(nullptr) , m_signalMapper(new QSignalMapper(this)) { connect(m_signalMapper, SIGNAL(mapped(int)), SLOT(emitPhonemeChanged(int))); } QHash< int, QByteArray > PhonemeModel::roleNames() const { QHash roles; roles[TitleRole] = "title"; roles[IdRole] = "id"; roles[DataRole] = "dataRole"; return roles; } Language * PhonemeModel::language() const { return m_language; } void PhonemeModel::setLanguage(Language *language) { beginResetModel(); m_language = language; emit languageChanged(); endResetModel(); } QVariant PhonemeModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } if (index.row() >= m_language->phonemes().count()) { return QVariant(); } - Phoneme * const phoneme = m_language->phonemes().at(index.row()); + auto phoneme = m_language->phonemes().at(index.row()); switch(role) { case Qt::DisplayRole: return !phoneme->title().isEmpty()? QVariant(phoneme->title()): QVariant(i18nc("@item:inlistbox:", "unknown")); case Qt::ToolTipRole: return QVariant(phoneme->title()); case TitleRole: return phoneme->title(); case IdRole: return phoneme->id(); case DataRole: - return QVariant::fromValue(phoneme); + return QVariant::fromValue(phoneme.get()); default: return QVariant(); } } int PhonemeModel::rowCount(const QModelIndex& parent) const { if (!m_language) { return 0; } if (parent.isValid()) { return 0; } return m_language->phonemes().count(); } void PhonemeModel::onPhonemeAboutToBeAdded(Phoneme *phoneme, int index) { connect(phoneme, SIGNAL(titleChanged()), m_signalMapper, SLOT(map())); beginInsertRows(QModelIndex(), index, index); } void PhonemeModel::onPhonemeAdded() { updateMappings(); endInsertRows(); } void PhonemeModel::onPhonemesAboutToBeRemoved(int first, int last) { beginRemoveRows(QModelIndex(), first, last); } void PhonemeModel::onPhonemesRemoved() { endRemoveRows(); } void PhonemeModel::emitPhonemeChanged(int row) { emit phonemeChanged(row); emit dataChanged(index(row, 0), index(row, 0)); } QVariant PhonemeModel::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", "Phoneme")); } void PhonemeModel::updateMappings() { if (!m_language) { qCDebug(ARTIKULATE_LOG) << "Aborting to update mappings, language not set."; return; } int phonemes = m_language->phonemes().count(); for (int i = 0; i < phonemes; i++) { - m_signalMapper->setMapping(m_language->phonemes().at(i), i); + m_signalMapper->setMapping(m_language->phonemes().at(i).get(), i); } }