Members of the KDE Community are recommended to subscribe to the kde-community mailing list at https://mail.kde.org/mailman/listinfo/kde-community to allow them to participate in important discussions and receive other important announcements

Commit 37fd379b authored by Hartmut Riesenbeck's avatar Hartmut Riesenbeck

Bugfix: StatisticsView doesn't display correct values

Summary:
The color bars in LessonStatisticsView show only the correct values
when the widget was just created. Changing practice language, mode or
direction doesn't change the displayed values.

Added missing method calls in StatisticsMainWindow slot methods.
Implemented improved word counting for gender, conjugation and
comparison practice, to provide correct statistics values for this
practice modes.

Removed wrongly displayed horizontal scroll bar in LessonStatisticsView.
Modified column width calculation, not to use a hard coded magic number,
which does not fit to plasma anymore. Use instead the viewport width of
the LessonStatisticsView widget.

BUG: 387602

Reviewers: #kde_edu, apol

Reviewed By: apol

Subscribers: apol

Tags: #kde_edu

Differential Revision: https://phabricator.kde.org/D9187
parent c931d920
{
"phabricator.uri" : "https://phabricator.kde.org/"
}
......@@ -54,7 +54,7 @@ public:
public slots:
/** Set the new source kvtml file
* @param doc the new file */
void setDocument(KEduVocDocument *doc);
virtual void setDocument(KEduVocDocument *doc);
protected:
virtual KEduVocContainer *rootContainer() const = 0;
......
......@@ -25,7 +25,7 @@
#include <QtDBus>
#include <QTreeWidget>
ConjugationOptions::ConjugationOptions(KEduVocDocument* doc, QWidget * parent)
ConjugationOptions::ConjugationOptions(KEduVocDocument *doc, QWidget *parent)
: QWidget(parent)
, m_doc(doc)
, m_language(0)
......@@ -37,6 +37,8 @@ ConjugationOptions::ConjugationOptions(KEduVocDocument* doc, QWidget * parent)
layout->addWidget(m_treeWidget);
layout->setMargin(0);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
connect(m_treeWidget, &QTreeWidget::itemChanged,
this, &ConjugationOptions::processCheckBoxChanged);
}
void ConjugationOptions::setLanguages(int from, int to)
......@@ -51,7 +53,9 @@ void ConjugationOptions::setLanguages(int from, int to)
void ConjugationOptions::setupTenses()
{
m_treeWidget->blockSignals(true);
m_treeWidget->clear();
m_checkStates.clear();
DocumentSettings documentSettings(m_doc->url().url() + QString::number(m_language));
documentSettings.load();
......@@ -64,21 +68,27 @@ void ConjugationOptions::setupTenses()
tenseItem->setText(0, tenseName);
if (activeTenses.contains(tenseName)) {
tenseItem->setCheckState(0, Qt::Checked);
m_checkStates[tenseItem] = Qt::Checked;
} else {
tenseItem->setCheckState(0, Qt::Unchecked);
m_checkStates[tenseItem] = Qt::Unchecked;
}
tenseItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable);
m_treeWidget->addTopLevelItem(tenseItem);
}
m_treeWidget->blockSignals(false);
}
void ConjugationOptions::updateSettings()
{
qDebug() << "Save language selection";
QTreeWidgetItem* parentItem = m_treeWidget->invisibleRootItem();
QTreeWidgetItem *parentItem = m_treeWidget->invisibleRootItem();
if (parentItem == nullptr) {
return;
}
QStringList activeTenses;
for (int i = 0; i < parentItem->childCount(); i++) {
QTreeWidgetItem* tenseItem = parentItem->child(i);
QTreeWidgetItem *tenseItem = parentItem->child(i);
if (tenseItem->checkState(0) == Qt::Checked) {
activeTenses.append(tenseItem->text(0));
}
......@@ -87,3 +97,17 @@ void ConjugationOptions::updateSettings()
documentSettings.setConjugationTenses(activeTenses);
documentSettings.save();
}
void ConjugationOptions::processCheckBoxChanged(QTreeWidgetItem *item, int column)
{
if (column != 0) {
return;
}
Qt::CheckState newCheckState = item->checkState(column);
if (m_checkStates.contains(item) && (m_checkStates[item] != newCheckState)) {
m_checkStates[item] = newCheckState;
updateSettings();
emit checkBoxChanged();
}
}
......@@ -15,26 +15,36 @@
#define CONJUGATIONOPTIONS_H
#include <QtWidgets/QWidget>
#include <QMap>
class KEduVocDocument;
class QTreeWidget;
class QTreeWidgetItem;
class ConjugationOptions : public QWidget
{
Q_OBJECT
public:
ConjugationOptions(KEduVocDocument* doc, QWidget *parent);
ConjugationOptions(KEduVocDocument *doc, QWidget *parent);
public Q_SLOTS:
void setLanguages(int from, int to);
void updateSettings();
signals:
void checkBoxChanged();
private:
void setupTenses();
KEduVocDocument* m_doc;
private slots:
void processCheckBoxChanged(QTreeWidgetItem *item, int column);
private:
KEduVocDocument *m_doc;
int m_language;
QTreeWidget* m_treeWidget;
QTreeWidget *m_treeWidget;
QMap<QTreeWidgetItem*, Qt::CheckState> m_checkStates;
};
#endif
......@@ -47,7 +47,7 @@ public:
}
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const Q_DECL_OVERRIDE
const QModelIndex &index) const Q_DECL_OVERRIDE
{
QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, 0);
......@@ -57,16 +57,23 @@ public:
}
// Draw the colored bar.
KEduVocContainer *container = index.data(StatisticsModel::Container).value<KEduVocContainer*>();
KEduVocContainer *container = index.data(StatisticsModel::Container)
.value<KEduVocContainer*>();
QStringList activeConjugationTenses = index.data(StatisticsModel::ActiveConjugationTenses)
.toStringList();
WordCount wordCount;
wordCount.fillFromContainer(*container, index.column() - ContainerModel::FirstDataColumn);
wordCount.fillFromContainerForPracticeMode(
*container,
index.column() - ContainerModel::FirstDataColumn,
activeConjugationTenses
);
ConfidenceColors colors(ConfidenceColors::ProgressiveColorScheme);
paintColorBar(*painter, option.rect, wordCount, colors); // in utils
// Draw the text telling the percentage on top of the bar.
painter->drawText(option.rect, Qt::AlignCenter,
QStringLiteral("%1%").arg(index.data(StatisticsModel::TotalPercent).toInt()));
QStringLiteral("%1%").arg(index.data(StatisticsModel::TotalPercent).toInt()));
}
};
......@@ -137,8 +144,7 @@ void LessonStatisticsView::sectionResized(int index,
void LessonStatisticsView::adjustColumnWidths()
{
int firstWidth = columnWidth(0) + columnWidth(1);
// Subtract 5 here otherwise we get a horizontal scrollbar.
int totalWidth = width() - firstWidth - 5;
int totalWidth = viewport()->width() - firstWidth;
int columnCount = model()->columnCount(QModelIndex());
int visibleColumns = 0;
for (int i = ContainerModel::FirstDataColumn; i < columnCount; ++i) {
......
......@@ -30,16 +30,16 @@ public:
LessonStatisticsView(QWidget *parent);
void setModel(ContainerModel *model) Q_DECL_OVERRIDE;
private Q_SLOTS:
void removeGrades();
void removeGradesChildren();
public Q_SLOTS:
void adjustColumnWidths();
protected:
void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE;
private Q_SLOTS:
void removeGrades();
void removeGradesChildren();
void sectionResized(int index, int /*oldSize*/, int /*newSize*/);
void adjustColumnWidths();
private:
void setModel(QAbstractItemModel *model) Q_DECL_OVERRIDE {
......
......@@ -195,6 +195,7 @@ void StatisticsMainWindow::languagesChanged()
emit languagesChanged(knownLanguage, learningLanguage);
updateVisibleColumns();
updateModelSettings();
}
void StatisticsMainWindow::initPracticeMode()
......@@ -231,6 +232,8 @@ void StatisticsMainWindow::practiceModeSelected(int mode)
setPracticeDirectionForPracticeMode(Prefs::practiceDirection(), previousPracticeMode);
m_ui->practiceDirection->setCurrentIndex(practiceDirectionForPracticeMode(mode));
}
updateModelSettings();
}
void StatisticsMainWindow::practiceDirectionChanged(int mode)
......@@ -240,6 +243,8 @@ void StatisticsMainWindow::practiceDirectionChanged(int mode)
if (Prefs::rememberPracticeDirection()) {
setPracticeDirectionForPracticeMode(mode, Prefs::practiceMode());
}
updateVisibleColumns();
updateModelSettings();
}
void StatisticsMainWindow::rememberPracticeDirectionChanged(bool checked)
......@@ -273,6 +278,7 @@ void StatisticsMainWindow::updateVisibleColumns()
}
m_ui->lessonStatistics->setColumnHidden(i, isHidden);
m_ui->lessonStatistics->adjustColumnWidths();
}
}
......@@ -287,8 +293,11 @@ void StatisticsMainWindow::showConjugationOptions(bool visible)
QHBoxLayout* layout = new QHBoxLayout(m_ui->modeSpecificOptions);
layout->setMargin(0);
layout->addWidget(m_conjugationOptions);
connect(this, SIGNAL(languagesChanged(int, int)), m_conjugationOptions, SLOT(setLanguages(int, int)));
connect(this, QOverload<int, int>::of(&StatisticsMainWindow::languagesChanged),
m_conjugationOptions, &ConjugationOptions::setLanguages);
m_conjugationOptions->setLanguages(Prefs::knownLanguage(), Prefs::learningLanguage());
connect(m_conjugationOptions, &ConjugationOptions::checkBoxChanged,
this, &StatisticsMainWindow::updateModelSettings);
}
m_conjugationOptions->setVisible(visible);
}
......@@ -318,3 +327,10 @@ void StatisticsMainWindow::setPracticeDirectionForPracticeMode(int direction, in
directions[mode] = direction;
Prefs::setPracticeDirectionsByPracticeMode(directions);
}
void StatisticsMainWindow::updateModelSettings()
{
m_statisticsModel->updateDocumentSettings();
m_ui->lessonStatistics->expandAll();
}
......@@ -50,6 +50,7 @@ private slots:
void practiceDirectionChanged(int mode);
void rememberPracticeDirectionChanged(bool checked);
void updateVisibleColumns();
void updateModelSettings();
private:
void initActions();
......
......@@ -15,12 +15,16 @@
#include "statisticsmodel.h"
#include "statisticslegendwidget.h"
#include "utils.h"
#include <KEduVocExpression>
#include <KEduVocTranslation>
#include <KEduVocWordtype>
#include <KLocalizedString>
#include <QGradient>
#include <QDebug>
StatisticsModel::StatisticsModel(QObject * parent)
StatisticsModel::StatisticsModel(QObject *parent)
: ContainerModel(KEduVocContainer::Lesson, parent)
{
}
......@@ -37,31 +41,59 @@ QVariant StatisticsModel::headerData(int section, Qt::Orientation orientation, i
return ContainerModel::headerData(section, orientation, role);
}
QVariant StatisticsModel::data(const QModelIndex & index, int role) const
QVariant StatisticsModel::data(const QModelIndex &index, int role) const
{
Q_ASSERT(!m_documentSettings.isEmpty());
KEduVocContainer *container = static_cast<KEduVocContainer*>(index.internalPointer());
// Entrie count
if (index.column() == TotalCountColumn) {
if (role == Qt::DisplayRole) {
switch (Prefs::practiceDirection()) {
case Prefs::EnumPracticeDirection::KnownToLearning:
return entryCountForPracticeMode(container, Prefs::learningLanguage());
case Prefs::EnumPracticeDirection::LearningToKnown:
return entryCountForPracticeMode(container, Prefs::knownLanguage());
case Prefs::EnumPracticeDirection::MixedDirectionsWordsOnly:
case Prefs::EnumPracticeDirection::MixedDirectionsWithSound:
return entryCountForPracticeMode(container, Prefs::knownLanguage())
+ entryCountForPracticeMode(container, Prefs::learningLanguage());
default:
return entryCountForPracticeMode(container, Prefs::learningLanguage());
}
}
if (role == Qt::TextAlignmentRole) {
return Qt::AlignRight;
}
}
// Colorbars
if (index.column() >= FirstDataColumn) {
KEduVocContainer *container = static_cast<KEduVocContainer*>(index.internalPointer());
QVariant var;
int translation = index.column() - FirstDataColumn;
switch (role) {
case Container:
// Return a pointer to the container we are working on.
var.setValue(container);
return var;
{
// Return a pointer to the container we are working on.
QVariant var;
var.setValue(container);
return var;
}
case TotalPercent: // Average grade
return container->averageGrade(index.column() - FirstDataColumn, KEduVocContainer::Recursive);
return averageGradeForPracticeMode(container, translation);
case TotalCount:
return container->entryCount(KEduVocContainer::Recursive);
return entryCountForPracticeMode(container, translation);
case ActiveConjugationTenses:
return m_documentSettings.at(translation)->conjugationTenses();
default:
if (role >= Qt::UserRole) {
return container->expressionsOfGrade(
index.column() - FirstDataColumn, role - Grade0, KEduVocContainer::Recursive);
if ((role >= Grade0) && (role <= Grade7)) {
return expressionsOfGradeForPracticeMode(container, translation, role - Grade0);
}
}
}
// checkboxes
// Checkboxes
if (index.column() == 0 && role == Qt::CheckStateRole) {
KEduVocContainer *container = static_cast<KEduVocContainer*>(index.internalPointer());
if (container->inPractice()) {
return Qt::Checked;
} else {
......@@ -72,9 +104,41 @@ QVariant StatisticsModel::data(const QModelIndex & index, int role) const
return ContainerModel::data(index, role);
}
int StatisticsModel::averageGradeForPracticeMode(KEduVocContainer *container, int translation) const
{
WordCount wordCount;
wordCount.fillFromContainerForPracticeMode(
*container,
translation,
m_documentSettings.at(translation)->conjugationTenses()
);
return wordCount.percentageCompleted();
}
int StatisticsModel::entryCountForPracticeMode(KEduVocContainer *container, int translation) const
{
WordCount wordCount;
wordCount.fillFromContainerForPracticeMode(
*container,
translation,
m_documentSettings.at(translation)->conjugationTenses()
);
return wordCount.totalWords - wordCount.invalid;
}
int StatisticsModel::expressionsOfGradeForPracticeMode(KEduVocContainer *container,
int translation, grade_t grade) const
{
WordCount wordCount;
wordCount.fillFromContainerForPracticeMode(
*container,
translation,
m_documentSettings.at(translation)->conjugationTenses()
);
return wordCount.grades[grade];
}
Qt::ItemFlags StatisticsModel::flags(const QModelIndex & index) const
Qt::ItemFlags StatisticsModel::flags(const QModelIndex &index) const
{
if (index.isValid()) {
if (index.column() == 0) {
......@@ -85,7 +149,7 @@ Qt::ItemFlags StatisticsModel::flags(const QModelIndex & index) const
return 0;
}
int StatisticsModel::columnCount(const QModelIndex & parent) const
int StatisticsModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_doc->identifierCount() + FirstDataColumn;
......@@ -96,10 +160,42 @@ Qt::DropActions StatisticsModel::supportedDragActions() const
return 0;
}
KEduVocContainer * StatisticsModel::rootContainer() const
KEduVocContainer *StatisticsModel::rootContainer() const
{
if (!m_doc) {
return 0;
}
return m_doc->lesson();
}
void StatisticsModel::loadDocumentsSettings()
{
m_documentSettings.clear();
if (m_doc == nullptr) {
return;
}
for (int i = 0 ; i < m_doc->identifierCount(); ++i) {
m_documentSettings << QSharedPointer<DocumentSettings>(
new DocumentSettings(m_doc->url().url() + QString::number(i))
);
m_documentSettings.last()->load();
}
}
void StatisticsModel::setDocument(KEduVocDocument *doc)
{
beginResetModel();
m_doc = doc;
loadDocumentsSettings();
endResetModel();
}
void StatisticsModel::updateDocumentSettings()
{
beginResetModel();
loadDocumentsSettings();
endResetModel();
}
......@@ -18,6 +18,9 @@
#include "containermodel.h"
#include "prefs.h"
#include "documentsettings.h"
class StatisticsModel : public ContainerModel
{
......@@ -35,7 +38,8 @@ public:
Grade5,
Grade6,
Grade7,
Container
Container,
ActiveConjugationTenses
};
explicit StatisticsModel(QObject *parent = 0);
......@@ -49,8 +53,24 @@ public:
/** Indicate supported drag actions
@return enum of actions supported **/
Qt::DropActions supportedDragActions() const Q_DECL_OVERRIDE;
void updateDocumentSettings();
public slots:
virtual void setDocument(KEduVocDocument *doc) Q_DECL_OVERRIDE;
protected:
KEduVocContainer * rootContainer() const Q_DECL_OVERRIDE;
KEduVocContainer *rootContainer() const Q_DECL_OVERRIDE;
private:
int averageGradeForPracticeMode(KEduVocContainer *container, int translation) const;
int entryCountForPracticeMode(KEduVocContainer *container, int translation) const;
int expressionsOfGradeForPracticeMode(KEduVocContainer *container, int translation,
grade_t grade) const;
void loadDocumentsSettings();
private:
QList<QSharedPointer<DocumentSettings>> m_documentSettings;
};
// For index.data()
......
......@@ -79,21 +79,93 @@ void WordCount::fillFromContainer(KEduVocContainer &container, int translationIn
foreach (KEduVocExpression *entry, container.entries(recursive)) {
KEduVocTranslation &translation(*entry->translation(translationIndex));
evaluateWord(translation, translation.text());
}
}
++totalWords;
if (translation.isEmpty()) {
++invalid;
} else if (translation.preGrade() > 0) {
// Initial phase (we assume correctness, i.e. if pregrade>0 then grade = 0)
++initialWords;
++pregrades[translation.preGrade()];
} else {
// Long term or unpracticed
++grades[translation.grade()];
void WordCount::fillFromContainerForPracticeMode(KEduVocContainer &container, int translationIndex,
const QStringList &activeConjugationTenses,
KEduVocContainer::EnumEntriesRecursive recursive)
{
KEduVocWordFlags wordTypeToProcess(KEduVocWordFlag::NoInformation);
switch (Prefs::practiceMode()) {
case Prefs::EnumPracticeMode::GenderPractice:
wordTypeToProcess = KEduVocWordFlag::Noun;
break;
case Prefs::EnumPracticeMode::ConjugationPractice:
wordTypeToProcess = KEduVocWordFlag::Verb;
break;
case Prefs::EnumPracticeMode::ComparisonPractice:
wordTypeToProcess = KEduVocWordFlag::Adjective | KEduVocWordFlag::Adverb;
break;
default:
fillFromContainer(container, translationIndex, recursive);
return;
}
clear();
foreach (KEduVocExpression *entry, container.entries(recursive)) {
KEduVocTranslation &translation(*entry->translation(translationIndex));
if (isValidForProcessing(translation, wordTypeToProcess)) {
switch (wordTypeToProcess) {
case KEduVocWordFlag::Noun:
{
KEduVocText article = translation.article();
evaluateWord(article, translation.text());
}
break;
case KEduVocWordFlag::Verb:
{
QStringList conjugationTenses = translation.conjugationTenses();
foreach(const QString &activeTense, activeConjugationTenses)
{
if (conjugationTenses.contains(activeTense)) {
KEduVocConjugation conj = translation.getConjugation(activeTense);
foreach (KEduVocWordFlags key, conj.keys()) {
KEduVocText person = conj.conjugation(key);
evaluateWord(person, person.text());
}
}
}
}
break;
case KEduVocWordFlag::Adjective | KEduVocWordFlag::Adverb:
{
KEduVocText comparative = translation.comparativeForm();
evaluateWord(comparative, comparative.text());
KEduVocText superlative = translation.superlativeForm();
evaluateWord(superlative, superlative.text());
}
break;
}
}
}
}
bool WordCount::isValidForProcessing(KEduVocTranslation &trans, KEduVocWordFlags wordType) const
{
return !trans.isEmpty()
&& (trans.wordType() != nullptr)
&& ((trans.wordType()->wordType() & wordType) != 0);
}
void WordCount::evaluateWord(const KEduVocText &item, const QString &text)
{
++totalWords;
if (text.isEmpty()) {
++invalid;
} else if (item.preGrade() > 0) {
// Initial phase (we assume correctness, i.e. if pregrade>0 then grade = 0)
++initialWords;
++pregrades[item.preGrade()];
} else {
// Long term or unpracticed
++grades[item.grade()];
}
}
// ----------------------------------------------------------------
// class confidenceColors
......
......@@ -20,10 +20,12 @@
// KEduVocDocument library
#include <keduvoccontainer.h>
#include <KEduVocWordtype>
class QPainter;
class QRect;
class KEduVocTranslation;
// The WordCount struct contains the number of words in each category.
// This could be used for number of words due, total number of words, etc.
......@@ -36,6 +38,11 @@ struct WordCount {
void fillFromContainer(KEduVocContainer &container, int translationIndex,
KEduVocContainer::EnumEntriesRecursive recursive = KEduVocContainer::Recursive);
// Fill the WordCount data from the container for the selected practice mode.
void fillFromContainerForPracticeMode(KEduVocContainer &container, int translationIndex,
const QStringList &activeConjugationTenses,
KEduVocContainer::EnumEntriesRecursive recursive = KEduVocContainer::Recursive);
int grades[KV_MAX_GRADE + 1]; // Number of entries in each grade including grade=0, pregrade=0
int pregrades[KV_MAX_GRADE + 1]; // Number of entries in each grade including grade=0, pregrade=0
int invalid; // Number of invalid entries (not always applicable);
......@@ -44,6 +51,10 @@ struct WordCount {
// This is the sum of the numbers in pregrades[].
int totalWords; // Total number of words
// This is the sum of grades[], pregrades[] and invalid
private:
bool isValidForProcessing(KEduVocTranslation &trans, KEduVocWordFlags wordType) const;
void evaluateWord(const KEduVocText &item, const QString &text);
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment