diff --git a/src/core/drawertrainingactions.cpp b/src/core/drawertrainingactions.cpp index a44dca7..b358735 100644 --- a/src/core/drawertrainingactions.cpp +++ b/src/core/drawertrainingactions.cpp @@ -1,90 +1,92 @@ /* * Copyright 2018 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 "drawertrainingactions.h" #include "trainingaction.h" #include "course.h" #include "unit.h" #include #include DrawerTrainingActions::DrawerTrainingActions(QObject* parent) : QObject(parent) - , m_course(nullptr) { } -void DrawerTrainingActions::setCourse(Course *course) -{ - if (course == m_course) { - return; - } - m_course = course; - emit courseChanged(course); - - updateActions(); -} - -Course * DrawerTrainingActions::course() const -{ - return m_course; -} - void DrawerTrainingActions::setSession(TrainingSession *session) { if (session == m_session) { return; } + if (m_session) { + disconnect(m_session, &TrainingSession::courseChanged, this, &DrawerTrainingActions::updateActions); + } + m_session = session; + connect(m_session, &TrainingSession::courseChanged, this, &DrawerTrainingActions::updateActions); + emit sessionChanged(); + updateActions(); } TrainingSession * DrawerTrainingActions::session() const { return m_session; } QList DrawerTrainingActions::actions() const { return m_actions; } void DrawerTrainingActions::updateActions() { + if (!m_session) { + return; + } + // cleanup for (const auto &action : m_actions) { action->deleteLater(); } m_actions.clear(); - if (!m_course) { + if (!m_session->course()) { return; } - for (const auto &unit : m_course->unitList()) { + for (const auto &unit : m_session->course()->unitList()) { auto action = new TrainingAction(unit->title()); - m_actions.append(action); for (const auto &phrase : unit->phraseList()) { + if (phrase->sound().isEmpty()) { + continue; + } action->appendChild(new TrainingAction(phrase, this, unit)); } + if (action->hasChildren()) { + m_actions.append(action); + } else { + action->deleteLater(); + } } + emit actionsChanged(); } diff --git a/src/core/drawertrainingactions.h b/src/core/drawertrainingactions.h index defe091..2f371c9 100644 --- a/src/core/drawertrainingactions.h +++ b/src/core/drawertrainingactions.h @@ -1,63 +1,58 @@ /* * Copyright 2018 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 DRAWERTRAININGACTIONS_H #define DRAWERTRAININGACTIONS_H #include "artikulatecore_export.h" #include "trainingaction.h" #include class Course; class ARTIKULATECORE_EXPORT DrawerTrainingActions : public QObject { Q_OBJECT - Q_PROPERTY(Course *course READ course WRITE setCourse NOTIFY courseChanged) Q_PROPERTY(TrainingSession *session READ session WRITE setSession NOTIFY sessionChanged) Q_PROPERTY(QList actions READ actions NOTIFY actionsChanged) public: DrawerTrainingActions(QObject *parent = nullptr); - void setCourse(Course *course); - Course * course() const; void setSession(TrainingSession *session); TrainingSession * session() const; QList actions() const; -private: +private Q_SLOTS: void updateActions(); Q_SIGNALS: - void courseChanged(Course *course); void actionsChanged(); void sessionChanged(); /** * Notify that training view shall be displayed. */ void triggerTrainingView(); private: - Course *m_course{nullptr}; TrainingSession *m_session{nullptr}; QList m_actions; }; #endif diff --git a/src/core/resourcemanager.cpp b/src/core/resourcemanager.cpp index 07226cd..e4ffca3 100644 --- a/src/core/resourcemanager.cpp +++ b/src/core/resourcemanager.cpp @@ -1,468 +1,473 @@ /* * 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 "resourcemanager.h" #include "language.h" #include "course.h" #include "skeleton.h" #include "unit.h" #include "phrase.h" #include "phoneme.h" #include "phonemegroup.h" #include "resources/languageresource.h" #include "resources/courseresource.h" #include "resources/skeletonresource.h" #include "settings.h" #include "liblearnerprofile/src/profilemanager.h" #include "liblearnerprofile/src/learninggoal.h" #include #include #include #include #include #include #include #include #include #include "artikulate_debug.h" #include #include ResourceManager::ResourceManager(QObject *parent) : QObject(parent) { } void ResourceManager::loadCourseResources() { + //TODO fix this method such that it may be called many times of e.g. updating + // reload config, could be changed in dialogs Settings::self()->load(); // register skeleton resources QDir skeletonRepository = QDir(Settings::courseRepositoryPath()); skeletonRepository.setFilter(QDir::Files | QDir::Hidden); if (!skeletonRepository.cd("skeletons")) { qCritical() << "There is no subdirectory \"skeletons\" in directory " << skeletonRepository.path() << " cannot load skeletons."; } else { // read skeletons QFileInfoList list = skeletonRepository.entryInfoList(); for (int i = 0; i < list.size(); ++i) { QFileInfo fileInfo = list.at(i); addSkeleton(QUrl::fromLocalFile(fileInfo.absoluteFilePath())); } } // register contributor course files QDir courseRepository = QDir(Settings::courseRepositoryPath()); if (!courseRepository.cd("courses")) { qCritical() << "There is no subdirectory \"courses\" in directory " << courseRepository.path() << " cannot load courses."; } else { // find courses courseRepository.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); QFileInfoList courseDirList = courseRepository.entryInfoList(); // traverse all course directories foreach (const QFileInfo &info, courseDirList) { QDir courseDir = QDir(info.absoluteFilePath()); courseDir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); QFileInfoList courseLangDirList = courseDir.entryInfoList(); // traverse all language directories for each course foreach (const QFileInfo &langInfo, courseLangDirList) { QString languageId = langInfo.fileName(); QDir courseLangDir = QDir(langInfo.absoluteFilePath()); courseLangDir.setFilter(QDir::Files); QStringList nameFilters; nameFilters.append("*.xml"); QFileInfoList courses = courseLangDir.entryInfoList(nameFilters); // find and add course files foreach (const QFileInfo &courseInfo, courses) { CourseResource * course = addCourse(QUrl::fromLocalFile(courseInfo.filePath())); if (course != nullptr) { course->setContributorResource(true); } } } } } // register GHNS course resources QStringList dirs = QStandardPaths::standardLocations(QStandardPaths::DataLocation); foreach (const QString &testdir, dirs) { QDirIterator it(testdir + "/courses/", QDirIterator::Subdirectories); while (it.hasNext()) { QDir dir(it.next()); dir.setFilter(QDir::Files | QDir::NoSymLinks); QFileInfoList list = dir.entryInfoList(); for (int i = 0; i < list.size(); ++i) { QFileInfo fileInfo = list.at(i); if (fileInfo.completeSuffix() != "xml") { continue; } addCourse(QUrl::fromLocalFile(fileInfo.absoluteFilePath())); } } } //TODO this signal should only be emitted when repository was added/removed // yet the call to this method is very seldom and emitting it too often is not that harmful emit repositoryChanged(); } void ResourceManager::loadLanguageResources() { // load language resources // all other resources are only loaded on demand QStringList dirs = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); foreach (const QString &testdir, dirs) { QDir dir(testdir + "/artikulate/languages/"); dir.setFilter(QDir::Files | QDir::NoSymLinks); QFileInfoList list = dir.entryInfoList(); for (int i = 0; i < list.size(); ++i) { QFileInfo fileInfo = list.at(i); if (fileInfo.completeSuffix() != "xml") { continue; } addLanguage(QUrl::fromLocalFile(fileInfo.absoluteFilePath())); } } } void ResourceManager::sync() { QMap< QString, QList< CourseResource* > >::iterator iter; for (iter = m_courseResources.begin(); iter != m_courseResources.end(); ++iter) { foreach (auto const &courseRes, iter.value()) { courseRes->sync(); } } foreach (auto const &courseRes, m_skeletonResources) { courseRes->sync(); } } bool ResourceManager::modified() const { QMap< QString, QList< CourseResource* > >::const_iterator iter; for (iter = m_courseResources.constBegin(); iter != m_courseResources.constEnd(); ++iter) { foreach (auto const &courseRes, iter.value()) { if (courseRes->isOpen() && courseRes->course()->modified()) { return true; } } } foreach (auto const &courseRes, m_skeletonResources) { if (courseRes->isOpen() && courseRes->skeleton()->modified()) { return true; } } return false; } void ResourceManager::addLanguage(const QUrl &languageFile) { if (m_loadedResources.contains(languageFile.toLocalFile())) { return; } LanguageResource *resource = new LanguageResource(this, languageFile); emit languageResourceAboutToBeAdded(resource, m_languageResources.count()); m_languageResources.append(resource); m_loadedResources.append(languageFile.toLocalFile()); m_courseResources.insert(resource->identifier(), QList()); emit languageResourceAdded(); } bool ResourceManager::isRepositoryManager() const { return !Settings::courseRepositoryPath().isEmpty(); } QString ResourceManager::repositoryUrl() const { return Settings::courseRepositoryPath(); } QList< LanguageResource* > ResourceManager::languageResources() const { return m_languageResources; } Language * ResourceManager::language(int index) const { Q_ASSERT(index >= 0 && index < m_languageResources.count()); return m_languageResources.at(index)->language(); } Language * ResourceManager::language(LearnerProfile::LearningGoal *learningGoal) const { if (!learningGoal) { return nullptr; } if (learningGoal->category() != LearnerProfile::LearningGoal::Language) { qCritical() << "Cannot translate non-language learning goal to language"; return nullptr; } foreach (LanguageResource *resource, m_languageResources) { if (resource->identifier() == learningGoal->identifier()) { return resource->language(); } } qCritical() << "No language registered with identifier " << learningGoal->identifier() << ": aborting"; return nullptr; } QList< CourseResource* > ResourceManager::courseResources(Language *language) { - Q_ASSERT(language); if (!language) { - return QList< CourseResource* >(); + QList courses; + for (auto iter = m_courseResources.constBegin(); iter != m_courseResources.constEnd(); ++iter) { + courses.append(iter.value()); + } + return courses; } - // return empty list if no course available + // return empty list if no course available for language if (!m_courseResources.contains(language->id())) { return QList< CourseResource* >(); } return m_courseResources[language->id()]; } Course * ResourceManager::course(Language *language, int index) const { Q_ASSERT(m_courseResources.contains(language->id())); Q_ASSERT(index >= 0 && index < m_courseResources[language->id()].count()); return m_courseResources[language->id()].at(index)->course(); } void ResourceManager::reloadCourseOrSkeleton(Course *courseOrSkeleton) { if (!courseOrSkeleton) { qCritical() << "Cannot reload non-existing course"; return; } if (!courseOrSkeleton->file().isValid()) { qCritical() << "Cannot reload temporary file, aborting."; return; } // figure out if this is a course or a skeleton if (courseOrSkeleton->language()) { // only course files have a language //TODO better add a check if this is contained in the course list // to catch possible errors QUrl file = courseOrSkeleton->file(); m_loadedResources.removeOne(courseOrSkeleton->file().toLocalFile()); removeCourse(courseOrSkeleton); addCourse(file); } else { foreach (SkeletonResource *resource, m_skeletonResources) { if (resource->identifier() == courseOrSkeleton->id()) { resource->reload(); return; } } } } void ResourceManager::updateCourseFromSkeleton(Course *course) { //TODO implement status information that are shown at mainwindow if (course->foreignId().isEmpty()) { qCritical() << "No skeleton ID specified, aborting update."; return; } Course *skeleton = nullptr; QList::ConstIterator iter = m_skeletonResources.constBegin(); while (iter != m_skeletonResources.constEnd()) { if ((*iter)->identifier() == course->foreignId()) { skeleton = (*iter)->skeleton(); break; } ++iter; } if (!skeleton) { qCritical() << "Could not find skeleton with id " << course->foreignId() << ", aborting update."; return; } // update now foreach (Unit *unitSkeleton, skeleton->unitList()) { // import unit if not exists Unit *currentUnit = nullptr; bool found = false; foreach (Unit *unit, course->unitList()) { if (unit->foreignId() == unitSkeleton->id()) { found = true; currentUnit = unit; break; } } if (found == false) { currentUnit = new Unit(course); currentUnit->setId(QUuid::createUuid().toString()); currentUnit->setTitle(unitSkeleton->title()); currentUnit->setForeignId(unitSkeleton->id()); currentUnit->setCourse(course); course->addUnit(currentUnit); course->setModified(true); } // update phrases foreach (Phrase *phraseSkeleton, unitSkeleton->phraseList()) { bool found = false; foreach (Phrase *phrase, currentUnit->phraseList()) { if (phrase->foreignId() == phraseSkeleton->id()) { if (phrase->i18nText() != phraseSkeleton->text()) { phrase->setEditState(Phrase::Unknown); phrase->seti18nText(phraseSkeleton->text()); } found = true; break; } } if (found == false) { Phrase *newPhrase = new Phrase(course); newPhrase->setForeignId(phraseSkeleton->id()); newPhrase->setId(QUuid::createUuid().toString()); newPhrase->setText(phraseSkeleton->text()); newPhrase->seti18nText(phraseSkeleton->text()); newPhrase->setType(phraseSkeleton->type()); newPhrase->setUnit(currentUnit); currentUnit->addPhrase(newPhrase); course->setModified(true); } } } // FIXME deassociate removed phrases qCDebug(ARTIKULATE_LOG) << "Update performed!"; } CourseResource * ResourceManager::addCourse(const QUrl &courseFile) { CourseResource *resource = new CourseResource(this, courseFile); if (resource->language().isEmpty()) { qCritical() << "Could not load course, language unknown:" << courseFile.toLocalFile(); return nullptr; } // skip already loaded resources if (m_loadedResources.contains(courseFile.toLocalFile())) { return nullptr; } m_loadedResources.append(courseFile.toLocalFile()); addCourseResource(resource); emit languageCoursesChanged(); return resource; } void ResourceManager::addCourseResource(CourseResource *resource) { Q_ASSERT(m_courseResources.contains(resource->language())); if (m_courseResources.contains(resource->language())) { emit courseResourceAboutToBeAdded(resource, m_courseResources[resource->language()].count()); } else { emit courseResourceAboutToBeAdded(resource, 0); m_courseResources.insert(resource->language(), QList()); } m_courseResources[resource->language()].append(resource); emit courseResourceAdded(); } void ResourceManager::removeCourse(Course *course) { for (int index = 0; index < m_courseResources[course->language()->id()].length(); ++index) { if (m_courseResources[course->language()->id()].at(index)->course() == course) { emit courseResourceAboutToBeRemoved(index); m_courseResources[course->language()->id()].removeAt(index); course->deleteLater(); return; } } } Course * ResourceManager::createCourse(Language *language, Skeleton *skeleton) { // set path QString path = QString("%1/%2/%3/%4/%4.xml") .arg(Settings::courseRepositoryPath()) .arg("courses") .arg(skeleton->id()) .arg(language->id()); CourseResource * courseRes = new CourseResource(this, QUrl::fromLocalFile(path)); Q_ASSERT(courseRes); Course *course = courseRes->course(); Q_ASSERT(course); course->setId(QUuid::createUuid().toString()); course->setTitle(skeleton->title()); course->setDescription(skeleton->description()); course->setFile(QUrl::fromLocalFile(path)); course->setLanguage(language); // set skeleton course->setForeignId(skeleton->id()); addCourseResource(courseRes); return course; } void ResourceManager::addSkeleton(const QUrl &skeletonFile) { SkeletonResource *resource = new SkeletonResource(this, skeletonFile); addSkeletonResource(resource); } void ResourceManager::addSkeletonResource(SkeletonResource *resource) { // skip already loaded resources if (m_loadedResources.contains(resource->path().toLocalFile())) { return; } m_loadedResources.append(resource->path().toLocalFile()); emit skeletonAboutToBeAdded(resource->skeleton(), m_skeletonResources.count()); m_skeletonResources.append(resource); emit skeletonAdded(); } void ResourceManager::removeSkeleton(Skeleton *skeleton) { for (int index = 0; index < m_skeletonResources.length(); ++index) { if (m_skeletonResources.at(index)->identifier() == skeleton->id()) { emit skeletonAboutToBeRemoved(index, index); m_skeletonResources.removeAt(index); emit skeletonRemoved(); skeleton->deleteLater(); return; } } } QList< SkeletonResource* > ResourceManager::skeletonResources() { return m_skeletonResources; } diff --git a/src/core/resourcemanager.h b/src/core/resourcemanager.h index 6e9bf3a..53e1f36 100644 --- a/src/core/resourcemanager.h +++ b/src/core/resourcemanager.h @@ -1,214 +1,216 @@ /* * 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 RESOURCEMANAGER_H #define RESOURCEMANAGER_H #include "artikulatecore_export.h" #include #include #include #include #include "liblearnerprofile/src/learninggoal.h" class SkeletonResource; class CourseResource; class LanguageResource; class Skeleton; class Language; class Course; class ProfileManager; class QUrl; namespace LearnerProfile { class ProfileManager; } /** * \class ResourceManager * This class loads and stores all data files of the application. */ class ARTIKULATECORE_EXPORT ResourceManager : public QObject { Q_OBJECT - Q_PROPERTY(bool isRepositoryManager READ isRepositoryManager NOTIFY repositoryChanged); - Q_PROPERTY(QString repositoryUrl READ repositoryUrl NOTIFY repositoryChanged); + Q_PROPERTY(bool isRepositoryManager READ isRepositoryManager NOTIFY repositoryChanged) + Q_PROPERTY(QString repositoryUrl READ repositoryUrl NOTIFY repositoryChanged) public: explicit ResourceManager(QObject *parent = nullptr); /** * Load all course resources. * This loading is very fast, since course files are only partly (~20 top lines) parsed and * the complete parsing is postproned until first access. + * + * This method is safe to be called several times for incremental updates. */ - void loadCourseResources(); + Q_INVOKABLE void loadCourseResources(); /** * This method loads all language files that are provided in the standard directories * for this application. */ void loadLanguageResources(); /** * save all changes to course resources */ void sync(); /** * \return \c true if any course or skeleton is modified, otherwise \c false */ bool modified() const; /** * \return \c true if a repository is used, else \c false */ Q_INVOKABLE bool isRepositoryManager() const; /** * \return path to working repository, if one is set */ QString repositoryUrl() const; /** * \return list of all available language specifications */ QList languageResources() const; /** * \return language by \p index */ Q_INVOKABLE Language * language(int index) const; /** * \return language by \p learningGoal */ Q_INVOKABLE Language * language(LearnerProfile::LearningGoal* learningGoal) const; /** * \return list of all loaded courses for language \p language */ QList courseResources(Language *language); Q_INVOKABLE Course * course(Language *language, int index) const; /** * Reset the file for this course or skeleton. * * \param course the course to be reloaded */ Q_INVOKABLE void reloadCourseOrSkeleton(Course *course); /** * Imports units and phrases from skeleton, deassociates removed ones. * * \param course the course to be update */ void updateCourseFromSkeleton(Course *course); /** * Add language to resource manager by parsing the given language specification file. * * \param languageFile is the local XML file containing the language */ void addLanguage(const QUrl &languageFile); /** * Adds course to resource manager by parsing the given course specification file. * * \param courseFile is the local XML file containing the course * \return true if loaded successfully, otherwise false */ CourseResource * addCourse(const QUrl &courseFile); /** * Adds course to resource manager. If the course's language is not registered, the language * is registered by this method. * * \param resource the course resource to add to resource manager */ void addCourseResource(CourseResource *resource); /** * Remove course from resource manager. If the course is modified its changes are NOT * written. For writing changes, the Course::sync() method must be called directly. * * \param course is the course to be removed */ void removeCourse(Course *course); /** * Create new course for \p language and derived from \p skeleton. * * \return created course */ Q_INVOKABLE Course * createCourse(Language *language, Skeleton *skeleton); /** * Adds skeleton resource to resource manager * * \param resource the skeleton resource to add to resource manager */ void addSkeleton(const QUrl &skeletonFile); /** * Adds skeleton resource to resource manager * * \param resource the skeleton resource to add to resource manager */ void addSkeletonResource(SkeletonResource *resource); /** * Remove skeleton from resource manager. If the skeleton is modified its changes are NOT * written. For writing changes, the Skeleton::sync() method must be called directly. * * \param skeleton is the skeleton to be removed */ void removeSkeleton(Skeleton *skeleton); /** * \return list of all loaded skeletons resources */ QList skeletonResources(); Q_SIGNALS: void languageResourceAdded(); void languageResourceAboutToBeAdded(LanguageResource*,int); void languageResourceRemoved(); void languageResourceAboutToBeRemoved(int); void repositoryChanged(); void courseResourceAdded(); void courseResourceAboutToBeAdded(CourseResource*,int); void courseResourceAboutToBeRemoved(int); void skeletonAdded(); void skeletonAboutToBeAdded(Course*,int); void skeletonRemoved(); void skeletonAboutToBeRemoved(int,int); void languageCoursesChanged(); private: QList m_languageResources; QMap > m_courseResources; //!> (language-id, course-resource) QList m_skeletonResources; QStringList m_loadedResources; }; #endif // RESOURCEMANAGER_H diff --git a/src/core/trainingaction.cpp b/src/core/trainingaction.cpp index b92bed2..baf05bb 100644 --- a/src/core/trainingaction.cpp +++ b/src/core/trainingaction.cpp @@ -1,82 +1,87 @@ /* * Copyright 2018 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 "trainingaction.h" #include "trainingactionicon.h" #include "drawertrainingactions.h" #include "trainingsession.h" TrainingAction::TrainingAction(QObject *parent) : QObject(parent) , m_text(QString()) , m_icon(new TrainingActionIcon(this, QString())) //TODO "rating-unrated" vs. "rating" { } TrainingAction::TrainingAction(const QString &text, QObject *parent) : QObject(parent) , m_text(text) , m_icon(new TrainingActionIcon(this, QString())) //TODO "rating-unrated" vs. "rating" { } TrainingAction::TrainingAction(Phrase *phrase, DrawerTrainingActions *drawerTrainingActions, QObject* parent) : QObject(parent) , m_icon(new TrainingActionIcon(this, QString())) , m_phrase(phrase) , m_drawerTrainingActions(drawerTrainingActions) { if (m_phrase) { m_text = phrase->text(); } } void TrainingAction::appendChild(QObject* child) { m_children.append(child); emit childrenChanged(); } +bool TrainingAction::hasChildren() const +{ + return m_children.count() > 0; +} + void TrainingAction::trigger() { if (m_phrase && m_drawerTrainingActions && m_drawerTrainingActions->session()) { m_drawerTrainingActions->session()->setPhrase(m_phrase); emit m_drawerTrainingActions->triggerTrainingView(); } } bool TrainingAction::enabled() const { return m_enabled; } void TrainingAction::setEnabled(bool enabled) { if (enabled == m_enabled) { return; } m_enabled = enabled; emit enabledChanged(m_enabled); } QObject * TrainingAction::icon() const { return m_icon; } diff --git a/src/core/trainingaction.h b/src/core/trainingaction.h index 9688d81..95aaaef 100644 --- a/src/core/trainingaction.h +++ b/src/core/trainingaction.h @@ -1,73 +1,74 @@ /* * Copyright 2018 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 TRAININGACTION_H #define TRAININGACTION_H #include "artikulatecore_export.h" #include "trainingactionicon.h" #include "phrase.h" #include "trainingsession.h" #include #include class DrawerTrainingActions; class ARTIKULATECORE_EXPORT TrainingAction : public QObject { Q_OBJECT Q_PROPERTY(QString text MEMBER m_text CONSTANT) Q_PROPERTY(QObject* icon READ icon CONSTANT) Q_PROPERTY(bool visible MEMBER m_visible CONSTANT) Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) Q_PROPERTY(bool checked MEMBER m_checked CONSTANT) Q_PROPERTY(QString tooltip MEMBER m_tooltip CONSTANT) Q_PROPERTY(QList children MEMBER m_children NOTIFY childrenChanged) Q_PROPERTY(bool checkable MEMBER m_checkable CONSTANT) Q_SIGNALS: void changed(); void childrenChanged(); void enabledChanged(bool enabled); public: TrainingAction(QObject *parent = nullptr); TrainingAction(const QString &text, QObject *parent = nullptr); TrainingAction(Phrase *phrase, DrawerTrainingActions *drawerActions, QObject *parent = nullptr); void appendChild(QObject *child); + bool hasChildren() const; Q_INVOKABLE void trigger(); bool enabled() const; void setEnabled(bool enabled); QObject * icon() const; private: QString m_text; TrainingActionIcon *m_icon{nullptr}; bool m_visible{true}; bool m_enabled{true}; bool m_checked{false}; bool m_checkable{false}; QString m_tooltip{QString()}; QList m_children; Phrase *m_phrase{nullptr}; DrawerTrainingActions * m_drawerTrainingActions{nullptr}; }; #endif diff --git a/src/core/trainingsession.cpp b/src/core/trainingsession.cpp index a7d1867..a2d8181 100644 --- a/src/core/trainingsession.cpp +++ b/src/core/trainingsession.cpp @@ -1,218 +1,203 @@ /* * Copyright 2013-2016 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 "trainingsession.h" #include "core/language.h" #include "core/course.h" #include "core/unit.h" #include "core/phrase.h" #include "core/phonemegroup.h" #include "profilemanager.h" #include "learner.h" #include "artikulate_debug.h" TrainingSession::TrainingSession(QObject *parent) : QObject(parent) , m_profileManager(nullptr) - , m_language(nullptr) , m_course(nullptr) , m_unit(nullptr) , m_phrase(nullptr) { } void TrainingSession::setProfileManager(LearnerProfile::ProfileManager *manager) { if (m_profileManager == manager) { return; } m_profileManager = manager; } -Language * TrainingSession::language() const -{ - return m_language; -} - -void TrainingSession::setLanguage(Language *language) -{ - if (m_language == language) { - return; - } - m_language = language; - emit languageChanged(); -} - Course * TrainingSession::course() const { return m_course; } void TrainingSession::setCourse(Course *course) { if (!course) { return; } if (m_course == course) { return; } m_course = course; - if (m_course && m_course->unitList().count() > 0) { - setUnit(m_course->unitList().first()); - } - - // lazy loading of training data - LearnerProfile::LearningGoal * goal = m_profileManager->goal( - LearnerProfile::LearningGoal::Language, m_course->id()); - if (!goal) { - goal = m_profileManager->registerGoal( - LearnerProfile::LearningGoal::Language, - course->language()->id(), - course->language()->i18nTitle() - ); - } - auto data = m_profileManager->progressValues(m_profileManager->activeProfile(), - goal, - m_course->id() - ); - Q_FOREACH(Unit *unit, m_course->unitList()) { - Q_FOREACH(Phrase *phrase, unit->phraseList()) { - auto iter = data.find(phrase->id()); - if (iter != data.end()) { - phrase->setProgress(iter.value()); - } - } - } +// if (m_course && m_course->unitList().count() > 0) { +// setUnit(m_course->unitList().first()); +// } + +// // lazy loading of training data +// LearnerProfile::LearningGoal * goal = m_profileManager->goal( +// LearnerProfile::LearningGoal::Language, m_course->id()); +// if (!goal) { +// goal = m_profileManager->registerGoal( +// LearnerProfile::LearningGoal::Language, +// course->language()->id(), +// course->language()->i18nTitle() +// ); +// } +// auto data = m_profileManager->progressValues(m_profileManager->activeProfile(), +// goal, +// m_course->id() +// ); +// Q_FOREACH(Unit *unit, m_course->unitList()) { +// Q_FOREACH(Phrase *phrase, unit->phraseList()) { +// auto iter = data.find(phrase->id()); +// if (iter != data.end()) { +// phrase->setProgress(iter.value()); +// } +// } +// } emit courseChanged(); } Unit * TrainingSession::unit() const { return m_unit; } void TrainingSession::setUnit(Unit *unit) { if (m_unit == unit) { return; } m_unit = unit; if (m_unit && m_unit->phraseList().count() > 0) { setPhrase(m_unit->phraseList().first()); } return unitChanged(); } Phrase * TrainingSession::phrase() const { return m_phrase; } void TrainingSession::setPhrase(Phrase *phrase) { if (m_phrase == phrase) { return; } setUnit(phrase->unit()); m_phrase = phrase; return phraseChanged(); } Phrase * TrainingSession::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) { return unit->course()->unitList().at(uIndex + 1)->phraseList().first(); } } return nullptr; } void TrainingSession::showNextPhrase() { // possibly update goals of learner updateGoal(); m_phrase->updateProgress(Phrase::Progress::Done); // store training activity LearnerProfile::LearningGoal * goal = m_profileManager->goal( LearnerProfile::LearningGoal::Language, m_course->language()->id()); m_profileManager->recordProgress(m_profileManager->activeProfile(), goal, m_course->id(), m_phrase->id(), static_cast(LearnerProfile::ProfileManager::Skip), m_phrase->progress() ); setPhrase(nextPhrase()); } void TrainingSession::skipPhrase() { // possibly update goals of learner updateGoal(); m_phrase->updateProgress(Phrase::Progress::Skip); // store training activity LearnerProfile::LearningGoal * goal = m_profileManager->goal( LearnerProfile::LearningGoal::Language, m_course->language()->id()); m_profileManager->recordProgress(m_profileManager->activeProfile(), goal, m_course->id(), m_phrase->id(), static_cast(LearnerProfile::ProfileManager::Skip), m_phrase->progress() ); setPhrase(nextPhrase()); } bool TrainingSession::hasNextPhrase() const { return nextPhrase() != nullptr; } void TrainingSession::updateGoal() { if (!m_profileManager) { qCWarning(ARTIKULATE_LOG) << "No ProfileManager registered, aborting operation"; return; } LearnerProfile::Learner *learner = m_profileManager->activeProfile(); if (!learner) { qCWarning(ARTIKULATE_LOG) << "No active Learner registered, aborting operation"; return; } LearnerProfile::LearningGoal * goal = m_profileManager->goal( LearnerProfile::LearningGoal::Language, m_course->language()->id()); learner->addGoal(goal); learner->setActiveGoal(goal); } diff --git a/src/core/trainingsession.h b/src/core/trainingsession.h index aa57a23..588f6e5 100644 --- a/src/core/trainingsession.h +++ b/src/core/trainingsession.h @@ -1,88 +1,83 @@ /* * 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 TRAININGSESSION_H #define TRAININGSESSION_H #include "artikulatecore_export.h" #include "course.h" #include "phrase.h" class QString; class Language; class Course; class Unit; class PhonemeGroup; namespace LearnerProfile { class ProfileManager; } /** * \class TrainingSession */ class ARTIKULATECORE_EXPORT TrainingSession : public QObject { Q_OBJECT - Q_PROPERTY(Language *language READ language WRITE setLanguage NOTIFY languageChanged) Q_PROPERTY(Course *course READ course WRITE setCourse NOTIFY courseChanged) 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) public: explicit TrainingSession(QObject *parent = nullptr); void setProfileManager(LearnerProfile::ProfileManager *manager); - Language * language() const; - void setLanguage(Language *language); Course * course() const; void setCourse(Course *course); Unit * unit() const; void setUnit(Unit *unit); PhonemeGroup * phonemeGroup() const; void setPhonemeGroup(PhonemeGroup *phonemeGroup); Phrase::Type phraseType() const; void setPhraseType(Phrase::Type type); Phrase * phrase() const; void setPhrase(Phrase *phrase); bool hasPreviousPhrase() const; bool hasNextPhrase() const; Q_INVOKABLE void showNextPhrase(); Q_INVOKABLE void skipPhrase(); Q_SIGNALS: - void languageChanged(); void courseChanged(); void unitChanged(); void phraseChanged(); -private:; +private: Q_DISABLE_COPY(TrainingSession) Phrase * nextPhrase() const; void updateGoal(); LearnerProfile::ProfileManager *m_profileManager; - Language *m_language; Course *m_course; Unit *m_unit; Phrase *m_phrase; }; #endif diff --git a/src/models/coursemodel.cpp b/src/models/coursemodel.cpp index 7e91a05..ca23b31 100644 --- a/src/models/coursemodel.cpp +++ b/src/models/coursemodel.cpp @@ -1,224 +1,213 @@ /* * Copyright 2013 Andreas Cord-Landwehr * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "coursemodel.h" #include "core/language.h" #include "core/course.h" #include "core/resourcemanager.h" #include "core/resources/courseresource.h" #include #include "artikulate_debug.h" #include #include CourseModel::CourseModel(QObject *parent) : QAbstractListModel(parent) , m_resourceManager(nullptr) , m_language(nullptr) , m_signalMapper(new QSignalMapper(this)) { connect(m_signalMapper, static_cast(&QSignalMapper::mapped), this, &CourseModel::emitCourseChanged); connect(this, &CourseModel::resourceManagerChanged, this, &CourseModel::rowCountChanged); connect(this, &CourseModel::languageChanged, this, &CourseModel::rowCountChanged); } -CourseModel::~CourseModel() -{ - -} - QHash< int, QByteArray > CourseModel::roleNames() const { QHash roles; roles[TitleRole] = "title"; roles[DescriptionRole] = "description"; roles[IdRole] = "id"; + roles[LanguageRole] = "language"; roles[DataRole] = "dataRole"; return roles; } void CourseModel::setResourceManager(ResourceManager *resourceManager) { if (m_resourceManager == resourceManager) { return; } beginResetModel(); if (m_resourceManager) { m_resourceManager->disconnect(this); } m_resourceManager = resourceManager; m_resources.clear(); if (m_resourceManager) { - connect(m_resourceManager, SIGNAL(courseResourceAboutToBeAdded(CourseResource*,int)), - SLOT(onCourseResourceAboutToBeAdded(CourseResource*,int))); - connect(m_resourceManager, SIGNAL(courseResourceAdded()), - SLOT(onCourseResourceAdded())); - connect(m_resourceManager, SIGNAL(courseResourceAboutToBeRemoved(int)), - SLOT(onCourseResourceAboutToBeRemoved(int))); + connect(m_resourceManager, &ResourceManager::courseResourceAboutToBeAdded, + this, &CourseModel::onCourseResourceAboutToBeAdded); + connect(m_resourceManager, &ResourceManager::courseResourceAdded, + this, &CourseModel::onCourseResourceAdded); + connect(m_resourceManager, &ResourceManager::courseResourceAboutToBeRemoved, + this, &CourseModel::onCourseResourceAboutToBeRemoved); } - if (m_language && m_resourceManager) { + if (m_resourceManager) { m_resources = m_resourceManager->courseResources(m_language); } endResetModel(); emit resourceManagerChanged(); } ResourceManager * CourseModel::resourceManager() const { return m_resourceManager; } Language * CourseModel::language() const { return m_language; } void CourseModel::setLanguage(Language *language) { emit beginResetModel(); m_language = language; - m_resources.clear(); - if (m_language) { - m_resources = m_resourceManager->courseResources(m_language); - } + m_resources = m_resourceManager->courseResources(m_language); emit languageChanged(); emit endResetModel(); emit rowCountChanged(); } QVariant CourseModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } if (index.row() >= m_resources.count()) { return QVariant(); } Course * const course = m_resources.at(index.row())->course(); switch(role) { case Qt::DisplayRole: return !course->title().isEmpty()? QVariant(course->title()): QVariant(i18nc("@item:inlistbox:", "unknown")); case Qt::ToolTipRole: return QVariant(course->title()); case TitleRole: return course->title(); case DescriptionRole: return course->description(); case IdRole: return course->id(); case ContributerResourceRole: return m_resources.at(index.row())->isContributorResource(); + case LanguageRole: + return QVariant::fromValue(course->language()); case DataRole: return QVariant::fromValue(course); default: return QVariant(); } } int CourseModel::rowCount(const QModelIndex& parent) const { - if (!m_language) { - return 0; - } if (parent.isValid()) { return 0; } return m_resources.count(); } void CourseModel::onCourseResourceAboutToBeAdded(CourseResource *resource, int index) { + Q_UNUSED(index); beginInsertRows(QModelIndex(), m_resources.count(), m_resources.count()); m_resources.append(resource); connect(resource->course(), SIGNAL(titleChanged()), m_signalMapper, SLOT(map())); //TODO add missing signals } void CourseModel::onCourseResourceAdded() { updateMappings(); endInsertRows(); emit rowCountChanged(); } void CourseModel::onCourseResourceAboutToBeRemoved(int index) { - if (!m_language) { + if (index >= m_resourceManager->courseResources(m_language).count()) { return; } CourseResource *originalResource = m_resourceManager->courseResources(m_language).at(index); int modelIndex = m_resources.indexOf(originalResource); if (modelIndex == -1) { qCWarning(ARTIKULATE_LOG) << "Cannot remove course from model, not registered"; return; } beginRemoveRows(QModelIndex(), modelIndex, modelIndex); m_resources.removeAt(modelIndex); endRemoveRows(); emit rowCountChanged(); } void CourseModel::emitCourseChanged(int row) { emit courseChanged(row); emit dataChanged(index(row, 0), index(row, 0)); } QVariant CourseModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) { return QVariant(); } if (orientation == Qt::Vertical) { return QVariant(section + 1); } return QVariant(i18nc("@title:column", "Course")); } void CourseModel::updateMappings() { - if (!m_language) { - qCDebug(ARTIKULATE_LOG) << "Aborting to update mappings, language not set."; - return; - } int courses = m_resources.count(); for (int i = 0; i < courses; i++) { m_signalMapper->setMapping(m_resources.at(i)->course(), i); } } QVariant CourseModel::course(int row) const { return data(index(row, 0), CourseModel::DataRole); } diff --git a/src/models/coursemodel.h b/src/models/coursemodel.h index 013b6ec..bf5bba3 100644 --- a/src/models/coursemodel.h +++ b/src/models/coursemodel.h @@ -1,87 +1,88 @@ /* * Copyright 2013-2015 Andreas Cord-Landwehr * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef COURSEMODEL_H #define COURSEMODEL_H #include class ResourceManager; class Course; class CourseResource; class Language; class QSignalMapper; class CourseModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(ResourceManager *resourceManager READ resourceManager WRITE setResourceManager NOTIFY resourceManagerChanged) Q_PROPERTY(Language *language READ language WRITE setLanguage NOTIFY languageChanged) Q_PROPERTY(int size READ rowCount NOTIFY rowCountChanged) public: enum courseRoles { TitleRole = Qt::UserRole + 1, DescriptionRole, IdRole, ContributerResourceRole, + LanguageRole, DataRole }; explicit CourseModel(QObject *parent = nullptr); - virtual ~CourseModel(); + ~CourseModel() override = default; /** * Reimplemented from QAbstractListModel::roleNames() */ - virtual QHash roleNames() const Q_DECL_OVERRIDE; + QHash roleNames() const override; void setResourceManager(ResourceManager *resourceManager); ResourceManager * resourceManager() const; void setLanguage(Language *language); Language * language() const; - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; Q_INVOKABLE QVariant course(int index) const; Q_SIGNALS: void courseChanged(int index); void resourceManagerChanged(); void languageChanged(); void rowCountChanged(); private Q_SLOTS: void onCourseResourceAboutToBeAdded(CourseResource *resource, int index); void onCourseResourceAdded(); void onCourseResourceAboutToBeRemoved(int index); void emitCourseChanged(int row); private: /** * Updates internal mappings of course signals. */ void updateMappings(); ResourceManager *m_resourceManager; Language *m_language; QList m_resources; QSignalMapper *m_signalMapper; }; #endif // COURSEMODEL_H diff --git a/src/qml/ArtikulateDrawer.qml b/src/qml/ArtikulateDrawer.qml index 9f2b1ad..fb83a9d 100644 --- a/src/qml/ArtikulateDrawer.qml +++ b/src/qml/ArtikulateDrawer.qml @@ -1,152 +1,151 @@ /* * Copyright 2018 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.Layouts 1.3 import QtQuick.Controls 2.0 as QQC2 import org.kde.kirigami 2.0 as Kirigami import artikulate 1.0 Kirigami.GlobalDrawer { id: root title: "Artikulate" titleIcon: "artikulate" resetMenuOnTriggered: false bottomPadding: 0 property QtObject pageStack // enforce drawer always to be open modal: false handleVisible: false topContent: [ ColumnLayout { spacing: 0 Layout.fillWidth: true Layout.leftMargin: -root.leftPadding Layout.rightMargin: -root.rightPadding ActionListItem { action: Kirigami.Action { text: i18n("Training") iconName: "artikulate" onTriggered: { root.pageStack.clear(); root.pageStack.push(welcomePageComponent); } } } Kirigami.Separator { Layout.fillWidth: true } } ] // ordinary Kirigami actions are filled from training units/phrases actions: trainingActions.actions DrawerTrainingActions { id: trainingActions - course: g_trainingSession.course session: g_trainingSession onTriggerTrainingView: { root.pageStack.clear(); root.pageStack.push(trainingPageComponent); } } //TODO integrate again // [ // Kirigami.Action { // text: i18n("Help") // iconName: "help-about" // Kirigami.Action { // text: i18n("Artikulate Handbook") // iconName: "help-contents" // onTriggered: { // triggerAction("help_contents"); // globalDrawer.resetMenu(); // } // } // Kirigami.Action { // text: i18n("Report Bug") // iconName: "tools-report-bug" // onTriggered: { // triggerAction("help_report_bug"); // globalDrawer.resetMenu(); // } // } // Kirigami.Action { // text: i18n("About Artikulate") // iconName: "artikulate" // onTriggered: { // triggerAction("help_about_app") // globalDrawer.resetMenu(); // } // } // Kirigami.Action { // text: i18n("About KDE") // iconName: "help-about" // onTriggered: { // triggerAction("help_about_kde") // globalDrawer.resetMenu(); // } // } // } // ] ColumnLayout { spacing: 0 Layout.fillWidth: true Layout.leftMargin: -root.leftPadding Layout.rightMargin: -root.rightPadding Kirigami.Separator { Layout.fillWidth: true } ActionListItem { action: Kirigami.Action { text: i18n("Statistics") iconName: "user-properties" onTriggered: { root.pageStack.pop(); root.pageStack.push(profileSettingsPageComponent); } } } ActionListItem { action: Kirigami.Action { text: i18n("Settings") iconName: "settings-configure" onTriggered: triggerSettingsDialog() } } ActionListItem { action: Kirigami.Action { text: i18n("Download Training") iconName: "get-hot-new-stuff" onTriggered: { root.pageStack.pop(); root.pageStack.push(downloadPageComponent); } } } } } diff --git a/src/qml/DownloadPage.qml b/src/qml/DownloadPage.qml index 65077ee..49ac500 100644 --- a/src/qml/DownloadPage.qml +++ b/src/qml/DownloadPage.qml @@ -1,115 +1,119 @@ /* * Copyright 2018 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.1 as QQC2 import org.kde.kirigami 2.0 as Kirigami import org.kde.newstuff 1.0 as KNS Kirigami.Page { id: root title: i18n("Download Training Material") Component { id: courseDownloadItem Kirigami.AbstractListItem { id: listItem height: 50 width: parent.width text: model.name + readonly property var status: model.status + onStatusChanged: { + g_resourceManager.loadCourseResources(); + } checkable: false RowLayout { id: layout spacing: Kirigami.Units.smallSpacing*2 Kirigami.Icon { height: Kirigami.Units.iconSizes.smallMedium; width: height; SequentialAnimation on opacity { loops: Animation.Infinite; running: model.status == KNS.ItemsModel.InstallingStatus || model.status == KNS.ItemsModel.UpdatingStatus NumberAnimation { to: 0; duration: 500; } NumberAnimation { to: 1; duration: 500; } onRunningChanged: { if (!running) parent.opacity = 1; } } source: { // use complete list of KNS status messages if (model.status == KNS.ItemsModel.InvalidStatus) return "emblem-error"; if (model.status == KNS.ItemsModel.DownloadableStatus) return "vcs-added"; if (model.status == KNS.ItemsModel.InstalledStatus) return "vcs-normal"; if (model.status == KNS.ItemsModel.UpdateableStatus) return "vcs-update-required"; if (model.status == KNS.ItemsModel.DeletedStatus) return "vcs-added"; if (model.status == KNS.ItemsModel.InstallingStatus) return "vcs-locally-modified"; if (model.status == KNS.ItemsModel.UpdatingStatus) return "vcs-locally-modified"; return "emblem-error"; } } QQC2.Label { id: labelItem Layout.fillWidth: true text: listItem.text color: layout.indicateActiveFocus && (listItem.highlighted || listItem.checked || listItem.pressed) ? listItem.activeTextColor : listItem.textColor elide: Text.ElideRight font: listItem.font } QQC2.Button { visible: (model.status == KNS.ItemsModel.UpdateableStatus) ? true : false; text: i18n("update") onClicked: newStuffModel.installItem(model.index) } QQC2.Button { visible: (model.status == KNS.ItemsModel.DownloadableStatus || model.status == KNS.ItemsModel.DeletedStatus) ? true : false; text: i18n("install") onClicked: newStuffModel.installItem(model.index) } QQC2.Button { visible: (model.status == KNS.ItemsModel.InstalledStatus || model.status == KNS.ItemsModel.UpdateableStatus) ? true : false; text: i18n("remove") onClicked: newStuffModel.uninstallItem(model.index) } } } } ColumnLayout { ListView { id: listView width: root.width - 40 height: 50 * listView.count delegate: courseDownloadItem model: KNS.ItemsModel { id: newStuffModel; engine: newStuffEngine.engine; } KNS.Engine { id: newStuffEngine; configFile: ":/artikulate/config/artikulate.knsrc"; onMessage: console.log("KNS Message: " + message); onIdleMessage: console.log("KNS Idle: " + message); onBusyMessage: console.log("KNS Busy: " + message); onErrorMessage: console.log("KNS Error: " + message); } } } } diff --git a/src/qml/LanguageSwitcher.qml b/src/qml/LanguageSwitcher.qml deleted file mode 100644 index cdf0a46..0000000 --- a/src/qml/LanguageSwitcher.qml +++ /dev/null @@ -1,156 +0,0 @@ -/* - * 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 . - */ - -import QtQuick 2.1 -import QtQuick.Controls 1.2 -import artikulate 1.0 - -Item { - id: root - - property Language selectedLanguage - property ResourceManager resourceManager - - signal languageSelected(variant language) - - width: 300 - height: Math.max(buttonLeft.height, languageView.height) - - // emit language selection - Connections { - target: buttonRight - onClicked: { - selectedLanguage = languageModel.language(languageView.currentIndex) - languageSelected(selectedLanguage) - } - } - Connections { - target: buttonLeft - onClicked: { - selectedLanguage = languageModel.language(languageView.currentIndex) - languageSelected(selectedLanguage) - } - } - - // react on changed goals - Component.onCompleted: { - for (var i = 0; i < languageView.count; ++i) { - if (g_trainingSession.language.id == languageModel.language(i).id) { - languageView.currentIndex = i - } - } - } - - Component { - id: itemDelegate - - Row { - id: languageInfo - property Language language: model.dataRole - - width: root.width - buttonLeft.width - buttonRight.width - 20 - height: theme.mediumIconSize - spacing: 10 - - Icon { - id: icon - icon: "artikulate-language" - width: theme.mediumIconSize - height: theme.mediumIconSize - anchors.verticalCenter: parent.verticalCenter - } - Label { - id: languageTitleLabel - anchors.verticalCenter: parent.verticalCenter - height: paintedHeight - font.pointSize: 1.5 * theme.fontPointSize - text: language != null ? language.title + " / " + language.i18nTitle : "" - } - } - } - - ListView { - id: languageView - - width: root.width - buttonLeft.width - buttonRight.width - 20 - height: theme.mediumIconSize - - clip: true - snapMode: ListView.SnapToItem - orientation: ListView.Vertical - model: LanguageModel { - id: languageModel - view: LanguageModel.NonEmptyGhnsOnlyLanguages - resourceModel: LanguageResourceModel { resourceManager: root.resourceManager } - } - delegate: itemDelegate - } - - Row { - visible: languageView.count == 0 - spacing: 10 - anchors { - left: languageView.left - top: languageView.top - } - Icon { - id: icon - icon: "dialog-information" - width: theme.mediumIconSize - height: theme.mediumIconSize - anchors.verticalCenter: parent.verticalCenter - } - Label { - id: favoritesUnsetInformation - anchors.verticalCenter: parent.verticalCenter - height: paintedHeight - font.pointSize: 1.5 * theme.fontPointSize - text: i18n("Please download a course") + languageModel.rows - } - } - - ToolButton { - id: buttonLeft - anchors { - left: languageView.right - leftMargin: 10 - top: languageView.top - } - iconName: "arrow-left" - enabled: languageView.currentIndex > 0 && languageView.count > 0 - onClicked: { - languageView.decrementCurrentIndex() - } - } - - ToolButton { - id : buttonRight - anchors { - left: buttonLeft.right - leftMargin: 10 - top: languageView.top - } - enabled: languageView.currentIndex < languageView.count - 1 && languageView.count > 0 - iconName: "arrow-right" - onClicked: { - languageView.incrementCurrentIndex() - } - } -} diff --git a/src/qml/WelcomePage.qml b/src/qml/WelcomePage.qml index 72b6e2e..6d97752 100644 --- a/src/qml/WelcomePage.qml +++ b/src/qml/WelcomePage.qml @@ -1,93 +1,86 @@ /* - * Copyright 2015-2017 Andreas Cord-Landwehr + * 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 org.kde.kirigami 2.0 as Kirigami2 +import QtQuick.Layouts 1.3 +import org.kde.kirigami 2.4 as Kirigami import artikulate 1.0 -Kirigami2.Page { +Kirigami.ScrollablePage { id: root title: i18n("Welcome to Artikulate") - Column { - spacing: 20 - - Row { - spacing: 20 - - Icon { - id: langIcon - icon: "language-artikulate" - width: 48 - height: 48 - } + Kirigami.CardsListView { + id: listView + width: root.width - 40 + model: CourseModel { + id: courseModel + resourceManager: g_resourceManager + } - QQC2.ComboBox { - id: comboLanguage - width: 200 - model: LanguageModel { - id: languageModel - resourceModel: LanguageResourceModel { - resourceManager: g_resourceManager + delegate: Kirigami.AbstractCard { + contentItem: Item { + implicitWidth: delegateLayout.implicitWidth + implicitHeight: delegateLayout.implicitHeight + GridLayout { + id: delegateLayout + anchors { + left: parent.left + top: parent.top + right: parent.right } - } - textRole: "title" - onCurrentIndexChanged: { - if (languageModel.language(currentIndex)) { - g_trainingSession.language = languageModel.language(currentIndex) - } - } - } - - QQC2.ComboBox { - id: comboCourse - enabled: { - courseFilterModel.filteredCount == 0 ? false : true - } - width: 200 - model: CourseFilterModel { - id: courseFilterModel - view: { - kcfg_UseContributorResources - ? CourseFilterModel.AllResources - : CourseFilterModel.OnlyGetHotNewStuffResources + 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 } - courseModel: CourseModel { - id: courseModel - resourceManager: g_resourceManager - language: g_trainingSession.language - onLanguageChanged: { - if (courseFilterModel.course(0)) { - g_trainingSession.course = courseFilterModel.course(0) - } + ColumnLayout { + Kirigami.Heading { + level: 2 + text: model.language.title + " / " + model.title + } + Kirigami.Separator { + Layout.fillWidth: true + } + QQC2.Label { + Layout.fillWidth: true + wrapMode: Text.WordWrap + text: model.description } } - } - textRole: "title" - onCurrentIndexChanged: { - if (courseFilterModel.course(currentIndex)) { - g_trainingSession.course = courseFilterModel.course(currentIndex) + QQC2.Button { + Layout.alignment: Qt.AlignRight|Qt.AlignVCenter + Layout.columnSpan: 2 + text: qsTr("Start Training") + onClicked: { + showPassiveNotification("Starting training session for course " + model.title + "."); + g_trainingSession.course = model.dataRole + } } } } } } } diff --git a/src/resources.qrc b/src/resources.qrc index 2a7eb72..b399c19 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -1,30 +1,29 @@ - + qml/ActionListItem.qml qml/ArtikulateDrawer.qml qml/CheckListItem.qml qml/CourseSwitcher.qml qml/DownloadPage.qml qml/Editor.qml - qml/LanguageSwitcher.qml qml/ListItem.qml qml/Main.qml qml/PhonemeUnitSelector.qml qml/PhraseEditor.qml qml/PhraseEditorEditStateComponent.qml qml/PhraseEditorSoundComponent.qml qml/PhraseEditorTypeComponent.qml qml/ProfileSelector.qml qml/ProfileSettingsPage.qml qml/ProfileUserImageItem.qml qml/SoundPlayer.qml qml/SoundRecorder.qml qml/TrainerCourseStatistics.qml qml/TrainingPage.qml qml/UnitEditor.qml qml/WelcomePage.qml artikulate.knsrc