diff --git a/autotests/unittests/editorsession/test_editorsession.cpp b/autotests/unittests/editorsession/test_editorsession.cpp index b278909..efd9cf8 100644 --- a/autotests/unittests/editorsession/test_editorsession.cpp +++ b/autotests/unittests/editorsession/test_editorsession.cpp @@ -1,231 +1,225 @@ /* * 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 "../mocks/editablecoursestub.h" #include "../mocks/languagestub.h" #include #include void TestEditorSession::init() { // no initialization of test case } void TestEditorSession::cleanup() { // no cleanup after test run } void TestEditorSession::createEditorSession() { auto languageGerman = std::make_shared("de"); auto languageEnglish = std::make_shared("en"); std::shared_ptr course = EditableCourseStub::create(languageGerman, QVector>()); course->setLanguage(languageGerman); auto skeleton = SkeletonResource::create(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("de"); auto languageEnglish = std::make_shared("en"); std::shared_ptr courseGerman = EditableCourseStub::create(languageGerman, QVector>()); courseGerman->setId("course-german"); std::shared_ptr courseEnglish = EditableCourseStub::create(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("de"); auto languageEnglish = std::make_shared("en"); std::shared_ptr courseGermanA = EditableCourseStub::create(languageGerman, QVector>()); courseGermanA->setId("course-german"); courseGermanA->setForeignId("testskeletonA"); std::shared_ptr courseGermanB = EditableCourseStub::create(languageGerman, QVector>()); courseGermanB->setId("course-german"); courseGermanB->setForeignId("testskeletonB"); std::shared_ptr courseEnglishA = EditableCourseStub::create(languageEnglish, QVector>()); courseEnglishA->setId("course-english"); courseEnglishA->setForeignId("testskeletonA"); auto skeletonA = SkeletonResource::create(QUrl(), nullptr); skeletonA->setId("testskeletonA"); auto skeletonB = SkeletonResource::create(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("de"); // course auto unitA = Unit::create(); auto unitB = Unit::create(); std::shared_ptr phraseA1 = Phrase::create(); std::shared_ptr phraseA2 = Phrase::create(); std::shared_ptr phraseB1 = Phrase::create(); std::shared_ptr phraseB2 = Phrase::create(); // 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 = EditableCourseStub::create(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()->self(), phraseA1); QVERIFY(course.get() == session.course()); // test direct unit setters session.setActiveUnit(unitA.get()); QCOMPARE(session.activeUnit()->self(), unitA); session.setActiveUnit(unitB.get()); QCOMPARE(session.activeUnit()->self(), unitB); // test direct phrase setters session.setActivePhrase(phraseA1.get()); QCOMPARE(session.activePhrase()->self(), phraseA1); QCOMPARE(session.activeUnit()->self(), unitA); session.setActivePhrase(phraseB1.get()); QCOMPARE(session.activePhrase()->self(), phraseB1); QCOMPARE(session.activeUnit()->self(), unitB); // test phrase forward iterators session.setActivePhrase(phraseA1.get()); QCOMPARE(session.activeUnit()->self(), unitA); QCOMPARE(session.activePhrase()->id(), phraseA1->id()); QVERIFY(session.hasNextPhrase()); session.switchToNextPhrase(); QCOMPARE(session.activeUnit()->self(), unitA); QCOMPARE(session.activePhrase()->id(), phraseA2->id()); session.switchToNextPhrase(); QCOMPARE(session.activePhrase()->self(), phraseB1); session.switchToNextPhrase(); QCOMPARE(session.activePhrase()->self(), phraseB2); QVERIFY(!session.hasNextPhrase()); // at the end, do not iterate further session.switchToNextPhrase(); QCOMPARE(session.activePhrase()->self(), phraseB2); // test phrase backward iterators QVERIFY(session.hasPreviousPhrase()); session.switchToPreviousPhrase(); QCOMPARE(session.activePhrase()->self(), 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/src/core/editorsession.cpp b/src/core/editorsession.cpp index bb399b2..c28cd82 100644 --- a/src/core/editorsession.cpp +++ b/src/core/editorsession.cpp @@ -1,357 +1,258 @@ /* * 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/unit.h" #include "core/iunit.h" #include "core/phrase.h" #include "core/trainingaction.h" #include "core/contributorrepository.h" #include "artikulate_debug.h" EditorSession::EditorSession(QObject *parent) : ISessionActions(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; - } + for (const auto &skeleton : m_repository->skeletons()) { + if (skeleton->id() == m_course->id()) { + return true; } } - setCourse(newCourse); - emit skeletonChanged(); + return false; } ILanguage * EditorSession::language() const { - return m_language; + if (m_course && m_course->language()) { + return m_course->language().get(); + } + return nullptr; } 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; - } updateTrainingActions(); emit languageChanged(); emit courseChanged(); } -void EditorSession::setCourseByLanguage(ILanguage *language) -{ - if (!skeletonMode()) { - 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()) { - setActiveUnit(units.constFirst().get()); - return; - } - } -} - IUnit * EditorSession::activeUnit() const { if (auto phrase = activePhrase()) { return phrase->unit().get(); } return nullptr; } void EditorSession::setActiveUnit(IUnit *unit) { // checking phrases in increasing order ensures that always the first phrase is selected for (int i = 0; i < m_actions.count(); ++i) { for (int j = 0; j < m_actions.at(i)->actions().count(); ++j) { const auto testPhrase = qobject_cast(m_actions.at(i)->actions().at(j))->phrase(); if (unit == testPhrase->unit().get()) { if (auto action = activeAction()) { action->setChecked(false); } m_indexUnit = i; m_indexPhrase = j; if (auto action = activeAction()) { action->setChecked(true); } emit phraseChanged(); return; } } } } void EditorSession::setActivePhrase(IPhrase * phrase) { for (int i = 0; i < m_actions.count(); ++i) { for (int j = 0; j < m_actions.at(i)->actions().count(); ++j) { const auto testPhrase = qobject_cast(m_actions.at(i)->actions().at(j))->phrase(); if (phrase == testPhrase) { if (auto action = activeAction()) { action->setChecked(false); } m_indexUnit = i; m_indexPhrase = j; if (auto action = activeAction()) { action->setChecked(true); } emit phraseChanged(); return; } } } } IPhrase * EditorSession::activePhrase() const { if (const auto action = activeAction()) { return action->phrase(); } return nullptr; } void EditorSession::switchToPreviousPhrase() { if (hasPreviousPhrase()) { if (m_indexPhrase == 0) { qCDebug(ARTIKULATE_CORE()) << "switching to previous unit"; if (m_indexUnit > 0) { --m_indexUnit; m_indexPhrase = m_actions.at(m_indexUnit)->actions().count() - 1; } } else { --m_indexPhrase; } if (auto action = activeAction()) { action->setChecked(true); } emit phraseChanged(); } else { qCWarning(ARTIKULATE_CORE()) << "The is no previous phrase, aborting"; } } void EditorSession::switchToNextPhrase() { if (hasNextPhrase()) { if (m_indexPhrase >= m_actions.at(m_indexUnit)->actions().count() - 1) { qCDebug(ARTIKULATE_CORE()) << "switching to next unit"; if (m_indexUnit < m_actions.count() - 1) { ++m_indexUnit; m_indexPhrase = 0; } } else { ++m_indexPhrase; } if (auto action = activeAction()) { action->setChecked(true); } emit phraseChanged(); } else { qCWarning(ARTIKULATE_CORE()) << "The is no next phrase, aborting"; } } bool EditorSession::hasPreviousPhrase() const { return m_indexUnit > 0 || m_indexPhrase > 0; } bool EditorSession::hasNextPhrase() const { if (m_indexUnit < m_actions.count() - 1) { return true; } if (m_actions.constLast()) { if (m_indexPhrase < m_actions.constLast()->actions().count() - 1) { return true; } } return false; } void EditorSession::updateCourseFromSkeleton() { if (!m_course) { qCritical() << "Not updating course from skeleton, no one set."; return; } m_repository->updateCourseFromSkeleton(m_course->self()); } TrainingAction * EditorSession::activeAction() const { if (m_indexUnit < 0 || m_indexPhrase < 0) { return nullptr; } return qobject_cast(m_actions.at(m_indexUnit)->actions().at(m_indexPhrase)); } void EditorSession::updateTrainingActions() { for (const auto &action : qAsConst(m_actions)) { action->deleteLater(); } m_actions.clear(); if (!m_course) { m_indexUnit = -1; m_indexPhrase = -1; return; } const auto unitList = m_course->units(); for (const auto &unit : qAsConst(unitList)) { auto action = new TrainingAction(unit->title(), this); const auto phraseList = unit->phrases(); for (const auto &phrase : qAsConst(phraseList)) { if (phrase->sound().isEmpty()) { continue; } action->appendChild(new TrainingAction(phrase, this, unit.get())); } if (action->hasChildren()) { m_actions.append(action); } else { action->deleteLater(); } } // update indices m_indexUnit = -1; m_indexPhrase = -1; if (m_course->units().count() > 0) { m_indexUnit = 0; if (m_course->units().constFirst()->phrases().count() > 0) { m_indexPhrase = 0; } } } QVector EditorSession::trainingActions() const { return m_actions; } diff --git a/src/core/editorsession.h b/src/core/editorsession.h index 9891987..65469fa 100644 --- a/src/core/editorsession.h +++ b/src/core/editorsession.h @@ -1,131 +1,89 @@ /* * Copyright 2013-2015 Andreas Cord-Landwehr * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef EDITORSESSION_H #define EDITORSESSION_H #include "artikulatecore_export.h" #include "phrase.h" #include "isessionactions.h" class ILanguage; class IEditableCourse; class Unit; class IPhrase; 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 ISessionActions { Q_OBJECT Q_INTERFACES(ISessionActions) 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) - Q_PROPERTY(IUnit *unit READ activeUnit NOTIFY unitChanged) - Q_PROPERTY(ILanguage *language READ language NOTIFY languageChanged) - // editor elements depending on currently selected mode, skeleton and course /** - * @brief the displayed course (skeleton or course) depending on the user selection + * @brief language of the currently selected course or null if skeleton is selected */ - Q_PROPERTY(IEditableCourse *displayedCourse READ displayedCourse NOTIFY displayedCourseChanged) + Q_PROPERTY(ILanguage *language READ language NOTIFY languageChanged) + Q_PROPERTY(IEditableCourse *course READ course WRITE setCourse NOTIFY courseChanged) + Q_PROPERTY(IUnit *unit READ activeUnit NOTIFY unitChanged) Q_PROPERTY(IPhrase *phrase READ activePhrase WRITE setActivePhrase 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); ILanguage * 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(ILanguage *language); - IEditableCourse * displayedCourse() const; IUnit * activeUnit() const; void setActiveUnit(IUnit *unit); IPhrase * activePhrase() const; void setActivePhrase(IPhrase *phrase) override; - IPhrase::Type phraseType() const; - void setPhraseType(IPhrase::Type type); bool hasPreviousPhrase() const; bool hasNextPhrase() const; Q_INVOKABLE void switchToPreviousPhrase(); Q_INVOKABLE void switchToNextPhrase(); Q_INVOKABLE void updateCourseFromSkeleton(); TrainingAction * activeAction() const override; QVector trainingActions() const override; -private Q_SLOTS: - void updateDisplayedUnit(); - Q_SIGNALS: - void editSkeletonChanged(); void skeletonModeChanged(); - void skeletonChanged(); void languageChanged(); - void displayedCourseChanged(); void unitChanged(); private: Q_DISABLE_COPY(EditorSession) void updateTrainingActions(); IEditableRepository * m_repository{ nullptr }; bool m_editSkeleton{ false }; - IEditableCourse *m_skeleton{ nullptr }; - ILanguage *m_language{ nullptr }; IEditableCourse *m_course{ nullptr }; QVector m_actions; int m_indexUnit{-1}; int m_indexPhrase{-1}; }; #endif diff --git a/src/qml/CourseConfigurationPage.qml b/src/qml/CourseConfigurationPage.qml index 15f7e47..d34ccf8 100644 --- a/src/qml/CourseConfigurationPage.qml +++ b/src/qml/CourseConfigurationPage.qml @@ -1,87 +1,90 @@ /* * 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 . */ import QtQuick 2.1 import QtQuick.Layouts 1.3 import QtQuick.Controls 2.4 import QtQuick.Dialogs 1.2 import org.kde.kirigami 2.7 as Kirigami Kirigami.Page { id: root - //TODO adapt to editor session if course or skeleton: requires refactoring of editorsession to only operate on one course - title: i18n("Course Configuration") + title: g_editorSession.skeletonMode ? i18n("Prototype Configuration") : i18n("Course Configuration") Kirigami.FormLayout { anchors.fill: parent Kirigami.Separator { - Kirigami.FormData.label: i18n("Course Description") + Kirigami.FormData.label: g_editorSession.skeletonMode ? i18n("Prototype Description") : i18n("Course Description") Kirigami.FormData.isSection: true } TextField { Kirigami.FormData.label: i18n("Title:") text: g_editorSession.course.title Layout.preferredWidth: .7 * root.width onAccepted: g_editorSession.course.title = text } TextField { + visible: !g_editorSession.skeletonMode Kirigami.FormData.label: i18n("Localized Title:") text: g_editorSession.course.i18nTitle Layout.preferredWidth: .7 * root.width onAccepted: g_editorSession.course.i18nTitle = text } TextField { Kirigami.FormData.label: i18n("Description:") text: g_editorSession.course.description Layout.preferredWidth: .7 * root.width onAccepted: g_editorSession.course.description = text } TextField { + visible: !g_editorSession.skeletonMode Kirigami.FormData.label: i18n("Language:") text: g_editorSession.course.languageTitle readOnly: true Layout.preferredWidth: .7 * root.width } Kirigami.Separator { + visible: !g_editorSession.skeletonMode Kirigami.FormData.label: i18n("Prototype") Kirigami.FormData.isSection: true } Button { + visible: !g_editorSession.skeletonMode Kirigami.FormData.label: i18n("Update from Prototype:") enabled: g_editorSession.isSkeletonMode Layout.minimumWidth: 200 text: i18n("Update") icon.name: "view-refresh" ToolTip.visible: hovered ToolTip.delay: 1000 ToolTip.timeout: 5000 ToolTip.text: i18n("Update the course with elements from prototype.") onClicked: g_editorSession.updateCourseFromSkeleton() } // TODO add export functionalities // Kirigami.Separator { // Kirigami.FormData.label: i18n("Export") // Kirigami.FormData.isSection: true // } } } diff --git a/src/qml/EditCoursePage.qml b/src/qml/EditCoursePage.qml index 1646824..6a675d7 100644 --- a/src/qml/EditCoursePage.qml +++ b/src/qml/EditCoursePage.qml @@ -1,122 +1,57 @@ /* * 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 . */ import QtQuick 2.5 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 import QtQml.Models 2.2 import org.kde.kirigami 2.7 as Kirigami import artikulate 1.0 Kirigami.ScrollablePage { id: root title: i18n("Edit Course") actions { left: Kirigami.Action { text: i18n("Previous") tooltip: i18n("Switch to previous phrase.") iconName: "go-previous" enabled: g_editorSession.hasPreviousPhrase onTriggered: g_editorSession.switchToPreviousPhrase() } right: Kirigami.Action { text: i18n("Next") tooltip: i18n("Switch to next phrase.") iconName: "go-next" enabled: g_editorSession.hasNextPhrase onTriggered: g_editorSession.switchToNextPhrase() } } ColumnLayout { - id: main - - spacing: 10 - - LanguageModel { - id: languageModel - view: LanguageModel.AllLanguages - resourceModel: LanguageResourceModel { - repository: g_repository - } - } - CourseModel { - id: courseModel - } - UnitModel { - id: selectedUnitModel - course: g_editorSession.displayedCourse - } - - RowLayout { - id: createNewCourseRow - visible: g_editorSession.isSkeletonMode - Label { - text: i18n("There is no course in the selected language.") - } - ComboBox { // course selection only necessary when we do not edit skeleton derived course - id: comboCourse - visible: !g_editorSession.skeletonMode - Layout.fillWidth: true - model: CourseFilterModel { - id: courseFilterModel - courseModel: courseModel - language: g_editorSession.language - } - textRole: "title" - onCurrentIndexChanged: { - if (courseFilterModel.course(currentIndex)) { - g_editorSession.course = courseFilterModel.course(languageSelectionComboBox.currentIndex) - } - } - onVisibleChanged: { - if (visible && courseFilterModel.course(currentIndex)) { - g_editorSession.course = courseFilterModel.course(languageSelectionComboBox.currentIndex) - } - } - } - Button { - text: i18n("Create Course") - icon.name: "journal-new" - onClicked: { - g_editorSession.course = g_repository.createCourse(languageModel.language(languageSelectionComboBox.currentIndex), g_editorSession.skeleton) - } - } - Button { // add units only if skeleton - id: newUnitButton - visible: !g_editorSession.skeletonMode || g_editorSession.editSkeleton - icon.name: "list-add" - text: i18n("New Unit") - onClicked: phraseModel.course.createUnit() - } - } - - ColumnLayout { - PhraseEditor { - visible: g_editorSession.phrase !== null - phrase: g_editorSession.phrase - isSkeletonPhrase: g_editorSession.editSkeleton - Layout.minimumWidth: Math.floor(main.width * 0.6) - Layout.fillHeight: true - } + PhraseEditor { + visible: g_editorSession.phrase !== null + phrase: g_editorSession.phrase + isSkeletonPhrase: g_editorSession.skeletonMode + Layout.fillHeight: true } } } diff --git a/src/qml/EditorSkeletonSelectionPage.qml b/src/qml/EditorSkeletonSelectionPage.qml index bc0779e..b6b1fc3 100644 --- a/src/qml/EditorSkeletonSelectionPage.qml +++ b/src/qml/EditorSkeletonSelectionPage.qml @@ -1,84 +1,84 @@ /* * Copyright 2015-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 . */ import QtQuick 2.1 import QtQuick.Controls 2.1 as QQC2 import QtQuick.Layouts 1.3 import org.kde.kirigami 2.7 as Kirigami import artikulate 1.0 Kirigami.ScrollablePage { id: root title: i18n("Select Prototype") Kirigami.CardsListView { id: listView width: root.width - 40 model: SkeletonModel { id: courseModel } delegate: Kirigami.AbstractCard { contentItem: Item { implicitHeight: delegateLayout.implicitHeight GridLayout { id: delegateLayout anchors { left: parent.left top: parent.top right: parent.right } rowSpacing: Kirigami.Units.largeSpacing columnSpacing: Kirigami.Units.largeSpacing columns: width > Kirigami.Units.gridUnit * 20 ? 4 : 2 Kirigami.Icon { source: "language-artikulate" Layout.fillHeight: true Layout.maximumHeight: Kirigami.Units.iconSizes.huge Layout.preferredWidth: height } ColumnLayout { Kirigami.Heading { level: 2 text: i18nc("@title:window prototype name", "%1", model.title) } Kirigami.Separator { Layout.fillWidth: true } QQC2.Label { Layout.fillWidth: true wrapMode: Text.WordWrap text: model.description } } QQC2.Button { Layout.alignment: Qt.AlignRight|Qt.AlignVCenter Layout.columnSpan: 2 text: i18nc("@action:button", "Edit Prototype") onClicked: { showPassiveNotification("Selected prototype for editor: " + model.title + "."); - g_editorSession.skeleton = model.dataRole + g_editorSession.course = model.dataRole } } } } } } }