- recover from backup files
[Sak.git] / sakhits.cpp
blob5ff0bd54d6f5c47cb4cd7616508ef060bb6d43f0
1 /*************************-**************************************************
2 * Copyright (C) 2007 by Arrigo Zanette *
3 * zanettea@gmail.com *
4 ***************************************************************************/
6 #include <QtGui>
7 #include <QSettings>
9 #include "sak.h"
10 #include "backupper.h"
11 #include "sakwidget.h"
12 #include "saksubwidget.h"
13 #include "sakmessageitem.h"
14 #include "pixmapviewer.h"
15 #include "timeline.h"
16 #include "backupper.h"
17 #include "piechart.h"
20 //BEGIN Hits >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
22 //BEGIN MyDateItemDelegate
25 MyDateItemDelegate::MyDateItemDelegate(QObject *parent)
26 : QItemDelegate(parent)
30 QWidget *MyDateItemDelegate::createEditor(QWidget *parent,
31 const QStyleOptionViewItem &/* option */,
32 const QModelIndex & index ) const
34 QDateTimeEdit *editor = new QDateTimeEdit(parent);
35 editor->setCalendarPopup(true);
36 editor->setDisplayFormat(DATETIMEFORMAT);
37 editor->setDateTime(QDateTime::fromString(index.data().toString(), DATETIMEFORMAT));
38 return editor;
41 void MyDateItemDelegate::setEditorData(QWidget *editor,
42 const QModelIndex &index) const
44 const QDateTime& value = index.model()->data(index, Qt::DisplayRole).toDateTime();
46 QDateTimeEdit *e = static_cast<QDateTimeEdit*>(editor);
47 e->setDateTime(value);
50 void MyDateItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
51 const QModelIndex &index) const
53 QDateTimeEdit *e = static_cast<QDateTimeEdit*>(editor);
54 e->interpretText();
55 const QDateTime& value = e->dateTime();
57 model->setData(index, value.toString(DATETIMEFORMAT), Qt::EditRole);
60 void MyDateItemDelegate::updateEditorGeometry(QWidget *editor,
61 const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
63 editor->setGeometry(option.rect);
66 //END MyDateItemDelegate
68 //BEGIN TaskItemDelegate
70 TaskItemDelegate::TaskItemDelegate(Sak* sak, QObject *parent)
71 : QItemDelegate(parent), m_sak(sak)
75 QWidget *TaskItemDelegate::createEditor(QWidget *parent,
76 const QStyleOptionViewItem &/* option */,
77 const QModelIndex & index ) const
79 QComboBox *editor = new QComboBox(parent);
80 int j = -1;
81 int i=0;
82 QString current = index.data().toString();
83 foreach(const Task& t, *m_sak->tasks()) {
84 editor->addItem(t.icon, t.title);
85 if (t.title == current)
86 j = i;
88 Q_ASSERT(j = -1);
89 editor->setCurrentIndex(j);
90 return editor;
93 void TaskItemDelegate::setEditorData(QWidget *editor,
94 const QModelIndex &index) const
96 const QString& value ( index.data().toString() );
98 QComboBox *e = static_cast<QComboBox*>(editor);
99 for(int i=0; i<e->count(); i++) {
100 if (e->itemText(i) == value) {
101 e->setCurrentIndex(i);
102 break;
107 void TaskItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
108 const QModelIndex &index) const
110 QComboBox *e = static_cast<QComboBox*>(editor);
111 model->setData(index, e->currentText(), Qt::EditRole);
112 model->setData(index, e->itemIcon(e->currentIndex()), Qt::DecorationRole);
115 void TaskItemDelegate::updateEditorGeometry(QWidget *editor,
116 const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
118 editor->setGeometry(option.rect);
121 //END TaskItemDelegate
124 //BEGIN SubTaskItemDelegate
126 SubTaskItemDelegate::SubTaskItemDelegate(Sak* s, QObject *parent)
127 : QItemDelegate(parent), m_sak(s)
131 QWidget *SubTaskItemDelegate::createEditor(QWidget *parent,
132 const QStyleOptionViewItem &/* option */,
133 const QModelIndex & index ) const
135 QComboBox *editor = new QComboBox(parent);
136 editor->setEditable(true);
138 int j = -1;
139 QString taskTitle = index.model()->index(index.row(), index.column()-1, index.parent()).data().toString();
141 QHash<QString, Task>::iterator titr = m_sak->tasks()->find(taskTitle);
142 if (titr == m_sak->tasks()->end()) {
143 return editor;
145 const Task& task(titr.value());
147 QString current = index.data().toString();
148 QHash< QString, Task::SubTask >::const_iterator itr = task.subTasks.begin(), end = task.subTasks.end();
149 for(int i=0; itr != end; itr++, i++) {
150 editor->addItem(itr->title);
151 if (itr->title == current)
152 j = i;
155 editor->setCurrentIndex(j);
156 return editor;
159 void SubTaskItemDelegate::setEditorData(QWidget *editor,
160 const QModelIndex &index) const
162 const QString& value ( index.data().toString() );
164 QComboBox *e = static_cast<QComboBox*>(editor);
165 for(int i=0; i<e->count(); i++) {
166 if (e->itemText(i) == value) {
167 e->setCurrentIndex(i);
168 break;
173 void SubTaskItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
174 const QModelIndex &index) const
176 QComboBox *e = static_cast<QComboBox*>(editor);
177 model->setData(index, e->currentText(), Qt::EditRole);
178 model->setData(index, e->itemIcon(e->currentIndex()), Qt::DecorationRole);
181 void SubTaskItemDelegate::updateEditorGeometry(QWidget *editor,
182 const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
184 editor->setGeometry(option.rect);
187 //END TaskItemDelegate
191 void Sak::addDefaultHit()
193 QTreeWidgetItem* i = new QTreeWidgetItem;
194 Task::Hit p(QDateTime::currentDateTime(), 0);
195 i->setText(0, p.timestamp.toString(DATETIMEFORMAT));
196 i->setText(1, m_editedTasks.begin().key());
197 i->setIcon(1, m_editedTasks.begin().value().icon);
198 i->setSizeHint(0, QSize(32, 32));
199 i->setText(3, QString("%1").arg(p.duration));
200 i->setFlags(i->flags() | Qt::ItemIsEditable);
201 hitsList->addTopLevelItem( i );
202 m_changedHit=true;
203 m_editedTasks[i->text(1)].hits[i->text(2)] << p;
204 i->setData(1, Qt::UserRole, qVariantFromValue(HitElement(&m_editedTasks[i->text(1)], i->text(2), p.timestamp, p.duration)));
207 void Sak::exportHits()
209 QString fileName = QFileDialog::getSaveFileName();
210 QFile file(fileName);
211 if (!file.open(QIODevice::Truncate|QIODevice::ReadWrite)) {
212 QMessageBox::warning(0, QString("Error saving"), QString("Error opening file %1").arg(fileName));
213 return;
215 QTextStream stream(&file);
216 for (int i=0; i<hitsList->topLevelItemCount(); i++) {
217 QTreeWidgetItem* w = hitsList->topLevelItem ( i );
218 QString name(w->text(1));
219 QDateTime timestamp(QDateTime::fromString(w->text(0), DATETIMEFORMAT));
220 QHash<QString, Task>::iterator titr = m_tasks.find(name);
221 stream << w->text(1) << ";" << w->text(0) << ";" << w->text(2) << ";" << w->text(3) << ";\n";
223 file.flush();
224 file.close();
228 QList<HitElement> Sak::createHitsList(const QDateTime& from , const QDateTime& to )
230 QMap<QDateTime, HitElement> hits;
231 foreach(const Task& t, m_editedTasks) {
232 QHash<QString, QList< Task::Hit> > ::const_iterator hitr = t.hits.begin(), hend = t.hits.end();
233 while(hitr != hend) {
234 const QList< Task::Hit > & l(hitr.value());
235 for (int i=0; i<l.count(); i++) {
236 const QDateTime& d = l[i].timestamp;
237 if ( (!from.isValid() || d >= from) && ( !to.isValid() || d <= to) ) {
238 hits.insertMulti(d, HitElement((Task*)&t, hitr.key(), d, l[i].duration));
241 hitr++;
244 return hits.values();
248 QMap<double,QPair<Task*, QString> > Sak::createSummaryList(const QList<HitElement>& hits)
250 QHash<QPair<Task*, QString>, double> summaryMap;
251 foreach(const HitElement& hit, hits) {
252 if (!hit.subtask.isEmpty()) {
253 summaryMap[QPair<Task*, QString>(hit.task, hit.subtask)] += Task::hours(hit.duration);
255 summaryMap[QPair<Task*, QString>(hit.task, "")] += Task::hours(hit.duration);
257 QMap<double, QPair<Task*, QString> > summaryOrderedMap;
258 QHash<QPair<Task*, QString>, double>::const_iterator itr = summaryMap.begin();
259 while(itr != summaryMap.end()) {
260 summaryOrderedMap.insertMulti(itr.value(), itr.key());
261 itr++;
263 return summaryOrderedMap;
268 void Sak::saveHitChanges()
270 if (m_changedHit) {
271 if ( QMessageBox::question ( 0, "Hit list changed", "Hit list has changed: do you want to save changes?", QMessageBox::Save | QMessageBox::Discard, QMessageBox::Discard) == QMessageBox::Save ) {
272 m_tasks = m_editedTasks;
273 } else m_editedTasks = m_tasks; // undo changes
274 m_changedHit=false;
275 selectedStartDate(QDate());
276 populateTasks();
277 // add subtasks, if missing
279 QHash<QString, Task>::iterator itr = m_tasks.begin();
280 while(itr != m_tasks.end()) {
281 itr->updateSubTasks();
282 itr++;
288 bool Sak::hitsListEventFilter(QEvent* e)
290 if (e->type() == QEvent::ContextMenu) {
291 QContextMenuEvent* me = dynamic_cast<QContextMenuEvent*>(e);
292 if (!me) return false;
293 m_addHitMenu->popup(me->globalPos());
294 return true;
295 } else if (e->type() == QEvent::KeyRelease) {
296 QKeyEvent* ke = dynamic_cast<QKeyEvent*>(e);
297 if (!ke) return false;
298 if ( !hitsList->hasFocus() || (ke->key() != Qt::Key_Delete && ke->key() != Qt::Key_Backspace) ) return false;
299 QList<QTreeWidgetItem*> selected = hitsList->selectedItems();
300 if (selected.isEmpty()) return false;
301 foreach(QTreeWidgetItem* ii, selected) {
302 hitsListItemChanged(hitsList->takeTopLevelItem(hitsList->indexOfTopLevelItem (ii)), -1);
304 return true;
305 } else if (e->type() == QEvent::Hide) {
306 saveHitChanges();
308 return false;
311 void Sak::selectTodayDate()
313 QDateTime dt1 = QDateTime::currentDateTime();
314 dt1.setTime(QTime(0,0,0));
315 QDateTime dt2 = dt1.addDays(1);
316 selectedStartDate(dt1.date());
317 selectedEndDate(dt2.date());
320 void Sak::selectThisWeeDate()
322 QDateTime dt1 = QDateTime::currentDateTime();
323 dt1.setTime(QTime(0,0,0));
324 dt1 = dt1.addDays(-dt1.date().dayOfWeek()+1);
325 QDateTime dt2 = dt1.addDays(7);
326 selectedStartDate(dt1.date());
327 selectedEndDate(dt2.date());
330 void Sak::selectThisMonthDate()
332 QDateTime dt1 = QDateTime::currentDateTime();
333 dt1.setTime(QTime(0,0,0));
334 dt1 = dt1.addDays(-dt1.date().day()+1);
335 QDateTime dt2 = dt1.addMonths(1);
336 selectedStartDate(dt1.date());
337 selectedEndDate(dt2.date());
340 void Sak::selectLastWeekDate()
342 QDateTime dt2 = QDateTime::currentDateTime();
343 dt2.setTime(QTime(23,59,59));
344 QDateTime dt1 = dt2.addDays(-7);
345 selectedStartDate(dt1.date());
346 selectedEndDate(dt2.date());
349 void Sak::selectLastMonthDate()
351 QDateTime dt2 = QDateTime::currentDateTime();
352 dt2.setTime(QTime(23,59,59));
353 QDateTime dt1 = dt2.addDays(-30);
354 selectedStartDate(dt1.date());
355 selectedEndDate(dt2.date());
358 void Sak::selectedStartDate(const QDate& _date)
360 QDate date = _date;
361 if (!date.isValid()) {
362 date = cal1->selectedDate();
365 if (sender() != cal1) {
366 cal1->setSelectedDate(date);
368 if (sender() != cal3) {
369 cal3->setSelectedDate(date);
371 if (cal2->selectedDate() < cal1->selectedDate()) {
372 cal2->setSelectedDate(date.addDays(1));
373 cal4->setSelectedDate(date.addDays(1));
375 cal2->setMinimumDate(date);
376 cal4->setMinimumDate(date);
377 const QList<HitElement>& hits ( createHitsList(QDateTime(cal1->selectedDate()), QDateTime(cal2->selectedDate())) );
378 populateHitsList(hits);
379 if (hitsTimeline)
380 hitsTimeline->setPeriod(QDateTime(cal1->selectedDate()), QDateTime(cal2->selectedDate()));
383 void Sak::selectedEndDate(const QDate& _date)
385 QDate date = _date;
386 if (!date.isValid())
387 date = cal1->selectedDate();
389 if (sender() != cal2) {
390 cal2->setSelectedDate(date);
392 if (sender() != cal4) {
393 cal4->setSelectedDate(date);
395 if (cal1->selectedDate() > date) {
396 cal2->setSelectedDate(cal1->selectedDate().addDays(1));
397 cal4->setSelectedDate(cal1->selectedDate().addDays(1));
399 const QList<HitElement>& hits ( createHitsList(QDateTime(cal1->selectedDate()), QDateTime(cal2->selectedDate())) );
401 populateHitsList(hits);
402 hitsTimeline->setPeriod(QDateTime(cal1->selectedDate()), QDateTime(cal2->selectedDate()));
406 void Sak::hitsListItemChanged(QTreeWidgetItem* i, int column)
408 hitsList->blockSignals(true);
409 m_changedHit = true;
410 // save change in structure m_editedTasks
411 while(1) {
412 const HitElement& origHit = i->data(1, Qt::UserRole).value<HitElement>();
413 // find hit in m_editedTasks
414 Q_ASSERT(origHit.task);
415 Task& t(*origHit.task);
416 Q_ASSERT(m_editedTasks.contains(t.title));
417 Task& et(m_editedTasks[t.title]);
419 QList< Task::Hit >& origList(et.hits[origHit.subtask]);
421 int hitPosition = origList.indexOf(Task::Hit(origHit.timestamp, origHit.duration));
422 if (hitPosition==-1) {
423 qWarning() << "CANNOT FIND IN TASK LIST "<< t.title << origHit.subtask << origHit.timestamp << origHit.duration;
424 break;
426 if (!m_editedTasks.contains(i->text(1))) {
427 i->setText(1, t.title);
428 qDebug() << "Task " << i->text(1) << " does not exists -> undo change";
429 } else if (column == -1) {
430 qDebug() << "remove hit from task " << t.title;
431 origList.takeAt(hitPosition);
432 } else {
433 qDebug() << "remove hit from task " << t.title;
434 origList.takeAt(hitPosition);
435 Task& nt(m_editedTasks[i->text(1)]);
436 Task::Hit p(QDateTime::fromString(i->text(0), DATETIMEFORMAT), i->text(3).toUInt());
437 QString subTask = column == 2 || nt.subTasks.contains(i->text(2)) ? i->text(2) : "";
438 i->setData(1, Qt::UserRole, qVariantFromValue(HitElement(&nt, subTask, p.timestamp, p.duration)));
439 qDebug() << "insert hit into task " << i->text(1) << p.timestamp;
440 nt.hits[subTask] << p;
441 if (subTask != i->text(2)) {
442 i->setText(2, subTask);
444 if (nt.bgColor != i->backgroundColor(0) || nt.fgColor != i->foreground(0).color()) {
445 for(int j=0; j<4; j++) {
446 i->setBackground(j,nt.bgColor);
447 i->setForeground(j,nt.fgColor);
451 break;
453 if (column == 1) {
454 if (!i) {}
455 else if (m_editedTasks.contains(i->text(column))) {
456 i->setIcon(column, m_editedTasks[i->text(column)].icon);
457 } else {
458 i->setIcon(column, QIcon());
461 hitsList->blockSignals(false);
463 if ( !m_updatingFromTimeline ) {
464 // update timeline
465 const QList<HitElement>& hits ( createHitsList(QDateTime(cal1->selectedDate()), QDateTime(cal2->selectedDate())) );
466 populateHitsTimeline(hits, hitsTimeline);
470 void Sak::hitsSelectedInList(QTreeWidgetItem* current, QTreeWidgetItem* /*prev*/)
472 if (current) {
473 // clear current selection
474 QList<QGraphicsItem*> sitems = hitsTimeline->scene()->selectedItems();
475 for(int j=0; j<sitems.count(); j++) {
476 HitItem* tmp = dynamic_cast<HitItem*>(sitems[j]);
477 if (tmp) {
478 tmp->setZValue(tmp->timestamp().toTime_t());
479 tmp->setSelected(false);
484 QPointF center (QDateTime::fromString(current->text(0), DATETIMEFORMAT).toTime_t() / 60.0, 0);
485 int duration = current->text(3).toUInt();
486 QList<QGraphicsItem*> items = hitsTimeline->scene()->items();
487 for(int i=0; i<items.count(); i++) {
488 HitItem* hitem = dynamic_cast<HitItem*>(items[i]);
489 if (hitem && hitem->timestamp() == QDateTime::fromString(current->text(0), DATETIMEFORMAT) && hitem->task()->title == current->text(1) && hitem->subtask() == current->text(2) && hitem->duration() == duration) {
490 hitem->setZValue(1e20);
491 hitem->setSelected(true);
492 break;
495 hitsTimeline->centerOn(center);
499 void Sak::hitsSelectedInTimeline(HitItem* hitem)
501 m_updatingFromTimeline = true;
503 const Task* t = hitem->task();
504 QList<QTreeWidgetItem *> items = hitsList->findItems(hitem->timestamp().toString(DATETIMEFORMAT), Qt::MatchExactly, 0);
505 qDebug() << "look for " << hitem->timestamp().toString(DATETIMEFORMAT);
507 QString tmp(QString ("%1").arg(hitem->duration()));
508 qDebug() << "duration " << tmp;
509 foreach(QTreeWidgetItem* item, items) {
510 if (item->text(1) == t->title && item->text(2) == hitem->subtask() && item->text(3) == tmp) {
511 hitsList->clearSelection();
512 item->setSelected(true);
513 if (hitem->timestamp() != hitem->newTimestamp() || hitem->duration() != hitem->newDuration()) {
514 item->setText(0, hitem->newTimestamp().toString(DATETIMEFORMAT));
515 item->setText(3, QString("%1").arg(hitem->newDuration()));
516 hitem->commitChanges();
518 hitsList->scrollToItem(item);
522 m_updatingFromTimeline = false;
527 void Sak::populateHitsList(const QList<HitElement>& hits, QTreeWidget* theHitsList )
529 if (theHitsList == 0)
530 theHitsList = hitsList;
531 Q_ASSERT(theHitsList);
532 saveHitChanges();
533 theHitsList->clear();
535 QVector<double> o;
536 double totOverestimation;
537 HitElement::overestimations(hits, o, totOverestimation);
538 QList<QTreeWidgetItem*> widgets;
539 int i=0;
540 foreach(const HitElement& hit, hits) {
541 QTreeWidgetItem* w = new QTreeWidgetItem;
542 if (hit.task->title == "<away>") {
543 hit.task->fgColor = Qt::gray;
545 w->setText(0, hit.timestamp.toString(DATETIMEFORMAT));
546 w->setSizeHint(0, QSize(24, 24));
547 w->setText(1, hit.task->title);
548 w->setData(1, Qt::UserRole, qVariantFromValue(hit));
549 w->setText(2, hit.subtask);
550 w->setText(3, QString("%1").arg(hit.duration));
551 for(int i=0; i<4; i++) {
552 w->setBackground(i,hit.task->bgColor);
553 w->setForeground(i,hit.task->fgColor);
555 #if MARK_OVERESTIMATIONS
556 if (o[i] != 0) {
557 w->setBackground(0,Qt::red);
558 w->setBackground(1,Qt::red);
559 w->setBackground(2,Qt::red);
560 w->setBackground(3,Qt::red);
561 w->setBackground(4,Qt::red);
562 w->setText(4,QString("%1").arg(o[i]));
564 w->setText(4,QString("%1").arg(o[i]));
565 #endif
566 //w->setIcon(1, hit.task->icon);
567 if (hit.editable)
568 w->setFlags(w->flags() | Qt::ItemIsEditable);
569 else w->setDisabled(true);
570 widgets << w;
571 i++;
573 theHitsList->addTopLevelItems(widgets);
574 widgets.clear();
576 if (summaryList) {
577 theHitsList = summaryList;
578 theHitsList->clear();
579 const QMap<double, QPair<Task*, QString> >& map(createSummaryList(hits));
580 QMap<double, QPair<Task*, QString> >::const_iterator itr = map.begin();
581 QHash<Task*, QTreeWidgetItem*> topLevels;
582 QList<QTreeWidgetItem*> widgets;
583 QList<QLabel*> labels;
584 for(; itr != map.end(); itr++) { // first be sure to insert top levels
585 QTreeWidgetItem* topLevel;
586 if (!topLevels.contains(itr.value().first)) {
587 QTreeWidgetItem* w = new QTreeWidgetItem(QTreeWidgetItem::UserType);
588 Task* t = itr.value().first;
589 w->setData(0, Qt::UserRole, t->title);
590 QLabel* l = t->url.size() ? new QLabel( QString("<a href=\"%2\" style=\"color:%3\" >%1</a>").arg( t->title ).arg(t->url).arg(t->fgColor.name())) : new QLabel(QString("<span style=\"color:%2\">%1</span>").arg(t->title).arg(t->fgColor.name()));
591 l->setOpenExternalLinks(true);
593 w->setData(0, Qt::UserRole, t->title);
594 // w->setIcon(0, t->icon);
595 w->setBackgroundColor(0, t->bgColor);
596 w->setForeground(0, t->fgColor);
597 w->setBackgroundColor(1, t->bgColor);
598 w->setForeground(1, t->fgColor);
599 topLevels[itr.value().first] = w;
600 widgets << w;
601 labels << l;
603 topLevel = topLevels[itr.value().first];
604 if (itr.value().second == "")
605 topLevel->setText(1, QString("%1").arg(topLevel->text(1).toInt() + itr.key(), 4, 'f', 2, ' '));
606 if (!itr.value().second.isEmpty()) {
607 QTreeWidgetItem* w = new QTreeWidgetItem(QTreeWidgetItem::UserType);
608 Task* t = itr.value().first;
609 w->setText(0, itr.value().second);
610 w->setText(1, QString("%1").arg(itr.key(), 4, 'f', 2, ' '));
611 w->setBackgroundColor(0, t->bgColor);
612 w->setForeground(0, t->fgColor);
613 w->setBackgroundColor(1, t->bgColor);
614 w->setForeground(1, t->fgColor);
615 topLevel->addChild(w);
619 theHitsList->addTopLevelItems(topLevels.values());
620 for( int i=0; i<widgets.size(); i++) {
621 theHitsList->setItemWidget(widgets.at(i), 0, labels.at(i));
625 populateHitsTimeline(hits, hitsTimeline);
626 summaryChart->setHits(hits);
629 void Sak::populateHitsTimeline(const QList<HitElement>& hits, Timeline* timeline )
631 if (!timeline) return;
633 m_updatingFromTimeline = true;
635 QGraphicsScene* scene = timeline->scene();
636 if (scene) {
637 QList<QGraphicsItem*> items = scene->items();
638 foreach(QGraphicsItem* item, items) {
639 if (dynamic_cast<HitItem*>(item)) {
640 scene->removeItem(item);
641 delete item;
644 foreach(HitElement e, hits) {
645 HitItem* item = new HitItem(e.task, e.timestamp, e.duration, e.subtask);
646 connect(item, SIGNAL(changed()), timeline, SLOT(selectionChanged()));
647 scene->addItem(item);
651 m_updatingFromTimeline = false;
656 void Sak::interactiveMergeHits()
658 if (!m_incremental->foundPieces.count()) return;
659 QDialog mergeDialog;
660 mergeDialog.setWindowTitle("Merge sparse hits");
661 mergeDialog.setModal(true);
662 QTreeWidget* theHitsList = newHitsList();
663 QMap<QDateTime, HitElement> hits;
664 QMap<QDateTime, Incremental::Hit >::iterator itr = m_incremental->foundPieces.begin();
665 while(itr != m_incremental->foundPieces.end()) {
666 QHash<QString, Task>::iterator titr = m_tasks.find(itr.value().task);
667 if (titr == m_tasks.end()) {
668 qDebug() << "Discard piece for unknown task " << itr.value().task;
669 } else {
670 hits.insertMulti(itr.key(), HitElement(&titr.value(), itr.value().subtask, itr.key(), itr.value().duration));
672 itr++;
674 if (hits.count() == 0) return;
676 qDebug() << "hits: " << hits.count() << hits.begin().key() << (--hits.end()).key();
677 QList<HitElement> okHits (createHitsList(hits.begin().key(), (--hits.end()).key()));
678 QMap<QDateTime, HitElement> tmpHits;
679 for(int i=0; i<okHits.count(); i++) {
680 HitElement& cmp(okHits[i]);
681 QMap<QDateTime, HitElement>::iterator itr = hits.find(cmp.timestamp);
682 bool skip=false;
683 while(itr != hits.end() && itr.key() == cmp.timestamp) {
684 const HitElement& cur ( itr.value() );
685 if (cmp.task->title == cur.task->title && cmp.subtask == cur.subtask && cmp.duration == cur.duration) {
686 itr = hits.erase(itr);
687 skip = true;
688 } else itr++;
690 if (skip) continue;
691 okHits[i].editable=false;
692 tmpHits.insertMulti(okHits[i].timestamp, okHits[i]);
695 if (!hits.count()) return;
696 else hits.unite(tmpHits);
698 populateHitsList(hits.values(), theHitsList);
699 mergeDialog.setMinimumWidth(750);
700 mergeDialog.setMinimumHeight(600);
701 QVBoxLayout mainLayout(&mergeDialog);
702 mainLayout.addWidget(theHitsList);
703 QPushButton* ok = new QPushButton("Do merge!");
704 QPushButton* cancel = new QPushButton("Cancel");
705 QHBoxLayout* buttons = new QHBoxLayout;
706 buttons->addWidget(ok);
707 buttons->addWidget(cancel);
708 mainLayout.addLayout(buttons);
709 connect(ok, SIGNAL(clicked()), &mergeDialog, SLOT(accept()));
710 connect(cancel, SIGNAL(clicked()), &mergeDialog, SLOT(reject()));
711 // do auto merging
712 if ( mergeDialog.exec() ) {
713 for (int i=0; i<theHitsList->topLevelItemCount(); i++) {
714 QTreeWidgetItem* w = theHitsList->topLevelItem ( i );
715 if (!w->isDisabled()) {
716 QString name(w->text(1));
717 QDateTime timestamp(QDateTime::fromString(w->text(0), DATETIMEFORMAT));
718 int duration (w->text(3).toUInt());
719 QHash<QString, Task>::iterator titr = m_tasks.find(name);
720 Q_ASSERT(titr != m_tasks.end());
721 (*titr).hits[w->text(2)] << Task::Hit(timestamp, duration);
724 flush();
725 m_incremental->clearMergedPieces();