diff --git a/src/core/editorsession.cpp b/src/core/editorsession.cpp index 55c04f4..6ebe8b2 100644 --- a/src/core/editorsession.cpp +++ b/src/core/editorsession.cpp @@ -1,240 +1,251 @@ /* * 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_skeletonMode; + return m_skeleton != nullptr; } void EditorSession::setEditSkeleton(bool enabled) { if (m_editSkeleton == enabled) { return; } m_editSkeleton = enabled; - if (enabled) { - m_tmpCourseWhileSkeletonEditing = m_course; -// setCourse(m_skeleton); //FIXME port skeleton for this - } else { - setCourse(m_tmpCourseWhileSkeletonEditing); - m_tmpCourseWhileSkeletonEditing = nullptr; - } 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; - if (m_skeletonMode != true) { - m_skeletonMode = true; - emit skeletonModeChanged(); - } IEditableCourse *newCourse{ nullptr }; if (m_skeleton && m_repository) { for (const auto &course : m_repository->editableCourses()) { if (course->foreignId() == m_skeleton->id()) { newCourse = course; 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_skeleton == nullptr || m_skeleton->id() != course->foreignId()) { - for (const auto &skeleton : m_repository->skeletons()) { - if (skeleton->id() == course->foreignId()) { - m_skeleton = skeleton; - emit skeletonChanged(); - break; - } - } - } 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; + break; + } + } + m_skeleton = newSkeleton; + emit skeletonChanged(); + } + // update language m_language = m_course->language(); } else { m_language = nullptr; } emit languageChanged(); + emit courseChanged(); +} + +IEditableCourse * EditorSession::displayedCourse() const +{ + IEditableCourse * course{ nullptr }; + if (m_editSkeleton) { + course = m_skeleton; + } else { + course = m_course; + } + return course; +} - if (m_course && !m_course->unitList().isEmpty()) { - setUnit(m_course->unitList().constFirst()); +void EditorSession::updateDisplayedUnit() +{ + auto course = displayedCourse(); + if (course && !course->unitList().isEmpty()) { + setUnit(course->unitList().constFirst()); } else { setUnit(nullptr); } - emit courseChanged(); } Unit * EditorSession::unit() const { return m_unit; } void EditorSession::setUnit(Unit *unit) { if (m_unit == unit) { return; } m_unit = unit; // different than above, do not directly enter phrases // but first show editing information for units setPhrase(nullptr); emit unitChanged(); } void EditorSession::setPhrase(Phrase *phrase) { if (m_phrase == phrase) { return; } if (phrase) { setUnit(phrase->unit()); } m_phrase = phrase; emit phraseChanged(); } Phrase * EditorSession::phrase() const { return m_phrase; } Phrase * EditorSession::previousPhrase() const { if (!m_phrase) { return nullptr; } const int index = m_phrase->unit()->phraseList().indexOf(m_phrase); if (index > 0) { return m_phrase->unit()->phraseList().at(index - 1); } else { Unit *unit = m_phrase->unit(); int uIndex = unit->course()->unitList().indexOf(unit); if (uIndex > 0) { return unit->course()->unitList().at(uIndex - 1)->phraseList().last(); } } return nullptr; } Phrase * EditorSession::nextPhrase() const { if (!m_phrase) { return nullptr; } const int index = m_phrase->unit()->phraseList().indexOf(m_phrase); if (index < m_phrase->unit()->phraseList().length() - 1) { return m_phrase->unit()->phraseList().at(index + 1); } else { Unit *unit = m_phrase->unit(); int uIndex = unit->course()->unitList().indexOf(unit); if (uIndex < unit->course()->unitList().length() - 1) { Unit *nextUnit = unit->course()->unitList().at(uIndex + 1); if (nextUnit->phraseList().isEmpty()) { return nullptr; } return nextUnit->phraseList().constFirst(); } } return nullptr; } void EditorSession::switchToPreviousPhrase() { setPhrase(previousPhrase()); } void EditorSession::switchToNextPhrase() { setPhrase(nextPhrase()); } bool EditorSession::hasPreviousPhrase() const { return previousPhrase() != nullptr; } bool EditorSession::hasNextPhrase() const { return nextPhrase() != nullptr; } void EditorSession::updateCourseFromSkeleton() { if (!m_course) { qCritical() << "Not updating course from skeleton, no one set."; return; } // m_resourceManager->updateCourseFromSkeleton(m_course); //FIXME } diff --git a/src/core/editorsession.h b/src/core/editorsession.h index fb6cb59..c796fd0 100644 --- a/src/core/editorsession.h +++ b/src/core/editorsession.h @@ -1,118 +1,127 @@ /* * 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 QString; 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(Language *language READ language NOTIFY languageChanged) 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); + IEditableCourse * displayedCourse() const; Unit * unit() const; void setUnit(Unit *unit); Phrase * phrase() 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; 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_skeletonMode{ false }; bool m_editSkeleton{ false }; IEditableCourse *m_skeleton{ nullptr }; Language *m_language{ nullptr }; IEditableCourse *m_course{ nullptr }; - IEditableCourse *m_tmpCourseWhileSkeletonEditing{ nullptr }; Unit *m_unit{ nullptr }; Phrase *m_phrase{ nullptr }; }; #endif diff --git a/src/qml/Editor.qml b/src/qml/Editor.qml index 1dcdf29..f9b2e42 100644 --- a/src/qml/Editor.qml +++ b/src/qml/Editor.qml @@ -1,262 +1,255 @@ /* * 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.Controls 1.4 as QQC1 import QtQuick.Layouts 1.2 import QtQml.Models 2.2 import artikulate 1.0 Item { id: root width: 400 //parent.width height: 400 //parent.height Item { id: theme property string backgroundColor: "#ffffff" property int smallIconSize: 18 property int smallMediumIconSize: 22 property int mediumIconSize: 32 property int fontPointSize: 11 } LanguageModel { id: languageModel view: LanguageModel.AllLanguages resourceModel: LanguageResourceModel { repository: g_repository } } CourseModel { id: courseModel language: g_editorSession.language } UnitModel { id: selectedUnitModel - course: g_editorSession.course + course: g_editorSession.displayedCourse } ColumnLayout { id: main anchors { fill: parent topMargin: 20 rightMargin: 20 bottomMargin: 20 leftMargin: 20 } spacing: 10 RowLayout { Label { text: i18n("Course Prototype:") } ComboBox { Layout.minimumWidth: 300 model: SkeletonModel { id: skeletonModel repository: g_repository } textRole: "title" onCurrentIndexChanged: { g_editorSession.skeleton = skeletonModel.skeleton(currentIndex) } } Button { id: buttonEditSkeleton Layout.minimumWidth: 200 text: i18n("Edit Prototype") icon.name: "code-class" checkable: true + enabled: g_editorSession.skeletonMode onClicked: g_editorSession.editSkeleton = checked } Item { Layout.fillWidth: true } Button { id: buttonSyncFromSkeleton enabled: !buttonEditSkeleton.checked Layout.minimumWidth: 200 text: i18n("Sync Prototype") 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() } - CheckBox { - Layout.alignment: Qt.AlignRight - enabled: false//FIXME for now deactivating non-skeleton mode - text: i18n("Prototype Mode") - checked: g_editorSession.skeletonMode - onClicked: { - g_editorSession.skeletonMode = !g_editorSession.skeletonMode - } - } } RowLayout { id: languageRow Label { text: i18n("Language:") } ComboBox { Layout.minimumWidth: 200 Layout.fillWidth: true enabled: !buttonEditSkeleton.checked model: languageModel textRole: "i18nTitle" onCurrentIndexChanged: { - g_editorSession.language = languageModel.language(currentIndex) +// g_editorSession.language = languageModel.language(currentIndex) + //FIXME language selection shall not change the language but the course } } } RowLayout { id: courseRow visible: { if (buttonEditSkeleton.checked) { return false } if (g_editorSession.skeletonMode && g_editorSession.course !== null) { return false } if (!g_editorSession.skeletonMode && g_editorSession.language !== null && g_editorSession.course !== null ) { return false } return true } 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 + enabled: !g_editorSession.skeletonMode Layout.fillWidth: true model: courseModel textRole: "title" onCurrentIndexChanged: { if (courseModel.course(currentIndex)) { g_editorSession.course = courseModel.course(currentIndex) } } onVisibleChanged: { if (visible && courseModel.course(currentIndex)) { g_editorSession.course = courseModel.course(currentIndex) } } } Button { text: i18n("Create Course") icon.name: "journal-new" onClicked: { g_editorSession.course = g_repository.createCourse(g_editorSession.language, g_editorSession.skeleton) } } Item { Layout.fillHeight: true } //dummy } RowLayout { id: mainRow visible: g_editorSession.course !== null Layout.fillHeight: true ColumnLayout { ScrollView { Layout.minimumWidth: Math.floor(main.width * 0.3) Layout.fillHeight: true QQC1.TreeView { id: phraseTree height: { mainRow.height - (newUnitButton.visible ? newUnitButton.height : 0) - 10 } width: Math.floor(main.width * 0.3) - 20 QQC1.TableViewColumn { title: i18n("Units & Phrases") role: "text" } model: PhraseModel { id: phraseModel - course: g_editorSession.course + course: g_editorSession.displayedCourse } selection: ItemSelectionModel { model: phraseTree.model } itemDelegate: Item { Text { anchors.verticalCenter: parent.verticalCenter color: styleData.textColor elide: styleData.elideMode text: styleData.value } } onClicked: { if (phraseModel.isPhrase(index)) { g_editorSession.phrase = phraseModel.phrase(index) } else { g_editorSession.phrase = null g_editorSession.unit = phraseModel.unit(index) } } Connections { target: g_editorSession onPhraseChanged: { if (g_editorSession.phrase === null) { return } phraseTree.expand(phraseModel.indexUnit(g_editorSession.phrase.unit)) phraseTree.selection.setCurrentIndex( phraseModel.indexPhrase(g_editorSession.phrase), ItemSelectionModel.ClearAndSelect) } } } } 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 { UnitEditor { visible: g_editorSession.unit !== null && g_editorSession.phrase === null unit: g_editorSession.unit editPhrases: g_editorSession.skeletonMode && g_editorSession.editSkeleton } 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 } } } } }