/*************************************************************************** * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ktouch.h" #include "ktouchchartwidget.h" KTouchStatistics::KTouchStatistics(TQWidget* parent) : KTouchStatisticsDlg(parent) { sessionsRadio->setChecked(true); WPMRadio->setChecked(true); eventRadio->setChecked(true); connect(closeButton, TQT_SIGNAL(clicked()), this, TQT_SLOT(accept()) ); connect(lectureCombo, TQT_SIGNAL(activated(int)), this, TQT_SLOT(lectureActivated(int)) ); connect(clearButton, TQT_SIGNAL(clicked()), this, TQT_SLOT(clearHistory()) ); // connect the radio buttons with the chart update function connect(buttonGroup1, TQT_SIGNAL(clicked(int)), this, TQT_SLOT(updateChartTab()) ); connect(buttonGroup2, TQT_SIGNAL(clicked(int)), this, TQT_SLOT(updateChartTab()) ); connect(buttonGroup3, TQT_SIGNAL(clicked(int)), this, TQT_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(); TQMapConstIterator it = stats.m_lectureStats.begin(); m_currentIndex = 0; while (it != stats.m_lectureStats.end()) { TQString 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(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?"),TQString(),KStdGuiItem::del()) == KMessageBox::Continue) { KTouchPtr->clearStatistics(); // clear statistics data in KTouch // clear and reset local copy m_allStats.clear(); TQString 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 TQString levelnums; int last_level = -2; int levels_count = 0; std::set::iterator last_it = m_currSessionStats.m_levelNums.end(); --last_it; for (std::set::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(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 += TQString("%1").arg(*it+1); } else { ++levels_count; // increase level count } last_level = *it; } levelLabel1->setText(levelnums); // general stats group elapsedTimeLCD->display(static_cast(m_currSessionStats.m_elapsedTime)); totalCharsLCD->display(static_cast(m_currSessionStats.m_totalChars) ); wrongCharsLCD->display(static_cast(m_currSessionStats.m_totalChars-m_currSessionStats.m_correctChars) ); wordsLCD->display(static_cast(m_currSessionStats.m_words) ); // typing rate group if (m_currSessionStats.m_elapsedTime == 0) { charSpeedLCD->display(0); wordSpeedLCD->display(0); } else { charSpeedLCD->display(static_cast(m_currSessionStats.m_correctChars/m_currSessionStats.m_elapsedTime*60.0) ); wordSpeedLCD->display(static_cast(m_currSessionStats.m_words/m_currSessionStats.m_elapsedTime*60.0) ); } // accuracy correctnessBar->setProgress(static_cast( (100.0*m_currSessionStats.m_correctChars)/m_currSessionStats.m_totalChars) ); // create sorted list of missed characters std::list charList( m_currSessionStats.m_charStats.begin(), m_currSessionStats.m_charStats.end()); charList.sort(higher_miss_hit_ratio); std::list::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( TQString("%1").arg(m_currLevelStats.m_levelNum+1) ); // general stats group elapsedTimeLCDLevel->display(static_cast(m_currLevelStats.m_elapsedTime)); totalCharsLCDLevel->display(static_cast(m_currLevelStats.m_totalChars) ); wrongCharsLCDLevel->display(static_cast(m_currLevelStats.m_totalChars-m_currLevelStats.m_correctChars) ); wordsLCDLevel->display(static_cast(m_currLevelStats.m_words) ); // typing rate group if (m_currLevelStats.m_elapsedTime == 0) { charSpeedLCDLevel->display(0); wordSpeedLCDLevel->display(0); } else { charSpeedLCDLevel->display(static_cast(m_currLevelStats.m_correctChars/m_currLevelStats.m_elapsedTime*60.0) ); wordSpeedLCDLevel->display(static_cast(m_currLevelStats.m_words/m_currLevelStats.m_elapsedTime*60.0) ); } // accuracy correctnessBarLevel->setProgress(static_cast( (100.0*m_currLevelStats.m_correctChars)/m_currLevelStats.m_totalChars) ); // create sorted list of missed characters std::list charList( m_currLevelStats.m_charStats.begin(), m_currLevelStats.m_charStats.end()); charList.sort(higher_miss_hit_ratio); std::list::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 TQMapConstIterator it = m_allStats.m_lectureStats.begin(); int index = m_lectureIndex; while (index-- > 0) ++it; std::vector< std::pair > data; TQString 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 (TQValueVector::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 = TQDateTime::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 (TQValueVector::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 = TQDateTime::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 (TQValueVector::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 = TQDateTime::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 (TQValueVector::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 = TQDateTime::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; iisChecked()) { 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( TQt::gray ); } else { chartTypeButtonGroup->setEnabled(true); chartWidget->setEraseColor( TQt::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 ); } // ---------------------------------------------------------------------------- */