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");
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();
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();
60 out
.writeEndElement();
62 out
.writeEndElement();
68 void parseHits(QXmlStreamReader
& in
, Task
& task
, const QString subtask
)
70 QMultiMap
<int, Task::Hit
> sortedHits
;
72 QXmlStreamReader::TokenType token
= in
.readNext();
73 if (token
== QXmlStreamReader::StartElement
) {
74 QString elementName
= in
.name().toString();
76 } else if (elementName
.compare("h", Qt::CaseInsensitive
) == 0){
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) {
89 QMultiMap
<int, Task::Hit
>::iterator itr
= sortedHits
.begin();
91 while(itr
!= sortedHits
.end()) {
92 if (itr
.value() == prev
)
93 itr
= sortedHits
.erase(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");
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();
123 QXmlStreamReader::TokenType token
= in
.readNext();
124 if (token
== QXmlStreamReader::StartElement
) {
125 QString elementName
= in
.name().toString();
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) {
138 void parseSubtasks(QXmlStreamReader
& in
, Task
& task
)
141 QXmlStreamReader::TokenType token
= in
.readNext();
142 if (token
== QXmlStreamReader::StartElement
) {
143 QString elementName
= in
.name().toString();
145 } else if (elementName
.compare("subtask", Qt::CaseInsensitive
) == 0) {
146 parseSubtask(in
, task
);
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());
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();
182 task
.dueDate
= QDate::fromString(due
.toString(), "dd/MM/yyyy");
184 QXmlStreamReader::TokenType token
;
186 token
= in
.readNext();
187 if (token
== QXmlStreamReader::StartElement
) {
188 QString elementName
= in
.name().toString();
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
);
198 //qDebug() << "token type " << token;
208 double Task::workedHours(const QDateTime
& from
, const QDateTime
& to
) const
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;
219 worked
+= hours(hit
.duration
); // multiple of quarter of hour
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();
242 totOverestimation
= 0;
243 // ensure tasks are in list
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
;
253 subtaskHours
= HitElement::overestimations(hitlist
, o
, subtaskOverestimation
);
254 subTasks
[hitr
.key()].totHours
= subtaskHours
;
255 totHours
+= subtaskHours
;
256 totOverestimation
+=subtaskOverestimation
;
259 return totHours
>= 0;
262 // QPair<QDateTime, quint8>* Task::findHit(QDateTime t, quint8 d)
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)
274 double HitElement::overestimations(const QList
<HitElement
>& hits
, QVector
<double>& overestimation
, double& totOverestimation
)
276 QList
<HitElement
> sortedList(hits
);
280 QDateTime t
= QDateTime::currentDateTime();
281 overestimation
.resize(sortedList
.count());
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;
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
;
296 overestimation
[i
] = 0;
297 totOverestimation
-= diff
;
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;