diff --git a/autotests/testcoursefiles.cpp b/autotests/testcoursefiles.cpp index df7f5bd..da05115 100644 --- a/autotests/testcoursefiles.cpp +++ b/autotests/testcoursefiles.cpp @@ -1,141 +1,141 @@ /* * 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 "testcoursefiles.h" #include "core/resourcemanager.h" #include "core/course.h" #include "core/language.h" #include "core/unit.h" #include "core/phrase.h" #include "core/resources/languageresource.h" #include "core/resources/courseresource.h" #include "../src/settings.h" #include #include #include #include #include #include #include #include TestCourseFiles::TestCourseFiles() : m_systemUseCourseRepositoryValue(Settings::useCourseRepository()) { } void TestCourseFiles::init() { //FIXME has to be ported // KGlobal::dirs()->addResourceDir("appdata" , "./testcourses/"); // KGlobal::dirs()->addResourceDir("appdata" , "./"); // KGlobal::dirs()->addResourceDir("appdata" , "./autotests/"); // KGlobal::dirs()->addResourceDir("appdata" , "./autotests/testcourses/"); Settings::setUseCourseRepository(false); Settings::self()->save(); } void TestCourseFiles::cleanup() { // reset value Settings::setUseCourseRepository(m_systemUseCourseRepositoryValue); Settings::self()->save(); } void TestCourseFiles::courseSchemeValidationTest() { QUrl schemeFile = QUrl::fromLocalFile(QStringLiteral("schemes/course.xsd")); QXmlSchema courseSchema; QVERIFY(courseSchema.load(schemeFile)); QVERIFY(courseSchema.isValid()); QUrl skeletonFile = QUrl::fromLocalFile(QStringLiteral("schemes/skeleton.xsd")); QXmlSchema skeletonScheme; QVERIFY(skeletonScheme.load(skeletonFile)); QVERIFY(skeletonScheme.isValid()); } void TestCourseFiles::fileLoadSaveCompleteness() { ResourceManager manager; manager.addLanguage(QUrl::fromLocalFile(QStringLiteral("data/languages/de.xml"))); manager.addCourse(QUrl::fromLocalFile(QStringLiteral("data/courses/de.xml"))); // test to encure further logic - QVERIFY(manager.courseResources(manager.languageResources().first()->language()).count() == 1); + QVERIFY(manager.courseResources(manager.languageResources().constFirst()->language()).count() == 1); - Course *testCourse = manager.courseResources(manager.languageResources().first()->language()).first()->course(); + Course *testCourse = manager.courseResources(manager.languageResources().constFirst()->language()).constFirst()->course(); QTemporaryFile outputFile; outputFile.open(); QUrl oldFileName = testCourse->file(); testCourse->setFile(QUrl::fromLocalFile(outputFile.fileName())); - testCourse->setLanguage(manager.languageResources().first()->language()); + testCourse->setLanguage(manager.languageResources().constFirst()->language()); testCourse->sync(); testCourse->setFile(oldFileName); // restore for later tests QFile file(outputFile.fileName()); if (!file.open(QIODevice::ReadOnly)) { qCritical() << "Could not open file to read."; } //TODO this only works, since the resource manager not checks uniqueness of course ids! manager.addCourse(QUrl::fromLocalFile(outputFile.fileName())); - Course *compareCourse = manager.courseResources(manager.languageResources().first()->language()).last()->course(); + Course *compareCourse = manager.courseResources(manager.languageResources().constFirst()->language()).constLast()->course(); // test that we actually call the different files QVERIFY(testCourse->file().toLocalFile() != compareCourse->file().toLocalFile()); QVERIFY(testCourse->id() == compareCourse->id()); QVERIFY(testCourse->foreignId() == compareCourse->foreignId()); QVERIFY(testCourse->title() == compareCourse->title()); QVERIFY(testCourse->description() == compareCourse->description()); QVERIFY(testCourse->language()->id() == compareCourse->language()->id()); QVERIFY(testCourse->unitList().count() == compareCourse->unitList().count()); - Unit *testUnit = testCourse->unitList().first(); - Unit *compareUnit = compareCourse->unitList().first(); + Unit *testUnit = testCourse->unitList().constFirst(); + Unit *compareUnit = compareCourse->unitList().constFirst(); QVERIFY(testUnit->id() == compareUnit->id()); QVERIFY(testUnit->foreignId() == compareUnit->foreignId()); QVERIFY(testUnit->title() == compareUnit->title()); QVERIFY(testUnit->phraseList().count() == compareUnit->phraseList().count()); - Phrase *testPhrase = testUnit->phraseList().first(); + Phrase *testPhrase = testUnit->phraseList().constFirst(); Phrase *comparePhrase = new Phrase(this); // Note that this actually means that we DO NOT respect phrase orders by list order! foreach (Phrase *phrase, compareUnit->phraseList()) { if (testPhrase->id() == phrase->id()) { comparePhrase = phrase; break; } } QVERIFY(testPhrase->id() == comparePhrase->id()); QVERIFY(testPhrase->foreignId() == comparePhrase->foreignId()); QVERIFY(testPhrase->text() == comparePhrase->text()); QVERIFY(testPhrase->type() == comparePhrase->type()); QVERIFY(testPhrase->sound().fileName() == comparePhrase->sound().fileName()); QVERIFY(testPhrase->phonemes().count() == comparePhrase->phonemes().count()); //FIXME implement phoneme checks after phonemes are fully implemented } QTEST_GUILESS_MAIN(TestCourseFiles) diff --git a/liblearnerprofile/src/learner.cpp b/liblearnerprofile/src/learner.cpp index 145d526..08dd9d0 100644 --- a/liblearnerprofile/src/learner.cpp +++ b/liblearnerprofile/src/learner.cpp @@ -1,205 +1,205 @@ /* * Copyright 2013-2016 Andreas Cord-Landwehr * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 6 of version 3 of the license. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "learner.h" #include "learner_p.h" #include "learninggoal.h" #include #include #include #include #include "liblearner_debug.h" using namespace LearnerProfile; Learner::Learner(QObject *parent) : QObject(parent) , d(new LearnerPrivate) { connect(this, &Learner::goalAdded, this, &Learner::goalCountChanged); connect(this, static_cast(&Learner::goalRemoved), this, &Learner::goalCountChanged); } Learner::~Learner() { } QString Learner::name() const { return d->m_name; } void Learner::setName(const QString &name) { if (name == d->m_name) { return; } d->m_name = name; emit nameChanged(); } int Learner::identifier() const { return d->m_identifier; } void Learner::setIdentifier(int identifier) { if (identifier == d->m_identifier) { return; } d->m_identifier = identifier; emit identifierChanged(); } QString Learner::imageUrl() const { QString path = d->imagePath(); - if (!QFileInfo(path).exists()) { + if (!QFileInfo::exists(path)) { return QString(); } return "file://" + path; } void Learner::clearImage() { const QString path {d->imagePath()}; - if (!QFileInfo(path).exists()) { + if (!QFileInfo::exists(path)) { return; } QFile file; if (!file.remove(path)) { qCCritical(LIBLEARNER_LOG) << "could not remove image:" << path; } emit imageChanged(); } void Learner::importImage(const QString &path) { - if (!QFileInfo(path).exists()) { + if (!QFileInfo::exists(path)) { qCWarning(LIBLEARNER_LOG) << "image path points to a non-existing file, aborting: " << path; return; } // create image directory if it does not exist QDir dir; if (!dir.exists(d->imageDirectory())) { dir.mkdir(d->imageDirectory()); } QPixmap image = QPixmap(path); image = image.scaled(120, 120); if (!image.save(d->imagePath(), "PNG")) { qCCritical(LIBLEARNER_LOG()) << "could not save scaled image to" << d->imagePath(); } emit imageChanged(); qCDebug(LIBLEARNER_LOG) << "saved scaled image from " << path << " at " << d->imagePath(); } QList< LearningGoal* > Learner::goals() const { return d->m_goals; } void Learner::addGoal(LearnerProfile::LearningGoal *goal) { if (d->m_goals.contains(goal)) { return; } emit goalAboutToBeAdded(goal, d->m_goals.count()); d->m_goals.append(goal); emit goalAdded(); } void Learner::removeGoal(LearnerProfile::LearningGoal *goal) { int index = d->m_goals.indexOf(goal); if (index < 0) { qCritical() << "Cannot remove goal, not found: aborting"; return; } emit goalAboutToBeRemoved(index); d->m_goals.removeAt(index); emit goalRemoved(); emit goalRemoved(this, goal); } bool Learner::hasGoal(LearningGoal* goal) const { foreach (LearningGoal *cmpGoal, d->m_goals) { if (goal->identifier() == cmpGoal->identifier()) { return true; } } return false; } void Learner::setActiveGoal(LearningGoal *goal) { if (d->m_activeGoal.contains(goal->category()) && d->m_activeGoal[goal->category()] == goal) { return; } d->m_activeGoal.insert(goal->category(), goal); emit activeGoalChanged(); } void Learner::setActiveGoal(Learner::Category categoryLearner, const QString &identifier) { // TODO:Qt5 change method parameter to LearningGoal::Category // workaround for Q_INVOKABLE access of enum LearningGoal::Category category = static_cast(categoryLearner); if (d->m_activeGoal.contains(category) && d->m_activeGoal[category]->identifier() == identifier) { return; } foreach (LearningGoal *goal, d->m_goals) { if (goal->category() == category && goal->identifier() == identifier) { setActiveGoal(goal); return; } } qCritical() << "Could not select learning goal with ID " << identifier << ": not registered for this learner"; } LearningGoal * Learner::activeGoal(Learner::Category categoryLearner) const { // TODO:Qt5 change method parameter to LearningGoal::Category // workaround for Q_INVOKABLE access of enum LearningGoal::Category category = static_cast(categoryLearner); if (!d->m_activeGoal.contains(category)) { qCWarning(LIBLEARNER_LOG) << "(Learner " << identifier() << ") No current learning goal set for category " << category << " : fall back to first in list"; foreach (LearningGoal *goal, d->m_goals) { if (goal->category() == category) { return goal; } } qCWarning(LIBLEARNER_LOG) << "No learning goals of category " << category << " registered"; return nullptr; } return d->m_activeGoal[category]; } diff --git a/liblearnerprofile/src/learninggoal.cpp b/liblearnerprofile/src/learninggoal.cpp index c81e765..ebe4cc8 100644 --- a/liblearnerprofile/src/learninggoal.cpp +++ b/liblearnerprofile/src/learninggoal.cpp @@ -1,94 +1,94 @@ /* * Copyright 2013 Andreas Cord-Landwehr * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 6 of version 3 of the license. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "learninggoal.h" using namespace LearnerProfile; // private class LearningGoalPrivate class LearningGoalPrivate { public: LearningGoalPrivate() : m_name(QString()) , m_identifier(QString()) , m_category(LearningGoal::Unspecified) { } ~LearningGoalPrivate() {} QString m_name; QString m_identifier; LearningGoal::Category m_category; }; // methods of LearningGoal LearningGoal::LearningGoal(QObject *parent) : QObject(parent) , d(new LearningGoalPrivate) { // create goal of unspecified category } LearningGoal::LearningGoal(LearningGoal::Category category, const QString &identifier, QObject *parent) : QObject(parent) , d(new LearningGoalPrivate) { d->m_category = category; d->m_identifier = identifier; } QString LearningGoal::name() const { return d->m_name; } LearningGoal::~LearningGoal() { } void LearningGoal::setName(const QString &name) { if (name == d->m_name) { return; } d->m_name = name; - nameChanged(); + emit nameChanged(); } QString LearningGoal::identifier() const { return d->m_identifier; } void LearningGoal::setIdentifier(const QString &identifier) { if (identifier == d->m_identifier) { return; } d->m_identifier = identifier; emit identifierChanged(); } LearningGoal::Category LearningGoal::category() const { return d->m_category; } diff --git a/liblearnerprofile/src/models/learninggoalmodel.cpp b/liblearnerprofile/src/models/learninggoalmodel.cpp index 05a4462..0eca6d4 100644 --- a/liblearnerprofile/src/models/learninggoalmodel.cpp +++ b/liblearnerprofile/src/models/learninggoalmodel.cpp @@ -1,243 +1,243 @@ /* * Copyright 2013-2016 Andreas Cord-Landwehr * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 6 of version 3 of the license. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "learninggoalmodel.h" #include "profilemanager.h" #include "learner.h" #include #include #include #include #include "liblearner_debug.h" using namespace LearnerProfile; // private class LearningGoalModelPrivate class LearningGoalModelPrivate { public: LearningGoalModelPrivate() : m_profileManager(nullptr) , m_learner(nullptr) , m_signalMapper(new QSignalMapper()) { } ~LearningGoalModelPrivate() { delete m_signalMapper; } void updateGoals(); void updateMappings(); ProfileManager *m_profileManager; Learner *m_learner; QList m_goals; QSignalMapper *m_signalMapper; }; void LearningGoalModelPrivate::updateGoals() { m_goals.clear(); // set all registered goals from profile manager if (m_profileManager) { foreach (LearningGoal *goal, m_profileManager->goals()) { m_goals.append(goal); } } // TODO add learner status information } void LearningGoalModelPrivate::updateMappings() { if (!m_profileManager) { return; } int goals = m_goals.count(); for (int i = 0; i < goals; ++i) { m_signalMapper->setMapping(m_goals.at(i), i); } } // class LearningGoalModel LearningGoalModel::LearningGoalModel(QObject *parent) : QAbstractListModel(parent) , d(new LearningGoalModelPrivate) { connect(d->m_signalMapper, static_cast(&QSignalMapper::mapped), this, &LearningGoalModel::emitLearningGoalChanged); } LearningGoalModel::~LearningGoalModel() { } QHash< int, QByteArray > LearningGoalModel::roleNames() const { QHash roles; roles[TitleRole] = "title"; roles[IdRole] = "id"; roles[DataRole] = "dataRole"; return roles; } void LearningGoalModel::setProfileManager(ProfileManager *profileManager) { if (d->m_profileManager == profileManager) { return; } beginResetModel(); if (d->m_profileManager) { d->m_profileManager->disconnect(this); } d->m_profileManager = profileManager; d->updateGoals(); d->updateMappings(); endResetModel(); emit profileManagerChanged(); } ProfileManager * LearningGoalModel::profileManager() const { return d->m_profileManager; } Learner * LearningGoalModel::learner() const { return d->m_learner; } void LearningGoalModel::setLearner(Learner *learner) { if (!learner) { return; } - emit beginResetModel(); + beginResetModel(); if (d->m_learner) { learner->disconnect(this); } d->m_learner = learner; d->updateGoals(); d->updateMappings(); connect(learner, &Learner::goalAboutToBeAdded, this, &LearningGoalModel::onLearningGoalAboutToBeAdded); connect(learner, &Learner::goalAdded, this, &LearningGoalModel::onLearningGoalAdded); connect(learner, &Learner::goalAboutToBeRemoved, this, &LearningGoalModel::onLearningGoalAboutToBeRemoved); emit learnerChanged(); - emit endResetModel(); + endResetModel(); } QVariant LearningGoalModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (index.row() >= d->m_goals.count()) { return QVariant(); } LearningGoal * const goal = d->m_goals.at(index.row()); switch(role) { case Qt::DisplayRole: return !goal->name().isEmpty()? QVariant(goal->name()): QVariant(i18nc("@item:inlistbox:", "unknown")); case Qt::ToolTipRole: return QVariant(goal->name()); case TitleRole: return goal->name(); case IdRole: return goal->identifier(); case DataRole: return QVariant::fromValue(goal); default: return QVariant(); } } int LearningGoalModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return d->m_goals.count(); } void LearningGoalModel::onLearningGoalAboutToBeAdded(LearningGoal *goal, int index) { Q_UNUSED(index) beginInsertRows(QModelIndex(), d->m_goals.count(), d->m_goals.count()); d->m_goals.append(goal); d->updateMappings(); } void LearningGoalModel::onLearningGoalAdded() { endInsertRows(); } void LearningGoalModel::onLearningGoalAboutToBeRemoved(int index) { if (!d->m_learner) { return; } if (index < 0 || d->m_goals.count() <= index) { qCWarning(LIBLEARNER_LOG) << "Cannot remove learning goal from model, not registered"; return; } beginRemoveRows(QModelIndex(), index, index); d->m_goals.removeAt(index); d->updateMappings(); endRemoveRows(); } void LearningGoalModel::emitLearningGoalChanged(int row) { emit learningGoalChanged(row); emit dataChanged(index(row, 0), index(row, 0)); } QVariant LearningGoalModel::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", "Learning Goal")); } QVariant LearningGoalModel::learningGoal(int row) const { return data(index(row, 0), LearningGoalModel::DataRole); } diff --git a/liblearnerprofile/src/storage.cpp b/liblearnerprofile/src/storage.cpp index f8921a0..3995f05 100644 --- a/liblearnerprofile/src/storage.cpp +++ b/liblearnerprofile/src/storage.cpp @@ -1,679 +1,679 @@ /* * Copyright 2013-2016 Andreas Cord-Landwehr * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 6 of version 3 of the license. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "storage.h" #include "learner.h" #include "liblearner_debug.h" #include #include #include #include #include #include #include using namespace LearnerProfile; Storage::Storage(QObject* parent) : QObject(parent) , m_databasePath(QStandardPaths::writableLocation( QStandardPaths::DataLocation) + QLatin1Char('/') + "learnerdata.db") , m_errorMessage(QString()) { } Storage::Storage(const QString databasePath, QObject* parent) : QObject(parent) , m_databasePath(databasePath) , m_errorMessage(QString()) { qCDebug(LIBLEARNER_LOG) << "Initialize with custom DB path:" << m_databasePath; } QString Storage::errorMessage() const { return m_errorMessage; } void Storage::raiseError(const QSqlError &error) { - m_errorMessage = QStringLiteral("%1 : %2").arg(error.driverText()).arg(error.databaseText()); + m_errorMessage = QStringLiteral("%1 : %2").arg(error.driverText(), error.databaseText()); emit errorMessageChanged(); } bool Storage::storeProfile(Learner *learner) { QSqlDatabase db = database(); // test whether ID is present QSqlQuery idExistsQuery(db); idExistsQuery.prepare(QStringLiteral("SELECT COUNT(*) FROM profiles WHERE id = :id")); idExistsQuery.bindValue(QStringLiteral(":id"), learner->identifier()); idExistsQuery.exec(); if (db.lastError().isValid()) { qCritical() << "ExistsQuery: " << db.lastError().text(); raiseError(db.lastError()); return false; } // go to first result row that contains the count idExistsQuery.next(); if (idExistsQuery.value(0).toInt() < 1) { // in case learner ID is not found in database QSqlQuery insertProfileQuery(db); insertProfileQuery.prepare(QStringLiteral("INSERT INTO profiles (id, name) VALUES (?, ?)")); insertProfileQuery.bindValue(0, learner->identifier()); insertProfileQuery.bindValue(1, learner->name()); insertProfileQuery.exec(); if (insertProfileQuery.lastError().isValid()) { raiseError(insertProfileQuery.lastError()); db.rollback(); return false; } } else { // update name otherwise QSqlQuery updateProfileQuery(db); updateProfileQuery.prepare(QStringLiteral("UPDATE profiles SET name = :name WHERE id = :id")); updateProfileQuery.bindValue(QStringLiteral(":id"), learner->identifier()); updateProfileQuery.bindValue(QStringLiteral(":name"), learner->name()); updateProfileQuery.exec(); if (updateProfileQuery.lastError().isValid()) { qCritical() << updateProfileQuery.lastError().text(); raiseError(updateProfileQuery.lastError()); db.rollback(); return false; } } // store existing learning goal relations foreach (LearningGoal *goal, learner->goals()) { QSqlQuery relationExistsQuery(db); relationExistsQuery.prepare("SELECT COUNT(*) FROM learner_goals " "WHERE goal_category = :goalCategory " "AND goal_identifier = :goalIdentifier " "AND profile_id = :profileId " ); relationExistsQuery.bindValue(QStringLiteral(":goalCategory"), goal->category()); relationExistsQuery.bindValue(QStringLiteral(":goalIdentifier"), goal->identifier()); relationExistsQuery.bindValue(QStringLiteral(":profileId"), learner->identifier()); relationExistsQuery.exec(); if (db.lastError().isValid()) { qCritical() << "ExistsQuery: " << db.lastError().text(); raiseError(db.lastError()); return false; } // go to first result row that contains the count relationExistsQuery.next(); if (relationExistsQuery.value(0).toInt() < 1) { QSqlQuery insertProfileQuery(db); insertProfileQuery.prepare(QStringLiteral("INSERT INTO learner_goals (goal_category, goal_identifier, profile_id) VALUES (?, ?, ?)")); insertProfileQuery.bindValue(0, goal->category()); insertProfileQuery.bindValue(1, goal->identifier()); insertProfileQuery.bindValue(2, learner->identifier()); insertProfileQuery.exec(); } } // remove deleted relations QSqlQuery cleanupRelations(db); cleanupRelations.prepare(QStringLiteral("DELETE FROM learner_goals WHERE ")); //TODO change creation of relations to same way as remove-relations: explicit connections return true; } bool Storage::removeProfile(Learner *learner) { QSqlDatabase db = database(); QSqlQuery removeProfileQuery(db); // delete learner removeProfileQuery.prepare(QStringLiteral("DELETE FROM profiles WHERE id = ?")); removeProfileQuery.bindValue(0, learner->identifier()); removeProfileQuery.exec(); if (removeProfileQuery.lastError().isValid()) { qCritical() << removeProfileQuery.lastError().text(); raiseError(removeProfileQuery.lastError()); db.rollback(); return false; } // delete learning goal relations QSqlQuery removeGoalRelationQuery(db); removeGoalRelationQuery.prepare(QStringLiteral("DELETE FROM learner_goals WHERE profile_id = ?")); removeGoalRelationQuery.bindValue(0, learner->identifier()); removeGoalRelationQuery.exec(); if (removeGoalRelationQuery.lastError().isValid()) { qCritical() << removeGoalRelationQuery.lastError().text(); raiseError(removeGoalRelationQuery.lastError()); db.rollback(); return false; } return true; } bool Storage::removeRelation(Learner *learner, LearningGoal *goal) { QSqlDatabase db = database(); QSqlQuery removeGoalRelationQuery(db); removeGoalRelationQuery.prepare( "DELETE FROM learner_goals " "WHERE goal_category = :goalCategory " "AND goal_identifier = :goalIdentifier " "AND profile_id = :profileId " ); removeGoalRelationQuery.bindValue(QStringLiteral(":goalCategory"), goal->category()); removeGoalRelationQuery.bindValue(QStringLiteral(":goalIdentifier"), goal->identifier()); removeGoalRelationQuery.bindValue(QStringLiteral(":profileId"), learner->identifier()); removeGoalRelationQuery.exec(); if (db.lastError().isValid()) { qCritical() << "ExistsQuery: " << db.lastError().text(); raiseError(db.lastError()); return false; } return true; } QList< Learner* > Storage::loadProfiles(QList goals) { QSqlDatabase db = database(); QSqlQuery profileQuery(db); profileQuery.prepare(QStringLiteral("SELECT id, name FROM profiles")); profileQuery.exec(); if (profileQuery.lastError().isValid()) { qCritical() << profileQuery.lastError().text(); raiseError(profileQuery.lastError()); return QList(); } QList profiles; while (profileQuery.next()) { Learner* profile = new Learner(); profile->setIdentifier(profileQuery.value(0).toInt()); profile->setName(profileQuery.value(1).toString()); profiles.append(profile); } // associate to goals QSqlQuery goalRelationQuery(db); goalRelationQuery.prepare(QStringLiteral("SELECT goal_category, goal_identifier, profile_id FROM learner_goals")); goalRelationQuery.exec(); if (goalRelationQuery.lastError().isValid()) { qCritical() << goalRelationQuery.lastError().text(); raiseError(goalRelationQuery.lastError()); return QList(); } while (goalRelationQuery.next()) { Learner *learner = nullptr; LearningGoal *goal = nullptr; foreach (Learner *cmpProfile, profiles) { if (cmpProfile->identifier() == goalRelationQuery.value(2).toInt()) { learner = cmpProfile; break; } } if (!learner) { qCCritical(LIBLEARNER_LOG) << "Could not retrieve learner from database."; return QList(); } foreach (LearningGoal *cmpGoal, goals) { if (cmpGoal->category() == goalRelationQuery.value(0).toInt() && cmpGoal->identifier() == goalRelationQuery.value(1).toString()) { goal = cmpGoal; break; } } if (learner->goals().contains(goal)) { continue; } if (goal) { learner->addGoal(goal); } } return profiles; } bool Storage::storeGoal(LearningGoal *goal) { QSqlDatabase db = database(); // test whether ID is present QSqlQuery goalExistsQuery(db); goalExistsQuery.prepare(QStringLiteral("SELECT COUNT(*) FROM goals WHERE category = :category AND identifier = :identifier")); goalExistsQuery.bindValue(QStringLiteral(":identifier"), goal->identifier()); goalExistsQuery.bindValue(QStringLiteral(":category"), static_cast(goal->category())); goalExistsQuery.exec(); if (db.lastError().isValid()) { qCritical() << "ExistsQuery: " << db.lastError().text(); raiseError(db.lastError()); return false; } // go to first result row that contains the count goalExistsQuery.next(); if (goalExistsQuery.value(0).toInt() < 1) { // in case learner ID is not found in database QSqlQuery insertGoalQuery(db); insertGoalQuery.prepare(QStringLiteral("INSERT INTO goals (category, identifier, name) VALUES (?, ?, ?)")); insertGoalQuery.bindValue(0, static_cast(goal->category())); insertGoalQuery.bindValue(1, goal->identifier()); insertGoalQuery.bindValue(2, goal->name()); insertGoalQuery.exec(); if (insertGoalQuery.lastError().isValid()) { raiseError(insertGoalQuery.lastError()); db.rollback(); return false; } return true; } else { // update name otherwise QSqlQuery updateGoalQuery(db); updateGoalQuery.prepare(QStringLiteral("UPDATE goals SET name = :name WHERE category = :category AND identifier = :identifier")); updateGoalQuery.bindValue(QStringLiteral(":category"), static_cast(goal->category())); updateGoalQuery.bindValue(QStringLiteral(":identifier"), goal->identifier()); updateGoalQuery.bindValue(QStringLiteral(":name"), goal->name()); updateGoalQuery.exec(); if (updateGoalQuery.lastError().isValid()) { qCritical() << updateGoalQuery.lastError().text(); raiseError(updateGoalQuery.lastError()); db.rollback(); return false; } return true; } } QList< LearningGoal* > Storage::loadGoals() { QSqlDatabase db = database(); QSqlQuery goalQuery(db); goalQuery.prepare(QStringLiteral("SELECT category, identifier, name FROM goals")); goalQuery.exec(); if (goalQuery.lastError().isValid()) { qCritical() << goalQuery.lastError().text(); raiseError(goalQuery.lastError()); return QList(); } QList goals; while (goalQuery.next()) { LearningGoal::Category category = static_cast(goalQuery.value(0).toInt()); QString identifier = goalQuery.value(1).toString(); QString name = goalQuery.value(2).toString(); LearningGoal* goal = new LearningGoal(category, identifier); goal->setName(name); goals.append(goal); } return goals; } bool Storage::storeProgressLog(Learner *learner, LearningGoal *goal, const QString &container, const QString &item, int payload, const QDateTime &time) { QSqlDatabase db = database(); QSqlQuery insertQuery(db); insertQuery.prepare("INSERT INTO learner_progress_log " "(goal_category, goal_identifier, profile_id, item_container, item, payload, date) " "VALUES (:gcategory, :gidentifier, :pid, :container, :item, :payload, :date)"); insertQuery.bindValue(QStringLiteral(":gcategory"), static_cast(goal->category())); insertQuery.bindValue(QStringLiteral(":gidentifier"), goal->identifier()); insertQuery.bindValue(QStringLiteral(":pid"), learner->identifier()); insertQuery.bindValue(QStringLiteral(":container"), container); insertQuery.bindValue(QStringLiteral(":item"), item); insertQuery.bindValue(QStringLiteral(":payload"), payload); insertQuery.bindValue(QStringLiteral(":date"), time.toString(Qt::ISODate)); insertQuery.exec(); if (insertQuery.lastError().isValid()) { raiseError(insertQuery.lastError()); qCCritical(LIBLEARNER_LOG) << "DB Error:" << m_errorMessage; db.rollback(); return false; } return true; } QList> Storage::readProgressLog(Learner *learner, LearningGoal *goal, const QString &container, const QString &item) { QSqlDatabase db = database(); QSqlQuery logQuery(db); logQuery.prepare("SELECT date, payload FROM learner_progress_log " "WHERE goal_category = :goalcategory " "AND goal_identifier = :goalid " "AND profile_id = :profileid " "AND item_container = :container " "AND item = :item"); logQuery.bindValue(QStringLiteral(":goalcategory"), static_cast(goal->category())); logQuery.bindValue(QStringLiteral(":goalid"), goal->identifier()); logQuery.bindValue(QStringLiteral(":profileid"), learner->identifier()); logQuery.bindValue(QStringLiteral(":container"), container); logQuery.bindValue(QStringLiteral(":item"), item); logQuery.exec(); if (logQuery.lastError().isValid()) { qCritical() << logQuery.lastError().text(); raiseError(logQuery.lastError()); return QList>(); } QList> log; while (logQuery.next()) { const QDateTime date{logQuery.value(0).toDateTime()}; int payload{logQuery.value(1).toInt()}; log.append(qMakePair(date, payload)); } return log; } bool Storage::storeProgressValue(Learner *learner, LearningGoal *goal, const QString &container, const QString &item, int payload) { QSqlDatabase db = database(); QSqlQuery query(db); // test if already payload stored query.prepare("SELECT payload FROM learner_progress_value " "WHERE goal_category = :gcategory " "AND goal_identifier = :gidentifier " "AND profile_id = :pid " "AND item_container = :container " "AND item = :item"); query.bindValue(QStringLiteral(":gcategory"), static_cast(goal->category())); query.bindValue(QStringLiteral(":gidentifier"), goal->identifier()); query.bindValue(QStringLiteral(":pid"), learner->identifier()); query.bindValue(QStringLiteral(":container"), container); query.bindValue(QStringLiteral(":item"), item); query.exec(); if (query.lastError().isValid()) { qCritical() << query.lastError().text(); raiseError(query.lastError()); return false; } // if query contains values, perform update query if (query.next()) { query.finish(); // release resources from previous query query.prepare("UPDATE learner_progress_value " "SET payload = :payload " "WHERE goal_category = :gcategory " "AND goal_identifier = :gidentifier " "AND profile_id = :pid " "AND item_container = :container " "AND item = :item"); query.bindValue(QStringLiteral(":payload"), static_cast(payload)); query.bindValue(QStringLiteral(":gcategory"), static_cast(goal->category())); query.bindValue(QStringLiteral(":gidentifier"), goal->identifier()); query.bindValue(QStringLiteral(":pid"), learner->identifier()); query.bindValue(QStringLiteral(":container"), container); query.bindValue(QStringLiteral(":item"), item); query.exec(); if (query.lastError().isValid()) { qCritical() << query.lastError().text(); raiseError(query.lastError()); db.rollback(); return false; } return true; } // else insert new row else { query.finish(); // release resources from previous query query.prepare("INSERT INTO learner_progress_value " "(goal_category, goal_identifier, profile_id, item_container, item, payload) " "VALUES (:gcategory, :gidentifier, :pid, :container, :item, :payload)"); query.bindValue(QStringLiteral(":gcategory"), static_cast(goal->category())); query.bindValue(QStringLiteral(":gidentifier"), goal->identifier()); query.bindValue(QStringLiteral(":pid"), learner->identifier()); query.bindValue(QStringLiteral(":container"), container); query.bindValue(QStringLiteral(":item"), item); query.bindValue(QStringLiteral(":payload"), static_cast(payload)); query.exec(); if (query.lastError().isValid()) { qCritical() << query.lastError().text(); raiseError(query.lastError()); db.rollback(); return false; } return true; } Q_UNREACHABLE(); return false; } QHash Storage::readProgressValues(Learner *learner, LearningGoal *goal, const QString &container) { QSqlDatabase db = database(); QSqlQuery query(db); query.prepare("SELECT item, payload FROM learner_progress_value " "WHERE goal_category = :goalcategory " "AND goal_identifier = :goalid " "AND profile_id = :profileid " "AND item_container = :container"); query.bindValue(QStringLiteral(":goalcategory"), static_cast(goal->category())); query.bindValue(QStringLiteral(":goalid"), goal->identifier()); query.bindValue(QStringLiteral(":profileid"), learner->identifier()); query.bindValue(QStringLiteral(":container"), container); query.exec(); if (query.lastError().isValid()) { qCritical() << query.lastError().text(); raiseError(query.lastError()); return QHash(); } QHash values; while (query.next()) { const QString item{query.value(0).toString()}; const int payload{query.value(1).toInt()}; values.insert(item, payload); } return values; } int Storage::readProgressValue(Learner *learner, LearningGoal *goal, const QString &container, const QString &item) { QSqlDatabase db = database(); QSqlQuery query(db); query.prepare("SELECT payload FROM learner_progress_value " "WHERE goal_category = :goalcategory " "AND goal_identifier = :goalid " "AND profile_id = :profileid " "AND item_container = :container " "AND item = :item"); query.bindValue(QStringLiteral(":goalcategory"), static_cast(goal->category())); query.bindValue(QStringLiteral(":goalid"), goal->identifier()); query.bindValue(QStringLiteral(":profileid"), learner->identifier()); query.bindValue(QStringLiteral(":container"), container); query.bindValue(QStringLiteral(":item"), item); query.exec(); if (query.lastError().isValid()) { qCritical() << query.lastError().text(); raiseError(query.lastError()); return -1; } if (query.next()) { return query.value(0).toInt(); } return -1; } QSqlDatabase Storage::database() { if (QSqlDatabase::contains(QSqlDatabase::defaultConnection)) { return QSqlDatabase::database(QSqlDatabase::defaultConnection); } // create data directory if it does not exist QDir dir = QDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); if (!dir.exists()) { dir.mkpath(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); } qCDebug(LIBLEARNER_LOG) << "Database path: " << m_databasePath; QSqlDatabase db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE")); db.setDatabaseName(m_databasePath); if (!db.open()) { qCritical() << "Could not open database: " << db.lastError().text(); raiseError(db.lastError()); return db; } if (!updateSchema()) { qCritical() << "Database scheme not correct."; return db; } // return correctly set up database return db; } bool Storage::updateSchema() { QSqlDatabase db = database(); // check database version format db.exec("CREATE TABLE IF NOT EXISTS metadata (" "key TEXT PRIMARY KEY, " "value TEXT" ")"); if (db.lastError().isValid()) { qCritical() << db.lastError().text(); raiseError(db.lastError()); return false; } QSqlQuery versionQuery = db.exec(QStringLiteral("SELECT value FROM metadata WHERE key = 'version'")); if (db.lastError().isValid()) { qCritical() << db.lastError().text(); raiseError(db.lastError()); return false; } if (versionQuery.next()) { QString version = versionQuery.value(0).toString(); if (version != QLatin1String("1")) { m_errorMessage = i18n("Invalid database version '%1'.", version); emit errorMessageChanged(); return false; } } else { if (!db.transaction()) { qCWarning(LIBLEARNER_LOG) << db.lastError().text(); raiseError(db.lastError()); return false; } db.exec(QStringLiteral("INSERT INTO metadata (key, value) VALUES ('version', '1')")); if (db.lastError().isValid()) { qCritical() << db.lastError().text(); raiseError(db.lastError()); return false; } if (!db.commit()) { qCritical() << db.lastError().text(); raiseError(db.lastError()); return false; } } // table for learner profiles db.exec("CREATE TABLE IF NOT EXISTS profiles (" "id INTEGER PRIMARY KEY AUTOINCREMENT, " "name TEXT" ")"); if (db.lastError().isValid()) { qCritical() << db.lastError().text(); raiseError(db.lastError()); return false; } // table for registered learning goals db.exec("CREATE TABLE IF NOT EXISTS goals (" "category INTEGER, " // LearningGoal::Category "identifier TEXT, " // identifier, unique per Category "name TEXT, " // name "PRIMARY KEY ( category, identifier )" ")"); if (db.lastError().isValid()) { qCritical() << db.lastError().text(); raiseError(db.lastError()); return false; } // table for learner - learningGoal relations db.exec("CREATE TABLE IF NOT EXISTS learner_goals (" "id INTEGER PRIMARY KEY AUTOINCREMENT, " "goal_category INTEGER, " // LearningGoal::Category "goal_identifier TEXT, " // LearningGoal::Identifier "profile_id INTEGER " // Learner::Identifier ")"); if (db.lastError().isValid()) { qCritical() << db.lastError().text(); raiseError(db.lastError()); return false; } // table for full progress data log db.exec("CREATE TABLE IF NOT EXISTS learner_progress_log (" "id INTEGER PRIMARY KEY AUTOINCREMENT, " "goal_category INTEGER, " // LearningGoal::Category "goal_identifier TEXT, " // LearningGoal::Identifier "profile_id INTEGER, " // Learner::Identifier "item_container TEXT, " "item TEXT, " "payload INTEGER, " "date TEXT" ")"); if (db.lastError().isValid()) { qCritical() << db.lastError().text(); raiseError(db.lastError()); return false; } // table for progress data quick access db.exec("CREATE TABLE IF NOT EXISTS learner_progress_value (" "id INTEGER PRIMARY KEY AUTOINCREMENT, " "goal_category INTEGER, " // LearningGoal::Category "goal_identifier TEXT, " // LearningGoal::Identifier "profile_id INTEGER, " // Learner::Identifier "item_container TEXT, " "item TEXT, " "payload INTEGER" ")"); if (db.lastError().isValid()) { qCritical() << db.lastError().text(); raiseError(db.lastError()); return false; } return true; } diff --git a/libsound/src/qtmultimediabackend/qtmultimediacapturebackend.cpp b/libsound/src/qtmultimediabackend/qtmultimediacapturebackend.cpp index fbe3d16..91097dc 100644 --- a/libsound/src/qtmultimediabackend/qtmultimediacapturebackend.cpp +++ b/libsound/src/qtmultimediabackend/qtmultimediacapturebackend.cpp @@ -1,84 +1,83 @@ /* * Copyright 2016 Andreas Cord-Landwehr * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "qtmultimediacapturebackend.h" #include "libsound_debug.h" #include #include #include QtMultimediaCaptureBackend::QtMultimediaCaptureBackend(QObject *parent) : CaptureBackendInterface(parent) , m_recorder(new QAudioRecorder) { - QString selectedInput = m_recorder->defaultAudioInput(); QAudioEncoderSettings audioSettings; audioSettings.setCodec(QStringLiteral("audio/vorbis")); audioSettings.setQuality(QMultimedia::HighQuality); m_recorder->setAudioSettings(audioSettings); } QtMultimediaCaptureBackend::~QtMultimediaCaptureBackend() { m_recorder->deleteLater(); m_recorder = nullptr; } CaptureDeviceController::State QtMultimediaCaptureBackend::captureState() const { switch (m_recorder->state()) { case QMediaRecorder::StoppedState: return CaptureDeviceController::StoppedState; break; case QMediaRecorder::RecordingState: return CaptureDeviceController::RecordingState; break; case QMediaRecorder::PausedState: return CaptureDeviceController::PausedState; break; default: return CaptureDeviceController::StoppedState; } } void QtMultimediaCaptureBackend::startCapture(const QString &filePath) { m_recorder->setOutputLocation(QUrl::fromLocalFile(filePath)); m_recorder->record(); } void QtMultimediaCaptureBackend::stopCapture() { m_recorder->stop(); } QStringList QtMultimediaCaptureBackend::devices() const { return m_recorder->audioInputs(); } void QtMultimediaCaptureBackend::setDevice(const QString &deviceIdentifier) { if (devices().contains(deviceIdentifier)) { m_recorder->setAudioInput(deviceIdentifier); } else { qCDebug(LIBSOUND_LOG) << "Could not set unknown capture device:" << deviceIdentifier; } } diff --git a/src/core/course.cpp b/src/core/course.cpp index 5534564..c3c8f50 100644 --- a/src/core/course.cpp +++ b/src/core/course.cpp @@ -1,353 +1,345 @@ /* * 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 "course.h" #include "unit.h" #include "language.h" #include "resources/resourceinterface.h" #include "resources/courseresource.h" #include "resourcemanager.h" #include "phonemegroup.h" #include "artikulate_debug.h" #include #include #include #include Course::Course(ResourceInterface *resource) : QObject(resource) , m_resource(qobject_cast(resource)) , m_language(nullptr) , m_modified(false) { } Course::~Course() { foreach (Unit *unit, m_unitList) { unit->deleteLater(); } m_unitList.clear(); // clear phonom units QMultiMap< PhonemeGroup*, QList< QPair > >::iterator groupIter = m_phonemeUnitList.begin(); while (groupIter != m_phonemeUnitList.end()) { QList< QPair >::iterator itemIter = groupIter->begin(); while (itemIter != groupIter->end()) { itemIter->first->deleteLater(); // delete phoneme itemIter->second->deleteLater(); // delete unit ++itemIter; } groupIter->clear(); ++groupIter; } m_phonemeUnitList.clear(); m_phonemeGroupList.clear(); } QString Course::id() const { return m_id; } void Course::setId(const QString &id) { if (id != m_id) { m_id = id; emit idChanged(); setModified(); } } QString Course::foreignId() const { return m_foreignId; } void Course::setForeignId(const QString &id) { m_foreignId = id; } QString Course::title() const { return m_title; } QString Course::i18nTitle() const { return m_resource->i18nTitle(); } void Course::setTitle(const QString &title) { if (QString::compare(title, m_title) != 0) { m_title = title; emit titleChanged(); setModified(); } } QString Course::description() const { return m_description; } void Course::setDescription(const QString &description) { m_description = description; emit descriptionChanged(); } Language * Course::language() const { return m_language; } void Course::setLanguage(Language *language) { Q_ASSERT(language); // TODO this should happen in the ctor foreach (PhonemeGroup *group, language->phonemeGroups()) { addPhonemeGroup(group); } m_language = language; emit languageChanged(); } QUrl Course::file() const { return m_file; } void Course::setFile(const QUrl &file) { m_file = file; } QList< Unit* > Course::unitList() const { return m_unitList; } void Course::addUnit(Unit *unit) { QList::ConstIterator iter = m_unitList.constBegin(); while (iter != m_unitList.constEnd()) { if (unit->id() == (*iter)->id()) { qCWarning(ARTIKULATE_LOG) << "Unit already contained in this course, aborting"; return; } ++iter; } emit unitAboutToBeAdded(unit, m_unitList.length()); m_unitList.append(unit); - connect(unit, &Unit::modified, [=]() { + connect(unit, &Unit::modified, this, [=]() { setModified(true); }); // these connections are only present for "normal units" and take care to register // there phrases also at phoneme units connect(unit, SIGNAL(phraseAdded(Phrase*)), this, SLOT(registerPhrasePhonemes(Phrase*))); connect(unit, SIGNAL(phraseRemoved(Phrase*)), this, SLOT(removePhrasePhonemes(Phrase*))); emit unitAdded(); setModified(true); } Unit * Course::createUnit() { // find first unused id QStringList unitIds; foreach (Unit *unit, m_unitList) { unitIds.append(unit->id()); } QString id = QUuid::createUuid().toString(); while (unitIds.contains(id)) { id = QUuid::createUuid().toString(); qCWarning(ARTIKULATE_LOG) << "Unit id generator has found a collision, recreating id."; } // create unit Unit *unit = new Unit(this); unit->setCourse(this); unit->setId(id); unit->setTitle(i18n("New Unit")); addUnit(unit); return unit; } Phrase * Course::createPhrase(Unit *unit) { // find globally unique phrase id inside course QStringList phraseIds; foreach (Unit *unit, m_unitList) { foreach (Phrase *phrase, unit->phraseList()) { phraseIds.append(phrase->id()); } } QString id = QUuid::createUuid().toString(); while (phraseIds.contains(id)) { id = QUuid::createUuid().toString(); qCWarning(ARTIKULATE_LOG) << "Phrase id generator has found a collision, recreating id."; } // create unit Phrase *phrase = new Phrase(this); phrase->setId(id); phrase->setText(QLatin1String("")); phrase->setType(Phrase::Word); unit->addPhrase(phrase); return phrase; } QList< Unit* > Course::phonemeUnitList(PhonemeGroup *phonemeGroup) const { QList list; - QList< QPair >::ConstIterator iter = m_phonemeUnitList.value(phonemeGroup).constBegin(); - while (iter != m_phonemeUnitList.value(phonemeGroup).constEnd()) { - list.append(iter->second); - ++iter; + for (const auto &group : m_phonemeUnitList.value(phonemeGroup)) { + list.append(group.second); } return list; } Unit * Course::phonemeUnit(Phoneme *phoneme) const { - foreach (PhonemeGroup *group, m_phonemeUnitList.keys()) { - m_phonemeUnitList.value(group); - QList< QPair >::ConstIterator iter = m_phonemeUnitList.value(group).constBegin(); - while (iter != m_phonemeUnitList.value(group).constEnd()) { - if (iter->first == phoneme) { - return iter->second; + for (auto group = m_phonemeUnitList.keyBegin(); group != m_phonemeUnitList.keyEnd(); ++group) { + m_phonemeUnitList.value(*group); + for (const auto &phonemeUnit : m_phonemeUnitList.value(*group)) { + if (phonemeUnit.first == phoneme) { + return phonemeUnit.second; } - ++iter; } } - return 0; + return nullptr; } PhonemeGroup * Course::phonemeGroup(Unit *unit) const { - foreach (PhonemeGroup *group, m_phonemeUnitList.keys()) { - m_phonemeUnitList.value(group); - QList< QPair >::ConstIterator iter = m_phonemeUnitList.value(group).constBegin(); - while (iter != m_phonemeUnitList.value(group).constEnd()) { - if (iter->second == unit) { - return group; + for (auto group = m_phonemeUnitList.keyBegin(); group != m_phonemeUnitList.keyEnd(); ++group) { + m_phonemeUnitList.value(*group); + for (const auto &phonemeUnit : m_phonemeUnitList.value(*group)) { + if (phonemeUnit.second == unit) { + return *group; } - ++iter; } } - return 0; + return nullptr; } void Course::addPhonemeGroup(PhonemeGroup *phonemeGroup) { if (m_phonemeUnitList.contains(phonemeGroup)) { qCWarning(ARTIKULATE_LOG) << "Phoneme group already contained in this course, aborting"; return; } emit phonemeGroupAboutToBeAdded(phonemeGroup, m_phonemeGroupList.count()); // add to phoneme list m_phonemeGroupList.append(phonemeGroup); m_phonemeUnitList.insert(phonemeGroup, QList< QPair >()); emit phonemeGroupAdded(); setModified(); } QList Course::phonemeGroupList() const { return m_phonemeGroupList; } bool Course::modified() const { return m_modified; } void Course::setModified(bool modified) { if (m_modified == modified) { return; } m_modified = modified; emit modifiedChanged(); } void Course::sync() { - if (!m_file.isValid() || m_file.isEmpty() || m_resource == 0) { + if (!m_file.isValid() || m_file.isEmpty() || m_resource == nullptr) { qCritical() << "Path" << m_file.toLocalFile() << "not valid, aborting sync operation."; return; } m_resource->sync(); setModified(false); } bool Course::isContributorResource() const { return m_resource->isContributorResource(); } void Course::registerPhrasePhonemes(Phrase *phrase) { // iterate over all phonemes of this phrase foreach (Phoneme *phoneme, phrase->phonemes()) { // try to find corresponding phonem groups (phonem groups are registered on course creation) foreach (PhonemeGroup *group, m_phonemeGroupList) { if (!group->contains(phoneme)) { continue; } // either add phrase to existing unit or register a new one bool phraseRegistered = false; - QList< QPair >::ConstIterator iter = m_phonemeUnitList.value(group).constBegin(); - while (iter != m_phonemeUnitList.value(group).constEnd()) { - if (iter->first->id() == phoneme->id()) { - iter->second->addPhrase(phrase); + for (const auto &phonemeUnit : m_phonemeUnitList.value(group)) { + if (phonemeUnit.first->id() == phoneme->id()) { + phonemeUnit.second->addPhrase(phrase); phraseRegistered = true; } - ++iter; } // otherwise, need to create a new unit if (phraseRegistered == false) { // create unit based on the phoneme group Unit *unit = new Unit(this); unit->setId(phoneme->id()); unit->setTitle(phoneme->title()); unit->setCourse(this); m_phonemeUnitList[group].append(qMakePair(phoneme, unit)); unit->addPhrase(phrase); } } } } void Course::removePhrasePhonemes(Phrase* phrase) { qCritical() << "Not yet implemented!"; } diff --git a/src/core/drawertrainingactions.cpp b/src/core/drawertrainingactions.cpp index 5147c44..b99fff5 100644 --- a/src/core/drawertrainingactions.cpp +++ b/src/core/drawertrainingactions.cpp @@ -1,66 +1,67 @@ /* * Copyright 2018-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 "drawertrainingactions.h" #include "trainingaction.h" #include "course.h" #include #include DrawerTrainingActions::DrawerTrainingActions(QObject* parent) : QObject(parent) { } void DrawerTrainingActions::setSession(TrainingSession *session) { if (session == m_session) { return; } if (m_session) { disconnect(m_session, &TrainingSession::courseChanged, this, &DrawerTrainingActions::actionsChanged); disconnect(m_session, &TrainingSession::phraseChanged, this, &DrawerTrainingActions::triggerTrainingView); } m_session = session; connect(m_session, &TrainingSession::courseChanged, this, &DrawerTrainingActions::actionsChanged); connect(m_session, &TrainingSession::phraseChanged, this, &DrawerTrainingActions::triggerTrainingView); emit sessionChanged(); emit actionsChanged(); } TrainingSession * DrawerTrainingActions::session() const { return m_session; } QList DrawerTrainingActions::actions() const { if (!m_session) { return QList(); } QList actions; - for (const auto &action : m_session->trainingActions()) { + const auto trainingActions = m_session->trainingActions(); + for (const auto &action : qAsConst(trainingActions)) { actions.append(qobject_cast(action)); } return actions; } diff --git a/src/core/editorsession.cpp b/src/core/editorsession.cpp index 016115e..8b86e96 100644 --- a/src/core/editorsession.cpp +++ b/src/core/editorsession.cpp @@ -1,279 +1,279 @@ /* * 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/skeleton.h" #include "core/language.h" #include "core/course.h" #include "core/resources/courseresource.h" #include "core/resources/languageresource.h" #include "core/unit.h" #include "core/phrase.h" #include "core/resourcemanager.h" #include "artikulate_debug.h" EditorSession::EditorSession(QObject *parent) : QObject(parent) , m_resourceManager(nullptr) , m_skeletonMode(true) , m_editSkeleton(false) , m_skeleton(nullptr) , m_language(nullptr) , m_course(nullptr) , m_tmpCourseWhileSkeletonEditing(nullptr) , m_unit(nullptr) , m_phrase(nullptr) { } void EditorSession::setResourceManager(ResourceManager *manager) { m_resourceManager = manager; } void EditorSession::setSkeletonMode(bool enabled) { if (m_skeletonMode == enabled) { return; } m_skeletonMode = enabled; emit skeletonModeChanged(); } bool EditorSession::skeletonMode() const { return m_skeletonMode; } void EditorSession::setEditSkeleton(bool enabled) { if (m_editSkeleton == enabled) { return; } m_editSkeleton = enabled; if (enabled) { m_tmpCourseWhileSkeletonEditing = m_course; setCourse(m_skeleton); } else { setCourse(m_tmpCourseWhileSkeletonEditing); m_tmpCourseWhileSkeletonEditing = nullptr; } emit editSkeletonChanged(); } bool EditorSession::isEditSkeleton() const { return m_editSkeleton; } Skeleton * EditorSession::skeleton() const { return m_skeleton; } void EditorSession::setSkeleton(Skeleton *skeleton) { if (m_skeleton == skeleton) { return; } m_skeleton = skeleton; Language *language = m_language; if (!m_language) { - language = m_resourceManager->languageResources().first()->language(); + language = m_resourceManager->languageResources().constFirst()->language(); } if (m_skeleton) { bool found = false; int resources = m_resourceManager->courseResources(language).count(); for (int i=0; i < resources; ++i) { Course * course = m_resourceManager->course(language, i); if (course->foreignId() == m_skeleton->id()) { setCourse(course); found = true; break; } } if (!found) { setCourse(nullptr); } } emit skeletonChanged(); } Language * EditorSession::language() const { return m_language; } void EditorSession::setLanguage(Language *language) { if (m_language == language) { return; } m_language = language; if (m_skeletonMode) { bool found = false; if (m_skeleton) { int resources = m_resourceManager->courseResources(m_language).count(); for (int i=0; i < resources; ++i) { Course * course = m_resourceManager->course(m_language, i); if (course->foreignId() == m_skeleton->id()) { setCourse(course); found = true; break; } } } if (!found) { setCourse(nullptr); } } else { // not skeleton mode if (m_resourceManager->courseResources(m_language).count() > 0) { setCourse(m_resourceManager->course(m_language, 0)); } } emit languageChanged(); } Course * EditorSession::course() const { return m_course; } void EditorSession::setCourse(Course *course) { if (m_course == course) { return; } m_course = course; if (m_course && !m_course->unitList().isEmpty()) { - setUnit(m_course->unitList().first()); + setUnit(m_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); - return unitChanged(); + emit unitChanged(); } void EditorSession::setPhrase(Phrase *phrase) { if (m_phrase == phrase) { return; } if (phrase) { setUnit(phrase->unit()); } m_phrase = phrase; - return phraseChanged(); + 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().first(); + 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); } diff --git a/src/core/resourcemanager.cpp b/src/core/resourcemanager.cpp index 3118d8d..406d651 100644 --- a/src/core/resourcemanager.cpp +++ b/src/core/resourcemanager.cpp @@ -1,475 +1,474 @@ /* * 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(QStringLiteral("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(QStringLiteral("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(QStringLiteral("*.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() != QLatin1String("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() != QLatin1String("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) { if (!language) { 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 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()) { delete resource; qCritical() << "Could not load course, language unknown:" << courseFile.toLocalFile(); return nullptr; } // skip already loaded resources if (m_loadedResources.contains(courseFile.toLocalFile())) { delete resource; 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 = QStringLiteral("%1/%2/%3/%4/%4.xml") - .arg(Settings::courseRepositoryPath()) - .arg(QStringLiteral("courses")) - .arg(skeleton->id()) - .arg(language->id()); + .arg(Settings::courseRepositoryPath(), + QStringLiteral("courses"), + skeleton->id(), + 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/trainingsession.cpp b/src/core/trainingsession.cpp index 36bd919..00c12d6 100644 --- a/src/core/trainingsession.cpp +++ b/src/core/trainingsession.cpp @@ -1,271 +1,273 @@ /* * Copyright 2013-2019 Andreas Cord-Landwehr * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "trainingsession.h" #include "core/language.h" #include "core/course.h" #include "core/unit.h" #include "core/phrase.h" #include "profilemanager.h" #include "learner.h" #include "trainingaction.h" #include "artikulate_debug.h" TrainingSession::TrainingSession(QObject *parent) : QObject(parent) , m_profileManager(nullptr) , m_course(nullptr) , m_unit(nullptr) { } void TrainingSession::setProfileManager(LearnerProfile::ProfileManager *manager) { if (m_profileManager == manager) { return; } m_profileManager = manager; } 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()); + setUnit(m_course->unitList().constFirst()); } // 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()); + setPhrase(m_unit->phraseList().constFirst()); } - return unitChanged(); + emit unitChanged(); } TrainingAction * TrainingSession::activeAction() const { if (m_indexUnit < 0 || m_indexPhrase < 0) { return nullptr; } return qobject_cast(m_actions.at(m_indexUnit)->actions().at(m_indexPhrase)); } Phrase * TrainingSession::activePhrase() const { if (const auto action = activeAction()) { return action->phrase(); } return nullptr; } void TrainingSession::setPhrase(Phrase *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; } } } } void TrainingSession::accept() { Q_ASSERT(m_indexUnit >= 0); Q_ASSERT(m_indexPhrase >= 0); if (m_indexUnit < 0 || m_indexPhrase < 0) { return; } auto phrase = activePhrase(); // possibly update goals of learner updateGoal(); 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(), phrase->id(), static_cast(LearnerProfile::ProfileManager::Skip), phrase->progress() ); selectNextPhrase(); } void TrainingSession::skip() { Q_ASSERT(m_indexUnit >= 0); Q_ASSERT(m_indexPhrase >= 0); if (m_indexUnit < 0 || m_indexPhrase < 0) { return; } // possibly update goals of learner updateGoal(); auto phrase = activePhrase(); 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(), phrase->id(), static_cast(LearnerProfile::ProfileManager::Skip), phrase->progress() ); selectNextPhrase(); } void TrainingSession::selectNextPhrase() { if (auto action = activeAction()) { action->setChecked(false); } // try to find next phrase, otherwise return completed if (m_indexPhrase >= m_actions.at(m_indexUnit)->actions().count() - 1) { if (m_indexUnit >= m_actions.count() - 1) { emit completed(); } else { ++m_indexUnit; m_indexPhrase = 0; } } else { ++m_indexPhrase; } if (auto action = activeAction()) { action->setChecked(true); } emit phraseChanged(); } bool TrainingSession::hasNext() const { return m_indexUnit < m_actions.count() - 1 || m_indexPhrase < m_actions.last()->actions().count() - 1; } 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); } QVector TrainingSession::trainingActions() { // cleanup - for (const auto &action : m_actions) { + for (const auto &action : qAsConst(m_actions)) { action->deleteLater(); } m_actions.clear(); if (!m_course) { return QVector(); } - for (const auto &unit : m_course->unitList()) { + const auto unitList = m_course->unitList(); + for (const auto &unit : qAsConst(unitList)) { auto action = new TrainingAction(unit->title()); - for (const auto &phrase : unit->phraseList()) { + const auto phraseList = unit->phraseList(); + for (const auto &phrase : qAsConst(phraseList)) { if (phrase->sound().isEmpty()) { continue; } action->appendChild(new TrainingAction(phrase, this, unit)); } if (action->hasChildren()) { m_actions.append(action); } else { action->deleteLater(); } } return m_actions; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index eb27ed8..f915add 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,219 +1,218 @@ /* * 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 "mainwindow.h" #include "ui/resourcesdialogpage.h" #include "ui/sounddevicedialogpage.h" #include "ui/appearencedialogpage.h" #include "core/resourcemanager.h" #include "core/trainingsession.h" #include "core/editorsession.h" #include "core/resources/courseresource.h" #include "models/languagemodel.h" #include "settings.h" #include "liblearnerprofile/src/profilemanager.h" #include "liblearnerprofile/src/learner.h" #include "libsound/src/outputdevicecontroller.h" #include "artikulate_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace LearnerProfile; MainWindow::MainWindow() : m_actionCollection(new KActionCollection(this, QStringLiteral("artikulate"))) , m_helpMenu(new KHelpMenu) , m_resourceManager(new ResourceManager(this)) , m_profileManager(new LearnerProfile::ProfileManager(this)) , m_trainingSession(new TrainingSession(this)) { rootContext()->setContextObject(new KLocalizedContext(this)); // load saved sound settings OutputDeviceController::self().setVolume(Settings::audioOutputVolume()); // load resources m_resourceManager->loadLanguageResources(); if (m_resourceManager->languageResources().count() == 0) { qFatal("No language resources found, cannot start application."); } m_resourceManager->loadCourseResources(); m_trainingSession->setProfileManager(m_profileManager); // create menu setupActions(); // set view rootContext()->setContextProperty(QStringLiteral("g_resourceManager"), m_resourceManager); rootContext()->setContextProperty(QStringLiteral("g_trainingSession"), m_trainingSession); rootContext()->setContextProperty(QStringLiteral("g_profileManager"), m_profileManager); rootContext()->setContextProperty(QStringLiteral("kcfg_UseContributorResources"), Settings::useCourseRepository()); rootContext()->setContextProperty(QStringLiteral("kcfg_ShowMenuBar"), Settings::showMenuBar()); // set starting screen load(QUrl(QStringLiteral("qrc:/artikulate/qml/Main.qml"))); // settings from kcfg values // updateTrainingPhraseFont(); //FIXME deactivated while porting // create training profile if none exists: if (!m_profileManager->activeProfile()) { m_profileManager->addProfile(i18n("Unnamed Identity")); } // connect to QML signals; - connect(rootObjects().first(), SIGNAL(triggerSettingsDialog()), + connect(rootObjects().constFirst(), SIGNAL(triggerSettingsDialog()), this, SLOT(showSettingsDialog())); - connect(rootObjects().first(), SIGNAL(triggerAction(QString)), + connect(rootObjects().constFirst(), SIGNAL(triggerAction(QString)), this, SLOT(triggerAction(QString))); - connect(rootObjects().first(), SIGNAL(switchMenuBarVisibility()), + connect(rootObjects().constFirst(), SIGNAL(switchMenuBarVisibility()), this, SLOT(switchMenuBarVisibility())); // set font for the phrase in trainer to default from kcfg file - QObject *phraseText = rootObjects().first()->findChild(QStringLiteral("phraseText")); + QObject *phraseText = rootObjects().constFirst()->findChild(QStringLiteral("phraseText")); if (phraseText) { phraseText->setProperty("font", Settings::trainingPhraseFont()); } } MainWindow::~MainWindow() { // save current settings for case of closing Settings::self()->save(); m_profileManager->sync(); } ResourceManager * MainWindow::resourceManager() const { return m_resourceManager; } KActionCollection * MainWindow::actionCollection() { return m_actionCollection; } void MainWindow::setupActions() { QAction *settingsAction = new QAction(i18nc("@item:inmenu", "Configure Artikulate"), this); connect(settingsAction, &QAction::triggered, this, &MainWindow::showSettingsDialog); actionCollection()->addAction(QStringLiteral("settings"), settingsAction); settingsAction->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); QAction *configLearnerProfileAction = new QAction(i18nc("@item:inmenu", "Learner Profile"), this); connect(configLearnerProfileAction, &QAction::triggered, this, &MainWindow::configLearnerProfile); actionCollection()->addAction(QStringLiteral("config_learner_profile"), configLearnerProfileAction); configLearnerProfileAction->setIcon(QIcon::fromTheme(QStringLiteral("user-identity"))); KStandardAction::helpContents(m_helpMenu, SLOT(appHelpActivated()), actionCollection()); KStandardAction::reportBug(m_helpMenu, SLOT(reportBug()), actionCollection()); KStandardAction::aboutKDE(m_helpMenu, SLOT(aboutKDE()), actionCollection()); KStandardAction::aboutApp(m_helpMenu, SLOT(aboutApplication()), actionCollection()); KStandardAction::quit(qApp, SLOT(quit()), actionCollection()); } void MainWindow::showSettingsDialog() { if (KConfigDialog::showDialog(QStringLiteral("settings"))) { return; } QPointer dialog = new KConfigDialog(0, QStringLiteral("settings"), Settings::self()); ResourcesDialogPage *resourceDialog = new ResourcesDialogPage(m_resourceManager); SoundDeviceDialogPage *soundDialog = new SoundDeviceDialogPage(); AppearenceDialogPage *appearenceDialog = new AppearenceDialogPage(); resourceDialog->loadSettings(); soundDialog->loadSettings(); appearenceDialog->loadSettings(); dialog->addPage(soundDialog, i18nc("@item:inmenu", "Sound Devices"), QStringLiteral("audio-headset"), i18nc("@title:tab", "Sound Device Settings"), true); dialog->addPage(appearenceDialog, i18nc("@item:inmenu", "Fonts"), QStringLiteral("preferences-desktop-font"), i18nc("@title:tab", "Training Phrase Font"), true); dialog->addPage(resourceDialog, i18nc("@item:inmenu", "Course Resources"), QStringLiteral("repository"), i18nc("@title:tab", "Resource Repository Settings"), true); // connect(dialog, SIGNAL(settingsChanged(const QString&)), resourceDialog, SLOT(loadSettings())); // connect(dialog, SIGNAL(settingsChanged(const QString&)), soundDialog, SLOT(loadSettings())); connect(dialog.data(), &QDialog::accepted, resourceDialog, &ResourcesDialogPage::saveSettings); connect(dialog.data(), &QDialog::accepted, soundDialog, &SoundDeviceDialogPage::saveSettings); connect(dialog.data(), &QDialog::accepted, appearenceDialog, &AppearenceDialogPage::saveSettings); connect(dialog.data(), &QDialog::accepted, this, &MainWindow::updateTrainingPhraseFont); connect(dialog.data(), &QDialog::accepted, this, &MainWindow::updateKcfgUseContributorResources); connect(dialog.data(), &QDialog::finished, soundDialog, &SoundDeviceDialogPage::stopPlaying); connect(dialog.data(), &QDialog::finished, soundDialog, &SoundDeviceDialogPage::stopRecord); dialog->exec(); } void MainWindow::updateTrainingPhraseFont() { - QObject *phraseText = rootObjects().first()->findChild(QStringLiteral("phraseText")); + QObject *phraseText = rootObjects().constFirst()->findChild(QStringLiteral("phraseText")); if (!phraseText) { qCDebug(ARTIKULATE_LOG) << "no phraseText context object found, aborting"; return; } - QFont f = phraseText->property("font").value(); phraseText->setProperty("font", Settings::trainingPhraseFont()); } void MainWindow::updateKcfgUseContributorResources() { rootContext()->setContextProperty(QStringLiteral("kcfg_UseContributorResources"), Settings::useCourseRepository()); } void MainWindow::configLearnerProfile() { qCritical() << "Not implemented"; //FIXME } void MainWindow::triggerAction(const QString &actionName) { QAction * action = actionCollection()->action(actionName); if (action) { action->trigger(); } else { qCritical() << "Action is not registered:" << actionName; } } void MainWindow::switchMenuBarVisibility() { Settings::setShowMenuBar(!Settings::showMenuBar()); rootContext()->setContextProperty(QStringLiteral("kcfg_ShowMenuBar"), Settings::showMenuBar()); } bool MainWindow::queryClose() { Settings::self()->save(); // FIXME make sure all learner data is written to database return true; } diff --git a/src/mainwindow_editor.cpp b/src/mainwindow_editor.cpp index df979ec..64ef868 100644 --- a/src/mainwindow_editor.cpp +++ b/src/mainwindow_editor.cpp @@ -1,198 +1,198 @@ /* * 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 "mainwindow_editor.h" #include "ui/resourcesdialogpage.h" #include "ui/sounddevicedialogpage.h" #include "ui/appearencedialogpage.h" #include "ui/exportghnsdialog.h" #include "core/resourcemanager.h" #include "core/editorsession.h" #include "core/resources/courseresource.h" #include "models/languagemodel.h" #include "settings.h" #include "libsound/src/outputdevicecontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include "artikulate_debug.h" #include #include #include #include #include #include #include #include #include #include using namespace LearnerProfile; MainWindowEditor::MainWindowEditor() : m_resourceManager(new ResourceManager(this)) , m_editorSession(new EditorSession(this)) , m_widget(new QQuickWidget) { m_editorSession->setResourceManager(m_resourceManager); setWindowIcon(QIcon::fromTheme(QStringLiteral("artikulate"))); setWindowTitle(qAppName()); setAutoSaveSettings(); // workaround for QTBUG-40765 qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); // load saved sound settings OutputDeviceController::self().setVolume(Settings::audioOutputVolume()); // load resources m_resourceManager->loadLanguageResources(); if (m_resourceManager->languageResources().count() == 0) { qFatal("No language resources found, cannot start application."); } m_resourceManager->loadCourseResources(); // create menu setupActions(); // set view m_widget->resize(QSize(800, 600)); m_widget->rootContext()->setContextObject(new KLocalizedContext(m_widget)); m_widget->rootContext()->setContextProperty(QStringLiteral("g_resourceManager"), m_resourceManager); m_widget->rootContext()->setContextProperty(QStringLiteral("editorSession"), m_editorSession); // set starting screen m_widget->setSource(QUrl(QStringLiteral("qrc:/artikulate/qml/Editor.qml"))); m_widget->setResizeMode(QQuickWidget::SizeRootObjectToView); QAction *newAct = KStandardAction::save(this, SLOT(save()), actionCollection()); actionCollection()->addAction(QStringLiteral("save"), newAct); // set status bar statusBar()->setEnabled(true); QLabel *repositoryLabel = new QLabel; repositoryLabel->setText(i18n("Course Repository: %1", m_resourceManager->repositoryUrl())); - connect(m_resourceManager, &ResourceManager::repositoryChanged, [=]() { + connect(m_resourceManager, &ResourceManager::repositoryChanged, this, [=]() { repositoryLabel->setText(i18n("Course Repository: %1", m_resourceManager->repositoryUrl())); }); statusBar()->insertWidget(0, repositoryLabel); createGUI(QStringLiteral("artikulateui_editor.rc")); setCentralWidget(m_widget); } MainWindowEditor::~MainWindowEditor() { // save current settings for case of closing Settings::self()->save(); } ResourceManager * MainWindowEditor::resourceManager() const { return m_resourceManager; } void MainWindowEditor::setupActions() { QAction *settingsAction = new QAction(i18nc("@item:inmenu", "Configure Artikulate"), this); connect(settingsAction, &QAction::triggered, this, &MainWindowEditor::showSettingsDialog); actionCollection()->addAction(QStringLiteral("settings"), settingsAction); settingsAction->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); QAction *exportAction = new QAction(i18nc("@item:inmenu", "Export GHNS Files"), this); - connect(exportAction, &QAction::triggered, [=]() { + connect(exportAction, &QAction::triggered, this, [=]() { QPointer dialog = new ExportGhnsDialog(m_resourceManager); dialog->exec(); }); actionCollection()->addAction(QStringLiteral("export_ghns"), exportAction); exportAction->setIcon(QIcon::fromTheme(QStringLiteral("document-export"))); KStandardAction::quit(this, SLOT(quit()), actionCollection()); setupGUI(Keys | Save | Create, QStringLiteral("artikulateui_editor.rc")); } void MainWindowEditor::showSettingsDialog() { if (KConfigDialog::showDialog(QStringLiteral("settings"))) { return; } - QPointer dialog = new KConfigDialog(0, QStringLiteral("settings"), Settings::self()); + QPointer dialog = new KConfigDialog(nullptr, QStringLiteral("settings"), Settings::self()); ResourcesDialogPage *resourceDialog = new ResourcesDialogPage(m_resourceManager); SoundDeviceDialogPage *soundDialog = new SoundDeviceDialogPage(); AppearenceDialogPage *appearenceDialog = new AppearenceDialogPage(); resourceDialog->loadSettings(); soundDialog->loadSettings(); appearenceDialog->loadSettings(); dialog->addPage(soundDialog, i18nc("@item:inmenu", "Sound Devices"), QStringLiteral("audio-headset"), i18nc("@title:tab", "Sound Device Settings"), true); dialog->addPage(appearenceDialog, i18nc("@item:inmenu", "Fonts"), QStringLiteral("preferences-desktop-font"), i18nc("@title:tab", "Training Phrase Font"), true); dialog->addPage(resourceDialog, i18nc("@item:inmenu", "Course Resources"), QStringLiteral("repository"), i18nc("@title:tab", "Resource Repository Settings"), true); connect(dialog.data(), &QDialog::accepted, resourceDialog, &ResourcesDialogPage::saveSettings); connect(dialog.data(), &QDialog::accepted, soundDialog, &SoundDeviceDialogPage::saveSettings); connect(dialog.data(), &QDialog::accepted, appearenceDialog, &AppearenceDialogPage::saveSettings); dialog->exec(); } void MainWindowEditor::save() { m_resourceManager->sync(); } void MainWindowEditor::quit() { if (queryClose()) { qApp->quit(); } } bool MainWindowEditor::queryClose() { if (!m_resourceManager->modified()) { return true; } int result = KMessageBox::warningYesNoCancel(nullptr, i18nc("@info", "The currently open course contains unsaved changes. Do you want to save them?")); switch(result) { case KMessageBox::Yes: m_resourceManager->sync(); return true; case KMessageBox::No: return true; default: return false; } } diff --git a/src/models/coursemodel.cpp b/src/models/coursemodel.cpp index ca23b31..d4045b7 100644 --- a/src/models/coursemodel.cpp +++ b/src/models/coursemodel.cpp @@ -1,213 +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); } 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, &ResourceManager::courseResourceAboutToBeAdded, this, &CourseModel::onCourseResourceAboutToBeAdded); connect(m_resourceManager, &ResourceManager::courseResourceAdded, this, &CourseModel::onCourseResourceAdded); connect(m_resourceManager, &ResourceManager::courseResourceAboutToBeRemoved, this, &CourseModel::onCourseResourceAboutToBeRemoved); } 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(); + beginResetModel(); m_language = language; m_resources = m_resourceManager->courseResources(m_language); emit languageChanged(); - emit endResetModel(); + 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 (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 (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() { 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/languageresourcemodel.cpp b/src/models/languageresourcemodel.cpp index fcda2f7..b9ce80b 100644 --- a/src/models/languageresourcemodel.cpp +++ b/src/models/languageresourcemodel.cpp @@ -1,277 +1,277 @@ /* * 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 "languageresourcemodel.h" #include "core/language.h" #include "core/course.h" #include "core/resourcemanager.h" #include "core/resources/languageresource.h" #include "core/resources/courseresource.h" #include #include #include #include "artikulate_debug.h" LanguageResourceModel::LanguageResourceModel(QObject* parent) : QAbstractListModel(parent) , m_resourceManager(nullptr) , m_view(LanguageModel::NonEmptyGhnsOnlyLanguages) , m_signalMapper(new QSignalMapper(this)) { connect(m_signalMapper, SIGNAL(mapped(int)), SLOT(emitLanguageChanged(int))); } QHash< int, QByteArray > LanguageResourceModel::roleNames() const { QHash roles; roles[TitleRole] = "title"; roles[I18nTitleRole] = "i18nTitle"; roles[IdRole] = "id"; roles[DataRole] = "dataRole"; roles[CourseNumberRole] = "courseNumberRole"; return roles; } void LanguageResourceModel::setResourceManager(ResourceManager *resourceManager) { if (m_resourceManager == resourceManager) { return; } beginResetModel(); if (m_resourceManager) { m_resourceManager->disconnect(this); } m_resourceManager = resourceManager; if (m_resourceManager) { connect(m_resourceManager, &ResourceManager::languageResourceAboutToBeAdded, this, &LanguageResourceModel::onLanguageResourceAboutToBeAdded); connect(m_resourceManager, &ResourceManager::languageResourceAdded, this, &LanguageResourceModel::onLanguageResourceAdded); connect(m_resourceManager, &ResourceManager::languageResourceAboutToBeRemoved, this, &LanguageResourceModel::onLanguageResourceAboutToBeRemoved); connect(m_resourceManager, &ResourceManager::languageResourceRemoved, this, &LanguageResourceModel::onLanguageResourceRemoved); connect(m_resourceManager, &ResourceManager::languageCoursesChanged, this, &LanguageResourceModel::updateDisplayedLanguages); } updateResources(); endResetModel(); emit resourceManagerChanged(); } ResourceManager * LanguageResourceModel::resourceManager() const { return m_resourceManager; } QVariant LanguageResourceModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (index.row() >= m_resources.count()) { return QVariant(); } Language * const language = m_resources.at(index.row())->language(); switch(role) { case Qt::DisplayRole: return !language->title().isEmpty() ? QVariant(language->title()): QVariant(i18nc("@item:inlistbox:", "unknown")); case Qt::ToolTipRole: return QVariant(language->title()); case TitleRole: return language->title(); case I18nTitleRole: return language->i18nTitle(); case IdRole: return language->id(); case DataRole: return QVariant::fromValue(language); case CourseNumberRole: return m_resources.count(); default: return QVariant(); } } int LanguageResourceModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return m_resources.count(); } void LanguageResourceModel::onLanguageResourceAboutToBeAdded(LanguageResource *resource, int index) { if (!displayResource(resource)) { return; } beginInsertRows(QModelIndex(), index, index); m_resources.append(resource); connect(resource->language(), SIGNAL(titleChanged()), m_signalMapper, SLOT(map())); connect(resource->language(), SIGNAL(phonemesChanged()), m_signalMapper, SLOT(map())); connect(resource->language(), SIGNAL(phonemeGroupsChanged()), m_signalMapper, SLOT(map())); } void LanguageResourceModel::onLanguageResourceAdded() { updateMappings(); endInsertRows(); } void LanguageResourceModel::onLanguageResourceAboutToBeRemoved(int index) { if (!m_resourceManager) { return; } LanguageResource *originalResource = m_resourceManager->languageResources().at(index); int modelIndex = m_resources.indexOf(originalResource); if (modelIndex == -1) { qCWarning(ARTIKULATE_LOG) << "Cannot remove language from model, not registered"; return; } beginRemoveRows(QModelIndex(), modelIndex, modelIndex); originalResource->disconnect(m_signalMapper); m_resources.removeAt(modelIndex); } void LanguageResourceModel::onLanguageResourceRemoved() { endRemoveRows(); } void LanguageResourceModel::emitLanguageChanged(int row) { emit languageChanged(row); emit dataChanged(index(row, 0), index(row, 0)); } QVariant LanguageResourceModel::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", "Language")); } void LanguageResourceModel::setView(LanguageModel::LanguageResourceView view) { if (m_view == view) { return; } - emit beginResetModel(); + beginResetModel(); m_view = view; updateResources(); - emit endResetModel(); + endResetModel(); } void LanguageResourceModel::updateDisplayedLanguages() { - emit beginResetModel(); + beginResetModel(); updateResources(); - emit endResetModel(); + endResetModel(); } LanguageModel::LanguageResourceView LanguageResourceModel::view() const { return m_view; } bool LanguageResourceModel::displayResource(LanguageResource* resource) const { if (m_view == LanguageModel::AllLanguages) { return true; } // otherwise compute data needed for decision QList courses = m_resourceManager->courseResources(resource->language()); int contribCount = 0; if (m_view == LanguageModel::NonEmptyLanguages && courses.count() > 0) { return true; } // compute data for determining whether language shall be shown or not foreach (CourseResource *course, courses) { if (course->isContributorResource()) { ++contribCount; } } if (m_view == LanguageModel::NonEmptyContributorOnlyResources && contribCount > 0) { return true; } if (m_view == LanguageModel::NonEmptyGhnsOnlyLanguages && courses.count() - contribCount > 0) { return true; } return false; } void LanguageResourceModel::updateResources() { if (!m_resourceManager) { return; } m_resources.clear(); QList resources = m_resourceManager->languageResources(); foreach (LanguageResource *language, resources) { if (displayResource(language)) { m_resources.append(language); } } updateMappings(); } void LanguageResourceModel::updateMappings() { int languages = m_resources.count(); for (int i = 0; i < languages; i++) { m_signalMapper->setMapping(m_resources.at(i), i); } } diff --git a/src/models/phonememodel.cpp b/src/models/phonememodel.cpp index dd55f31..41c7b55 100644 --- a/src/models/phonememodel.cpp +++ b/src/models/phonememodel.cpp @@ -1,155 +1,153 @@ /* * Copyright 2013 Andreas Cord-Landwehr * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "phonememodel.h" #include "core/language.h" #include "core/phoneme.h" #include #include #include #include "artikulate_debug.h" PhonemeModel::PhonemeModel(QObject *parent) : QAbstractListModel(parent) , m_language(nullptr) , m_signalMapper(new QSignalMapper(this)) { connect(m_signalMapper, SIGNAL(mapped(int)), SLOT(emitPhonemeChanged(int))); } QHash< int, QByteArray > PhonemeModel::roleNames() const { QHash roles; roles[TitleRole] = "title"; roles[IdRole] = "id"; roles[DataRole] = "dataRole"; return roles; } Language * PhonemeModel::language() const { return m_language; } void PhonemeModel::setLanguage(Language *language) { - emit beginResetModel(); - + beginResetModel(); m_language = language; emit languageChanged(); - - emit endResetModel(); + endResetModel(); } QVariant PhonemeModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } if (index.row() >= m_language->phonemes().count()) { return QVariant(); } Phoneme * const phoneme = m_language->phonemes().at(index.row()); switch(role) { case Qt::DisplayRole: return !phoneme->title().isEmpty()? QVariant(phoneme->title()): QVariant(i18nc("@item:inlistbox:", "unknown")); case Qt::ToolTipRole: return QVariant(phoneme->title()); case TitleRole: return phoneme->title(); case IdRole: return phoneme->id(); case DataRole: return QVariant::fromValue(phoneme); default: return QVariant(); } } int PhonemeModel::rowCount(const QModelIndex& parent) const { if (!m_language) { return 0; } if (parent.isValid()) { return 0; } return m_language->phonemes().count(); } void PhonemeModel::onPhonemeAboutToBeAdded(Phoneme *phoneme, int index) { connect(phoneme, SIGNAL(titleChanged()), m_signalMapper, SLOT(map())); beginInsertRows(QModelIndex(), index, index); } void PhonemeModel::onPhonemeAdded() { updateMappings(); endInsertRows(); } void PhonemeModel::onPhonemesAboutToBeRemoved(int first, int last) { beginRemoveRows(QModelIndex(), first, last); } void PhonemeModel::onPhonemesRemoved() { endRemoveRows(); } void PhonemeModel::emitPhonemeChanged(int row) { emit phonemeChanged(row); emit dataChanged(index(row, 0), index(row, 0)); } QVariant PhonemeModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) { return QVariant(); } if (orientation == Qt::Vertical) { return QVariant(section + 1); } return QVariant(i18nc("@title:column", "Phoneme")); } void PhonemeModel::updateMappings() { if (!m_language) { qCDebug(ARTIKULATE_LOG) << "Aborting to update mappings, language not set."; return; } int phonemes = m_language->phonemes().count(); for (int i = 0; i < phonemes; i++) { m_signalMapper->setMapping(m_language->phonemes().at(i), i); } } diff --git a/src/models/phrasefiltermodel.cpp b/src/models/phrasefiltermodel.cpp index 0369012..ffb1ae3 100644 --- a/src/models/phrasefiltermodel.cpp +++ b/src/models/phrasefiltermodel.cpp @@ -1,116 +1,116 @@ /* * Copyright 2013 Andreas Cord-Landwehr * Copyright 2013 Samikshan Bairagya * * 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 "phrasefiltermodel.h" #include "models/phraselistmodel.h" #include #include #include "artikulate_debug.h" PhraseFilterModel::PhraseFilterModel(QObject *parent) : QSortFilterProxyModel(parent) , m_phraseModel(nullptr) , m_hideExcluded(true) { setHideExcluded(true); } PhraseListModel * PhraseFilterModel::phraseModel() const { return m_phraseModel; } void PhraseFilterModel::setHideExcluded(bool hide) { m_hideExcluded = hide; invalidateFilter(); emit hideExcludedChanged(); } bool PhraseFilterModel::isHideExcluded() const { return m_hideExcluded; } void PhraseFilterModel::setHideNotRecorded(bool hide) { m_hideNotRecorded = hide; invalidateFilter(); emit hideNotRecordedChanged(); } bool PhraseFilterModel::isHideNotRecorded() const { return m_hideNotRecorded; } void PhraseFilterModel::setPhraseModel(PhraseListModel* phraseModel) { if (phraseModel == m_phraseModel) { return; } m_phraseModel = phraseModel; setSourceModel(m_phraseModel); sort(0); emit phraseModelChanged(); } void PhraseFilterModel::setSortOption(PhraseFilterModel::SortOption option) { m_sortOption = option; invalidateFilter(); emit sortOptionChanged(); } PhraseFilterModel::SortOption PhraseFilterModel::sortOption() const { return m_sortOption; } int PhraseFilterModel::filteredCount() const { return rowCount(); } bool PhraseFilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) const { if (m_sortOption == Type) { return sourceModel()->data(left, PhraseListModel::TypeRole).toInt() < sourceModel()->data(right, PhraseListModel::TypeRole).toInt(); } return QSortFilterProxyModel::lessThan(left, right); } bool PhraseFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { int result = true; if (m_hideNotRecorded || m_hideExcluded) { QModelIndex index = sourceModel()->index(source_row, 0, source_parent); - bool notRecorded = sourceModel()->data(index, PhraseListModel::SoundFileRole).value().isEmpty(); + bool notRecorded = sourceModel()->data(index, PhraseListModel::SoundFileRole).toUrl().isEmpty(); bool excluded = sourceModel()->data(index, PhraseListModel::ExcludedRole).toBool(); result = !(notRecorded || excluded); } return result; } diff --git a/src/ui/resourcesdialogpage.cpp b/src/ui/resourcesdialogpage.cpp index bfbcc0b..9ca1ca9 100644 --- a/src/ui/resourcesdialogpage.cpp +++ b/src/ui/resourcesdialogpage.cpp @@ -1,71 +1,71 @@ /* * 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 "resourcesdialogpage.h" #include "core/resourcemanager.h" #include "core/language.h" #include "core/skeleton.h" #include "core/course.h" #include "settings.h" #include #include #include #include #include ResourcesDialogPage::ResourcesDialogPage(ResourceManager *m_resourceManager) : QWidget(nullptr) , m_resourceManager(m_resourceManager) , m_restartNeeded(false) { ui = new Ui::ResourcesDialogPage; ui->setupUi(this); - connect(ui->buttonSelectCourseRepository, &QToolButton::clicked, [=](){ + connect(ui->buttonSelectCourseRepository, &QToolButton::clicked, this, [=](){ const QString dir = QFileDialog::getExistingDirectory(this, i18n("Open Repository Directory"), QString(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); ui->kcfg_CourseRepositoryPath->setText(dir); }); } ResourcesDialogPage::~ResourcesDialogPage() { delete ui; } void ResourcesDialogPage::loadSettings() { // setup Ui with stored settings ui->kcfg_CourseRepositoryPath->setText(Settings::courseRepositoryPath()); ui->kcfg_UseCourseRepository->setChecked(Settings::useCourseRepository()); } void ResourcesDialogPage::saveSettings() { // save settings Settings::setUseCourseRepository(ui->kcfg_UseCourseRepository->isChecked()); Settings::setCourseRepositoryPath(ui->kcfg_CourseRepositoryPath->text()); Settings::self()->save(); // reloading resources m_resourceManager->loadCourseResources(); }