diff --git a/liblearnerprofile/src/learner.cpp b/liblearnerprofile/src/learner.cpp index 08dd9d0..579be55 100644 --- a/liblearnerprofile/src/learner.cpp +++ b/liblearnerprofile/src/learner.cpp @@ -1,205 +1,204 @@ /* * 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), + connect(this, &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::exists(path)) { return QString(); } return "file://" + path; } void Learner::clearImage() { const QString path {d->imagePath()}; 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::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/learner.h b/liblearnerprofile/src/learner.h index 23e2013..db08294 100644 --- a/liblearnerprofile/src/learner.h +++ b/liblearnerprofile/src/learner.h @@ -1,98 +1,97 @@ /* * 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 . */ #ifndef LEARNER_H #define LEARNER_H #include "liblearnerprofile_export.h" #include "learninggoal.h" #include namespace LearnerProfile { class LearnerPrivate; class LearningGoal; /** * \class Learner */ class LIBLEARNERPROFILE_EXPORT Learner : public QObject { Q_OBJECT Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) Q_PROPERTY(int id READ identifier WRITE setIdentifier NOTIFY identifierChanged) Q_PROPERTY(QString imageUrl READ imageUrl NOTIFY imageChanged) Q_PROPERTY(QList goals READ goals NOTIFY goalCountChanged) public: // TODO workaround for QT-BUG-26415, fixed in Qt5.0 // we must simulate the LearningGoal::Category enum in Learner to be able to allow its usage // as parameter for Q_INVOKABLE method // can be removed with Qt 5.0 migration Q_ENUMS(Category) enum Category { Unspecified = 0, Language = 1 }; explicit Learner(QObject *parent = nullptr); ~Learner(); QString name() const; void setName(const QString &name); /** * \return URL to image * \note since it is a local file the path begins with "file://" */ QString imageUrl() const; Q_INVOKABLE void clearImage(); void importImage(const QString &path); int identifier() const; void setIdentifier(int identifier); /** * \return list of all learning goals of learner */ QList goals() const; Q_INVOKABLE void addGoal(LearnerProfile::LearningGoal *goal); Q_INVOKABLE void removeGoal(LearnerProfile::LearningGoal *goal); Q_INVOKABLE bool hasGoal(LearnerProfile::LearningGoal *goal) const; void setActiveGoal(LearnerProfile::LearningGoal *goal); Q_INVOKABLE void setActiveGoal(LearnerProfile::Learner::Category category, const QString &identifier); Q_INVOKABLE LearnerProfile::LearningGoal * activeGoal(LearnerProfile::Learner::Category category) const; Q_SIGNALS: void nameChanged(); void imageChanged(); void identifierChanged(); void goalAboutToBeAdded(LearningGoal*,int); void goalAdded(); void goalAboutToBeRemoved(int); - void goalRemoved(); void goalRemoved(Learner*, LearningGoal*); void goalCountChanged(); void activeGoalChanged(); private: Q_DISABLE_COPY(Learner) const QScopedPointer d; }; } #endif // LEARNER_H diff --git a/liblearnerprofile/src/profilemanager.cpp b/liblearnerprofile/src/profilemanager.cpp index 4efe906..bcecc03 100644 --- a/liblearnerprofile/src/profilemanager.cpp +++ b/liblearnerprofile/src/profilemanager.cpp @@ -1,305 +1,304 @@ /* * Copyright 2013-2014 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 "profilemanager.h" #include "storage.h" #include "learner.h" #include #include #include "liblearner_debug.h" #include #include #include #include using namespace LearnerProfile; ///BEGIN: ProfileManagerPrivate namespace LearnerProfile { class ProfileManagerPrivate { public: ProfileManagerPrivate(); ~ProfileManagerPrivate() {} void sync(); QList m_profiles; Learner *m_activeProfile; QList m_goals; KConfig *m_config; Storage m_storage; }; } LearnerProfile::ProfileManagerPrivate::ProfileManagerPrivate() : m_profiles(QList()) , m_activeProfile(nullptr) , m_config(nullptr) { // load all profiles from storage m_goals.append(m_storage.loadGoals()); m_profiles.append(m_storage.loadProfiles(m_goals)); // set last used profile m_config = new KConfig(QStringLiteral("learnerprofilerc")); KConfigGroup activeProfileGroup(m_config, "ActiveProfile"); int lastProfileId = activeProfileGroup.readEntry("profileId", "0").toInt(); QList activeGoalsCategories = activeProfileGroup.readEntry("activeGoalsCategories", QList()); QList activeGoalsIdentifiers = activeProfileGroup.readEntry("activeGoalsIdentifiers", QList()); foreach (Learner *learner, m_profiles) { if (learner->identifier() == lastProfileId) { m_activeProfile = learner; // set active goals if (activeGoalsCategories.count() == activeGoalsIdentifiers.count()) { for (int i = 0; i < activeGoalsCategories.count(); ++i) { m_activeProfile->setActiveGoal( static_cast(activeGoalsCategories.at(i)), activeGoalsIdentifiers.at(i)); } } else { qCritical() << "Inconsistent goal category / identifier pairs found: aborting."; } break; } } if (m_activeProfile == nullptr) { qCDebug(LIBLEARNER_LOG) << "No last active profile found, falling back to first found profile"; if (m_profiles.size() > 0) { m_activeProfile = m_profiles.at(0); } } } void ProfileManagerPrivate::sync() { // sync last used profile data if (m_activeProfile) { KConfigGroup activeProfileGroup(m_config, "ActiveProfile"); activeProfileGroup.writeEntry("profileId", m_activeProfile->identifier()); // compute activer learning goals by category QList goalCatogries; QList goalIdentifiers; // compute used goals foreach (LearningGoal *goal, m_activeProfile->goals()) { if (!goalCatogries.contains(static_cast(goal->category()))) { goalCatogries.append(static_cast(goal->category())); } } // compute active goals foreach (int category, goalCatogries) { goalIdentifiers.append(m_activeProfile->activeGoal(static_cast(category))->identifier()); } activeProfileGroup.writeEntry("activeGoalsCategories", goalCatogries); activeProfileGroup.writeEntry("activeGoalsIdentifiers", goalIdentifiers); } else { qCritical() << "No active profile selected, aborting sync."; } m_config->sync(); //TODO only sync changed learner foreach (Learner *learner, m_profiles) { m_storage.storeProfile(learner); } } ///END: ProfileManagerPrivate ProfileManager::ProfileManager(QObject *parent) : QObject(parent) , d(new ProfileManagerPrivate) { connect (this, &ProfileManager::profileAdded, this, &ProfileManager::profileCountChanged); connect (this, &ProfileManager::profileRemoved, this, &ProfileManager::profileCountChanged); foreach (Learner *learner, d->m_profiles) { - connect (learner, SIGNAL(goalRemoved(Learner*,LearningGoal*)), - this, SLOT(removeLearningGoal(Learner*,LearningGoal*))); + connect(learner, &Learner::goalRemoved, this, &ProfileManager::removeLearningGoal); } } ProfileManager::~ProfileManager() { foreach (Learner *learner, d->m_profiles) { learner->deleteLater(); } } QList< Learner* > ProfileManager::profiles() const { return d->m_profiles; } int ProfileManager::profileCount() const { return profiles().length(); } void ProfileManager::openImageFileDialog() { const QString imagePath = QFileDialog::getOpenFileName( nullptr, i18n("Open Image"), QLatin1String(""), i18n("Image Files (*.png *.jpg *.bmp)")); d->m_activeProfile->importImage(imagePath); } Learner * ProfileManager::addProfile(const QString &name) { Learner *learner = new Learner(this); learner->setName(name); // set id int maxUsedId = 0; foreach (Learner *cpLearner, d->m_profiles) { if (cpLearner->identifier() >= maxUsedId) { maxUsedId = cpLearner->identifier(); } } learner->setIdentifier(maxUsedId + 1); d->m_profiles.append(learner); d->m_storage.storeProfile(learner); emit profileAdded(learner, d->m_profiles.count() - 1); if (activeProfile() == nullptr) { setActiveProfile(learner); } connect (learner, SIGNAL(goalRemoved(Learner*,LearningGoal*)), this, SLOT(removeLearningGoal(Learner*,LearningGoal*))); return learner; } void ProfileManager::removeProfile(Learner *learner) { int index = d->m_profiles.indexOf(learner); if (index < 0) { qCWarning(LIBLEARNER_LOG) << "Profile was not found, aborting"; return; } emit profileAboutToBeRemoved(index); d->m_profiles.removeAt(index); d->m_storage.removeProfile(learner); if (d->m_activeProfile == learner) { if (d->m_profiles.isEmpty()) { setActiveProfile(nullptr); } else { setActiveProfile(d->m_profiles.at(0)); } } emit profileRemoved(); } void ProfileManager::removeLearningGoal(Learner* learner, LearningGoal* goal) { d->m_storage.removeRelation(learner, goal); } Learner * ProfileManager::profile(int index) { if (index < 0 || index >= profiles().count()) { return nullptr; } return profiles().at(index); } QList< LearningGoal* > ProfileManager::goals() const { return d->m_goals; } LearningGoal * ProfileManager::registerGoal(LearningGoal::Category category, const QString &identifier, const QString &name) { // test whether goal is already registered foreach (LearningGoal *cmpGoal, d->m_goals) { if (cmpGoal->category() == category && cmpGoal->identifier() == identifier) { return cmpGoal; } } LearningGoal *goal = new LearningGoal(category, identifier, this); goal->setName(name); d->m_goals.append(goal); d->m_storage.storeGoal(goal); return goal; } LearnerProfile::LearningGoal * LearnerProfile::ProfileManager::goal( LearningGoal::Category category, const QString& identifier) const { foreach (LearningGoal *goal, d->m_goals) { if (goal->category() == category && goal->identifier() == identifier) { return goal; } } return nullptr; } void ProfileManager::recordProgress(Learner *learner, LearningGoal *goal, const QString &container, const QString &item, int logPayload, int valuePayload) { d->m_storage.storeProgressLog(learner, goal, container, item, logPayload, QDateTime::currentDateTime()); d->m_storage.storeProgressValue(learner, goal, container, item, valuePayload); } QHash ProfileManager::progressValues(Learner *learner, LearningGoal *goal, const QString &container) const { if (!learner || !goal) { return QHash(); } return d->m_storage.readProgressValues(learner, goal, container); } void ProfileManager::sync() { d->sync(); } void ProfileManager::sync(Learner *learner) { d->m_storage.storeProfile(learner); } Learner * ProfileManager::activeProfile() const { return d->m_activeProfile; } void ProfileManager::setActiveProfile(Learner* learner) { if (learner == d->m_activeProfile) { return; } d->m_activeProfile = learner; emit activeProfileChanged(); } diff --git a/src/core/course.cpp b/src/core/course.cpp index ccf5a7e..9f58ed8 100644 --- a/src/core/course.cpp +++ b/src/core/course.cpp @@ -1,340 +1,340 @@ /* * 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, 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*))); + // TODO: removing of phrase and upading of phonemes for that case is not implemented + connect(unit, &Unit::phraseAdded, this, &Course::registerPhrasePhonemes); 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; for (const auto &group : m_phonemeUnitList.value(phonemeGroup)) { list.append(group.second); } return list; } Unit * Course::phonemeUnit(Phoneme *phoneme) const { 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; } } } return nullptr; } PhonemeGroup * Course::phonemeGroup(Unit *unit) const { 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; } } } 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 == 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; for (const auto &phonemeUnit : m_phonemeUnitList.value(group)) { if (phonemeUnit.first->id() == phoneme->id()) { phonemeUnit.second->addPhrase(phrase); phraseRegistered = true; } } // 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); } } } } diff --git a/src/core/course.h b/src/core/course.h index c164c9f..ffde7b3 100644 --- a/src/core/course.h +++ b/src/core/course.h @@ -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 . */ #ifndef COURSE_H #define COURSE_H #include "artikulatecore_export.h" #include #include #include class ResourceInterface; class CourseResource; class QString; class Language; class Unit; class Phrase; class PhonemeGroup; class Phoneme; class ARTIKULATECORE_EXPORT Course : public QObject { Q_OBJECT Q_PROPERTY(QString id READ id WRITE setId NOTIFY idChanged) Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged) Q_PROPERTY(QString i18nTitle READ i18nTitle NOTIFY titleChanged) Q_PROPERTY(QString description READ description WRITE setDescription NOTIFY descriptionChanged) Q_PROPERTY(bool modified READ modified WRITE setModified NOTIFY modifiedChanged) Q_PROPERTY(Language * language READ language NOTIFY languageChanged) public: explicit Course(ResourceInterface *resource=0); ~Course(); QString id() const; void setId(const QString &id); QString foreignId() const; void setForeignId(const QString &id); QString title() const; QString i18nTitle() const; void setTitle(const QString &title); Language * language() const; void setLanguage(Language *language); QString description() const; void setDescription(const QString &description); QUrl file() const; void setFile(const QUrl &file); QList unitList() const; QList phonemeUnitList(PhonemeGroup *phonemeGroup) const; /** * \return the corresponding unit for phoneme \p phoneme */ Unit * phonemeUnit(Phoneme *phoneme) const; /** * \return the phoneme group containing the phoneme corresponding to \p unit */ PhonemeGroup * phonemeGroup(Unit *unit) const; void addUnit(Unit *unit); QList phonemeGroupList() const; void addPhonemeGroup(PhonemeGroup *phonemeGroup); /** * Create and add a new unit to course. * * \return pointer to the created unit */ Q_INVOKABLE Unit * createUnit(); /** * Create and add a new phrase and add it to the specified unit. The type of the created phrase * is initially Phrase::Word. * * \param unit the unit to that the created hprase shall be added * \return pointer to the created phrase */ Q_INVOKABLE Phrase * createPhrase(Unit *unit); /** * \return true if the course was modified after the last sync, otherwise false */ virtual bool modified() const; /** * Writes course object back to file and set \ref modified state to false. * If no file is set, no operation is performed. */ virtual Q_INVOKABLE void sync(); bool isContributorResource() const; public Q_SLOTS: void setModified(bool modified = true); void registerPhrasePhonemes(Phrase *phrase); Q_SIGNALS: void idChanged(); void titleChanged(); void descriptionChanged(); void modifiedChanged(); void languageChanged(); void unitAdded(); void unitAboutToBeAdded(Unit*,int); void unitsRemoved(); void unitsAboutToBeRemoved(int,int); void phonemeGroupAdded(); void phonemeGroupAboutToBeAdded(PhonemeGroup*,int); void phonemeGroupRemoved(); void phonemeGroupAboutToBeRemoved(int,int); private: Q_DISABLE_COPY(Course) CourseResource * const m_resource; QString m_id; QString m_foreignId; QString m_title; QString m_description; Language *m_language; QUrl m_file; bool m_modified; QList m_unitList; QList m_phonemeGroupList; - QMap< PhonemeGroup *, QList< QPair > >m_phonemeUnitList; + QMap>> m_phonemeUnitList; }; #endif // COURSE_H diff --git a/src/core/unit.cpp b/src/core/unit.cpp index 567a584..d582add 100644 --- a/src/core/unit.cpp +++ b/src/core/unit.cpp @@ -1,165 +1,164 @@ /* * Copyright 2013-2015 Andreas Cord-Landwehr * Copyright 2013 Oindrila Gupta * * 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 "unit.h" #include "phrase.h" #include #include #include #include #include #include "artikulate_debug.h" #include #include Unit::Unit(QObject *parent) : QObject(parent) , m_course(nullptr) , m_phraseSignalMapper(new QSignalMapper(this)) { } Unit::~Unit() { m_phraseSignalMapper->deleteLater(); } QString Unit::id() const { return m_id; } void Unit::setId(const QString &id) { if (id != m_id) { m_id = id; emit idChanged(); emit modified(); } } QString Unit::foreignId() const { return m_foreignId; } void Unit::setForeignId(const QString &id) { m_foreignId = id; } Course * Unit::course() const { return m_course; } void Unit::setCourse(Course *course) { if (course == m_course) { return; } m_course = course; emit courseChanged(); } QString Unit::title() const { return m_title; } void Unit::setTitle(const QString &title) { if (QString::compare(title, m_title) != 0) { m_title = title; emit titleChanged(); emit modified(); } } QList< Phrase* > Unit::phraseList() const { return m_phraseList; } void Unit::addPhrase(Phrase *phrase) { QList::ConstIterator iter = m_phraseList.constBegin(); while (iter != m_phraseList.constEnd()) { if (phrase->id() == (*iter)->id()) { qCWarning(ARTIKULATE_LOG) << "Phrase is already contained in this unit, aborting"; return; } ++iter; } phrase->setUnit(this); emit phraseAboutToBeAdded(phrase, m_phraseList.length()); m_phraseList.append(phrase); m_phraseSignalMapper->setMapping(phrase, phrase->id()); emit phraseAdded(phrase); - emit phraseAdded(); connect(phrase, &Phrase::typeChanged, m_phraseSignalMapper, static_cast(&QSignalMapper::map)); connect(phrase, &Phrase::modified, this, &Unit::modified); emit modified(); } QList Unit::excludedSkeletonPhraseList() const { QList excludedPhraseList; QList::ConstIterator iter = m_phraseList.constBegin(); while (iter != m_phraseList.constEnd()) { if ((*iter)->isExcluded() == true) { excludedPhraseList.append(*iter); } ++iter; } return excludedPhraseList; } void Unit::excludeSkeletonPhrase(const QString &phraseId) { foreach (Phrase *phrase, m_phraseList) { if (phrase->id() == phraseId) { phrase->setExcluded(true); emit modified(); return; } } qCWarning(ARTIKULATE_LOG) << "Could not exclude phrase with ID " << phraseId << ", no phrase with this ID."; } void Unit::includeSkeletonPhrase(const QString &phraseId) { foreach (Phrase *phrase, m_phraseList) { if (phrase->id() == phraseId) { phrase->setExcluded(false); emit modified(); return; } } qCWarning(ARTIKULATE_LOG) << "Could not include phrase with ID " << phraseId << ", no phrase with this ID."; } diff --git a/src/core/unit.h b/src/core/unit.h index f8540cd..130ee58 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -1,90 +1,88 @@ /* * 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 . */ #ifndef UNIT_H #define UNIT_H #include "artikulatecore_export.h" #include "phrase.h" #include #include #include class QSignalMapper; class QString; class Phrase; class Course; class ARTIKULATECORE_EXPORT Unit : public QObject { Q_OBJECT Q_PROPERTY(QString id READ id WRITE setId NOTIFY idChanged) Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged) Q_PROPERTY(Course *course READ course WRITE setCourse NOTIFY courseChanged) public: explicit Unit(QObject *parent = nullptr); ~Unit(); QString id() const; void setId(const QString &id); QString foreignId() const; void setForeignId(const QString &id); Course * course() const; void setCourse(Course* course); QString title() const; void setTitle(const QString &title); QList phraseList() const; void addPhrase(Phrase *phrase); QList excludedSkeletonPhraseList() const; /** * Removes phrase with ID \p phraseId from unit and adds ID to set * of excluded IDs. * * \param phraseId is the UID of the to be excluded phrase */ Q_INVOKABLE void excludeSkeletonPhrase(const QString &phraseId); Q_INVOKABLE void includeSkeletonPhrase(const QString &phraseId); Q_SIGNALS: void idChanged(); void titleChanged(); void courseChanged(); void displayPhraseTypeChanged(); void modified(); - void phraseAdded(); void phraseAdded(Phrase*); void phraseAboutToBeAdded(Phrase*,int); - void phraseRemoved(); void phraseRemoved(Phrase*); void phraseAboutToBeRemoved(int,int); private: Q_DISABLE_COPY(Unit) QString m_id; QString m_foreignId; Course *m_course; QString m_title; QList m_phraseList; QSignalMapper *m_phraseSignalMapper; }; #endif // UNIT_H diff --git a/src/models/phraselistmodel.cpp b/src/models/phraselistmodel.cpp index 64ee479..e7c15e5 100644 --- a/src/models/phraselistmodel.cpp +++ b/src/models/phraselistmodel.cpp @@ -1,210 +1,210 @@ /* * 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 "phraselistmodel.h" #include "core/unit.h" #include "core/phrase.h" #include #include #include PhraseListModel::PhraseListModel(QObject *parent) : QAbstractListModel(parent) , m_unit(nullptr) , m_signalMapper(new QSignalMapper(this)) { connect(m_signalMapper, SIGNAL(mapped(int)), SLOT(emitPhraseChanged(int))); // connect all phrase number operations to single signal connect(this, &PhraseListModel::typeChanged, this, &PhraseListModel::countChanged); connect(this, &PhraseListModel::unitChanged, this, &PhraseListModel::countChanged); } QHash< int, QByteArray > PhraseListModel::roleNames() const { QHash roles; roles[TextRole] = "text"; roles[SoundFileRole] = "soundFile"; roles[IdRole] = "id"; roles[TypeRole] = "type"; roles[ExcludedRole] = "excludedRole"; roles[DataRole] = "dataRole"; return roles; } void PhraseListModel::setUnit(Unit *unit) { if (m_unit == unit) { return; } beginResetModel(); if (m_unit) { m_unit->disconnect(this); foreach (Phrase *phrase, m_unit->phraseList()) { phrase->disconnect(this); } } m_unit = unit; if (m_unit) { // initial setting of signal mappings connect(m_unit, &Unit::phraseAboutToBeAdded, this, &PhraseListModel::onPhraseAboutToBeAdded); - connect(m_unit, SIGNAL(phraseAdded()), SLOT(onPhraseAdded())); + connect(m_unit, &Unit::phraseAdded, this, &PhraseListModel::onPhraseAdded); connect(m_unit, &Unit::phraseAboutToBeRemoved, this, &PhraseListModel::onPhrasesAboutToBeRemoved); - connect(m_unit, SIGNAL(phraseRemoved()), SLOT(onPhrasesRemoved())); + connect(m_unit, &Unit::phraseRemoved, this, &PhraseListModel::onPhrasesRemoved); // insert and connect all already existing phrases int phrases = m_unit->phraseList().count(); for (int i=0; i < phrases; ++i) { onPhraseAboutToBeAdded(m_unit->phraseList().at(i), i); endInsertRows(); emit countChanged(); } updateMappings(); } // emit done endResetModel(); emit unitChanged(); } Unit * PhraseListModel::unit() const { return m_unit; } QVariant PhraseListModel::data(const QModelIndex &index, int role) const { Q_ASSERT(m_unit); if (!index.isValid()) { return QVariant(); } if (index.row() >= m_unit->phraseList().count()) { return QVariant(); } Phrase * const phrase = m_unit->phraseList().at(index.row()); switch(role) { case Qt::DisplayRole: return !phrase->text().isEmpty()? QVariant(phrase->text()): QVariant(i18nc("@item:inlistbox:", "unknown")); case Qt::ToolTipRole: return QVariant(phrase->text()); case TextRole: return phrase->text(); case SoundFileRole: return phrase->sound(); case IdRole: return phrase->id(); case TypeRole: return phrase->type(); case ExcludedRole: return phrase->isExcluded(); case DataRole: return QVariant::fromValue(phrase); default: return QVariant(); } } int PhraseListModel::rowCount(const QModelIndex &parent) const { if (!m_unit) { return 0; } if (parent.isValid()) { return 0; } return m_unit->phraseList().count(); } void PhraseListModel::onPhraseAboutToBeAdded(Phrase *phrase, int index) { connect(phrase, SIGNAL(textChanged()), m_signalMapper, SLOT(map())); connect(phrase, SIGNAL(typeChanged()), m_signalMapper, SLOT(map())); connect(phrase, SIGNAL(excludedChanged()), m_signalMapper, SLOT(map())); beginInsertRows(QModelIndex(), index, index); } void PhraseListModel::onPhraseAdded() { updateMappings(); endInsertRows(); emit countChanged(); } void PhraseListModel::onPhrasesAboutToBeRemoved(int first, int last) { beginRemoveRows(QModelIndex(), first, last); } void PhraseListModel::onPhrasesRemoved() { endRemoveRows(); emit countChanged(); } void PhraseListModel::emitPhraseChanged(int row) { beginResetModel(); endResetModel(); //FIXME very inefficient, but workaround to force new filtering in phrasefiltermodel // to exclude possible new excluded phrases emit phraseChanged(row); emit dataChanged(index(row, 0), index(row, 0)); } QVariant PhraseListModel::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", "Phrase")); } int PhraseListModel::count() const { if (!m_unit) { return 0; } return m_unit->phraseList().count(); } void PhraseListModel::updateMappings() { if (!m_unit) { return; } int phrases = m_unit->phraseList().count(); for (int i = 0; i < phrases; ++i) { m_signalMapper->setMapping(m_unit->phraseList().at(i), i); } } diff --git a/src/models/phrasemodel.cpp b/src/models/phrasemodel.cpp index 28fceb2..6ae4506 100644 --- a/src/models/phrasemodel.cpp +++ b/src/models/phrasemodel.cpp @@ -1,338 +1,338 @@ /* * Copyright 2013-2015 Andreas Cord-Landwehr * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "phrasemodel.h" #include "core/course.h" #include "core/unit.h" #include "core/phrase.h" #include #include #include #include "artikulate_debug.h" PhraseModel::PhraseModel(QObject *parent) : QAbstractItemModel(parent) , m_course(nullptr) , m_unitSignalMapper(new QSignalMapper) , m_phraseSignalMapper(new QSignalMapper) { connect(m_unitSignalMapper, static_cast(&QSignalMapper::mapped), this, &PhraseModel::onUnitChanged); connect(m_phraseSignalMapper, static_cast(&QSignalMapper::mapped), this, &PhraseModel::onPhraseChanged); } QHash< int, QByteArray > PhraseModel::roleNames() const { QHash roles; roles[TextRole] = "text"; roles[DataRole] = "dataRole"; return roles; } void PhraseModel::setCourse(Course *course) { if (m_course == course) { return; } beginResetModel(); if (m_course) { m_course->disconnect(this); foreach (auto const &unit, m_course->unitList()) { unit->disconnect(this); foreach (auto const &phrase, unit->phraseList()) { phrase->disconnect(this); } } } m_course = course; if (m_course) { // connect to unit changes connect(m_course, &Course::unitAboutToBeAdded, this, &PhraseModel::onUnitAboutToBeAdded); connect(m_course, &Course::unitAdded, this, &PhraseModel::onUnitAdded); connect(m_course, &Course::unitsAboutToBeRemoved, this, &PhraseModel::onUnitsAboutToBeRemoved); connect(m_course, &Course::unitsRemoved, this, &PhraseModel::onUnitsRemoved); // initial setting of signal mappings foreach (auto const &unit, m_course->unitList()) { // connect to phrase changes connect(unit, &Unit::phraseAboutToBeAdded, this, &PhraseModel::onPhraseAboutToBeAdded); - connect(unit, static_cast(&Unit::phraseAdded), this, &PhraseModel::onPhraseAdded); + connect(unit, &Unit::phraseAdded, this, &PhraseModel::onPhraseAdded); connect(unit, &Unit::phraseAboutToBeRemoved, this, &PhraseModel::onPhrasesAboutToBeRemoved); - connect(unit, static_cast(&Unit::phraseRemoved), this, &PhraseModel::onPhrasesRemoved); + connect(unit, &Unit::phraseRemoved, this, &PhraseModel::onPhrasesRemoved); connect(unit, &Unit::titleChanged, m_unitSignalMapper, static_cast(&QSignalMapper::map)); // insert and connect all already existing phrases int phrases = unit->phraseList().count(); for (int i = 0; i < phrases; ++i) { onPhraseAboutToBeAdded(unit->phraseList().at(i), i); endInsertRows(); } } updateUnitMappings(); updatePhraseMappings(); } // emit done endResetModel(); emit courseChanged(); } Course * PhraseModel::course() const { return m_course; } QVariant PhraseModel::data(const QModelIndex &index, int role) const { Q_ASSERT(m_course); if (!index.isValid()) { return QVariant(); } if (!index.internalPointer()) { if (!m_course || m_course->unitList().size() == 0) { return QVariant(); } Unit *unit = m_course->unitList().at(index.row()); switch(role) { case TextRole: return unit->title(); case DataRole: return QVariant::fromValue(unit); default: return QVariant(); } } else { Unit *unit = static_cast(index.internalPointer()); switch(role) { case TextRole: return unit->phraseList().at(index.row())->text(); case DataRole: return QVariant::fromValue(unit->phraseList().at(index.row())); default: return QVariant(); } } } int PhraseModel::rowCount(const QModelIndex &parent) const { if (!m_course) { return 0; } // no valid index -> must be (invisible) root if (!parent.isValid()) { return m_course->unitList().count(); } // internal pointer -> must be a phrase if (parent.internalPointer()) { return 0; } // else -> must be a unit Unit *unit = m_course->unitList().at(parent.row()); return unit->phraseList().count(); } int PhraseModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 1; } QModelIndex PhraseModel::parent(const QModelIndex &child) const { if (!child.internalPointer() || !m_course) { return QModelIndex(); } Unit *parent = static_cast(child.internalPointer()); for (int i = 0; i < m_course->unitList().count(); ++i) { if (m_course->unitList().at(i) == parent) { return createIndex(i, 0); } } return QModelIndex(); } QModelIndex PhraseModel::index(int row, int column, const QModelIndex &parent) const { if (!parent.isValid()) { // unit elements return createIndex(row, column); } else { // phrase elements Unit *unit = m_course->unitList().at(parent.row()); if (unit) { return createIndex(row, column, unit); } } return QModelIndex(); } QModelIndex PhraseModel::indexPhrase(Phrase *phrase) const { if (!phrase) { return QModelIndex(); } Unit *unit = phrase->unit(); return createIndex(unit->phraseList().indexOf(phrase), 0, unit); } QModelIndex PhraseModel::indexUnit(Unit *unit) const { if (!unit || !m_course) { return QModelIndex(); } return createIndex(m_course->unitList().indexOf(unit), 0); } bool PhraseModel::isUnit(const QModelIndex &index) const { return (index.internalPointer() == nullptr); } void PhraseModel::onPhraseAboutToBeAdded(Phrase *phrase, int index) { int unitIndex = m_course->unitList().indexOf(phrase->unit()); connect(phrase, &Phrase::textChanged, m_phraseSignalMapper, static_cast(&QSignalMapper::map)); beginInsertRows(createIndex(unitIndex, 0), index, index); } void PhraseModel::onPhraseAdded() { endInsertRows(); updatePhraseMappings(); } void PhraseModel::onPhrasesAboutToBeRemoved(int first, int last) { //TODO better solution requires access to unit //TODO remove connections from m_phraseSignalMapper beginResetModel(); } void PhraseModel::onPhrasesRemoved() { endResetModel(); } void PhraseModel::onPhraseChanged(QObject *phrase) { Phrase *changedPhrase = qobject_cast(phrase); Q_ASSERT(changedPhrase); QModelIndex index = indexPhrase(changedPhrase); emit dataChanged(index, index); } void PhraseModel::onUnitAboutToBeAdded(Unit *unit, int index) { Q_UNUSED(unit) beginInsertRows(QModelIndex(), index, index); connect(unit, &Unit::titleChanged, m_unitSignalMapper, static_cast(&QSignalMapper::map)); } void PhraseModel::onUnitAdded() { endInsertRows(); updateUnitMappings(); } void PhraseModel::onUnitsAboutToBeRemoved(int first, int last) { for (int i = first; i <= last; ++i) { Unit *unit = m_course->unitList().at(i); disconnect(unit, &Unit::titleChanged, m_unitSignalMapper, static_cast(&QSignalMapper::map)); } beginRemoveRows(QModelIndex(), first, last); } void PhraseModel::onUnitsRemoved() { endRemoveRows(); } void PhraseModel::onUnitChanged(int index) { emit dataChanged(createIndex(index, 0), createIndex(index, 0)); } QVariant PhraseModel::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", "Phrase")); } bool PhraseModel::isPhrase(const QModelIndex &index) const { if (index.internalPointer()) { return true; } return false; } Phrase * PhraseModel::phrase(const QModelIndex &index) const { if (index.internalPointer()) { Unit *unit = static_cast(index.internalPointer()); return unit->phraseList().at(index.row()); } if (!m_course->unitList().at(index.row())->phraseList().isEmpty()) { return m_course->unitList().at(index.row())->phraseList().first(); } return nullptr; } Unit * PhraseModel::unit(const QModelIndex &index) const { return m_course->unitList().at(index.row()); } void PhraseModel::updateUnitMappings() { int units = m_course->unitList().count(); for (int i = 0; i < units; ++i) { m_unitSignalMapper->setMapping(m_course->unitList().at(i), i); } } void PhraseModel::updatePhraseMappings() { //TODO this might be quite costly for long units // better, implement access based on index pairs foreach (const Unit *unit, m_course->unitList()) { foreach (Phrase *phrase, unit->phraseList()) { m_phraseSignalMapper->setMapping(phrase, phrase); } } } diff --git a/src/models/profilemodel.cpp b/src/models/profilemodel.cpp index aa88e0d..1fca67f 100644 --- a/src/models/profilemodel.cpp +++ b/src/models/profilemodel.cpp @@ -1,175 +1,175 @@ /* * 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 "profilemodel.h" #include "liblearnerprofile/src/profilemanager.h" #include "liblearnerprofile/src/learner.h" #include #include #include #include "artikulate_debug.h" using namespace LearnerProfile; ProfileModel::ProfileModel(QObject *parent) : QAbstractListModel(parent) , m_profileManager(nullptr) , m_signalMapper(new QSignalMapper(this)) { connect(m_signalMapper, SIGNAL(mapped(int)), SLOT(emitProfileChanged(int))); } QHash< int, QByteArray > ProfileModel::roleNames() const { QHash roles; roles[IdRole] = "id"; roles[NameRole] = "name"; roles[DataRole] = "dataRole"; return roles; } void ProfileModel::setProfileManager(ProfileManager *profileManager) { if (m_profileManager == profileManager) { return; } beginResetModel(); if (m_profileManager) { m_profileManager->disconnect(this); foreach (Learner *learner, m_profileManager->profiles()) { learner->disconnect(this); } } m_profileManager = profileManager; if (m_profileManager) { // initial setting of signal mappings connect(m_profileManager, &ProfileManager::profileAdded, this, &ProfileModel::onProfileAdded); - connect(m_profileManager, SIGNAL(profileAboutToBeRemoved(int)), SLOT(onProfilesAboutToBeRemoved(int))); + connect(m_profileManager, &ProfileManager::profileAboutToBeRemoved, this, &ProfileModel::onProfileAboutToBeRemoved); // insert and connect all already existing profiles int profiles = m_profileManager->profiles().count(); for (int i = 0; i < profiles; ++i) { onProfileAdded(m_profileManager->profiles().at(i), i); } updateMappings(); } endResetModel(); } ProfileManager * ProfileModel::profileManager() const { return m_profileManager; } QVariant ProfileModel::data(const QModelIndex &index, int role) const { Q_ASSERT(m_profileManager); if (!index.isValid()) { return QVariant(); } if (index.row() >= m_profileManager->profiles().count()) { return QVariant(); } Learner * const learner = m_profileManager->profiles().at(index.row()); switch(role) { case Qt::DisplayRole: return !learner->name().isEmpty()? QVariant(learner->name()): QVariant(i18nc("@item:inlistbox:", "unknown")); case Qt::ToolTipRole: return QVariant(learner->name()); case IdRole: return learner->identifier(); case NameRole: return learner->name(); case DataRole: return QVariant::fromValue(learner); default: return QVariant(); } } int ProfileModel::rowCount(const QModelIndex &parent) const { if (!m_profileManager) { return 0; } if (parent.isValid()) { return 0; } return m_profileManager->profiles().count(); } void ProfileModel::onProfileAdded(Learner *learner, int index) { connect(learner, SIGNAL(nameChanged()), m_signalMapper, SLOT(map())); connect(learner, SIGNAL(identifierChanged()), m_signalMapper, SLOT(map())); beginInsertRows(QModelIndex(), index, index); updateMappings(); endInsertRows(); } void ProfileModel::onProfileAboutToBeRemoved(int index) { beginRemoveRows(QModelIndex(), index, index); endRemoveRows(); } void ProfileModel::emitProfileChanged(int row) { beginResetModel(); endResetModel(); //FIXME very inefficient, but workaround to force new filtering in phrasefiltermodel // to exclude possible new excluded phrases emit profileChanged(row); emit dataChanged(index(row, 0), index(row, 0)); } QVariant ProfileModel::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", "Profile")); } void ProfileModel::updateMappings() { if (!m_profileManager) { return; } int profiles = m_profileManager->profiles().count(); for (int i = 0; i < profiles; ++i) { m_signalMapper->setMapping(m_profileManager->profiles().at(i), i); } }