diff --git a/autotests/unittests/editorsession/test_editorsession.cpp b/autotests/unittests/editorsession/test_editorsession.cpp index f18332d..0605e02 100644 --- a/autotests/unittests/editorsession/test_editorsession.cpp +++ b/autotests/unittests/editorsession/test_editorsession.cpp @@ -1,238 +1,333 @@ /* * 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(std::shared_ptr language, QVector> units) : IEditableCourse() , m_language(language) , m_units(units) { + for (auto unit : m_units) { + unit->setCourse(this); + } } ~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(); } std::shared_ptr language() const override { return m_language; } void setLanguage(std::shared_ptr language) override { m_language = language; emit languageChanged(); } QVector> units() override { return m_units; } std::shared_ptr addUnit(std::unique_ptr unit) override { m_units.append(std::move(unit)); - return m_units.last(); + auto unitPtr = m_units.last(); + unitPtr->setCourse(this); + return unitPtr; } 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" }; 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() { auto languageGerman = std::make_shared(); languageGerman->setId("de"); auto languageEnglish = std::make_shared(); 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}, // skeletons {course} // courses }; EditorSession session; session.setRepository(&repository); QVERIFY(session.course() == nullptr); QVERIFY(session.language() == nullptr); QVERIFY(session.skeleton() == nullptr); } void TestEditorSession::nonSkeletonSwitchingBehavior() { auto languageGerman = std::make_shared(); languageGerman->setId("de"); auto languageEnglish = std::make_shared(); 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 {}, // skeletons {courseGerman, courseEnglish} // courses }; EditorSession session; session.setRepository(&repository); QVERIFY(session.course() == nullptr); session.setCourse(courseGerman.get()); QCOMPARE(session.course()->id(), courseGerman->id()); QVERIFY(session.language() != nullptr); QCOMPARE(session.language()->id(), languageGerman->id()); QVERIFY(session.language() != nullptr); QCOMPARE(session.language()->id(), languageGerman->id()); session.setCourse(courseEnglish.get()); QVERIFY(session.course() != nullptr); QCOMPARE(session.course()->id(), courseEnglish->id()); QVERIFY(session.language() != nullptr); QCOMPARE(session.language()->id(), languageEnglish->id()); } void TestEditorSession::skeletonSwitchingBehavior() { auto languageGerman = std::make_shared(); languageGerman->setId("de"); auto languageEnglish = std::make_shared(); 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 }; EditorSession session; session.setRepository(&repository); session.setSkeleton(skeletonA.get()); Q_ASSERT(session.skeleton() != nullptr); QCOMPARE(session.skeleton()->id(), skeletonA->id()); Q_ASSERT(session.course() != nullptr); QCOMPARE(session.course()->id(), courseGermanA->id()); session.setCourse(courseEnglishA.get()); Q_ASSERT(session.course() != nullptr); QCOMPARE(session.course()->id(), courseEnglishA->id()); session.setCourse(courseGermanB.get()); QVERIFY(session.skeleton() != nullptr); QCOMPARE(session.skeleton()->id(), skeletonB->id()); QVERIFY(session.course() != nullptr); QCOMPARE(session.course()->id(), courseGermanB->id()); QVERIFY(session.language() != nullptr); QCOMPARE(session.language()->id(), languageGerman->id()); } +void TestEditorSession::iterateCourse() +{ + // language + auto language = std::make_shared(); + language->setId("de"); + + // course + std::shared_ptr unitA(new Unit); + std::shared_ptr unitB(new Unit); + 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); + auto course = std::make_shared(language, QVector>({unitA, unitB})); + + EditableRepositoryStub repository{ + {language}, // languages + {}, // skeletons + {course} // courses + }; + EditorSession session; + session.setRepository(&repository); + session.setCourse(course.get()); + + // session assumed to initialize with first units's first phrase + QCOMPARE(session.activeUnit(), unitA.get()); + QCOMPARE(session.activePhrase(), phraseA1); + QVERIFY(course.get() == session.course()); + + // test direct unit setters + session.setUnit(unitA.get()); + QCOMPARE(session.activeUnit(), unitA.get()); + session.setUnit(unitB.get()); + QCOMPARE(session.activeUnit(), unitB.get()); + + // test direct phrase setters + session.setPhrase(phraseA1); + QCOMPARE(session.activePhrase(), phraseA1); + QCOMPARE(session.activeUnit(), unitA.get()); + session.setPhrase(phraseB1); + QCOMPARE(session.activePhrase(), phraseB1); + QCOMPARE(session.activeUnit(), unitB.get()); + + // test phrase forward iterators + session.setPhrase(phraseA1); + QCOMPARE(session.activeUnit(), unitA.get()); + QCOMPARE(session.activePhrase()->id(), phraseA1->id()); + QVERIFY(session.hasNextPhrase()); + session.switchToNextPhrase(); + QCOMPARE(session.activeUnit(), unitA.get()); + QCOMPARE(session.activePhrase()->id(), phraseA2->id()); + session.switchToNextPhrase(); + QCOMPARE(session.activePhrase(), phraseB1); + session.switchToNextPhrase(); + QCOMPARE(session.activePhrase(), phraseB2); + QVERIFY(!session.hasNextPhrase()); + + // at the end, do not iterate further + session.switchToNextPhrase(); + QCOMPARE(session.activePhrase(), phraseB2); + + // test phrase backward iterators + QVERIFY(session.hasPreviousPhrase()); + session.switchToPreviousPhrase(); + QCOMPARE(session.activePhrase(), phraseB1); + session.switchToPreviousPhrase(); + QCOMPARE(session.activePhrase()->id(), phraseA2->id()); + session.switchToPreviousPhrase(); + QCOMPARE(session.activePhrase()->id(), phraseA1->id()); + QVERIFY(!session.hasPreviousPhrase()); + + // at the end, do not iterate further + session.switchToPreviousPhrase(); + QCOMPARE(session.activePhrase()->id(), phraseA1->id()); + +} + QTEST_GUILESS_MAIN(TestEditorSession) diff --git a/autotests/unittests/editorsession/test_editorsession.h b/autotests/unittests/editorsession/test_editorsession.h index c600c5a..99c0dd5 100644 --- a/autotests/unittests/editorsession/test_editorsession.h +++ b/autotests/unittests/editorsession/test_editorsession.h @@ -1,60 +1,65 @@ /* * 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 TEST_EDITORSESSION_H #define TEST_EDITORSESSION_H #include class TestEditorSession : public QObject { Q_OBJECT public: TestEditorSession() = default; private Q_SLOTS: /** * Called before every test case. */ void init(); /** * Called after every test case. */ void cleanup(); /** * @brief Construct and destruct editor session and test initial values */ void createEditorSession(); /** * @brief Test switching behavior for courses without skeleton. */ void nonSkeletonSwitchingBehavior(); /** * @brief Test handling of skeletons and respective course switching */ void skeletonSwitchingBehavior(); + + /** + * @brief Test for all iterator functionality + */ + void iterateCourse(); }; #endif diff --git a/src/core/editorsession.cpp b/src/core/editorsession.cpp index 074fe5d..3726649 100644 --- a/src/core/editorsession.cpp +++ b/src/core/editorsession.cpp @@ -1,291 +1,338 @@ /* * 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.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.get(); break; } } m_skeleton = newSkeleton; emit skeletonChanged(); } // update 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.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(); if (course != nullptr) { auto units = course->units(); if (!units.isEmpty()) { setUnit(units.constFirst().get()); return; } } } Unit * EditorSession::unit() const { return m_unit; } +Unit * EditorSession::activeUnit() 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); + if (!m_unit->phraseList().isEmpty()) { + setPhrase(m_unit->phraseList().first()); + } else { + 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::activePhrase() const +{ + return m_phrase; +} + 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 { auto unit = m_phrase->unit(); int uIndex{ -1 }; for (int i = 0; i < unit->course()->units().size(); ++i) { auto testUnit = unit->course()->units().at(i); - if (testUnit->id() == unit->id()) { + if (testUnit.get() == unit) { uIndex = i; break; } } if (uIndex > 0) { return unit->course()->units().at(uIndex - 1)->phraseList().last(); } } - return nullptr; + return m_phrase; } 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{ -1 }; for (int i = 0; i < unit->course()->units().size(); ++i) { auto testUnit = unit->course()->units().at(i); - if (testUnit->id() == unit->id()) { + if (testUnit.get() == unit) { uIndex = i; break; } } if (uIndex < unit->course()->units().length() - 1) { auto nextUnit = unit->course()->units().at(uIndex + 1); if (nextUnit->phraseList().isEmpty()) { return nullptr; } return nextUnit->phraseList().constFirst(); } } - return nullptr; + return m_phrase; } void EditorSession::switchToPreviousPhrase() { - setPhrase(previousPhrase()); + if (hasPreviousPhrase()) { + setPhrase(previousPhrase()); + } } void EditorSession::switchToNextPhrase() { - setPhrase(nextPhrase()); + if (hasNextPhrase()) { + setPhrase(nextPhrase()); + } } bool EditorSession::hasPreviousPhrase() const { - return previousPhrase() != nullptr; + if (!m_course || !m_unit || !m_phrase) { + return false; + } + + const int phraseIndex = m_phrase->unit()->phraseList().indexOf(m_phrase); + int unitIndex = -1; + for (int i = 0; i < m_unit->course()->units().size(); ++i) { + if (m_unit->course()->units().at(i).get() == m_unit) { + unitIndex = i; + break; + } + } + if (unitIndex > 0 || phraseIndex > 0) { + return true; + } + return false; } bool EditorSession::hasNextPhrase() const { - return nextPhrase() != nullptr; + if (!m_course || !m_unit || !m_phrase) { + return false; + } + + const int phraseIndex = m_phrase->unit()->phraseList().indexOf(m_phrase); + int unitIndex = -1; + for (int i = 0; i < m_unit->course()->units().size(); ++i) { + if (m_unit->course()->units().at(i).get() == m_unit) { + unitIndex = i; + break; + } + } + if ((unitIndex >= 0 && unitIndex < m_course->units().size() - 1) + || (phraseIndex >= 0 && phraseIndex < m_unit->phraseList().size() - 1)) { + return true; + } + return false; } void EditorSession::updateCourseFromSkeleton() { if (!m_course) { qCritical() << "Not updating course from skeleton, no one set."; return; } //FIXME convert to interface // m_repository->updateCourseFromSkeleton(m_course); } diff --git a/src/core/editorsession.h b/src/core/editorsession.h index f6b7cc2..a65bbb3 100644 --- a/src/core/editorsession.h +++ b/src/core/editorsession.h @@ -1,131 +1,133 @@ /* * 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 EDITORSESSION_H #define EDITORSESSION_H #include "artikulatecore_export.h" #include "phrase.h" class Language; class IEditableCourse; class Unit; class SkeletonResource; class IEditableRepository; /** * \class EditorSession * * An object of this class is used to set the current state of the editor. By this, we put all logic * how language, skeleton and course fit to each other into this class. The main concept is that * we have to fundamentally different workflows that both are modeled in this class: * * 1. Skeleton based workflow * - a skeleton is selected * - every language is available, since eventually the course should be available in every language * - for every language, there is at most one course (there is none only in case it is not created yet) * - adding new units or phrases is only possible in the skeleton course * - every course can update/sync with the skeleton * * 2. Course based workflow * - there is no skeleton from which the course is derived * - the base is a language that is selected first * - for a language there can be none to arbitrarily many courses * * The main switch is \c EditorSession::setSkeletonMode(bool) */ class ARTIKULATECORE_EXPORT EditorSession : public QObject { Q_OBJECT Q_PROPERTY(bool skeletonMode READ skeletonMode NOTIFY skeletonModeChanged) Q_PROPERTY(bool editSkeleton READ isEditSkeleton WRITE setEditSkeleton NOTIFY editSkeletonChanged) Q_PROPERTY(IEditableCourse *skeleton READ skeleton WRITE setSkeleton NOTIFY skeletonChanged) Q_PROPERTY(IEditableCourse *course READ course WRITE setCourse NOTIFY courseChanged) // editor elements depending on curently selected mode, skeleton and course /** * @brief the displayed course (skeleton or course) depending on the user selection */ Q_PROPERTY(IEditableCourse *displayedCourse READ displayedCourse NOTIFY displayedCourseChanged) Q_PROPERTY(Language *language READ language NOTIFY languageChanged) Q_PROPERTY(Unit *unit READ unit WRITE setUnit NOTIFY unitChanged) Q_PROPERTY(Phrase *phrase READ phrase WRITE setPhrase NOTIFY phraseChanged) Q_PROPERTY(bool hasNextPhrase READ hasNextPhrase NOTIFY phraseChanged) Q_PROPERTY(bool hasPreviousPhrase READ hasPreviousPhrase NOTIFY phraseChanged) public: explicit EditorSession(QObject *parent = nullptr); void setRepository(IEditableRepository *repository); bool skeletonMode() const; void setEditSkeleton(bool enabled=true); bool isEditSkeleton() const; IEditableCourse * skeleton() const; void setSkeleton(IEditableCourse *skeleton); Language * language() const; IEditableCourse * course() const; void setCourse(IEditableCourse *course); /** * @brief Open course resource by specifying the language * @param language the target language */ Q_INVOKABLE void setCourseByLanguage(Language *language); IEditableCourse * displayedCourse() const; - Unit * unit() const; + Q_DECL_DEPRECATED Unit * unit() const; + Unit * activeUnit() const; void setUnit(Unit *unit); - Phrase * phrase() const; + Q_DECL_DEPRECATED Phrase * phrase() const; + Phrase * activePhrase() const; void setPhrase(Phrase *phrase); Phrase::Type phraseType() const; void setPhraseType(Phrase::Type type); bool hasPreviousPhrase() const; bool hasNextPhrase() const; Q_INVOKABLE void switchToPreviousPhrase(); Q_INVOKABLE void switchToNextPhrase(); Q_INVOKABLE void updateCourseFromSkeleton(); -private Q_SLOTS: - void updateDisplayedUnit(); - private: Phrase * nextPhrase() const; Phrase * previousPhrase() const; +private Q_SLOTS: + void updateDisplayedUnit(); + Q_SIGNALS: void editSkeletonChanged(); void skeletonModeChanged(); void skeletonChanged(); void languageChanged(); void courseChanged(); void displayedCourseChanged(); void unitChanged(); void phraseChanged(); private: Q_DISABLE_COPY(EditorSession) IEditableRepository * m_repository{ nullptr }; bool m_editSkeleton{ false }; IEditableCourse *m_skeleton{ nullptr }; Language *m_language{ nullptr }; IEditableCourse *m_course{ nullptr }; Unit *m_unit{ nullptr }; Phrase *m_phrase{ nullptr }; }; #endif