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());
17 out
.writeAttribute("url", QUrl::toPercentEncoding(task
.url
));
19 out
.writeStartElement("Description");
20 out
.writeCharacters(task
.description
);
21 out
.writeEndElement();
23 out
.writeStartElement("Icon");
25 QBuffer
buffer(&bytes
);
26 buffer
.open(QIODevice::WriteOnly
);
27 task
.icon
.save(&buffer
, "PNG"); // writes pixmap into bytes in PNG format
28 out
.writeCharacters(bytes
.toBase64());
29 out
.writeEndElement();
31 out
.writeStartElement("Subtasks");
32 QHash
< QString
, QList
< Task::Hit
> >::const_iterator itr
= task
.hits
.begin(), end
= task
.hits
.end();
34 out
.writeStartElement("Subtask");
35 out
.writeAttribute("title", itr
.key());
36 QHash
< QString
, Task::SubTask
>::const_iterator sitr
= task
.subTasks
.find(itr
.key());
37 if (sitr
!= task
.subTasks
.end()) {
38 const Task::SubTask
& st(sitr
.value());
39 if (st
.bgColor
.isValid())
40 out
.writeAttribute("bgcolor", st
.bgColor
.name());
41 if (st
.fgColor
.isValid())
42 out
.writeAttribute("fgcolor", st
.fgColor
.name());
43 out
.writeAttribute("active", QString("%1").arg(st
.active
));
44 out
.writeStartElement("Description");
45 out
.writeCharacters(st
.description
);
46 out
.writeEndElement();
48 out
.writeStartElement("Hits");
49 const QList
<Task::Hit
>& h(itr
.value());
50 for(int i
=h
.count()-1; i
>=0; i
--) {
51 if (h
[i
].timestamp
.isValid()) {
52 out
.writeEmptyElement("h");
53 out
.writeAttribute("t", h
[i
].timestamp
.toString(DATETIMEFORMAT
));
54 out
.writeAttribute("d", QString("%1").arg(h
[i
].duration
));
57 out
.writeEndElement();
58 out
.writeEndElement();
61 out
.writeEndElement();
63 out
.writeEndElement();
69 void parseHits(QXmlStreamReader
& in
, Task
& task
, const QString subtask
)
71 QMultiMap
<int, Task::Hit
> sortedHits
;
73 QXmlStreamReader::TokenType token
= in
.readNext();
74 if (token
== QXmlStreamReader::StartElement
) {
75 QString elementName
= in
.name().toString();
77 } else if (elementName
.compare("h", Qt::CaseInsensitive
) == 0){
79 QXmlStreamAttributes attrs
= in
.attributes();
80 hit
.timestamp
= QDateTime::fromString(attrs
.value("t").toString(), DATETIMEFORMAT
);
81 hit
.duration
= attrs
.value("d").toString().toUInt();
82 sortedHits
.insertMulti(hit
.timestamp
.toTime_t(), hit
);
84 } else if (token
== QXmlStreamReader::EndElement
&& in
.name().toString().compare("hits", Qt::CaseInsensitive
) == 0) {
90 QMultiMap
<int, Task::Hit
>::iterator itr
= sortedHits
.begin();
92 while(itr
!= sortedHits
.end()) {
93 if (itr
.value() == prev
)
94 itr
= sortedHits
.erase(itr
);
100 task
.hits
[subtask
] = sortedHits
.values();
105 void parseSubtask(QXmlStreamReader
& in
, Task
& task
)
107 QXmlStreamAttributes attrs
= in
.attributes();
108 QString title
= attrs
.value("title").toString();
109 QStringList tags
= attrs
.value("tags").toString().split(QRegExp("[,:]"));
110 Task::SubTask
& st(task
.subTasks
[title
]);
111 QStringRef bgColor
= attrs
.value("bgcolor");
112 QStringRef fgColor
= attrs
.value("fgcolor");
113 QStringRef active
= attrs
.value("active");
115 if (!bgColor
.isEmpty())
116 st
.bgColor
= QColor(bgColor
.toString());
117 if (!fgColor
.isEmpty())
118 st
.fgColor
= QColor(fgColor
.toString());
119 if (!active
.isEmpty())
120 st
.active
= active
.toString().toUInt();
124 QXmlStreamReader::TokenType token
= in
.readNext();
125 if (token
== QXmlStreamReader::StartElement
) {
126 QString elementName
= in
.name().toString();
128 } else if (elementName
.compare("description", Qt::CaseInsensitive
) == 0){
129 st
.description
= in
.readElementText();
130 } else if (elementName
.compare("hits", Qt::CaseInsensitive
) == 0) {
131 parseHits(in
, task
, st
.title
);
133 } else if (token
== QXmlStreamReader::EndElement
&& in
.name().toString().compare("subtask", Qt::CaseInsensitive
) == 0) {
139 void parseSubtasks(QXmlStreamReader
& in
, Task
& task
)
142 QXmlStreamReader::TokenType token
= in
.readNext();
143 if (token
== QXmlStreamReader::StartElement
) {
144 QString elementName
= in
.name().toString();
146 } else if (elementName
.compare("subtask", Qt::CaseInsensitive
) == 0) {
147 parseSubtask(in
, task
);
150 //qDebug() << " > token type " << token;
156 QXmlStreamReader
& operator>> ( QXmlStreamReader
& in
, Task
& task
)
158 QXmlStreamReader::TokenType token
= in
.readNext();
159 if (!in
.atEnd() && token
!= QXmlStreamReader::StartElement
) {
160 in
.raiseError("Expected start of Task element, got " + QString("%1").arg(token
));
162 if (!in
.atEnd() && in
.name().toString().compare("Task",Qt::CaseInsensitive
)) {
163 in
.raiseError("Expected token name \"Task\", got " + in
.name().toString());
166 QXmlStreamAttributes attrs
= in
.attributes();
167 task
.title
= attrs
.value("title").toString();
168 task
.tags
= attrs
.value("tags").toString().split(QRegExp("[:,]"));
169 task
.url
= QUrl::fromPercentEncoding(attrs
.value("url").toString().toUtf8());
170 QStringRef bgColor
= attrs
.value("bgcolor");
171 QStringRef fgColor
= attrs
.value("fgcolor");
172 QStringRef active
= attrs
.value("active");
173 QStringRef due
= attrs
.value("due");
174 QStringRef estimated
= attrs
.value("estimated");
175 if (!bgColor
.isEmpty())
176 task
.bgColor
= QColor(bgColor
.toString());
177 if (!fgColor
.isEmpty())
178 task
.fgColor
= QColor(fgColor
.toString());
179 if (!active
.isEmpty())
180 task
.active
= active
.toString().toUInt();
181 if (!estimated
.isEmpty())
182 task
.estimatedHours
= estimated
.toString().toUInt();
184 task
.dueDate
= QDate::fromString(due
.toString(), "dd/MM/yyyy");
186 QXmlStreamReader::TokenType token
;
188 token
= in
.readNext();
189 if (token
== QXmlStreamReader::StartElement
) {
190 QString elementName
= in
.name().toString();
192 } else if (elementName
.compare("description", Qt::CaseInsensitive
) == 0) {
193 task
.description
= in
.readElementText();
194 } else if (elementName
.compare("icon", Qt::CaseInsensitive
) == 0) {
195 task
.icon
.loadFromData(QByteArray::fromBase64(in
.readElementText().toAscii()), "PNG");
196 } else if (elementName
.compare("subtasks", Qt::CaseInsensitive
) == 0) {
197 parseSubtasks(in
, task
);
200 //qDebug() << "token type " << token;
210 double Task::workedHours(const QDateTime
& from
, const QDateTime
& to
) const
213 QHash
<QString
, QList
<Task::Hit
> >::const_iterator hitr
= hits
.begin(), hend
= hits
.end();
214 while(hitr
!= hend
) {
215 const QList
<Task::Hit
>& l(hitr
.value());
216 for(int i
=l
.count()-1; i
>=0; i
--) {
217 const Task::Hit
& hit ( l
[i
] );
218 if ( hit
.timestamp
< from
) return worked
;
219 if ( hit
.timestamp
> to
) continue;
221 worked
+= hours(hit
.duration
); // multiple of quarter of hour
230 void Task::updateSubTasks()
232 const QList
<QString
>& subtasks( hits
.keys() );
233 for(int i
=0; i
<subtasks
.count(); i
++) {
234 if (!subTasks
.contains(subtasks
[i
])) {
235 subTasks
[subtasks
[i
]].title
= subtasks
[i
];
240 bool Task::checkConsistency()
242 QHash
<QString
, QList
<Task::Hit
> >::const_iterator hitr
= hits
.begin(), hend
= hits
.end();
244 totOverestimation
= 0;
245 // ensure tasks are in list
247 while(hitr
!= hend
) {
248 const QList
<Task::Hit
>& l(hitr
.value());
249 QList
<HitElement
> hitlist
;
250 for(int i
=0; i
<l
.count(); i
++) {
251 hitlist
<< HitElement(this, hitr
.key(), l
[i
].timestamp
, l
[i
].duration
);
253 double subtaskHours
, subtaskOverestimation
;
255 subtaskHours
= HitElement::overestimations(hitlist
, o
, subtaskOverestimation
);
256 subTasks
[hitr
.key()].totHours
= subtaskHours
;
257 totHours
+= subtaskHours
;
258 totOverestimation
+=subtaskOverestimation
;
261 return totHours
>= 0;
264 // QPair<QDateTime, quint8>* Task::findHit(QDateTime t, quint8 d)
266 // QList< QPair<QDateTime, quint8> >::const_iterator itr = hits.begin(), end = hits.end();
267 // for (int i=0; i<hits.count(); i++) {
268 // if (hits[i].first == t && hits[i].second == d)
276 double HitElement::overestimations(const QList
<HitElement
>& hits
, QVector
<double>& overestimation
, double& totOverestimation
)
278 QList
<HitElement
> sortedList(hits
);
282 QDateTime t
= QDateTime::currentDateTime();
283 overestimation
.resize(sortedList
.count());
285 int count
= sortedList
.count()-1;
286 for (int i
=count
; i
>=0; i
--) {
287 if (sortedList
[i
].timestamp
> t
) {
288 overestimation
[i
] = ( sortedList
[i
].timestamp
.toTime_t() - t
.toTime_t() ) / 60;
289 //qWarning() << "SAK: inconsistency in task " << sortedList[i].task->title << ": " << sortedList[i].timestamp << " > " << t;
293 totHours
+= Task::hours(sortedList
[i
].duration
);
294 qint64 expected
= (t
.toTime_t() - sortedList
[i
].timestamp
.toTime_t());
295 qint64 got
= Task::hours(sortedList
[i
].duration
)*3600; //seconds
296 qint64 diff
= expected
- got
;
298 overestimation
[i
] = 0;
299 totOverestimation
-= diff
;
302 overestimation
[i
+1] = qCeil(-diff
/120);
303 overestimation
[i
] = qCeil(-diff
/120);
305 overestimation
[i
] = qCeil(-diff
/120);
306 //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 << ")";
308 t
=sortedList
[i
].timestamp
;
311 if (totOverestimation
> 60) {
312 totOverestimation
/= 3600.0;