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");
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();
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();
59 out
.writeEndElement();
61 out
.writeEndElement();
67 void parseHits(QXmlStreamReader
& in
, Task
& task
, const QString subtask
)
69 QMultiMap
<int, Task::Hit
> sortedHits
;
71 QXmlStreamReader::TokenType token
= in
.readNext();
72 if (token
== QXmlStreamReader::StartElement
) {
73 QString elementName
= in
.name().toString();
75 } else if (elementName
.compare("h", Qt::CaseInsensitive
) == 0){
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) {
88 QMultiMap
<int, Task::Hit
>::iterator itr
= sortedHits
.begin();
90 while(itr
!= sortedHits
.end()) {
91 if (itr
.value() == prev
)
92 itr
= sortedHits
.erase(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");
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();
121 QXmlStreamReader::TokenType token
= in
.readNext();
122 if (token
== QXmlStreamReader::StartElement
) {
123 QString elementName
= in
.name().toString();
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) {
136 void parseSubtasks(QXmlStreamReader
& in
, Task
& task
)
139 QXmlStreamReader::TokenType token
= in
.readNext();
140 if (token
== QXmlStreamReader::StartElement
) {
141 QString elementName
= in
.name().toString();
143 } else if (elementName
.compare("subtask", Qt::CaseInsensitive
) == 0) {
144 parseSubtask(in
, task
);
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());
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();
179 task
.dueDate
= QDate::fromString(due
.toString(), "dd/MM/yyyy");
181 QXmlStreamReader::TokenType token
;
183 token
= in
.readNext();
184 if (token
== QXmlStreamReader::StartElement
) {
185 QString elementName
= in
.name().toString();
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
);
195 //qDebug() << "token type " << token;
205 double Task::workedHours(const QDateTime
& from
, const QDateTime
& to
) const
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;
216 worked
+= hours(hit
.duration
); // multiple of quarter of hour
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();
239 totOverestimation
= 0;
240 // ensure tasks are in list
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
;
250 subtaskHours
= HitElement::overestimations(hitlist
, o
, subtaskOverestimation
);
251 subTasks
[hitr
.key()].totHours
= subtaskHours
;
252 totHours
+= subtaskHours
;
253 totOverestimation
+=subtaskOverestimation
;
256 return totHours
>= 0;
259 // QPair<QDateTime, quint8>* Task::findHit(QDateTime t, quint8 d)
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)
271 double HitElement::overestimations(const QList
<HitElement
>& hits
, QVector
<double>& overestimation
, double& totOverestimation
)
273 QList
<HitElement
> sortedList(hits
);
277 QDateTime t
= QDateTime::currentDateTime();
278 overestimation
.resize(sortedList
.count());
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;
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
;
293 overestimation
[i
] = 0;
294 totOverestimation
-= diff
;
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;