summaryrefslogtreecommitdiffstats
path: root/ktouch/src/ktouchstatistics.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ktouch/src/ktouchstatistics.cpp')
-rw-r--r--ktouch/src/ktouchstatistics.cpp475
1 files changed, 475 insertions, 0 deletions
diff --git a/ktouch/src/ktouchstatistics.cpp b/ktouch/src/ktouchstatistics.cpp
new file mode 100644
index 00000000..97b1d45c
--- /dev/null
+++ b/ktouch/src/ktouchstatistics.cpp
@@ -0,0 +1,475 @@
+/***************************************************************************
+ * ktouchstatistics.cpp *
+ * -------------------- *
+ * Copyright (C) 2000 by Håvard Frøiland, 2004 by Andreas Nicolai *
+ * ghorwin@users.sourceforge.net *
+ * *
+ * 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. *
+ ***************************************************************************/
+
+#include "ktouchstatistics.h"
+#include "ktouchstatistics.moc"
+
+#include <list>
+#include <vector>
+#include <utility>
+
+#include <qprogressbar.h>
+#include <qlcdnumber.h>
+#include <qlabel.h>
+#include <qradiobutton.h>
+#include <qbuttongroup.h>
+
+#include <kpushbutton.h>
+#include <kcombobox.h>
+#include <kurl.h>
+#include <kmessagebox.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <ktabwidget.h>
+
+#include "ktouch.h"
+#include "ktouchchartwidget.h"
+
+KTouchStatistics::KTouchStatistics(QWidget* parent)
+ : KTouchStatisticsDlg(parent)
+{
+ sessionsRadio->setChecked(true);
+ WPMRadio->setChecked(true);
+ eventRadio->setChecked(true);
+ connect(closeButton, SIGNAL(clicked()), this, SLOT(accept()) );
+ connect(lectureCombo, SIGNAL(activated(int)), this, SLOT(lectureActivated(int)) );
+ connect(clearButton, SIGNAL(clicked()), this, SLOT(clearHistory()) );
+ // connect the radio buttons with the chart update function
+ connect(buttonGroup1, SIGNAL(clicked(int)), this, SLOT(updateChartTab()) );
+ connect(buttonGroup2, SIGNAL(clicked(int)), this, SLOT(updateChartTab()) );
+ connect(buttonGroup3, SIGNAL(clicked(int)), this, SLOT(updateChartTab()) );
+
+ // TODO : temporarily remove detailed stats page and deactivate options
+ levelsRadio->setEnabled(false);
+}
+// ----------------------------------------------------------------------------
+
+void KTouchStatistics::run(const KURL& currentLecture, const KTouchStatisticsData& stats,
+ const KTouchLevelStats& currLevelStats,
+ const KTouchSessionStats& currSessionStats)
+{
+// kdDebug() << "[KTouchStatistics::run]" << endl;
+// kdDebug() << " currentLecture = '" << currentLecture << "'" << endl;
+
+ // fill lecture combo with data
+ // loop over all lecturestatistics
+ lectureCombo->clear();
+ QMapConstIterator<KURL, KTouchLectureStats> it = stats.m_lectureStats.begin();
+ m_currentIndex = 0;
+ while (it != stats.m_lectureStats.end()) {
+ QString t = it.data().m_lectureTitle;
+ // if current lecture, remember index and adjust text
+ if (it.key() == currentLecture ||
+ currentLecture.isEmpty() && it.key().url()=="default")
+ {
+ m_currentIndex = lectureCombo->count();
+ if (t == "default") t = i18n("Default level...");
+ lectureLabel1->setText(t);
+ lectureLabel2->setText(t);
+ t = i18n("***current*** ") + t;
+ }
+ lectureCombo->insertItem(t);
+ ++it;
+ }
+ if (lectureCombo->count()==0) {
+ // this shouldn't happen if the dialog is run with proper data
+ KMessageBox::information(this, i18n("No statistics data available yet!"));
+ return;
+ }
+
+ // remember stats
+ m_allStats = stats;
+ m_currLevelStats = currLevelStats;
+ m_currSessionStats = currSessionStats;
+ // modify current lecture entry
+ lectureCombo->setCurrentItem(m_currentIndex);
+ lectureActivated(m_currentIndex);
+ m_lectureIndex = m_currentIndex;
+
+ // update the current tabs with current session/level data
+ updateCurrentSessionTab();
+ updateCurrentLevelTab();
+ // set current session as current tab
+ tabWidget->showPage(currentTab);
+ exec();
+}
+// ----------------------------------------------------------------------------
+
+void KTouchStatistics::lectureActivated(int index) {
+ if (m_allStats.m_lectureStats.count()==0) {
+ // TODO : Reset all tabs to "empty" look
+ m_lectureIndex = 0;
+ return;
+ }
+ if (index >= static_cast<int>(m_allStats.m_lectureStats.count())) {
+ kdDebug() << "Lecture index out of range: " << index << " >= " << m_allStats.m_lectureStats.count() << endl;
+ return;
+ }
+ m_lectureIndex = index;
+ //kdDebug() << "Lecture stats changed: " << it.data().m_lectureTitle << endl;
+ // update the tabs
+ updateChartTab();
+}
+// ----------------------------------------------------------------------------
+
+void KTouchStatistics::clearHistory() {
+ if (KMessageBox::warningContinueCancel(this, i18n("Erase all statistics data for the current user?"),QString::null,KStdGuiItem::del())
+ == KMessageBox::Continue)
+ {
+ KTouchPtr->clearStatistics(); // clear statistics data in KTouch
+ // clear and reset local copy
+ m_allStats.clear();
+ QString s = lectureCombo->text(m_currentIndex);
+ lectureCombo->clear();
+ lectureCombo->insertItem(s);
+ m_currentIndex = 0;
+ lectureCombo->setCurrentItem(m_currentIndex);
+ lectureActivated(m_currentIndex);
+ updateChartTab();
+ }
+}
+// ----------------------------------------------------------------------------
+
+
+void KTouchStatistics::updateCurrentSessionTab() {
+ // session/level/info
+ QString levelnums;
+ int last_level = -2;
+ int levels_count = 0;
+ std::set<unsigned int>::iterator last_it = m_currSessionStats.m_levelNums.end();
+ --last_it;
+ for (std::set<unsigned int>::iterator it = m_currSessionStats.m_levelNums.begin();
+ it != m_currSessionStats.m_levelNums.end(); ++it)
+ {
+ // do we have a level number that is not a subsequent level of the
+ // previous?
+
+ if ((static_cast<unsigned int>(last_level + 1) != *it) ||
+ (it == last_it))
+ {
+ if (it != m_currSessionStats.m_levelNums.begin()) {
+ if (levels_count > 1) levelnums += "...";
+ else levelnums += ",";
+ }
+ levels_count = 0;
+ levelnums += QString("%1").arg(*it+1);
+
+ }
+ else {
+ ++levels_count; // increase level count
+ }
+ last_level = *it;
+ }
+ levelLabel1->setText(levelnums);
+ // general stats group
+ elapsedTimeLCD->display(static_cast<int>(m_currSessionStats.m_elapsedTime));
+ totalCharsLCD->display(static_cast<int>(m_currSessionStats.m_totalChars) );
+ wrongCharsLCD->display(static_cast<int>(m_currSessionStats.m_totalChars-m_currSessionStats.m_correctChars) );
+ wordsLCD->display(static_cast<int>(m_currSessionStats.m_words) );
+ // typing rate group
+ if (m_currSessionStats.m_elapsedTime == 0) {
+ charSpeedLCD->display(0);
+ wordSpeedLCD->display(0);
+ }
+ else {
+ charSpeedLCD->display(static_cast<int>(m_currSessionStats.m_correctChars/m_currSessionStats.m_elapsedTime*60.0) );
+ wordSpeedLCD->display(static_cast<int>(m_currSessionStats.m_words/m_currSessionStats.m_elapsedTime*60.0) );
+ }
+ // accuracy
+ correctnessBar->setProgress(static_cast<int>(
+ (100.0*m_currSessionStats.m_correctChars)/m_currSessionStats.m_totalChars) );
+ // create sorted list of missed characters
+ std::list<KTouchCharStats> charList( m_currSessionStats.m_charStats.begin(), m_currSessionStats.m_charStats.end());
+ charList.sort(higher_miss_hit_ratio);
+
+ std::list<KTouchCharStats>::const_iterator it=charList.begin();
+ unsigned int i=0;
+ for (; i<8 && it!=charList.end(); ++i, ++it) {
+ if (it->missHitRatio()==0)
+ break; // stop listing keys when their hit-miss-ration is zero
+ switch (i) {
+ case 0 : charLabel1->setText( it->m_char ); charProgress1->setEnabled(true);
+ charProgress1->setProgress( it->missHitRatio() ); break;
+ case 1 : charLabel2->setText( it->m_char ); charProgress2->setEnabled(true);
+ charProgress2->setProgress( it->missHitRatio() ); break;
+ case 2 : charLabel3->setText( it->m_char ); charProgress3->setEnabled(true);
+ charProgress3->setProgress( it->missHitRatio() ); break;
+ case 3 : charLabel4->setText( it->m_char ); charProgress4->setEnabled(true);
+ charProgress4->setProgress( it->missHitRatio() ); break;
+ case 4 : charLabel5->setText( it->m_char ); charProgress5->setEnabled(true);
+ charProgress5->setProgress( it->missHitRatio() ); break;
+ case 5 : charLabel6->setText( it->m_char ); charProgress6->setEnabled(true);
+ charProgress6->setProgress( it->missHitRatio() ); break;
+ case 6 : charLabel7->setText( it->m_char ); charProgress7->setEnabled(true);
+ charProgress7->setProgress( it->missHitRatio() ); break;
+ case 7 : charLabel8->setText( it->m_char ); charProgress8->setEnabled(true);
+ charProgress8->setProgress( it->missHitRatio() ); break;
+ }
+ }
+ // set remaining char labels and progress bars to zero
+ for(; i<8; ++i) {
+ switch (i) {
+ case 0 : charLabel1->setText(" "); charProgress1->setProgress(0); charProgress1->setEnabled(false); break;
+ case 1 : charLabel2->setText(" "); charProgress2->setProgress(0); charProgress2->setEnabled(false); break;
+ case 2 : charLabel3->setText(" "); charProgress3->setProgress(0); charProgress3->setEnabled(false); break;
+ case 3 : charLabel4->setText(" "); charProgress4->setProgress(0); charProgress4->setEnabled(false); break;
+ case 4 : charLabel5->setText(" "); charProgress5->setProgress(0); charProgress5->setEnabled(false); break;
+ case 5 : charLabel6->setText(" "); charProgress6->setProgress(0); charProgress6->setEnabled(false); break;
+ case 6 : charLabel7->setText(" "); charProgress7->setProgress(0); charProgress7->setEnabled(false); break;
+ case 7 : charLabel8->setText(" "); charProgress8->setProgress(0); charProgress8->setEnabled(false); break;
+ }
+ }
+}
+// ----------------------------------------------------------------------------
+
+void KTouchStatistics::updateCurrentLevelTab() {
+ // level info
+ levelLabel2->setText( QString("%1").arg(m_currLevelStats.m_levelNum+1) );
+ // general stats group
+ elapsedTimeLCDLevel->display(static_cast<int>(m_currLevelStats.m_elapsedTime));
+ totalCharsLCDLevel->display(static_cast<int>(m_currLevelStats.m_totalChars) );
+ wrongCharsLCDLevel->display(static_cast<int>(m_currLevelStats.m_totalChars-m_currLevelStats.m_correctChars) );
+ wordsLCDLevel->display(static_cast<int>(m_currLevelStats.m_words) );
+ // typing rate group
+ if (m_currLevelStats.m_elapsedTime == 0) {
+ charSpeedLCDLevel->display(0);
+ wordSpeedLCDLevel->display(0);
+ }
+ else {
+ charSpeedLCDLevel->display(static_cast<int>(m_currLevelStats.m_correctChars/m_currLevelStats.m_elapsedTime*60.0) );
+ wordSpeedLCDLevel->display(static_cast<int>(m_currLevelStats.m_words/m_currLevelStats.m_elapsedTime*60.0) );
+ }
+ // accuracy
+ correctnessBarLevel->setProgress(static_cast<int>(
+ (100.0*m_currLevelStats.m_correctChars)/m_currLevelStats.m_totalChars) );
+ // create sorted list of missed characters
+ std::list<KTouchCharStats> charList( m_currLevelStats.m_charStats.begin(), m_currLevelStats.m_charStats.end());
+ charList.sort(higher_miss_hit_ratio);
+
+ std::list<KTouchCharStats>::const_iterator it=charList.begin();
+ unsigned int i=0;
+ for (; i<8 && it!=charList.end(); ++i, ++it) {
+ if (it->missHitRatio()==0)
+ break; // stop listing keys when their hit-miss-ration is zero
+ switch (i) {
+ case 0 : charLabel1Level->setText( it->m_char ); charProgress1->setEnabled(true);
+ charProgress1Level->setProgress( it->missHitRatio() ); break;
+ case 1 : charLabel2Level->setText( it->m_char ); charProgress2->setEnabled(true);
+ charProgress2Level->setProgress( it->missHitRatio() ); break;
+ case 2 : charLabel3Level->setText( it->m_char ); charProgress3->setEnabled(true);
+ charProgress3Level->setProgress( it->missHitRatio() ); break;
+ case 3 : charLabel4Level->setText( it->m_char ); charProgress4->setEnabled(true);
+ charProgress4Level->setProgress( it->missHitRatio() ); break;
+ case 4 : charLabel5Level->setText( it->m_char ); charProgress5->setEnabled(true);
+ charProgress5Level->setProgress( it->missHitRatio() ); break;
+ case 5 : charLabel6Level->setText( it->m_char ); charProgress6->setEnabled(true);
+ charProgress6Level->setProgress( it->missHitRatio() ); break;
+ case 6 : charLabel7Level->setText( it->m_char ); charProgress7->setEnabled(true);
+ charProgress7Level->setProgress( it->missHitRatio() ); break;
+ case 7 : charLabel8Level->setText( it->m_char ); charProgress8->setEnabled(true);
+ charProgress8Level->setProgress( it->missHitRatio() ); break;
+ }
+ }
+ // set remaining char labels and progress bars to zero
+ for(; i<8; ++i) {
+ switch (i) {
+ case 0 : charLabel1Level->setText(" "); charProgress1Level->setProgress(0); charProgress1Level->setEnabled(false); break;
+ case 1 : charLabel2Level->setText(" "); charProgress2Level->setProgress(0); charProgress2Level->setEnabled(false); break;
+ case 2 : charLabel3Level->setText(" "); charProgress3Level->setProgress(0); charProgress3Level->setEnabled(false); break;
+ case 3 : charLabel4Level->setText(" "); charProgress4Level->setProgress(0); charProgress4Level->setEnabled(false); break;
+ case 4 : charLabel5Level->setText(" "); charProgress5Level->setProgress(0); charProgress5Level->setEnabled(false); break;
+ case 5 : charLabel6Level->setText(" "); charProgress6Level->setProgress(0); charProgress6Level->setEnabled(false); break;
+ case 6 : charLabel7Level->setText(" "); charProgress7Level->setProgress(0); charProgress7Level->setEnabled(false); break;
+ case 7 : charLabel8Level->setText(" "); charProgress8Level->setProgress(0); charProgress8Level->setEnabled(false); break;
+ }
+ }
+}
+// ----------------------------------------------------------------------------
+
+void KTouchStatistics::updateChartTab() {
+ // remove all current chart objects
+ chartWidget->clearObjectList();
+ // if no lecture data is available, return
+ if (m_allStats.m_lectureStats.count()==0 || m_lectureIndex >= m_allStats.m_lectureStats.count()) return;
+ // what kind of chart is required?
+ if (levelsRadio->isChecked()) {
+ // TODO : nothing yet
+ }
+ else {
+ // find correct lecture index
+ QMapConstIterator<KURL, KTouchLectureStats> it = m_allStats.m_lectureStats.begin();
+ int index = m_lectureIndex;
+ while (index-- > 0) ++it;
+ std::vector< std::pair<double, double> > data;
+ QString caption = "Session data";
+ switch (buttonGroup2->selectedId()) {
+ case 0 : // words per minute
+ // loop over all session data entries in *it and store words per minute data
+ for (QValueVector<KTouchSessionStats>::const_iterator session_it = (*it).m_sessionStats.begin();
+ session_it != (*it).m_sessionStats.end(); ++session_it)
+ {
+ double t = (*session_it).m_elapsedTime;
+ double wpm = (t == 0) ? 0 : (*session_it).m_words/t*60.0;
+ double tp = (*session_it).m_timeRecorded.toTime_t()/(3600.0*24);
+ data.push_back(std::make_pair(tp, wpm) );
+ }
+ // add current session if selected lecture matches
+ if (m_currentIndex == m_lectureIndex &&
+ m_currSessionStats.m_elapsedTime != 0)
+ {
+ double t = m_currSessionStats.m_elapsedTime;
+ double wpm = m_currSessionStats.m_words/t*60.0;
+ double tp = QDateTime::currentDateTime().toTime_t()/(3600.0*24);
+ data.push_back(std::make_pair(tp, wpm) );
+ }
+ chartWidget->LeftAxis.setLabel( i18n("Words per minute") );
+ chartWidget->LeftAxis.setLabelFormat(0, 'f', 0);
+ break;
+
+
+ case 1 : // chars per minute
+ // loop over all session data entries in *it and store chars per minute data
+ for (QValueVector<KTouchSessionStats>::const_iterator session_it = (*it).m_sessionStats.begin();
+ session_it != (*it).m_sessionStats.end(); ++session_it)
+ {
+ double t = (*session_it).m_elapsedTime;
+ double cpm = (t == 0) ? 0 : (*session_it).m_correctChars/t*60.0;
+ double tp = (*session_it).m_timeRecorded.toTime_t()/(3600.0*24);
+ data.push_back(std::make_pair(tp, cpm) );
+ }
+ // add current session
+ if (m_currentIndex == m_lectureIndex &&
+ m_currSessionStats.m_elapsedTime != 0)
+ {
+ double t = m_currSessionStats.m_elapsedTime;
+ double cpm = m_currSessionStats.m_correctChars/t*60.0;
+ double tp = QDateTime::currentDateTime().toTime_t()/(3600.0*24);
+ data.push_back(std::make_pair(tp, cpm) );
+ }
+ chartWidget->LeftAxis.setLabel( i18n("Characters per minute") );
+ chartWidget->LeftAxis.setLabelFormat(0, 'f', 0);
+ break;
+
+
+ case 2 : // correctness
+ // loop over all session data entries in *it and store correctness data
+ for (QValueVector<KTouchSessionStats>::const_iterator session_it = (*it).m_sessionStats.begin();
+ session_it != (*it).m_sessionStats.end(); ++session_it)
+ {
+ double tc = (*session_it).m_totalChars;
+ double corr = (tc == 0) ? 0 : (*session_it).m_correctChars/tc;
+ double tp = (*session_it).m_timeRecorded.toTime_t()/(3600.0*24);
+ data.push_back(std::make_pair(tp, corr) );
+ }
+ // add current session
+ if (m_currentIndex == m_lectureIndex &&
+ m_currSessionStats.m_totalChars != 0)
+ {
+ double tc = m_currSessionStats.m_totalChars;
+ double corr = m_currSessionStats.m_correctChars/tc;
+ double tp = QDateTime::currentDateTime().toTime_t()/(3600.0*24);
+ data.push_back(std::make_pair(tp, corr) );
+ }
+ chartWidget->LeftAxis.setLabel( i18n("Correctness") );
+ chartWidget->LeftAxis.setLabelFormat(0, 'g', 1);
+ break;
+
+
+ case 3 : // skill
+ // loop over all session data entries in *it and store correctness data
+ for (QValueVector<KTouchSessionStats>::const_iterator session_it = (*it).m_sessionStats.begin();
+ session_it != (*it).m_sessionStats.end(); ++session_it)
+ {
+ double tc = (*session_it).m_totalChars;
+ double corr = (tc == 0) ? 0 : (*session_it).m_correctChars/tc;
+ double t = (*session_it).m_elapsedTime;
+ double cpm = (t == 0) ? 0 : (*session_it).m_correctChars/t*60.0;
+ double skill = corr*cpm;
+ double tp = (*session_it).m_timeRecorded.toTime_t()/(3600.0*24);
+ data.push_back(std::make_pair(tp, skill) );
+ }
+ // add current session
+ if (m_currentIndex == m_lectureIndex &&
+ m_currSessionStats.m_totalChars != 0 &&
+ m_currSessionStats.m_elapsedTime != 0)
+ {
+ double tc = m_currSessionStats.m_totalChars;
+ double corr = m_currSessionStats.m_correctChars/tc;
+ double t = m_currSessionStats.m_elapsedTime;
+ double cpm = m_currSessionStats.m_correctChars/t*60.0;
+ double skill = corr*cpm;
+ double tp = QDateTime::currentDateTime().toTime_t()/(3600.0*24);
+ data.push_back(std::make_pair(tp, skill) );
+ }
+ chartWidget->LeftAxis.setLabel( i18n("Skill") );
+ chartWidget->LeftAxis.setLabelFormat(0, 'f', 0);
+ break;
+
+ default : return;
+ }
+ // Create plot object for session statistics
+ KPlotObject * ob;
+ if (data.size() <= 1) return;
+ ob = new KPlotObject(caption, "red", KPlotObject::CURVE, 2, KPlotObject::SOLID);
+
+ // Add some random points to the plot object
+ double min_x = 1e20;
+ double max_x = -1;
+ double max_y = -1;
+ for (unsigned int i=0; i<data.size(); ++i) {
+ double x;
+ if (timeRadio->isChecked()) {
+ x = data[i].first - data[0].first;
+ chartWidget->BottomAxis.setLabel( i18n( "Time since first practice session in days" ));
+ }
+ else {
+ x = i+1;
+ chartWidget->BottomAxis.setLabel( i18n( "Sessions" ));
+ }
+ ob->addPoint( DPoint(x, data[i].second) );
+ min_x = std::min(x, min_x);
+ max_x = std::max(x, max_x);
+ max_y = std::max(data[i].second, max_y);
+ }
+ if (max_y == 0) max_y = 1;
+ max_y *= 1.1;
+ chartWidget->setLimits( min_x, max_x, -0.1*max_y, max_y );
+// kdDebug() << min_x << " " << max_x << " " << max_y << endl;
+ // Add plot object to chart
+ chartWidget->addObject(ob);
+ }
+}
+// ----------------------------------------------------------------------------
+
+/*
+
+void KTouchStatistics::updateChartTab() {
+ if (m_trainer->m_sessionHistory.size()<2) {
+ chartTypeButtonGroup->setEnabled(false);
+ chartWidget->setEraseColor( Qt::gray );
+ }
+ else {
+ chartTypeButtonGroup->setEnabled(true);
+ chartWidget->setEraseColor( Qt::white );
+ };
+ if (charSpeedButton->isChecked())
+ chartWidget->setChartType( KTouchChartWidget::CharsPerMinute );
+ else if (wordSpeedButton->isChecked())
+ chartWidget->setChartType( KTouchChartWidget::WordsPerMinute );
+ else if (accuracyButton->isChecked())
+ chartWidget->setChartType( KTouchChartWidget::Accuracy );
+ else if (timeButton->isChecked())
+ chartWidget->setChartType( KTouchChartWidget::ElapsedTime );
+}
+// ----------------------------------------------------------------------------
+
+*/