fix start/stop/pause
[Sak.git] / task.cpp
bloba81f2c32f1a5567a1f09cde21aabd5431cad2893
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("active", QString("%1").arg(task.active));
11 if (task.dueDate.isValid())
12 out.writeAttribute("due", task.dueDate.toString("dd/MM/yyyy"));
13 out.writeAttribute("estimated", QString("%1").arg(task.estimatedHours));
14 out.writeAttribute("bgcolor", task.bgColor.name());
15 out.writeAttribute("fgcolor", task.fgColor.name());
17 out.writeStartElement("Description");
18 out.writeCharacters(task.description);
19 out.writeEndElement();
21 out.writeStartElement("Icon");
22 QByteArray bytes;
23 QBuffer buffer(&bytes);
24 buffer.open(QIODevice::WriteOnly);
25 task.icon.save(&buffer, "PNG"); // writes pixmap into bytes in PNG format
26 out.writeCharacters(bytes.toBase64());
27 out.writeEndElement();
29 out.writeStartElement("Subtasks");
30 QHash< QString, QList< Task::Hit > >::const_iterator itr = task.hits.begin(), end = task.hits.end();
31 while(itr != end) {
32 out.writeStartElement("Subtask");
33 out.writeAttribute("title", itr.key());
34 QHash< QString, Task::SubTask >::const_iterator sitr = task.subTasks.find(itr.key());
35 if (sitr != task.subTasks.end()) {
36 const Task::SubTask& st(sitr.value());
37 if (st.bgColor.isValid())
38 out.writeAttribute("bgcolor", st.bgColor.name());
39 if (st.fgColor.isValid())
40 out.writeAttribute("fgcolor", st.fgColor.name());
41 out.writeAttribute("active", QString("%1").arg(st.active));
42 out.writeStartElement("Description");
43 out.writeCharacters(st.description);
44 out.writeEndElement();
46 out.writeStartElement("Hits");
47 const QList<Task::Hit>& h(itr.value());
48 for(int i=h.count()-1; i>=0; i--) {
49 if (h[i].timestamp.isValid()) {
50 out.writeEmptyElement("h");
51 out.writeAttribute("t", h[i].timestamp.toString(DATETIMEFORMAT));
52 out.writeAttribute("d", QString("%1").arg(h[i].duration));
55 out.writeEndElement();
56 out.writeEndElement();
57 itr++;
59 out.writeEndElement();
61 out.writeEndElement();
62 return out;
67 void parseHits(QXmlStreamReader & in, Task & task, const QString subtask)
69 QMultiMap<int, Task::Hit> sortedHits;
70 while(!in.atEnd()) {
71 QXmlStreamReader::TokenType token = in.readNext();
72 if (token == QXmlStreamReader::StartElement) {
73 QString elementName = in.name().toString();
74 if (0) {
75 } else if (elementName.compare("h", Qt::CaseInsensitive) == 0){
76 Task::Hit hit;
77 QXmlStreamAttributes attrs = in.attributes();
78 hit.timestamp = QDateTime::fromString(attrs.value("t").toString(), DATETIMEFORMAT);
79 hit.duration = attrs.value("d").toString().toUInt();
80 sortedHits.insertMulti(hit.timestamp.toTime_t(), hit);
82 } else if (token == QXmlStreamReader::EndElement && in.name().toString().compare("hits", Qt::CaseInsensitive) == 0) {
83 break;
87 // remove duplicates
88 QMultiMap<int, Task::Hit>::iterator itr = sortedHits.begin();
89 Task::Hit prev;
90 while(itr != sortedHits.end()) {
91 if (itr.value() == prev)
92 itr = sortedHits.erase(itr);
93 else {
94 prev = itr.value();
95 itr++;
98 task.hits[subtask] = sortedHits.values();
103 void parseSubtask(QXmlStreamReader & in, Task & task)
105 QXmlStreamAttributes attrs = in.attributes();
106 QString title = attrs.value("title").toString();
107 Task::SubTask& st(task.subTasks[title]);
108 QStringRef bgColor = attrs.value("bgcolor");
109 QStringRef fgColor = attrs.value("fgcolor");
110 QStringRef active = attrs.value("active");
111 st.title = title;
112 if (!bgColor.isEmpty())
113 st.bgColor = QColor(bgColor.toString());
114 if (!fgColor.isEmpty())
115 st.fgColor = QColor(fgColor.toString());
116 if (!active.isEmpty())
117 st.active = active.toString().toUInt();
119 // read subtask
120 while(!in.atEnd()) {
121 QXmlStreamReader::TokenType token = in.readNext();
122 if (token == QXmlStreamReader::StartElement) {
123 QString elementName = in.name().toString();
124 if (0) {
125 } else if (elementName.compare("description", Qt::CaseInsensitive) == 0){
126 st.description = in.readElementText();
127 } else if (elementName.compare("hits", Qt::CaseInsensitive) == 0) {
128 parseHits(in, task, st.title);
130 } else if (token == QXmlStreamReader::EndElement && in.name().toString().compare("subtask", Qt::CaseInsensitive) == 0) {
131 return;
136 void parseSubtasks(QXmlStreamReader & in, Task & task)
138 while(!in.atEnd()) {
139 QXmlStreamReader::TokenType token = in.readNext();
140 if (token == QXmlStreamReader::StartElement ) {
141 QString elementName = in.name().toString();
142 if (0) {
143 } else if (elementName.compare("subtask", Qt::CaseInsensitive) == 0) {
144 parseSubtask(in, task);
146 } else {
147 //qDebug() << " > token type " << token;
153 QXmlStreamReader & operator>> ( QXmlStreamReader & in, Task & task )
155 QXmlStreamReader::TokenType token = in.readNext();
156 if (!in.atEnd() && token != QXmlStreamReader::StartElement) {
157 in.raiseError("Expected start of Task element, got " + QString("%1").arg(token));
159 if (!in.atEnd() && in.name().toString().compare("Task",Qt::CaseInsensitive)) {
160 in.raiseError("Expected token name \"Task\", got " + in.name().toString());
162 if (!in.atEnd()) {
163 QXmlStreamAttributes attrs = in.attributes();
164 task.title = attrs.value("title").toString();
165 QStringRef bgColor = attrs.value("bgcolor");
166 QStringRef fgColor = attrs.value("fgcolor");
167 QStringRef active = attrs.value("active");
168 QStringRef due = attrs.value("due");
169 QStringRef estimated = attrs.value("estimated");
170 if (!bgColor.isEmpty())
171 task.bgColor = QColor(bgColor.toString());
172 if (!fgColor.isEmpty())
173 task.fgColor = QColor(fgColor.toString());
174 if (!active.isEmpty())
175 task.active = active.toString().toUInt();
176 if (!estimated.isEmpty())
177 task.estimatedHours = estimated.toString().toUInt();
178 if (!due.isEmpty())
179 task.dueDate = QDate::fromString(due.toString(), "dd/MM/yyyy");
181 QXmlStreamReader::TokenType token;
182 while(!in.atEnd()) {
183 token = in.readNext();
184 if (token == QXmlStreamReader::StartElement) {
185 QString elementName = in.name().toString();
186 if (0) {
187 } else if (elementName.compare("description", Qt::CaseInsensitive) == 0) {
188 task.description = in.readElementText();
189 } else if (elementName.compare("icon", Qt::CaseInsensitive) == 0) {
190 task.icon.loadFromData(QByteArray::fromBase64(in.readElementText().toAscii()), "PNG");
191 } else if (elementName.compare("subtasks", Qt::CaseInsensitive) == 0) {
192 parseSubtasks(in, task);
194 } else {
195 //qDebug() << "token type " << token;
199 return in;
205 double Task::workedHours(const QDateTime& from, const QDateTime& to) const
207 double worked = 0;
208 QHash<QString, QList<Task::Hit > >::const_iterator hitr = hits.begin(), hend = hits.end();
209 while(hitr != hend) {
210 const QList<Task::Hit>& l(hitr.value());
211 for(int i=l.count()-1; i>=0; i--) {
212 const Task::Hit& hit ( l[i] );
213 if ( hit.timestamp < from) return worked;
214 if ( hit.timestamp > to ) continue;
215 else {
216 worked += hours(hit.duration); // multiple of quarter of hour
219 hitr++;
221 return worked;
225 void Task::updateSubTasks()
227 const QList<QString>& subtasks( hits.keys() );
228 for(int i=0; i<subtasks.count(); i++) {
229 if (!subTasks.contains(subtasks[i])) {
230 subTasks[subtasks[i]].title = subtasks[i];
235 bool Task::checkConsistency()
237 QHash<QString, QList<Task::Hit > >::const_iterator hitr = hits.begin(), hend = hits.end();
238 totHours = 0;
239 totOverestimation = 0;
240 // ensure tasks are in list
241 updateSubTasks();
242 while(hitr != hend) {
243 const QList<Task::Hit>& l(hitr.value());
244 QList<HitElement> hitlist;
245 for(int i=0; i<l.count(); i++) {
246 hitlist << HitElement(this, hitr.key(), l[i].timestamp, l[i].duration);
248 double subtaskHours, subtaskOverestimation;
249 QVector<double> o;
250 subtaskHours = HitElement::overestimations(hitlist, o, subtaskOverestimation);
251 subTasks[hitr.key()].totHours = subtaskHours;
252 totHours += subtaskHours;
253 totOverestimation +=subtaskOverestimation;
254 hitr++;
256 return totHours >= 0;
259 // QPair<QDateTime, quint8>* Task::findHit(QDateTime t, quint8 d)
260 // {
261 // QList< QPair<QDateTime, quint8> >::const_iterator itr = hits.begin(), end = hits.end();
262 // for (int i=0; i<hits.count(); i++) {
263 // if (hits[i].first == t && hits[i].second == d)
264 // return i;
265 // }
266 // return -1;
267 // }
270 // returns tot time
271 double HitElement::overestimations(const QList<HitElement>& hits, QVector<double>& overestimation, double& totOverestimation)
273 QList<HitElement> sortedList(hits);
274 double totHours = 0;
275 qSort(sortedList);
277 QDateTime t = QDateTime::currentDateTime();
278 overestimation.resize(sortedList.count());
279 totOverestimation=0;
280 int count = sortedList.count()-1;
281 for (int i=count; i>=0; i--) {
282 if (sortedList[i].timestamp > t) {
283 overestimation[i] = ( sortedList[i].timestamp.toTime_t() - t.toTime_t() ) / 60;
284 //qWarning() << "SAK: inconsistency in task " << sortedList[i].task->title << ": " << sortedList[i].timestamp << " > " << t;
285 totHours = -1;
286 return totHours;
287 } else {
288 totHours += Task::hours(sortedList[i].duration);
289 qint64 expected = (t.toTime_t() - sortedList[i].timestamp.toTime_t());
290 qint64 got = Task::hours(sortedList[i].duration)*3600; //seconds
291 qint64 diff = expected - got;
292 if (diff < 0) {
293 overestimation[i] = 0;
294 totOverestimation -= diff;
295 } if (diff < -59) {
296 if (i < count) {
297 overestimation[i+1] = ceil(-diff/120);
298 overestimation[i] = ceil(-diff/120);
300 overestimation[i] = ceil(-diff/120);
301 //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 << ")";
303 t=sortedList[i].timestamp;
306 if (totOverestimation > 60) {
307 totOverestimation /= 3600.0;
309 return totHours;