diff --git a/.arcconfig b/.arcconfig new file mode 100644 --- /dev/null +++ b/.arcconfig @@ -0,0 +1,3 @@ +{ + "phabricator.uri" : "https://phabricator.kde.org/" +} diff --git a/src/collection/readonlycontainermodel.h b/src/collection/readonlycontainermodel.h --- a/src/collection/readonlycontainermodel.h +++ b/src/collection/readonlycontainermodel.h @@ -54,7 +54,7 @@ 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; diff --git a/src/statistics/conjugationoptions.h b/src/statistics/conjugationoptions.h --- a/src/statistics/conjugationoptions.h +++ b/src/statistics/conjugationoptions.h @@ -15,26 +15,36 @@ #define CONJUGATIONOPTIONS_H #include +#include 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 m_checkStates; }; #endif diff --git a/src/statistics/conjugationoptions.cpp b/src/statistics/conjugationoptions.cpp --- a/src/statistics/conjugationoptions.cpp +++ b/src/statistics/conjugationoptions.cpp @@ -25,7 +25,7 @@ #include #include -ConjugationOptions::ConjugationOptions(KEduVocDocument* doc, QWidget * parent) +ConjugationOptions::ConjugationOptions(KEduVocDocument *doc, QWidget *parent) : QWidget(parent) , m_doc(doc) , m_language(0) @@ -37,6 +37,8 @@ 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::setupTenses() { + m_treeWidget->blockSignals(true); m_treeWidget->clear(); + m_checkStates.clear(); DocumentSettings documentSettings(m_doc->url().url() + QString::number(m_language)); documentSettings.load(); @@ -64,26 +68,46 @@ 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)); } } DocumentSettings documentSettings(m_doc->url().url() + QString::number(m_language)); 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(); + } +} + diff --git a/src/statistics/lessonstatisticsview.h b/src/statistics/lessonstatisticsview.h --- a/src/statistics/lessonstatisticsview.h +++ b/src/statistics/lessonstatisticsview.h @@ -30,16 +30,16 @@ 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 { diff --git a/src/statistics/lessonstatisticsview.cpp b/src/statistics/lessonstatisticsview.cpp --- a/src/statistics/lessonstatisticsview.cpp +++ b/src/statistics/lessonstatisticsview.cpp @@ -47,7 +47,7 @@ } 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 @@ } // Draw the colored bar. - KEduVocContainer *container = index.data(StatisticsModel::Container).value(); + KEduVocContainer *container = index.data(StatisticsModel::Container) + .value(); + 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::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) { diff --git a/src/statistics/statisticsmainwindow.h b/src/statistics/statisticsmainwindow.h --- a/src/statistics/statisticsmainwindow.h +++ b/src/statistics/statisticsmainwindow.h @@ -50,6 +50,7 @@ void practiceDirectionChanged(int mode); void rememberPracticeDirectionChanged(bool checked); void updateVisibleColumns(); + void updateModelSettings(); private: void initActions(); diff --git a/src/statistics/statisticsmainwindow.cpp b/src/statistics/statisticsmainwindow.cpp --- a/src/statistics/statisticsmainwindow.cpp +++ b/src/statistics/statisticsmainwindow.cpp @@ -195,6 +195,7 @@ emit languagesChanged(knownLanguage, learningLanguage); updateVisibleColumns(); + updateModelSettings(); } void StatisticsMainWindow::initPracticeMode() @@ -231,6 +232,8 @@ setPracticeDirectionForPracticeMode(Prefs::practiceDirection(), previousPracticeMode); m_ui->practiceDirection->setCurrentIndex(practiceDirectionForPracticeMode(mode)); } + + updateModelSettings(); } void StatisticsMainWindow::practiceDirectionChanged(int mode) @@ -240,6 +243,8 @@ if (Prefs::rememberPracticeDirection()) { setPracticeDirectionForPracticeMode(mode, Prefs::practiceMode()); } + updateVisibleColumns(); + updateModelSettings(); } void StatisticsMainWindow::rememberPracticeDirectionChanged(bool checked) @@ -273,6 +278,7 @@ } m_ui->lessonStatistics->setColumnHidden(i, isHidden); + m_ui->lessonStatistics->adjustColumnWidths(); } } @@ -287,8 +293,11 @@ 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::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 @@ directions[mode] = direction; Prefs::setPracticeDirectionsByPracticeMode(directions); } + +void StatisticsMainWindow::updateModelSettings() +{ + m_statisticsModel->updateDocumentSettings(); + m_ui->lessonStatistics->expandAll(); +} + diff --git a/src/statistics/statisticsmodel.h b/src/statistics/statisticsmodel.h --- a/src/statistics/statisticsmodel.h +++ b/src/statistics/statisticsmodel.h @@ -18,6 +18,9 @@ #include "containermodel.h" +#include "prefs.h" +#include "documentsettings.h" + class StatisticsModel : public ContainerModel { @@ -35,7 +38,8 @@ Grade5, Grade6, Grade7, - Container + Container, + ActiveConjugationTenses }; explicit StatisticsModel(QObject *parent = 0); @@ -49,8 +53,24 @@ /** 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> m_documentSettings; }; // For index.data() diff --git a/src/statistics/statisticsmodel.cpp b/src/statistics/statisticsmodel.cpp --- a/src/statistics/statisticsmodel.cpp +++ b/src/statistics/statisticsmodel.cpp @@ -15,12 +15,16 @@ #include "statisticsmodel.h" #include "statisticslegendwidget.h" +#include "utils.h" +#include +#include +#include #include #include #include -StatisticsModel::StatisticsModel(QObject * parent) +StatisticsModel::StatisticsModel(QObject *parent) : ContainerModel(KEduVocContainer::Lesson, parent) { } @@ -37,31 +41,59 @@ 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(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(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(index.internalPointer()); if (container->inPractice()) { return Qt::Checked; } else { @@ -72,9 +104,41 @@ 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 @@ 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 @@ 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( + 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(); +} + diff --git a/src/utils.h b/src/utils.h --- a/src/utils.h +++ b/src/utils.h @@ -20,10 +20,12 @@ // KEduVocDocument library #include +#include 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,14 +38,23 @@ 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); int initialWords; // Number of entries in initial phase (grade=0, pregrade>0) // 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); }; diff --git a/src/utils.cpp b/src/utils.cpp --- a/src/utils.cpp +++ b/src/utils.cpp @@ -79,21 +79,93 @@ 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