You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
537 lines
18 KiB
537 lines
18 KiB
/***************************************************************************
|
|
* ktouchstatisticsdata.cpp *
|
|
* ------------------------ *
|
|
* Copyright (C) 2005 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 "config.h"
|
|
#include "ktouchstatisticsdata.h"
|
|
|
|
#include <tqfile.h>
|
|
#include <tqstringlist.h>
|
|
#include <tqdom.h>
|
|
|
|
#include <kdebug.h>
|
|
#include <tdelocale.h>
|
|
#include <tdetempfile.h>
|
|
#include <tdeio/netaccess.h>
|
|
#include <sstream>
|
|
|
|
// remove this define if XML output should be made uncompressed
|
|
#define COMPRESSED_XML_STATISTICS
|
|
|
|
|
|
// *** KTouchCharStats ***
|
|
|
|
int KTouchCharStats::missHitRatio() const {
|
|
if (m_correctCount+m_wrongCount == 0) return 0;
|
|
return static_cast<int>((static_cast<double>(m_wrongCount)/(m_correctCount+m_wrongCount)*100));
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
|
|
TQTextStream& operator<<(TQTextStream &out, const KTouchCharStats &ch) {
|
|
return (out << ch.m_char.unicode() << " " << ch.m_correctCount << " " << ch.m_wrongCount);
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// *** KTouchLevelStats ***
|
|
|
|
void KTouchLevelStats::clear() {
|
|
m_levelNum = -1;
|
|
m_elapsedTime = 0;
|
|
m_words = 0;
|
|
m_totalChars = 0;
|
|
m_correctChars = 0;
|
|
m_charStats.clear();
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
|
|
bool KTouchLevelStats::read(TQDomNode in) {
|
|
// read the level number
|
|
TQDomNode n = in.attributes().namedItem("Number");
|
|
bool ok;
|
|
if (!n.isNull()) {
|
|
m_levelNum = n.nodeValue().toInt(&ok);
|
|
if (!ok) {
|
|
m_levelNum = -1;
|
|
return false; // need level number!
|
|
}
|
|
}
|
|
// read time
|
|
n = in.attributes().namedItem("Time");
|
|
if (!n.isNull()) {
|
|
m_elapsedTime = n.nodeValue().toDouble(&ok);
|
|
if (!ok) m_elapsedTime = 0;
|
|
}
|
|
// read total chars
|
|
n = in.attributes().namedItem("Chars");
|
|
if (!n.isNull()) {
|
|
m_totalChars = n.nodeValue().toInt(&ok);
|
|
if (!ok) m_totalChars = 0;
|
|
}
|
|
// read correct chars
|
|
n = in.attributes().namedItem("Corrects");
|
|
if (!n.isNull()) {
|
|
m_correctChars = n.nodeValue().toInt(&ok);
|
|
if (!ok) m_correctChars = 0;
|
|
}
|
|
// read number of words
|
|
n = in.attributes().namedItem("Words");
|
|
if (!n.isNull()) {
|
|
m_words = n.nodeValue().toInt(&ok);
|
|
if (!ok) m_words = 0;
|
|
}
|
|
// done with attributes, read recording time
|
|
n = in.namedItem("Time");
|
|
if (!n.isNull()) {
|
|
TQString timestring = n.firstChild().nodeValue();
|
|
if (timestring != TQString())
|
|
m_timeRecorded = TQDateTime::fromString(timestring, Qt::ISODate);
|
|
}
|
|
// read characters
|
|
n = in.namedItem("CharStats");
|
|
if (!n.isNull()) {
|
|
TQString char_data = n.firstChild().nodeValue();
|
|
//kdDebug() << "'" << char_data << "'" << endl;
|
|
std::stringstream strm(std::string(char_data.local8Bit()));
|
|
int ch, correct, wrong;
|
|
while (strm >> ch >> correct >> wrong)
|
|
m_charStats.insert( KTouchCharStats(TQChar(ch), correct, wrong) );
|
|
//kdDebug() << m_charStats.size() << endl;
|
|
}
|
|
return true;
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void KTouchLevelStats::write(TQDomDocument& doc, TQDomElement& root) const {
|
|
TQDomElement level = doc.createElement("LevelStats");
|
|
root.appendChild(level);
|
|
level.setAttribute("Number", m_levelNum);
|
|
level.setAttribute("Time", m_elapsedTime);
|
|
level.setAttribute("Chars", m_totalChars);
|
|
level.setAttribute("Corrects", m_correctChars);
|
|
level.setAttribute("Words", m_words);
|
|
// add time
|
|
TQDomElement e = doc.createElement("Time");
|
|
TQDomText tn = doc.createTextNode(m_timeRecorded.toString(Qt::ISODate));
|
|
e.appendChild(tn);
|
|
level.appendChild(e);
|
|
// add char stats
|
|
TQString char_data;
|
|
// we append for each missed char the char-unicode and the counters
|
|
for (std::set<KTouchCharStats>::const_iterator it=m_charStats.begin(); it!=m_charStats.end(); ++it)
|
|
char_data += TQString(" %1 %2 %3").arg(it->m_char.unicode())
|
|
.arg(it->m_correctCount).arg(it->m_wrongCount);
|
|
e = doc.createElement("CharStats");
|
|
tn = doc.createTextNode(char_data);
|
|
e.appendChild(tn);
|
|
level.appendChild(e);
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void KTouchLevelStats::addCorrectChar(TQChar key) {
|
|
++m_correctChars;
|
|
++m_totalChars;
|
|
// we only count non-space characters
|
|
if (key!=TQChar(' ')) {
|
|
std::set<KTouchCharStats>::iterator it = m_charStats.find( KTouchCharStats(key,0,0) );
|
|
if (it==m_charStats.end())
|
|
m_charStats.insert( KTouchCharStats(key,1,0) );
|
|
else
|
|
++(const_cast<KTouchCharStats&>(*it).m_correctCount);
|
|
// this const_cast is necessary because the current gcc 3.2 has a bug in the
|
|
// implementation of set::find(). The non-const overload is missing and find() always
|
|
// returns a const object. Maybe in the next release this will be fixed. Until then
|
|
// the const_cast helps.
|
|
}
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void KTouchLevelStats::removeCorrectChar() {
|
|
--m_correctChars;
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void KTouchLevelStats::addWrongChar(TQChar key) {
|
|
++m_totalChars;
|
|
if (key==TQChar(8) || key==TQChar(' '))
|
|
return; // don't remember wrong backspaces or spaces
|
|
std::set<KTouchCharStats>::iterator it = m_charStats.find( KTouchCharStats(key,0,0) );
|
|
if (it==m_charStats.end())
|
|
m_charStats.insert( KTouchCharStats(key,0,1) );
|
|
else
|
|
++(const_cast<KTouchCharStats&>(*it).m_wrongCount);
|
|
// this const_cast is necessary because the current gcc 3.2 has a bug in the
|
|
// implementation of set::find(). The non-const overload is missing and find() always
|
|
// returns a const object. Maybe in the next release this will be fixed. Until then
|
|
// the const_cast helps.
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
|
|
double KTouchLevelStats::correctness() const {
|
|
return (m_totalChars==0) ? 1.0 : static_cast<double>(m_correctChars)/m_totalChars;
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
|
|
double KTouchLevelStats::charSpeed() const {
|
|
return (m_elapsedTime==0) ? 0 : 60.0*m_correctChars/m_elapsedTime;
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
|
|
double KTouchLevelStats::wordSpeed() const {
|
|
return (m_elapsedTime==0) ? 0 : 60.0*m_words/m_elapsedTime;
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// *** KTouchSessionStats ***
|
|
|
|
void KTouchSessionStats::clear() {
|
|
m_elapsedTime = 0;
|
|
m_words = 0;
|
|
m_totalChars = 0;
|
|
m_correctChars = 0;
|
|
m_levelNums.clear();
|
|
m_charStats.clear();
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
|
|
bool KTouchSessionStats::read(TQDomNode in) {
|
|
// read time
|
|
TQDomNode n = in.attributes().namedItem("Time");
|
|
bool ok;
|
|
if (!n.isNull()) {
|
|
m_elapsedTime = n.nodeValue().toDouble(&ok);
|
|
if (!ok) m_elapsedTime = 0;
|
|
}
|
|
// read total chars
|
|
n = in.attributes().namedItem("Chars");
|
|
if (!n.isNull()) {
|
|
m_totalChars = n.nodeValue().toInt(&ok);
|
|
if (!ok) m_totalChars = 0;
|
|
}
|
|
// read correct chars
|
|
n = in.attributes().namedItem("Corrects");
|
|
if (!n.isNull()) {
|
|
m_correctChars = n.nodeValue().toInt(&ok);
|
|
if (!ok) m_correctChars = 0;
|
|
}
|
|
// read number of words
|
|
n = in.attributes().namedItem("Words");
|
|
if (!n.isNull()) {
|
|
m_words = n.nodeValue().toInt(&ok);
|
|
if (!ok) m_words = 0;
|
|
}
|
|
// done with attributes, read recording time
|
|
n = in.namedItem("Time");
|
|
if (!n.isNull()) {
|
|
TQString timestring = n.firstChild().nodeValue();
|
|
if (timestring != TQString())
|
|
m_timeRecorded = TQDateTime::fromString(timestring, Qt::ISODate);
|
|
}
|
|
// read level numbers
|
|
n = in.namedItem("LevelNums");
|
|
if (!n.isNull()) {
|
|
TQString str = n.firstChild().nodeValue();
|
|
std::stringstream strm(std::string(str.local8Bit()));
|
|
int l;
|
|
while (strm >> l)
|
|
m_levelNums.insert(l);
|
|
}
|
|
// read characters
|
|
n = in.namedItem("CharStats");
|
|
if (!n.isNull()) {
|
|
TQString char_data = n.firstChild().nodeValue();
|
|
//kdDebug() << "'" << char_data << "'" << endl;
|
|
std::stringstream strm(std::string(char_data.local8Bit()));
|
|
int ch, correct, wrong;
|
|
while (strm >> ch >> correct >> wrong)
|
|
m_charStats.insert( KTouchCharStats(TQChar(ch), correct, wrong) );
|
|
//kdDebug() << m_charStats.size() << endl;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void KTouchSessionStats::write(TQDomDocument& doc, TQDomElement& root) const {
|
|
TQDomElement session = doc.createElement("SessionStats");
|
|
root.appendChild(session);
|
|
session.setAttribute("Time", m_elapsedTime);
|
|
session.setAttribute("Chars", m_totalChars);
|
|
session.setAttribute("Corrects", m_correctChars);
|
|
session.setAttribute("Words", m_words);
|
|
// add time
|
|
TQDomElement e = doc.createElement("Time");
|
|
TQDomText tn = doc.createTextNode(m_timeRecorded.toString(Qt::ISODate));
|
|
e.appendChild(tn);
|
|
session.appendChild(e);
|
|
// add levels
|
|
TQString level_nums;
|
|
for (std::set<unsigned int>::const_iterator it = m_levelNums.begin(); it != m_levelNums.end(); ++it)
|
|
level_nums += TQString( " %1").arg(*it);
|
|
e = doc.createElement("LevelNums");
|
|
tn = doc.createTextNode(level_nums);
|
|
e.appendChild(tn);
|
|
session.appendChild(e);
|
|
// add char data
|
|
TQString char_data;
|
|
// we append for each missed char the char-unicode and the counters
|
|
for (std::set<KTouchCharStats>::const_iterator it=m_charStats.begin(); it!=m_charStats.end(); ++it)
|
|
char_data += TQString(" %1 %2 %3").arg(it->m_char.unicode())
|
|
.arg(it->m_correctCount).arg(it->m_wrongCount);
|
|
e = doc.createElement("CharStats");
|
|
tn = doc.createTextNode(char_data);
|
|
e.appendChild(tn);
|
|
session.appendChild(e);
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void KTouchSessionStats::addCorrectChar(TQChar key) {
|
|
++m_correctChars;
|
|
++m_totalChars;
|
|
// we only count non-space characters
|
|
if (key!=TQChar(' ')) {
|
|
std::set<KTouchCharStats>::iterator it = m_charStats.find( KTouchCharStats(key,0,0) );
|
|
if (it==m_charStats.end())
|
|
m_charStats.insert( KTouchCharStats(key,1,0) );
|
|
else
|
|
++(const_cast<KTouchCharStats&>(*it).m_correctCount);
|
|
// this const_cast is necessary because the current gcc 3.2 has a bug in the
|
|
// implementation of set::find(). The non-const overload is missing and find() always
|
|
// returns a const object. Maybe in the next release this will be fixed. Until then
|
|
// the const_cast helps.
|
|
}
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void KTouchSessionStats::removeCorrectChar() {
|
|
--m_correctChars;
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void KTouchSessionStats::addWrongChar(TQChar key) {
|
|
++m_totalChars;
|
|
if (key==TQChar(8) || key==TQChar(' '))
|
|
return; // don't remember wrong backspaces or spaces
|
|
std::set<KTouchCharStats>::iterator it = m_charStats.find( KTouchCharStats(key,0,0) );
|
|
if (it==m_charStats.end())
|
|
m_charStats.insert( KTouchCharStats(key,0,1) );
|
|
else
|
|
++(const_cast<KTouchCharStats&>(*it).m_wrongCount);
|
|
// this const_cast is necessary because the current gcc 3.2 has a bug in the
|
|
// implementation of set::find(). The non-const overload is missing and find() always
|
|
// returns a const object. Maybe in the next release this will be fixed. Until then
|
|
// the const_cast helps.
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// *** KTouchLectureStats ***
|
|
|
|
void KTouchLectureStats::clear() {
|
|
m_lectureTitle = TQString();
|
|
m_lectureURL = TQString();
|
|
m_levelStats.clear();
|
|
m_sessionStats.clear();
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
|
|
bool KTouchLectureStats::read(TQDomNode in) {
|
|
clear();
|
|
// read the URL
|
|
TQDomNode n = in.namedItem("URL");
|
|
if (!n.isNull()) m_lectureURL = n.firstChild().nodeValue();
|
|
else return false; // no URL, statistics useless
|
|
// read the Title
|
|
n = in.namedItem("Title");
|
|
if (!n.isNull()) m_lectureTitle = n.firstChild().nodeValue();
|
|
// read the level stats
|
|
n = in.namedItem("AllLevelStats");
|
|
if (!n.isNull()) {
|
|
// loop over all childs and read levels
|
|
TQDomNode levelNode = n.firstChild();
|
|
while (!levelNode.isNull()) {
|
|
//kdDebug() << "[KTouchLectureStats::read] Reading level stats" << endl;
|
|
KTouchLevelStats tmp;
|
|
if (tmp.read(levelNode)) m_levelStats.push_back(tmp);
|
|
levelNode = levelNode.nextSibling();
|
|
}
|
|
}
|
|
// read the session stats
|
|
n = in.namedItem("AllSessionStats");
|
|
if (!n.isNull()) {
|
|
// loop over all childs and read sessions
|
|
TQDomNode sessionNode = n.firstChild();
|
|
while (!sessionNode.isNull()) {
|
|
//kdDebug() << "[KTouchLectureStats::read] Reading session stats" << endl;
|
|
KTouchSessionStats tmp;
|
|
if (tmp.read(sessionNode)) m_sessionStats.push_back(tmp);
|
|
sessionNode = sessionNode.nextSibling();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void KTouchLectureStats::write(TQDomDocument& doc, TQDomElement& root) const {
|
|
if (m_lectureURL.isEmpty()) return; // Error here!
|
|
TQDomElement lecture = doc.createElement("LectureStats");
|
|
root.appendChild(lecture);
|
|
// store lecture URL
|
|
TQDomElement urlElement = doc.createElement("URL");
|
|
TQDomText urlText = doc.createTextNode(m_lectureURL.url());
|
|
urlElement.appendChild(urlText);
|
|
lecture.appendChild(urlElement);
|
|
// store title
|
|
TQString title = m_lectureTitle;
|
|
if (title.isEmpty())
|
|
title = m_lectureURL.fileName();
|
|
TQDomElement titleElement = doc.createElement("Title");
|
|
TQDomText titleText = doc.createTextNode(title);
|
|
titleElement.appendChild(titleText);
|
|
lecture.appendChild(titleElement);
|
|
// store level stats
|
|
TQDomElement levelStatsElement = doc.createElement("AllLevelStats");
|
|
lecture.appendChild(levelStatsElement);
|
|
for (TQValueVector<KTouchLevelStats>::ConstIterator it = m_levelStats.begin();
|
|
it != m_levelStats.end(); ++it)
|
|
{
|
|
it->write(doc, levelStatsElement);
|
|
}
|
|
// store session stats
|
|
TQDomElement sessionStatsElement = doc.createElement("AllSessionStats");
|
|
lecture.appendChild(sessionStatsElement);
|
|
for (TQValueVector<KTouchSessionStats>::ConstIterator it = m_sessionStats.begin();
|
|
it != m_sessionStats.end(); ++it)
|
|
{
|
|
it->write(doc, sessionStatsElement);
|
|
}
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// *** KTouchStatisticsData ***
|
|
|
|
void KTouchStatisticsData::clear() {
|
|
m_lectureStats.clear();
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
|
|
bool KTouchStatisticsData::read(TQWidget * window, const KURL& url) {
|
|
if (url.isEmpty()) return false;
|
|
// Ok, first download the contents as usual using the TDEIO lib
|
|
// File is only downloaded if not local, otherwise it's just opened
|
|
TQString target;
|
|
bool result = false;
|
|
if (TDEIO::NetAccess::download(url, target, window)) {
|
|
// Ok, that was successful, store the lectureURL and read the file
|
|
TQFile infile(target);
|
|
if ( !infile.open( IO_ReadOnly ) ) {
|
|
TDEIO::NetAccess::removeTempFile(target);
|
|
return false; // Bugger it... couldn't open it...
|
|
}
|
|
TQDomDocument doc;
|
|
|
|
#ifdef COMPRESSED_XML_STATISTICS
|
|
TQByteArray array;
|
|
array = infile.readAll();
|
|
array = tqUncompress(array);
|
|
doc.setContent( array );
|
|
#else
|
|
doc.setContent( &infile );
|
|
#endif // COMPRESSED_XML_STATISTICS
|
|
result = readStats(doc);
|
|
}
|
|
TDEIO::NetAccess::removeTempFile(target);
|
|
return result;
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
|
|
bool KTouchStatisticsData::write(TQWidget * window, const KURL& url) const {
|
|
// create the XML document
|
|
TQDomDocument doc;
|
|
writeStats(doc);
|
|
|
|
// and save it
|
|
TQString tmpFile;
|
|
KTempFile *temp=0;
|
|
if (url.isLocalFile())
|
|
tmpFile=url.path(); // for local files the path is sufficient
|
|
else {
|
|
temp=new KTempFile; // for remote files create a temporary file first
|
|
temp->setAutoDelete(true); // so we don't have to delete the file ourselves
|
|
tmpFile=temp->name();
|
|
}
|
|
|
|
TQFile outfile(tmpFile);
|
|
if ( !outfile.open( IO_WriteOnly ) ) {
|
|
if (temp) delete temp;
|
|
// kdDebug() << "Error creating lecture file!" << endl;
|
|
return false;
|
|
}
|
|
|
|
#ifdef COMPRESSED_XML_STATISTICS
|
|
TQByteArray array;
|
|
TQTextStream out(array, IO_WriteOnly);
|
|
out << doc.toString();
|
|
array = tqCompress(array);
|
|
outfile.writeBlock(array);
|
|
#else
|
|
TQTextStream out( &outfile );
|
|
out << doc.toString();
|
|
#endif // COMPRESSED_XML_STATISTICS
|
|
outfile.close();
|
|
|
|
// if we have a temporary file, we still need to upload it
|
|
if (temp) {
|
|
TDEIO::NetAccess::upload(tmpFile, url, window);
|
|
delete temp;
|
|
}
|
|
return true;
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
|
|
bool KTouchStatisticsData::readStats(TQDomDocument& doc) {
|
|
clear(); // clear current data
|
|
// retrieve the KTouchStatistics statistics
|
|
TQDomNodeList entries = doc.elementsByTagName("LectureStats");
|
|
bool result = true;
|
|
unsigned int i=0;
|
|
while (result && i < entries.count()) {
|
|
KTouchLectureStats stats;
|
|
result = stats.read(entries.item(i));
|
|
// do we have a valid URL?
|
|
if (stats.m_lectureURL.isEmpty()) result = false;
|
|
if (result)
|
|
m_lectureStats[stats.m_lectureURL] = stats;
|
|
++i;
|
|
}
|
|
return result;
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void KTouchStatisticsData::writeStats(TQDomDocument& doc) const {
|
|
TQDomElement root = doc.createElement( "KTouchStatistics" );
|
|
doc.appendChild(root);
|
|
// Store Lecture statistics
|
|
LectureStatsMap::ConstIterator it = m_lectureStats.begin();
|
|
while (it != m_lectureStats.end()) {
|
|
it.data().write(doc, root);
|
|
++it;
|
|
}
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
|