fixes and improvements
[Sak.git] / task.cpp
blob615c87cebe958b82c95c6b9b019249998d8533e7
1 #include "task.h"
2 #include <math.h>
5 QXmlStreamWriter & operator<< ( QXmlStreamWriter & out, const Task & task )
7 out.writeStartElement("Task");
9 out.writeAttribute("title", task.title);
10 out.writeAttribute("tags", task.tags.join(","));
11 out.writeAttribute("active", QString("%1").arg(task.active));
12 if (task.dueDate.isValid())
13 out.writeAttribute("due", task.dueDate.toString("dd/MM/yyyy"));
14 out.writeAttribute("estimated", QString("%1").arg(task.estimatedHours));
15 out.writeAttribute("bgcolor", task.bgColor.name());
16 out.writeAttribute("fgcolor", task.fgColor.name());
18 out.writeStartElement("Description");
19 out.writeCharacters(task.description);
20 out.writeEndElement();
22 out.writeStartElement("Icon");
23 QByteArray bytes;
24 QBuffer buffer(&bytes);
25 buffer.open(QIODevice::WriteOnly);
26 task.icon.save(&buffer, "PNG"); // writes pixmap into bytes in PNG format
27 out.writeCharacters(bytes.toBase64());
28 out.writeEndElement();
30 out.writeStartElement("Subtasks");
31 QHash< QString, QList< Task::Hit > >::const_iterator itr = task.hits.begin(), end = task.hits.end();
32 while(itr != end) {
33 out.writeStartElement("Subtask");
34 out.writeAttribute("title", itr.key());
35 QHash< QString, Task::SubTask >::const_iterator sitr = task.subTasks.find(itr.key());
36 if (sitr != task.subTasks.end()) {
37 const Task::SubTask& st(sitr.value());
38 if (st.bgColor.isValid())
39 out.writeAttribute("bgcolor", st.bgColor.name());
40 if (st.fgColor.isValid())
41 out.writeAttribute("fgcolor", st.fgColor.name());
42 out.writeAttribute("active", QString("%1").arg(st.active));
43 out.writeStartElement("Description");
44 out.writeCharacters(st.description);
45 out.writeEndElement();
47 out.writeStartElement("Hits");
48 const QList<Task::Hit>& h(itr.value());
49 for(int i=h.count()-1; i>=0; i--) {
50 if (h[i].timestamp.isValid()) {
51 out.writeEmptyElement("h");
52 out.writeAttribute("t", h[i].timestamp.toString(DATETIMEFORMAT));
53 out.writeAttribute("d", QString("%1").arg(h[i].duration));
56 out.writeEndElement();
57 out.writeEndElement();
58 itr++;
60 out.writeEndElement();
62 out.writeEndElement();
63 return out;
68 void parseHits(QXmlStreamReader & in, Task & task, const QString subtask)
70 QMultiMap<int, Task::Hit> sortedHits;
71 while(!in.atEnd()) {
72 QXmlStreamReader::TokenType token = in.readNext();
73 if (token == QXmlStreamReader::StartElement) {
74 QString elementName = in.name().toString();
75 if (0) {
76 } else if (elementName.compare("h", Qt::CaseInsensitive) == 0){
77 Task::Hit hit;
78 QXmlStreamAttributes attrs = in.attributes();
79 hit.timestamp = QDateTime::fromString(attrs.value("t").toString(), DATETIMEFORMAT);
80 hit.duration = attrs.value("d").toString().toUInt();
81 sortedHits.insertMulti(hit.timestamp.toTime_t(), hit);
83 } else if (token == QXmlStreamReader::EndElement && in.name().toString().compare("hits", Qt::CaseInsensitive) == 0) {
84 break;
88 // remove duplicates
89 QMultiMap<int, Task::Hit>::iterator itr = sortedHits.begin();
90 Task::Hit prev;
91 while(itr != sortedHits.end()) {
92 if (itr.value() == prev)
93 itr = sortedHits.erase(itr);
94 else {
95 prev = itr.value();
96 itr++;
99 task.hits[subtask] = sortedHits.values();
104 void parseSubtask(QXmlStreamReader & in, Task & task)
106 QXmlStreamAttributes attrs = in.attributes();
107 QString title = attrs.value("title").toString();
108 QStringList tags = attrs.value("tags").toString().split(QRegExp("[,:]"));
109 Task::SubTask& st(task.subTasks[title]);
110 QStringRef bgColor = attrs.value("bgcolor");
111 QStringRef fgColor = attrs.value("fgcolor");
112 QStringRef active = attrs.value("active");
113 st.title = title;
114 if (!bgColor.isEmpty())
115 st.bgColor = QColor(bgColor.toString());
116 if (!fgColor.isEmpty())
117 st.fgColor = QColor(fgColor.toString());
118 if (!active.isEmpty())
119 st.active = active.toString().toUInt();
121 // read subtask
122 while(!in.atEnd()) {
123 QXmlStreamReader::TokenType token = in.readNext();
124 if (token == QXmlStreamReader::StartElement) {
125 QString elementName = in.name().toString();
126 if (0) {
127 } else if (elementName.compare("description", Qt::CaseInsensitive) == 0){
128 st.description = in.readElementText();
129 } else if (elementName.compare("hits", Qt::CaseInsensitive) == 0) {
130 parseHits(in, task, st.title);
132 } else if (token == QXmlStreamReader::EndElement && in.name().toString().compare("subtask", Qt::CaseInsensitive) == 0) {
133 return;
138 void parseSubtasks(QXmlStreamReader & in, Task & task)
140 while(!in.atEnd()) {
141 QXmlStreamReader::TokenType token = in.readNext();
142 if (token == QXmlStreamReader::StartElement ) {
143 QString elementName = in.name().toString();
144 if (0) {
145 } else if (elementName.compare("subtask", Qt::CaseInsensitive) == 0) {
146 parseSubtask(in, task);
148 } else {
149 //qDebug() << " > token type " << token;
155 QXmlStreamReader & operator>> ( QXmlStreamReader & in, Task & task )
157 QXmlStreamReader::TokenType token = in.readNext();
158 if (!in.atEnd() && token != QXmlStreamReader::StartElement) {
159 in.raiseError("Expected start of Task element, got " + QString("%1").arg(token));
161 if (!in.atEnd() && in.name().toString().compare("Task",Qt::CaseInsensitive)) {
162 in.raiseError("Expected token name \"Task\", got " + in.name().toString());
164 if (!in.atEnd()) {
165 QXmlStreamAttributes attrs = in.attributes();
166 task.title = attrs.value("title").toString();
167 task.tags = attrs.value("tags").toString().split(QRegExp("[:,]"));
168 QStringRef bgColor = attrs.value("bgcolor");
169 QStringRef fgColor = attrs.value("fgcolor");
170 QStringRef active = attrs.value("active");
171 QStringRef due = attrs.value("due");
172 QStringRef estimated = attrs.value("estimated");
173 if (!bgColor.isEmpty())
174 task.bgColor = QColor(bgColor.toString());
175 if (!fgColor.isEmpty())
176 task.fgColor = QColor(fgColor.toString());
177 if (!active.isEmpty())
178 task.active = active.toString().toUInt();
179 if (!estimated.isEmpty())
180 task.estimatedHours = estimated.toString().toUInt();
181 if (!due.isEmpty())
182 task.dueDate = QDate::fromString(due.toString(), "dd/MM/yyyy");
184 QXmlStreamReader::TokenType token;
185 while(!in.atEnd()) {
186 token = in.readNext();
187 if (token == QXmlStreamReader::StartElement) {
188 QString elementName = in.name().toString();
189 if (0) {
190 } else if (elementName.compare("description", Qt::CaseInsensitive) == 0) {
191 task.description = in.readElementText();
192 } else if (elementName.compare("icon", Qt::CaseInsensitive) == 0) {
193 task.icon.loadFromData(QByteArray::fromBase64(in.readElementText().toAscii()), "PNG");
194 } else if (elementName.compare("subtasks", Qt::CaseInsensitive) == 0) {
195 parseSubtasks(in, task);
197 } else {
198 //qDebug() << "token type " << token;
202 return in;
208 double Task::workedHours(const QDateTime& from, const QDateTime& to) const
210 double worked = 0;
211 QHash<QString, QList<Task::Hit > >::const_iterator hitr = hits.begin(), hend = hits.end();
212 while(hitr != hend) {
213 const QList<Task::Hit>& l(hitr.value());
214 for(int i=l.count()-1; i>=0; i--) {
215 const Task::Hit& hit ( l[i] );
216 if ( hit.timestamp < from) return worked;
217 if ( hit.timestamp > to ) continue;
218 else {
219 worked += hours(hit.duration); // multiple of quarter of hour
222 hitr++;
224 return worked;
228 void Task::updateSubTasks()
230 const QList<QString>& subtasks( hits.keys() );
231 for(int i=0; i<subtasks.count(); i++) {
232 if (!subTasks.contains(subtasks[i])) {
233 subTasks[subtasks[i]].title = subtasks[i];
238 bool Task::checkConsistency()
240 QHash<QString, QList<Task::Hit > >::const_iterator hitr = hits.begin(), hend = hits.end();
241 totHours = 0;
242 totOverestimation = 0;
243 // ensure tasks are in list
244 updateSubTasks();
245 while(hitr != hend) {
246 const QList<Task::Hit>& l(hitr.value());
247 QList<HitElement> hitlist;
248 for(int i=0; i<l.count(); i++) {
249 hitlist << HitElement(this, hitr.key(), l[i].timestamp, l[i].duration);
251 double subtaskHours, subtaskOverestimation;
252 QVector<double> o;
253 subtaskHours = HitElement::overestimations(hitlist, o, subtaskOverestimation);
254 subTasks[hitr.key()].totHours = subtaskHours;
255 totHours += subtaskHours;
256 totOverestimation +=subtaskOverestimation;
257 hitr++;
259 return totHours >= 0;
262 // QPair<QDateTime, quint8>* Task::findHit(QDateTime t, quint8 d)
263 // {
264 // QList< QPair<QDateTime, quint8> >::const_iterator itr = hits.begin(), end = hits.end();
265 // for (int i=0; i<hits.count(); i++) {
266 // if (hits[i].first == t && hits[i].second == d)
267 // return i;
268 // }
269 // return -1;
270 // }
273 // returns tot time
274 double HitElement::overestimations(const QList<HitElement>& hits, QVector<double>& overestimation, double& totOverestimation)
276 QList<HitElement> sortedList(hits);
277 double totHours = 0;
278 qSort(sortedList);
280 QDateTime t = QDateTime::currentDateTime();
281 overestimation.resize(sortedList.count());
282 totOverestimation=0;
283 int count = sortedList.count()-1;
284 for (int i=count; i>=0; i--) {
285 if (sortedList[i].timestamp > t) {
286 overestimation[i] = ( sortedList[i].timestamp.toTime_t() - t.toTime_t() ) / 60;
287 //qWarning() << "SAK: inconsistency in task " << sortedList[i].task->title << ": " << sortedList[i].timestamp << " > " << t;
288 totHours = -1;
289 return totHours;
290 } else {
291 totHours += Task::hours(sortedList[i].duration);
292 qint64 expected = (t.toTime_t() - sortedList[i].timestamp.toTime_t());
293 qint64 got = Task::hours(sortedList[i].duration)*3600; //seconds
294 qint64 diff = expected - got;
295 if (diff < 0) {
296 overestimation[i] = 0;
297 totOverestimation -= diff;
298 } if (diff < -59) {
299 if (i < count) {
300 overestimation[i+1] = qCeil(-diff/120);
301 overestimation[i] = qCeil(-diff/120);
303 overestimation[i] = qCeil(-diff/120);
304 //qDebug() << "SAK: task " << sortedList[i].task->title << ": overestimated time from hit " << sortedList[i].timestamp << " and hit " << t << ". Expected " << expected/60 << " minutes, got " << got/60 << " (diff = " << diff/60 << ")";
306 t=sortedList[i].timestamp;
309 if (totOverestimation > 60) {
310 totOverestimation /= 3600.0;
312 return totHours;