diff --git a/src/models/learningprogressmodel.cpp b/src/models/learningprogressmodel.cpp index 1767ec6..19a31d2 100644 --- a/src/models/learningprogressmodel.cpp +++ b/src/models/learningprogressmodel.cpp @@ -1,281 +1,288 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) 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 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 "learningprogressmodel.h" #include #include "core/profile.h" #include "core/course.h" #include "core/lesson.h" #include "core/profiledataaccess.h" LearningProgressModel::LearningProgressModel(QObject* parent) : QSqlQueryModel(parent), m_profile(0), m_courseFilter(0), m_lessonFilter(0) { } Profile* LearningProgressModel::profile() const { return m_profile; } void LearningProgressModel::setProfile(Profile* profile) { if (profile != m_profile) { if (m_profile) { m_profile->disconnect(this); } m_profile = profile; if (m_profile) { connect(m_profile, SIGNAL(idChanged()), SLOT(update())); connect(m_profile, SIGNAL(destroyed()), SLOT(profileDestroyed())); } update(); emit profileChanged(); } } Course* LearningProgressModel::courseFilter() const { return m_courseFilter; } void LearningProgressModel::setCourseFilter(Course* courseFilter) { if (courseFilter != m_courseFilter) { if (m_courseFilter) { m_courseFilter->disconnect(this); } m_courseFilter = courseFilter; if (m_courseFilter) { connect(courseFilter, SIGNAL(idChanged()), SLOT(update())); } update(); emit courseFilterChanged(); } } Lesson* LearningProgressModel::lessonFilter() const { return m_lessonFilter; } void LearningProgressModel::setLessonFilter(Lesson* lessonFilter) { if (lessonFilter != m_lessonFilter) { if (m_lessonFilter) { m_lessonFilter->disconnect(this); } m_lessonFilter = lessonFilter; if (m_lessonFilter) { connect(m_lessonFilter, SIGNAL(idChanged()), SLOT(update())); } update(); emit lessonFilterChanged(); } } int LearningProgressModel::maxCharactersTypedPerMinute() const { int max = 0; for (int i = 0; i < rowCount(); i++) { max = qMax(max, charactersPerMinute(i)); } return max; } qreal LearningProgressModel::minAccuracy() const { qreal min = 1; for (int i = 0; i < rowCount(); i++) { min = qMin(min, accuracy(i)); } return min; } int LearningProgressModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent) const int originalColumnCount = QSqlQueryModel::columnCount(); return originalColumnCount > 0? originalColumnCount + 2: 0; } QVariant LearningProgressModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) return QVariant(); if (orientation == Qt::Vertical) return QSqlQueryModel::headerData(section, orientation, role); const int originalColumnCount = QSqlQueryModel::columnCount(); if (section < originalColumnCount) return QSqlQueryModel::headerData(section, orientation, role); switch (section - originalColumnCount) { case 0: return QVariant("accuracy"); case 1: return QVariant("characters_per_minute"); default: return QVariant(); } } QVariant LearningProgressModel::data(const QModelIndex &item, int role) const { const int originalColumnCount = QSqlQueryModel::columnCount(); if (item.column() < originalColumnCount) return QSqlQueryModel::data(item, role); switch (item.column() - originalColumnCount) { case 0: return accuracyData(item.row(), role); case 1: return charactersPerMinuteData(item.row(), role); default: return QVariant(); } } +QDateTime LearningProgressModel::date(int row) const +{ + LearningProgressModel* model = const_cast(this); + QSqlRecord record = model->record(row); + return QDateTime::fromMSecsSinceEpoch(record.value(0).value()); +} + int LearningProgressModel::charactersPerMinute(int row) const { const int charactersTyped = this->charactersTyped(row); const int elapsedTime = this->elapsedTime(row); return elapsedTime > 0? charactersTyped * 60000 / elapsedTime: 0; } int LearningProgressModel::charactersTyped(int row) const { LearningProgressModel* model = const_cast(this); QSqlRecord record = model->record(row); return record.value(1).toInt(); } int LearningProgressModel::errorCount(int row) const { LearningProgressModel* model = const_cast(this); QSqlRecord record = model->record(row); return record.value(2).toInt(); } int LearningProgressModel::elapsedTime(int row) const { LearningProgressModel* model = const_cast(this); QSqlRecord record = model->record(row); return record.value(3).toInt(); } qreal LearningProgressModel::accuracy(int row) const { const int charactersTyped = this->charactersTyped(row); const int errorCount = this->errorCount(row); const qreal accuracy = charactersTyped > 0? 1.0 - qreal(errorCount) / qreal(errorCount + charactersTyped): errorCount == 0? 1.0: 0.0; return accuracy; } QString LearningProgressModel::lessonId(int row) const { LearningProgressModel* model = const_cast(this); QSqlRecord record = model->record(row); return record.value(4).toString(); } void LearningProgressModel::update() { ProfileDataAccess access; clear(); if (m_profile) { const QSqlQuery query = access.learningProgressQuery(m_profile, m_courseFilter, m_lessonFilter); setQuery(query); } emit maxCharactersTypedPerMinuteChanged(); emit minAccuracyChanged(); } QVariant LearningProgressModel::accuracyData(int row, int role) const { const qreal accuracy = this->accuracy(row); switch(role) { case Qt::DisplayRole: return QVariant(accuracy); default: return QVariant(); } } QVariant LearningProgressModel::charactersPerMinuteData(int row, int role) const { int charactersPerMinute = this->charactersPerMinute(row); switch(role) { case Qt::DisplayRole: return QVariant(charactersPerMinute); default: return QVariant(); } } void LearningProgressModel::profileDestroyed() { setProfile(0); } diff --git a/src/models/learningprogressmodel.h b/src/models/learningprogressmodel.h index 09354ac..817c338 100644 --- a/src/models/learningprogressmodel.h +++ b/src/models/learningprogressmodel.h @@ -1,74 +1,75 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) 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 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 LEARNINGPROGRESSMODEL_H #define LEARNINGPROGRESSMODEL_H #include class Profile; class Course; class Lesson; class LearningProgressModel : public QSqlQueryModel { Q_OBJECT Q_PROPERTY(Profile* profile READ profile WRITE setProfile NOTIFY profileChanged) Q_PROPERTY(Course* courseFilter READ courseFilter WRITE setCourseFilter NOTIFY courseFilterChanged) Q_PROPERTY(Lesson* lessonFilter READ lessonFilter WRITE setLessonFilter NOTIFY lessonFilterChanged) Q_PROPERTY(int maxCharactersTypedPerMinute READ maxCharactersTypedPerMinute NOTIFY maxCharactersTypedPerMinuteChanged) Q_PROPERTY(qreal minAccuracy READ minAccuracy NOTIFY minAccuracyChanged) public: explicit LearningProgressModel(QObject* parent = 0); Profile* profile() const; void setProfile(Profile* profile); Course* courseFilter() const; void setCourseFilter(Course* courseFilter); Lesson* lessonFilter() const; void setLessonFilter(Lesson* lessonFilter); int maxCharactersTypedPerMinute() const; qreal minAccuracy() const; int columnCount(const QModelIndex& parent = QModelIndex()) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + Q_INVOKABLE QDateTime date(int row) const; Q_INVOKABLE int charactersPerMinute(int row) const; Q_INVOKABLE int charactersTyped(int row) const; Q_INVOKABLE int errorCount(int row) const; Q_INVOKABLE int elapsedTime(int row) const; Q_INVOKABLE qreal accuracy(int row) const; Q_INVOKABLE QString lessonId(int row) const; public slots: void update(); signals: void profileChanged(); void courseFilterChanged(); void lessonFilterChanged(); void maxCharactersTypedPerMinuteChanged(); void minAccuracyChanged(); private slots: void profileDestroyed(); private: QVariant data(const QModelIndex& item, int role = Qt::DisplayRole) const; QVariant accuracyData(int row, int role = Qt::DisplayRole) const; QVariant charactersPerMinuteData(int row, int role = Qt::DisplayRole) const; int m_charactersTypedFieldIndex; Profile* m_profile; Course* m_courseFilter; Lesson* m_lessonFilter; }; #endif // LEARNINGPROGRESSMODEL_H diff --git a/src/qml/common/LearningProgressChart.qml b/src/qml/common/LearningProgressChart.qml index 0dd4071..5921891 100644 --- a/src/qml/common/LearningProgressChart.qml +++ b/src/qml/common/LearningProgressChart.qml @@ -1,109 +1,115 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.9 import QtQuick.Layouts 1.3 import org.kde.charts 0.1 as Charts import ktouch 1.0 Charts.LineChart { id: root property Charts.Dimension accuracy: accuracyDimension property Charts.Dimension charactersPerMinute: charactersPerMinuteDimension pitch: 60 function minAccuracy(accuracy) { var canditades = [0.9, 0.8, 0.5] for (var i = 0; i < canditades.length; i++) { if (canditades[i] < accuracy) { return (canditades[i]) } } return 0; } dimensions: [ Charts.Dimension { id: accuracyDimension dataColumn: 5 color: "#ffb12d" minimumValue: root.minAccuracy(model.minAccuracy) maximumValue: 1.0 label: i18n("Accuracy") unit: "%" unitFactor: 100 }, Charts.Dimension { id: charactersPerMinuteDimension dataColumn: 6 color: "#38aef4" maximumValue: Math.max(Math.ceil(model.maxCharactersTypedPerMinute / 120) * 120, 120) label: i18n("Characters per Minute") } ] onElemEntered: { learningProgressPointTooltip.visualParent = elem learningProgressPointTooltip.row = row learningProgressPointTooltip.open() } onElemExited: { learningProgressPointTooltip.close() } Balloon { id: learningProgressPointTooltip property int row: -1 function findLessonTitle(id) { var course = model.courseFilter if (course) { for (var i = 0; i < course.lessonCount; i++) { if (course.lesson(i).id === id) { return course.lesson(i).title } } } return i18n("Unknown") } InformationTable { property list infoModel: [ InfoItem { - title: i18nc("Statistics on lesson:", "On:") + title: i18nc("Statistics on lesson:", "Lesson:") text: learningProgressPointTooltip.row !== -1? learningProgressPointTooltip.findLessonTitle(learningProgressModel.lessonId(learningProgressPointTooltip.row)): "" }, + InfoItem { + title: i18n("Training on:") + text: learningProgressPointTooltip.row !== -1? + learningProgressModel.date(learningProgressPointTooltip.row).toLocaleString(): + "" + }, InfoItem { title: i18n("Accuracy:") text: learningProgressPointTooltip.row !== -1? strFormatter.formatAccuracy(learningProgressModel.accuracy(learningProgressPointTooltip.row)): "" }, InfoItem { title: i18n("Characters per Minute:") text: learningProgressPointTooltip.row !== -1? learningProgressModel.charactersPerMinute(learningProgressPointTooltip.row): "" } ] width: 250 model: infoModel } } }