workingOn action
[Sak.git] / sak.cpp
blob2a474033405c332cb330e760785e9c3e00f4af5c
1 /***************************************************************************
2 * Copyright (C) 2007 by Arrigo Zanette *
3 * zanettea@gmail.com *
4 ***************************************************************************/
7 #include <QtGui>
8 #include <QCryptographicHash>
9 #include <QSettings>
10 #include <QGraphicsEllipseItem>
11 #include <QProgressDialog>
12 #include <cassert>
14 #include "sak.h"
15 #include "sakwidget.h"
16 #include "saksubwidget.h"
17 #include "sakmessageitem.h"
18 #include "pixmapviewer.h"
19 #include "timeline.h"
20 #include "backupper.h"
21 #include "piechart.h"
22 #ifdef USELIBGMAIL
23 #include "gmailstorage/gmailpyinterface.h"
24 #else
25 #include "gmailstorage/gmailmyinterface.h"
26 #endif
28 //END Task <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
31 // GView
32 #include <QtOpenGL>
34 #if defined(Q_WS_X11)
35 #include <QX11Info>
36 namespace X11
38 #include <X11/Xlib.h>
39 #undef KeyPress
40 #undef KeyRelease
41 static Window CurrentFocusWindow;
42 static int CurrentRevertToReturn;
44 #endif
46 static int grabbed;
49 class GView : public QGraphicsView
51 public:
52 // GView() {
53 // if (QGLFormat::hasOpenGL()) {
54 // qDebug() << "Using OpenGL";
55 // QGLWidget* w = new QGLWidget;
56 // w->setAttribute(Qt::WA_TranslucentBackground, true);
57 // setViewport(w);
58 // }
59 // }
60 // ~GView() {
61 // delete this->viewport();
62 // }
63 void drawBackground(QPainter* p, const QRectF & rect) {
64 viewport()->setAttribute(Qt::WA_TranslucentBackground, true);
65 setAttribute(Qt::WA_NoSystemBackground, true);
66 viewport()->setAttribute(Qt::WA_NoSystemBackground, true);
67 setAttribute(Qt::WA_TranslucentBackground, true);
68 if (backgroundPixmap.isNull()) {
69 QBrush brush(QColor(100,0,0,200));
70 p->setCompositionMode(QPainter::CompositionMode_Source);
71 p->fillRect(rect, brush);
72 } else {
73 p->drawPixmap(rect, backgroundPixmap, rect.translated(backgroundPixmap.rect().center() - viewport()->rect().center()));
76 QPixmap backgroundPixmap;
80 //BEGIN Sak basic >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
82 Sak::Sak(QObject* parent)
83 : QObject(parent)
84 , m_timerId(0)
85 , m_timeoutPopup(0)
86 , m_getFocusTimer(0)
87 , m_stopped(false)
88 , m_settings(0)
89 , m_changedHit(false)
90 , m_changedTask(false)
91 , m_subtaskView(false)
93 workingOnDeclared = 0;
94 m_desktopRect = qApp->desktop()->screenGeometry(QPoint(0,0));
96 m_subtaskCompleter = 0;
97 summaryList = hitsList = 0; trayIcon=0;
98 init();
100 if (QCoreApplication::arguments().contains("--clear")) {
101 QHash<QString, Task>::iterator itr = m_tasks.begin();
102 while(itr != m_tasks.end()) {
103 itr->hits.clear();
104 itr++;
108 if (m_tasks.count() <= 0)
109 m_settings->show();
111 m_previewing = false;
112 m_changedHit = false;
113 m_timerId = 0;
114 m_autoSaveTimer = startTimer(1000 * 60 * 45); // every 45 minutes
115 start();
117 // Need to go here, or after plasma reboot the icon will disappear
118 trayIconMenu = new QMenu(m_settings);
119 //trayIconMenu->addAction(minimizeAction);
120 //trayIconMenu->addAction(maximizeAction);
121 //trayIconMenu->addAction(restoreAction);
122 //trayIconMenu->addSeparator();
123 trayIconMenu->addAction(startAction);
124 trayIconMenu->addAction(stopAction);
125 trayIconMenu->addAction(workingOnAction);
126 trayIconMenu->addAction(flushAction);
127 trayIconMenu->addSeparator();
128 trayIconMenu->addAction(quitAction);
129 trayIcon = new QSystemTrayIcon(this);
130 trayIcon->setContextMenu(trayIconMenu);
131 trayIcon->setIcon( QIcon(":/images/icon.png") );
132 trayIcon->setToolTip( tr("Sistema Anti Kazzeggio") );
133 trayIcon->show();
134 connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(trayIconActivated(QSystemTrayIcon::ActivationReason)));
135 trayIcon->installEventFilter(this);
138 m_settings->setWindowIcon( QIcon(":/images/icon.png") );
139 m_settings->setWindowTitle("SaK - Sistema Anti Kazzeggio");
142 void Sak::init()
144 m_backupper = new Backupper;
145 m_incremental = new Incremental;
146 #ifdef USEGMAIL
147 m_gmail = new GmailPyInterface;
148 #else
149 m_gmail = NULL;
150 #endif
152 // load the data model
153 QSettings settings(QSettings::IniFormat, QSettings::UserScope, "ZanzaSoft", "SAK");
154 QByteArray tasksArray = settings.value("tasks").toByteArray();
155 QDataStream stream(&tasksArray, QIODevice::ReadWrite);
156 stream.setVersion(QDataStream::Qt_4_3);
159 { // read locastasks
160 QDir saveDir(QFileInfo(settings.fileName()).dir());
161 saveDir.mkdir("SakTasks");
162 saveDir.cd("SakTasks");
163 QStringList nameFilters;
164 nameFilters << "*.xml";
165 QStringList files = saveDir.entryList( nameFilters, QDir::Files);
166 foreach (QString taskXmlFileName, files) {
167 Task t( loadTaskFromFile(saveDir.filePath(taskXmlFileName)) );
168 m_tasks[t.title] = t;
173 // add subtasks, if missing
175 QHash<QString, Task>::iterator itr = m_tasks.begin();
176 while(itr != m_tasks.end()) {
177 itr->updateSubTasks();
178 itr++;
182 // reset awayTask
183 Task & awayTask = m_tasks["<away>"];
184 awayTask.title = "<away>";
185 awayTask.fgColor = Qt::gray;
186 awayTask.bgColor = Qt::white;
187 awayTask.icon = QPixmap(":/images/away.png");
189 m_editedTasks = m_tasks;
192 hitsTimeline = 0;
193 //merge piecies
194 interactiveMergeHits();
196 m_editedTasks = m_tasks;
198 setupSettingsWidget();
200 QPixmap askingLadyPixmap;
201 QPixmap viewBackgroundPixmap;
202 QVariant askingLadyVariant = settings.value("Lady");
203 if (askingLadyVariant.canConvert(QVariant::Pixmap)) {
204 askingLadyPixmap = askingLadyVariant.value<QPixmap>();
205 } else {
206 QString fileName = QFileInfo(settings.fileName()).absoluteDir().filePath( askingLadyVariant.toString() );
207 askingLadyPixmap = QPixmap(fileName);
209 if(!askingLadyPixmap.isNull()) {
210 askingLady->setPixmap(askingLadyPixmap);
213 QVariant viewBackgroundVariant = settings.value("View background");
214 if (viewBackgroundVariant.canConvert(QVariant::Pixmap)) {
215 viewBackgroundPixmap = viewBackgroundVariant.value<QPixmap>();
216 } else {
217 QString fileName = QFileInfo(settings.fileName()).absoluteDir().filePath( viewBackgroundVariant.toString() );
218 viewBackgroundPixmap = QPixmap(fileName);
221 if(!askingLadyPixmap.isNull()) {
222 askingLady->setPixmap(askingLadyPixmap);\
224 if (!viewBackgroundPixmap.isNull()) {
225 viewBackground->setPixmap(viewBackgroundPixmap);
229 m_settings->installEventFilter(this);
230 hitsList->installEventFilter(this);
231 tasksTree->installEventFilter(this);
232 tasksTree->setUniformRowHeights(false);
233 QTreeWidgetItem* headerItem = new QTreeWidgetItem;
234 headerItem->setSizeHint(0 , QSize(0,0));
235 headerItem->setSizeHint(1 , QSize(0,0));
236 headerItem->setSizeHint(2 , QSize(0,0));
237 tasksTree->setHeaderItem(headerItem);
239 connect(bgColorButton, SIGNAL(clicked()), this, SLOT(selectColor()));
240 connect(fgColorButton, SIGNAL(clicked()), this, SLOT(selectColor()));
241 connect(previewButton, SIGNAL(clicked()), this, SLOT(popup()));
242 connect(tasksTree, SIGNAL(itemSelectionChanged()), this, SLOT(selectedTask()));
243 connect(tasksTree, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(selectedTask()));
244 populateTasks();
246 connect(cal1, SIGNAL(clicked(QDate)), this, SLOT(selectedStartDate(QDate)));
247 connect(cal2, SIGNAL(clicked(QDate)), this, SLOT(selectedEndDate(QDate)));
248 connect(cal3, SIGNAL(clicked(QDate)), this, SLOT(selectedStartDate(QDate)));
249 connect(cal4, SIGNAL(clicked(QDate)), this, SLOT(selectedEndDate(QDate)));
250 connect(hitsList, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(hitsListItemChanged(QTreeWidgetItem*,int)));
251 selectedTask();
253 m_view = new GView;
254 m_view->setScene(new QGraphicsScene);
255 m_view->scene()->setSceneRect(m_desktopRect);
257 m_view->installEventFilter(this);
258 m_view->setFrameStyle(QFrame::NoFrame);
259 m_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
260 m_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
261 m_view->setWindowFlags(m_view->windowFlags() | Qt::WindowStaysOnTopHint | Qt::ToolTip );
262 //m_view->setWindowModality(Qt::ApplicationModal);
263 m_view->setAttribute(Qt::WA_QuitOnClose, false);
264 // enable transparency with Qt4.5
265 m_view->setAttribute(Qt::WA_TranslucentBackground, true);
266 m_view->setWindowIcon( QIcon(":/images/icon.png") );
267 m_view->setWindowTitle("SaK - Sistema Anti Kazzeggio");
269 m_currentInterval = durationSpinBox->value();
270 m_currentInterval = qMax((int)1, qMin((int)1440, m_currentInterval));
271 qDebug() << "SAK: pinging interval " << m_currentInterval << Task::hours(m_currentInterval) << " hours ";
273 hitsTimeline->setPeriod(QDateTime(cal1->selectedDate()), QDateTime(cal2->selectedDate()));
274 populateHitsList(createHitsList(QDateTime(cal1->selectedDate()), QDateTime(cal2->selectedDate())));
277 void Sak::start()
279 // ensure the timer is killed
280 if(m_timerId)
281 stop();
282 m_currentInterval = qMax((int)1, m_currentInterval);
283 int msecs = (int)(Task::hours(m_currentInterval)*3600.0*1000.0 / 2);
284 m_timerId = startTimer( msecs );
285 m_nextTimerEvent = QDateTime::currentDateTime().addMSecs(msecs);
286 startAction->setEnabled(false);
287 stopAction->setEnabled(true);
288 m_stopped=false;
291 void Sak::stop()
293 if(m_timerId) {
294 killTimer(m_timerId); m_timerId=-1;
296 stopAction->setEnabled(false);
297 startAction->setEnabled(true);
298 m_stopped=true;
301 void Sak::workingOn()
303 // 1. Select the duration
304 QDialog d;
305 QLabel* label = new QLabel("Please select the duration:");
306 QDoubleSpinBox* spinBox = new QDoubleSpinBox;
307 QPushButton* okButton = new QPushButton("Select task/subtask");
308 QPushButton* cancelButton = new QPushButton("Cancel");
309 okButton->hide();
310 connect(spinBox, SIGNAL(valueChanged(double)), okButton,SLOT(show()));
311 connect(cancelButton, SIGNAL(clicked()), &d, SLOT(reject()));
312 connect(okButton, SIGNAL(clicked()), &d, SLOT(accept()));
313 spinBox->setSuffix(" hours");
314 spinBox->setMinimum(0);
315 spinBox->setMaximum(24);
316 spinBox->setValue(0);
317 QVBoxLayout* mainLayout = new QVBoxLayout;
318 QHBoxLayout* buttons = new QHBoxLayout;
319 buttons->addWidget(cancelButton);
320 buttons->addWidget(okButton);
321 mainLayout->addWidget(label);
322 mainLayout->addWidget(spinBox);
323 mainLayout->addLayout(buttons);
324 d.setLayout(mainLayout);
325 d.exec();
326 workingOnDeclared = spinBox->value();
327 popup();
328 stopAction->trigger();
329 QTimer::singleShot(3600*1000*spinBox->value(), startAction, SLOT(trigger()));
332 void Sak::pause()
334 m_stopped=true;
335 stopAction->setEnabled(false);
336 startAction->setEnabled(true);
339 Task Sak::loadTaskFromFile(const QString& filePath)
341 QFile taskXmlFile(filePath);
342 Task t;
343 qDebug() << "Examine task file " << taskXmlFile.fileName();
344 if (!taskXmlFile.open(QIODevice::ReadOnly)) {
345 qDebug() << "Failed opening xml file " << taskXmlFile.fileName();
347 QByteArray data = taskXmlFile.readLine();
348 QXmlStreamReader stream(data);
349 QXmlStreamReader::TokenType token = stream.readNext(); // skip StartDocument
350 token = stream.readNext();
351 if ( token != QXmlStreamReader::Comment) {
352 qDebug() << "Skip file " << taskXmlFile.fileName() << " (want a file starting with a comment representing MD5, got" << token << ")";
353 return t;
355 QString md5 = stream.text().toString().trimmed();
356 qDebug() << "md5 = " << md5;
358 // check md5
359 data = taskXmlFile.readAll();
360 if ( md5 != QCryptographicHash::hash(data, QCryptographicHash::Md5).toHex() ) {
361 if (QMessageBox::No == QMessageBox::warning(0, "Corrupted file!",
362 QString("Check of file " + taskXmlFile.fileName() + " failed (maybe it has been edited by hand).\nDo you want to load it anyway?" )
363 ,QMessageBox::Yes | QMessageBox::No) ) {
364 qDebug() << "Skip file " << taskXmlFile.fileName() << " (bad md5 sum)";
365 return t;
369 // read rest of data
370 stream.clear();
371 stream.addData(data);
373 if ( stream.readNext() != QXmlStreamReader::StartDocument) {
374 qDebug() << "Skip file " << taskXmlFile.fileName() << " (want start document)";
375 return t;
377 stream >> t;
378 if (stream.error() != QXmlStreamReader::NoError) {
379 qDebug() << "Error reading task data from file " << taskXmlFile.fileName() << ":" << stream.errorString();
380 return Task();
382 // QFile tmp("/tmp/" + t.title + ".xml");
383 // tmp.open(QIODevice::ReadWrite);
384 // QXmlStreamWriter ss(&tmp);
385 // ss.setAutoFormatting(true);
386 // ss.setAutoFormattingIndent(2);
387 // ss.writeStartDocument();
388 // ss << t;
389 // ss.writeEndDocument();
390 // tmp.close();
391 else
392 return t;
395 void Sak::flushSettings()
397 QSettings settings(QSettings::IniFormat, QSettings::UserScope, "ZanzaSoft", "SAK");
398 // QByteArray tasksArray;
399 // QDataStream stream(&tasksArray, QIODevice::ReadWrite);
400 // stream.setVersion(QDataStream::Qt_4_0);
401 // stream << m_tasks;
402 // settings.setValue("tasks", tasksArray);
403 settings.setValue("Ping interval", durationSpinBox->value());
404 settings.setValue("Message", bodyEdit->toPlainText());
405 QDir settingsDir = QFileInfo(settings.fileName()).absoluteDir();
406 QString fileName = settingsDir.filePath( "lady.png");
407 askingLady->pixmap().save(fileName);
408 settings.setValue("Lady", "lady.png");
410 fileName = settingsDir.filePath( "viewbg.png");
411 viewBackground->pixmap().save(fileName);
412 settings.setValue("View background", "viewbg.png");
413 settings.sync();
416 void Sak::flush()
418 if (m_changedTask)
419 saveTaskChanges();
420 if (m_changedHit)
421 saveHitChanges();
423 if (!m_settings) return;
424 m_backupper->doCyclicBackup();
425 QSettings settings(QSettings::IniFormat, QSettings::UserScope, "ZanzaSoft", "SAK");
428 QDir saveDir(QFileInfo(settings.fileName()).dir());
429 saveDir.mkdir("SakTasks");
430 saveDir.cd("SakTasks");
432 foreach(Task t, m_tasks) {
433 if (t.title.isEmpty()) continue;
434 QFile xmlTaskSave(saveDir.filePath(t.title + ".xml"));
435 QByteArray taskArray;
436 QXmlStreamWriter stream(&taskArray);
437 stream.setAutoFormatting(true);
438 stream.setAutoFormattingIndent(2);
439 stream.writeStartDocument();
440 stream << t;
441 stream.writeEndDocument();
442 xmlTaskSave.open(QIODevice::ReadWrite | QIODevice::Truncate);
443 qDebug() << "Saving xml to file " << xmlTaskSave.fileName();
444 QByteArray hash;
445 hash.append("<!-- ");
446 hash.append( QCryptographicHash::hash(taskArray, QCryptographicHash::Md5).toHex() );
447 hash.append(" -->\n");
448 xmlTaskSave.write(hash);
449 xmlTaskSave.write(taskArray);
450 xmlTaskSave.close();
453 // remove files not matching a task
454 QStringList nameFilters;
455 nameFilters << "*.xml";
456 QStringList files = saveDir.entryList( nameFilters, QDir::Files);
457 foreach (QString taskXmlFileName, files) {
458 if (!m_tasks.contains(QFileInfo(taskXmlFileName).baseName())) {
459 qWarning()<< "Remove task " << QFileInfo(taskXmlFileName).baseName() << " from disk";
460 QFile(saveDir.filePath(taskXmlFileName)).remove();
465 m_incremental->clearAddedPieces();
468 //void Sak::saveAsDb()
470 // if (!m_settings) return;
471 // QString fileName = QFileDialog::getSaveFileName();
472 // QFile file(fileName);
473 // file.remove();
474 // flush();
475 // QSettings settingsQSettings::IniFormat, QSettings::UserScope, ("ZanzaSoft", "SAK");
476 // QFile file1(settings.fileName());
477 // if (!file1.copy(fileName)) {
478 // qWarning() << "Error copying " << settings.fileName() << " to " << fileName << file1.errorString();
479 // }
482 void Sak::exportDbCsv()
484 if (!m_settings) return;
485 QString fileName = QFileDialog::getSaveFileName();
486 QFile file(fileName);
487 if (!file.open(QIODevice::ReadWrite|QIODevice::Truncate)) {
488 QMessageBox::warning(0, "Error saving", QString("Error saving to file %1").arg(fileName));
489 return;
491 QTextStream stream(&file);
492 foreach(const Task& t, m_tasks) {
493 QHash< QString, QList< Task::Hit > >::const_iterator itr = t.hits.begin();
494 while(itr != t.hits.end()) {
495 QList< Task::Hit >::const_iterator hitr = itr.value().begin(), hend = itr.value().end();
496 while(hitr != hend) {
497 stream << t.title << ";" << itr.key() << ";" << hitr->timestamp.toString(DATETIMEFORMAT) << ";" << hitr->duration << ";\n";
498 hitr++;
500 itr++;
503 file.close();
507 void Sak::logInGmail()
509 m_gmail->forceLogin();
512 void Sak::saveToGmail()
514 if (!m_settings) return;
515 flush();
516 QSettings settings(QSettings::IniFormat, QSettings::UserScope, "ZanzaSoft", "SAK");
518 QDir saveDir(QFileInfo(settings.fileName()).dir());
519 saveDir.mkdir("SakTasks");
520 saveDir.cd("SakTasks");
521 QStringList nameFilters;
522 nameFilters << "*.xml";
523 QStringList files = saveDir.entryList( nameFilters, QDir::Files);
524 QStringList filePaths;
525 foreach (QString taskXmlFileName, files) {
526 filePaths << saveDir.filePath(taskXmlFileName);
528 m_gmail->storeTaskFiles(filePaths);
531 void Sak::importFromGmail()
533 QStringList filePaths = m_gmail->fetchLatestTasks();
536 void Sak::open(const QStringList& _fileNames)
538 QStringList fileNames = _fileNames.size()?_fileNames:QFileDialog::getOpenFileNames(0, "Open a new task", QString(), "*.xml" );
539 foreach(QString fileName, fileNames) {
540 QFile file(fileName);
541 if (!file.exists()) {
542 QMessageBox::warning(0, "Cannot find task", QString("Cannot find task file %1").arg(fileName));
545 QSettings settings(QSettings::IniFormat, QSettings::UserScope, "ZanzaSoft", "SAK");
546 QDir saveDir(QFileInfo(settings.fileName()).dir());
547 saveDir.mkdir("SakTasks");
548 saveDir.cd("SakTasks");
550 if ( QFile(saveDir.filePath(QFileInfo(fileName).completeBaseName())).exists() ) {
551 QMessageBox mbox(QMessageBox::Warning, "Save current task", "Current task will be overwritten by the new task: do you want to backup it to file before?");
552 QPushButton* overwriteButton = mbox.addButton("Overwrite", QMessageBox::YesRole);
553 QPushButton* mergeButton = mbox.addButton("Merge", QMessageBox::NoRole);
554 QPushButton* cancelButton = mbox.addButton("Cancel", QMessageBox::RejectRole);
555 mbox.setDefaultButton(cancelButton);
556 mbox.exec();
557 QAbstractButton* b = mbox.clickedButton();
558 if (b == cancelButton) { continue; }
559 else {
560 m_backupper->doCyclicBackup();
561 if (b == mergeButton) {
562 Task t = loadTaskFromFile(file.fileName());
563 QHash< QString, QList< Task::Hit > > ::const_iterator itr = t.hits.begin(), end = t.hits.end();
564 while(itr != end) {
565 QString subtask = itr.key();
566 foreach(Task::Hit hit, itr.value())
567 m_incremental->addPiece(t.title, subtask, hit.timestamp, hit.duration);
568 itr++;
570 interactiveMergeHits();
571 } else if (b == overwriteButton) {
572 file.copy(saveDir.filePath(QFileInfo(fileName).completeBaseName()));
578 if (!fileNames.isEmpty()) {
579 m_settings->hide();
580 destroy();
581 init();
582 m_settings->show();
583 start();
587 void Sak::destroy()
589 stop();
590 if (!m_settings) return;
591 flush();
592 flushSettings();
593 m_settings->deleteLater();
594 m_view->scene()->deleteLater();
595 m_view->deleteLater();
596 delete m_backupper;
597 delete m_incremental;
598 delete m_gmail;
599 m_previewing = false;
600 m_changedHit = false;
601 m_timerId = 0;
605 Sak::~Sak()
607 killTimer(m_autoSaveTimer); m_autoSaveTimer=-1;
608 destroy();
612 void Sak::layoutSubTasks( const QMap<int, SakSubWidget*> sortedWidgets, int currentRank) {
613 QMap<int, SakSubWidget*>::const_iterator itr = sortedWidgets.begin(), end = sortedWidgets.end();
614 QRect r = m_desktopRect;
615 for(int i=0; itr != end; i++, itr++) {
616 int h = (*itr)->size().height();
617 int w = (*itr)->size().width();
618 (*itr)->setPos(QPointF((r.width() - w)/2, (r.height()-h)/2 + (i - currentRank - 1) * (h+2)));
623 int Sak::taskCounter = 0;
625 bool Sak::eventFilter(QObject* obj, QEvent* e)
627 // if (obj == m_view) {
628 // qDebug() << "event : " << e->type();
629 // }
630 if (obj == tasksTree) {
631 return taskTreeEventFilter(e);
632 } else if (obj == hitsList || obj == summaryList) {
633 return hitsListEventFilter(e);
634 } else if (obj == summaryView) {
635 if (e->type() == QEvent::Resize || e->type() == QEvent::Show) {
636 summaryView->fitInView(summaryView->sceneRect(), Qt::KeepAspectRatio);
638 return false;
639 } else if (obj == m_settings && e->type() == QEvent::Close) {
640 flush();
641 flushSettings();
642 if (m_changedTask)
643 saveTaskChanges();
644 if (m_changedHit)
645 saveHitChanges();
646 if (trayIcon->isVisible()) {
647 m_settings->hide();
648 e->ignore();
649 return true;
651 } else if (obj == m_view && e->type() == QEvent::Wheel) {
652 QWheelEvent* we = dynamic_cast<QWheelEvent*>(e);
653 if (m_subtaskView) {
654 scrollSubTasks(we->delta() / 120);
655 } else scrollTasks(we->delta() / 120);
656 } else if (obj == m_view && e->type() == QEvent::KeyPress) {
657 QKeyEvent* ke = dynamic_cast<QKeyEvent*>(e);
658 if ((ke->modifiers() & Qt::AltModifier) && (ke->modifiers() & Qt::ControlModifier) ) {
659 clearView();
660 return true;
661 } else if ( ((ke->modifiers() & Qt::ControlModifier) && (ke->key() == Qt::Key_Backspace) )
662 || ((ke->modifiers() & Qt::ControlModifier) && (ke->key() == Qt::Key_Left) )) {
663 if (m_subtaskView) {
664 popup();
665 return true;
667 } else if (m_subtaskView && ke->key() == Qt::Key_Up) {
668 scrollSubTasks(-1);
669 return true;
670 } else if (m_subtaskView && ke->key() == Qt::Key_Down) {
671 scrollSubTasks(+1);
672 return true;
673 } else if (!m_subtaskView && ke->key() == Qt::Key_Left) {
674 scrollTasks(-1);
675 return true;
676 } else if (!m_subtaskView && ke->key() == Qt::Key_Right) {
677 scrollTasks(+1);
678 return true;
679 } else if (!m_subtaskView && ke->key() == Qt::Key_Escape) {
680 clearView();
681 return true;
682 } else { // forward events to current widget
683 if (!m_subtaskView) {
684 if (m_widgetsIterator == m_widgets.end()) return false;
685 SakWidget* currentShowing = m_widgetsIterator.value();
686 currentShowing->keyPressEvent(ke);
687 return true;
688 } else {
689 // autoscroll on text completion!!!
690 if (m_subwidgetsIterator == m_subwidgets.end()) return false;
691 SakSubWidget* currentShowing = m_subwidgetsIterator.value();
692 currentShowing->keyPressEvent(ke);
694 if (m_subWidgetRank != 0 && m_subtaskCompleter) {
695 QString completion(m_subtaskCompleter->completionPrefix());
696 if (ke->text().size() == 1) {
697 if (ke->key() == Qt::Key_Backslash || ke->key() == Qt::Key_Backspace)
698 completion.chop(1);
699 else completion += ke->text();
700 m_subtaskCompleter->setCompletionPrefix(completion);
703 QStringList list( ((QStringListModel*)m_subtaskCompleter->model())->stringList() );
704 int newRank = 1 + ((QStringListModel*)m_subtaskCompleter->model())->stringList().indexOf(m_subtaskCompleter->currentIndex().row() >= 0 && completion.size() ? m_subtaskCompleter->currentCompletion() : completion);
706 if (m_subWidgetRank != newRank) {
707 scrollSubTasks(newRank - m_subWidgetRank);
708 if (newRank == 0) {
709 QLineEdit* editor = dynamic_cast<QLineEdit*>((*m_subwidgets.begin())->widget());
710 if (editor) {
711 editor->setText(completion);
716 } else if (m_subtaskCompleter) {
717 QLineEdit* editor = dynamic_cast<QLineEdit*>((*m_subwidgets.begin())->widget());
718 if (editor) {
719 m_subtaskCompleter->setCompletionPrefix(editor->text());
723 return true;
726 } else if (obj == m_view && e->type() == QEvent::Show) {
727 grabKeyboard();
728 QTimer::singleShot(500, this, SLOT(grabKeyboard()));
729 } else if (obj == m_view && e->type() == QEvent::Close) {
730 if (trayIcon->isVisible()) {
731 return true;
733 } else if (obj && obj == trayIcon && e->type() == QEvent::ToolTip) {
734 QDateTime last = m_incremental->lastTimeStamp;
735 int seconds = QDateTime::currentDateTime().secsTo(m_nextTimerEvent);
736 int hours = seconds / 3600;
737 int minutes = (seconds / 60) % 60;
738 seconds %= 60;
739 trayIcon->setToolTip(tr(qPrintable(QString("<h2>Sistema Anti Kazzeggio</h2>Last registered hit at <b>%1</b>.<br />%2").arg(last.toString()).arg(m_timerId > 0 ? QString("Next hit in <b>%2:%3:%4</b>").arg(hours).arg(minutes).arg(seconds) : QString("<b>Paused</b>")))));
740 return false;
742 return false;
745 //END basic >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
748 //BEGIN Tasks >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
749 void Sak::addDefaultTask()
751 QString tentativeName;
752 do {
753 tentativeName = QString("Task %1").arg(taskCounter++);
754 } while(m_editedTasks.contains(tentativeName));
756 Task& t = m_editedTasks[tentativeName];
757 t.title = tentativeName;
758 QTreeWidgetItem* item = new QTreeWidgetItem(QStringList(tentativeName));
759 item->setData(0,Qt::UserRole, QVariant(QMetaType::VoidStar, &t));
760 tasksTree->addTopLevelItem(item);
761 m_changedTask=true;
764 void Sak::populateTasks()
766 tasksTree->clear();
768 QHash<QString, Task>::iterator itr = m_editedTasks.begin(), end=m_editedTasks.end();
769 for(; itr!=end; itr++) {
770 Task& t(itr.value());
771 t.checkConsistency();
773 if (t.title.isEmpty() || t.title == "<away>") continue; // skip away task
774 QTreeWidgetItem* item = new QTreeWidgetItem(QStringList(t.title));
775 item->setData(0, Qt::UserRole, QVariant(QMetaType::VoidStar, &t));
776 QIcon icon;
777 icon.addPixmap(t.icon);
778 item->setSizeHint(0, QSize(32,32));
779 item->setIcon(0, icon);
780 for(int i=0; i<3; i++) {
781 item->setForeground(i,t.fgColor);
782 item->setBackground(i,t.bgColor);
784 //item->setCheckState(1, t.active ? Qt::Checked : Qt::Unchecked);
785 //item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
786 item->setIcon(1,QIcon(t.active ? ":/images/active.png" : ":/images/inactive.png"));
787 item->setText(2,QString("%1 hours worked till now (overestimated %2)").arg(t.totHours, 4, 'f', 2, ' ').arg(t.totOverestimation));
788 foreach(Task::SubTask st, t.subTasks) {
789 if (!st.title.isEmpty()) {
790 QTreeWidgetItem* sitem = new QTreeWidgetItem(item, QStringList(st.title));
791 item->setData(0, Qt::UserRole, QVariant(QMetaType::VoidStar, &st));
792 sitem->setSizeHint(0, QSize(32,32));
793 QColor fgColor = st.fgColor.isValid() ? st.fgColor : t.fgColor;
794 QColor bgColor = st.bgColor.isValid() ? st.bgColor : t.bgColor;
795 for(int i=0; i<3; i++) {
796 sitem->setForeground(i,fgColor);
797 sitem->setBackground(i,bgColor);
799 sitem->setIcon(1,QIcon(st.active ? ":/images/active.png" : ":/images/inactive.png"));
800 sitem->setText(2,QString("%1 hours worked till now").arg(st.totHours,4,'f',2,' '));
803 tasksTree->addTopLevelItem(item);
807 void Sak::saveTaskChanges()
809 if (m_changedTask) {
810 commitCurrentTask();
811 if ( QMessageBox::question ( 0, "Task list changed", "Task list has changed: do you want to save changes?", QMessageBox::Save | QMessageBox::Discard, QMessageBox::Discard) == QMessageBox::Save ) {
812 m_tasks = m_editedTasks;
813 } else m_editedTasks = m_tasks; //. undo changes
814 m_changedTask=false;
815 selectedStartDate(QDate());
816 populateTasks();
820 void Sak::selectColor() {
821 if (tasksTree->selectedItems().isEmpty()) return;
823 if (sender() == fgColorButton) {
824 QColor c = QColorDialog::getColor(fgColorButton->palette().color(QPalette::ButtonText));
825 if (!c.isValid()) return;
826 QPalette p = fgColorButton->palette();
827 p.setColor(QPalette::ButtonText, c);
828 fgColorButton->setPalette(p);
829 bgColorButton->setPalette(p);
830 } else if (sender() == bgColorButton) {
831 QColor c = QColorDialog::getColor(bgColorButton->palette().color(QPalette::Button));
832 if (!c.isValid()) return;
833 QPalette p = bgColorButton->palette();
834 p.setColor(QPalette::Button, c);
835 fgColorButton->setPalette(p);
836 bgColorButton->setPalette(p);
838 commitCurrentTask();
841 bool Sak::taskTreeEventFilter(QEvent* e)
843 if (e->type() == QEvent::ContextMenu) {
844 QContextMenuEvent* me = dynamic_cast<QContextMenuEvent*>(e);
845 if (!me) return false;
846 m_addTaskMenu->popup(me->globalPos());
847 return true;
848 } else if (e->type() == QEvent::KeyRelease) {
849 QKeyEvent* ke = dynamic_cast<QKeyEvent*>(e);
850 if (!ke) return false;
851 if ( (ke->key() != Qt::Key_Delete && ke->key() != Qt::Key_Backspace) ) return false;
852 if (currentSubtask!="") {
853 QMessageBox whatToDo(QMessageBox::Warning, "Deleting subtask", "Deleting subtask " + currentSubtask + " of task " + currentTask);
854 QPushButton* moveHitsToParentButton = whatToDo.addButton("Move hits to task " + currentTask, QMessageBox::AcceptRole);
855 QPushButton* removeHitsButton = whatToDo.addButton("Remove hits", QMessageBox::AcceptRole);
856 QPushButton* cancelButton = whatToDo.addButton("Cancel", QMessageBox::RejectRole);
857 whatToDo.setDefaultButton(cancelButton);
858 whatToDo.exec();
859 if ( whatToDo.clickedButton() == cancelButton) return true;
860 if (m_editedTasks.find(currentTask) == m_editedTasks.end()) return true;
861 m_changedTask=true;
862 Task& t(m_editedTasks[currentTask]);
863 t.subTasks.take(currentSubtask);
864 if (whatToDo.clickedButton() == removeHitsButton) {
865 t.hits.take(currentSubtask);
866 } else if (whatToDo.clickedButton() == moveHitsToParentButton) {
867 QList<Task::Hit> sorter(t.hits.take(""));
868 sorter << t.hits.take(currentSubtask);
869 qStableSort(sorter.begin(), sorter.end());
870 t.hits[""] = sorter;
872 } else {
873 // remove file from disk
874 m_changedTask=true;
875 m_editedTasks.remove(currentTask);
877 tasksTree->clear();
878 populateTasks();
879 selectedStartDate(QDate());
880 return true;
881 } else if (e->type() == QEvent::Hide) {
882 saveTaskChanges();
884 return false;
888 void Sak::commitCurrentTask()
890 m_changedTask=true;
891 if (currentSubtask.isEmpty()) {
892 QString currentTitle = taskTitleEditor->text();
893 if (!currentTitle.isEmpty()) {
894 if (currentTitle != currentTask) {
895 if (m_editedTasks.contains(currentTitle)) {
896 QMessageBox::warning(0, "Conflict in task names", "Conflict in task names: current task " + currentTask + ", edited title " + currentTitle);
897 taskTitleEditor->setText(currentTask);
898 return;
899 } else if (m_editedTasks.contains(currentTask)) {
900 m_editedTasks[currentTitle] = m_editedTasks.take(currentTask);
901 m_editedTasks[currentTitle].title = currentTitle;
904 } else return;
905 Task& t = m_editedTasks[currentTitle];
906 t.description = taskTextEditor->toPlainText();
907 t.tags = taskTagsEditor->text().split(QRegExp("[,:]"));
908 t.bgColor = bgColorButton->palette().color(QPalette::Button);
909 t.fgColor = fgColorButton->palette().color(QPalette::ButtonText);
910 t.icon = taskPixmapViewer->pixmap();
911 QList<QTreeWidgetItem *> items = tasksTree->findItems(currentTask,Qt::MatchExactly,0);
912 foreach(QTreeWidgetItem* ii, items) {
913 ii->setText(0, currentTitle);
914 ii->setIcon(0, taskPixmapViewer->pixmap());
915 for (int i=0; i<3; i++) {
916 ii->setForeground(i, QColor(t.fgColor));
917 ii->setBackground(i, QColor(t.bgColor));
920 if (dueEditor->date() != dueEditor->minimumDate())
921 t.dueDate = dueEditor->date();
922 t.estimatedHours = estimatedHoursEditor->value();
923 currentTask=currentTitle;
925 if (tasksTree->selectedItems().size() != 1) return;
926 QTreeWidgetItem* item = tasksTree->selectedItems()[0];
927 item->setText(0, taskTitleEditor->text());
928 QIcon icon;
929 icon.addPixmap(t.icon);
930 item->setSizeHint(0, QSize(32,32));
931 item->setIcon(0, icon);
932 for(int i=0; i<3; i++) {
933 item->setForeground(i,t.fgColor);
934 item->setBackground(i,t.bgColor);
938 } else { // subtask edited
939 if (!m_editedTasks.contains(currentTask)) return;
940 Task& t(m_editedTasks[currentTask]);
941 QString currentTitle = taskTitleEditor->text();
942 // backup data
943 if (!currentTitle.isEmpty()) {
944 if (currentTitle != currentSubtask) {
945 if (t.subTasks.contains(currentTitle)) {
946 QMessageBox::warning(0, "Conflict in subtask names", "Conflict in subtask names");
947 taskTitleEditor->setText(currentSubtask);
948 return;
949 } else if (t.subTasks.contains(currentSubtask)) {
950 t.subTasks[currentTitle] = t.subTasks.take(currentSubtask);
951 t.subTasks[currentTitle].title = currentTitle;
952 t.hits[currentTitle] = t.hits.take(currentSubtask);
955 } else return;
956 Task::SubTask& st = t.subTasks[currentTitle];
957 st.description = taskTextEditor->toPlainText();
958 st.bgColor = bgColorButton->palette().color(QPalette::Button);
959 st.fgColor = fgColorButton->palette().color(QPalette::ButtonText);
960 QList<QTreeWidgetItem *> items = tasksTree->findItems(currentTask,Qt::MatchExactly,0);
961 foreach(QTreeWidgetItem* jj, items) {
962 for(int i=0; i<jj->childCount(); i++) {
963 QTreeWidgetItem* ii = jj->child(i);
964 if (ii->text(0) != currentSubtask) continue;
965 ii->setText(0, currentTitle);
966 for (int i=0; i<3; i++) {
967 ii->setForeground(i, QColor(st.fgColor));
968 ii->setBackground(i, QColor(st.bgColor));
972 currentSubtask = currentTitle;
974 if (tasksTree->selectedItems().size() != 1) return;
975 QTreeWidgetItem* item = tasksTree->selectedItems()[0];
976 item->setText(0, taskTitleEditor->text());
977 QIcon icon;
978 icon.addPixmap(t.icon);
979 item->setSizeHint(0, QSize(32,32));
980 item->setIcon(0, icon);
981 for(int i=0; i<3; i++) {
982 item->setForeground(i,st.fgColor);
983 item->setBackground(i,st.bgColor);
988 void Sak::selectedTask()
990 if (tasksTree->selectedItems().isEmpty()) {
991 taskPixmapViewer->setEnabled(false);
992 taskPixmapViewer->setPixmap(QPixmap());
993 taskTextEditor->setEnabled(false);
994 taskTitleEditor->setEnabled(false);
995 bgColorButton->setEnabled(false);
996 fgColorButton->setEnabled(false);
997 dueEditor->setEnabled(false);
998 estimatedHoursEditor->setEnabled(false);
999 return;
1003 // Disable notifications
1004 taskPixmapViewer->blockSignals(true);
1005 taskTextEditor->blockSignals(true);
1006 taskTitleEditor->blockSignals(true);
1007 taskTagsEditor->blockSignals(true);
1008 bgColorButton->blockSignals(true);
1009 fgColorButton->blockSignals(true);
1010 dueEditor->blockSignals(true);
1011 estimatedHoursEditor->blockSignals(true);
1014 QTreeWidgetItem* selectedItem = tasksTree->selectedItems().first();
1015 QTreeWidgetItem* parentItem = selectedItem->parent();
1016 QString tt = selectedItem->text(0);
1018 if (!parentItem) {
1019 taskPixmapViewer->setEnabled(true);
1020 taskTextEditor->setEnabled(true);
1021 taskTagsEditor->setEnabled(true);
1022 dueEditor->setEnabled(true);
1023 estimatedHoursEditor->setEnabled(true);
1024 bgColorButton->setEnabled(true);
1025 fgColorButton->setEnabled(true);
1026 taskPixmapViewer->setEnabled(true);
1027 } else {
1028 taskPixmapViewer->setEnabled(false);
1029 taskTextEditor->setEnabled(false);
1030 taskTagsEditor->setEnabled(false);
1031 taskPixmapViewer->setPixmap(QPixmap());
1032 dueEditor->setEnabled(false);
1033 estimatedHoursEditor->setEnabled(false);
1034 bgColorButton->setEnabled(false);
1035 fgColorButton->setEnabled(false);
1036 taskPixmapViewer->setEnabled(false);
1038 taskTitleEditor->setEnabled(true);
1041 if (!parentItem) { // editing a task
1042 if (!m_editedTasks.contains(tt)) return;
1043 const Task& t = m_editedTasks[tt];
1044 taskPixmapViewer->setPixmap(t.icon);
1045 taskTextEditor->setPlainText(t.description);
1046 taskTitleEditor->setText(t.title);
1047 QPalette p;
1048 p.setColor(QPalette::Button, t.bgColor);
1049 p.setColor(QPalette::ButtonText, t.fgColor);
1050 bgColorButton->setPalette(p);
1051 fgColorButton->setPalette(p);
1052 estimatedHoursEditor->setValue(t.estimatedHours);
1053 dueEditor->setDate(t.dueDate.isValid() ? t.dueDate : dueEditor->minimumDate());
1055 currentTask = t.title;
1056 currentSubtask = "";
1057 } else { // editing a subtask
1058 if (!m_editedTasks.contains(parentItem->text(0))) return;
1059 const Task& t = m_editedTasks[parentItem->text(0)];
1060 if (!t.subTasks.contains(tt)) return;
1061 const Task::SubTask& st = t.subTasks[tt];
1063 taskTextEditor->setPlainText(st.description);
1064 taskTitleEditor->setText(st.title);
1065 QPalette p;
1066 p.setColor(QPalette::Button, st.bgColor.isValid() ? st.bgColor : t.bgColor);
1067 p.setColor(QPalette::ButtonText, st.fgColor.isValid() ? st.fgColor : t.fgColor);
1068 bgColorButton->setPalette(p);
1069 fgColorButton->setPalette(p);
1071 currentTask = t.title;
1072 currentSubtask = st.title;
1075 // Re-enable notifications
1076 taskPixmapViewer->blockSignals(false);
1077 taskTextEditor->blockSignals(false);
1078 taskTitleEditor->blockSignals(false);
1079 taskTagsEditor->blockSignals(false);
1080 bgColorButton->blockSignals(false);
1081 fgColorButton->blockSignals(false);
1082 dueEditor->blockSignals(false);
1083 estimatedHoursEditor->blockSignals(false);
1086 void Sak::doubleClickedTask(QTreeWidgetItem* i, int column)
1088 if (column == 1) {
1089 m_changedTask=true;
1090 if (i->parent() == 0) {
1091 QHash<QString, Task>::iterator itr = m_editedTasks.find(i->text(0));
1092 Q_ASSERT(itr != m_editedTasks.end());
1093 bool& active ( itr.value().active );
1094 active = !active;
1095 i->setIcon(column, active ? QIcon(":/images/active.png") : QIcon(":/images/inactive.png"));
1096 } else {
1097 QHash<QString, Task>::iterator itr = m_editedTasks.find(i->parent()->text(0));
1098 Q_ASSERT(itr != m_editedTasks.end());
1099 bool& active ( itr.value().subTasks[i->text(0)].active );
1100 active = !active;
1101 i->setIcon(column, active ? QIcon(":/images/active.png") : QIcon(":/images/inactive.png"));
1103 ((QTreeWidget*)sender())->update();
1109 void Sak::timerEvent(QTimerEvent* e)
1111 if (e->timerId() == m_timerId) {
1112 if (!m_view->isVisible() && !m_settings->isVisible() && m_tasks.count() > 0) {
1113 popup();
1114 // close timer
1115 killTimer(m_timerId); m_timerId=-1;
1116 killTimer(m_timeoutPopup); m_timeoutPopup=-1;
1117 int msecs = (int)(qMax( 30000.0, Task::hours(m_currentInterval)*3600.0*1000.0/10.0));
1118 m_timeoutPopup = startTimer(msecs);
1119 // restart timer
1120 m_nextTimerEvent = QDateTime::currentDateTime().addMSecs(msecs);
1121 } else {
1122 if (m_settings && m_settings->isVisible() && !m_settings->isActiveWindow()) {
1123 trayIcon->showMessage("Delayed check point", "Delayed check point due to open settings. Close the setting window!", QSystemTrayIcon::Warning, -1);
1124 m_settings->close();
1126 qDebug() << "SAK: wait 5 seconds";
1127 killTimer(m_timerId); m_timerId=-1;
1128 m_timerId = startTimer(5000);
1129 m_nextTimerEvent = QDateTime::currentDateTime().addMSecs(5000);
1131 } else if (e->timerId() == m_timeoutPopup) {
1132 // ensure the timer is resetted
1133 killTimer(m_timeoutPopup); m_timeoutPopup=-1;
1134 killTimer(m_timerId); m_timerId = -1;
1136 if (!m_subtaskView) {
1137 // if not selecting subtasks clear everything and signal away
1138 workingOnTask("<away>","");
1139 trayIcon->showMessage("New away events", "You have missed a check point. Fix it in the detailed hit list.", QSystemTrayIcon::Information, 999999);
1140 clearView();
1142 } else { // wait 5 seconds
1143 m_nextTimerEvent = QDateTime::currentDateTime().addMSecs(5000);
1144 m_timeoutPopup = startTimer(5000);
1146 } else if (e->timerId() == m_autoSaveTimer) {
1147 flush();
1148 } else if (e->timerId() == m_getFocusTimer) {
1149 grabKeyboard();
1150 } else {
1151 qDebug() << "unknown timer event";
1155 void Sak::clearView()
1157 killTimer(m_timeoutPopup); m_timeoutPopup=-1;
1158 m_subtaskView=false;
1159 m_marker = 0;
1160 delete m_subtaskCompleter; m_subtaskCompleter = 0;
1162 if (m_view)
1163 m_view->releaseKeyboard();
1164 killTimer(m_getFocusTimer); m_getFocusTimer=-1;
1165 #if defined(Q_WS_X11)
1166 // restore focus to previous application
1167 grabbed=false;
1168 X11::XSetInputFocus((X11::Display*)QX11Info::display(), X11::CurrentFocusWindow, X11::CurrentRevertToReturn, CurrentTime);
1169 X11::XFlush((X11::Display*)QX11Info::display());
1170 #endif
1172 // restart normal timer
1173 if (!m_previewing) {
1174 killTimer(m_timerId); m_timerId=-1;
1175 int msecs = (int)(Task::hours(m_currentInterval)*3600.0*1000.0);
1176 m_timerId = startTimer(msecs);
1177 m_nextTimerEvent = QDateTime::currentDateTime().addMSecs(msecs);
1180 if (!m_view) return;
1181 QGraphicsScene* s = m_view->scene();
1182 QList<QGraphicsItem*> items = m_view->items();
1183 m_widgets.clear();
1184 m_widgetsIterator = m_widgets.end();
1185 s->deleteLater();
1186 m_view->close();
1187 m_view->setScene(new QGraphicsScene);
1188 m_view->scene()->setSceneRect(m_desktopRect);
1189 m_previewing = false;
1192 void Sak::workingOnTask(const QString& taskName, const QString& subTask)
1194 clearView();
1195 if (!m_previewing) {
1197 qDebug() << "Working on " << taskName ;
1198 if (m_tasks.contains(taskName)) {
1199 Task& t = m_tasks[taskName];
1201 if (t.title != "<away>" && !t.title.isEmpty()) {
1202 // update history
1203 int historyIndex = m_taskSelectionHistory.indexOf(t.title);
1204 if (historyIndex != -1) {
1205 m_taskSelectionHistory.takeAt(historyIndex);
1207 m_taskSelectionHistory.push_back(t.title);
1209 QList<QString> & subtaskSelectionHistory(m_subtaskSelectionHistory[t.title]);
1210 historyIndex = subtaskSelectionHistory.indexOf(subTask);
1211 if (historyIndex != -1) {
1212 subtaskSelectionHistory.takeAt(historyIndex);
1214 subtaskSelectionHistory.push_back(subTask);
1218 QDateTime now = QDateTime::currentDateTime();
1219 QHash<QString, Task>::iterator itr = m_tasks.begin();
1221 QHash<QString, QList< Task::Hit> >::iterator hitr = t.hits.begin();
1222 // merge last two hits of every subtask if they are close enough
1223 while(hitr != t.hits.end()) {
1224 QList<Task::Hit>& otHits( *hitr );
1225 if (otHits.count() > 1) {
1226 Task::Hit& lastHit(otHits[otHits.size() - 1]);
1227 Task::Hit& beforeLastHit(otHits[otHits.size() - 2]);
1229 // the list might be not sorted (eg. after startup merge)
1230 // so give up merge of last two (unsorted) hits
1231 if (lastHit.timestamp < beforeLastHit.timestamp) { hitr++; continue; }
1233 qDebug() << "Task: " << t.title << " : " << hitr.key();
1234 qDebug() << " Last hit: " << lastHit;
1235 qDebug() << " Before last hit: " << beforeLastHit;
1237 int diff = (lastHit.timestamp.toTime_t() - 30*lastHit.duration) - (beforeLastHit.timestamp.toTime_t() + 30*beforeLastHit.duration);
1238 qDebug() << " diff: " << diff;
1239 if (diff < 120 && diff > -60) { // at most 2 minutes apart and 1 overlapped
1240 beforeLastHit.timestamp = beforeLastHit.timestamp.addSecs(-30*beforeLastHit.duration);
1241 int secsToEnd = beforeLastHit.timestamp.secsTo(lastHit.timestamp.addSecs(30*m_currentInterval));
1242 if (secsToEnd > 24 * 3600 * 3600) {
1243 qWarning() << "TRAPPED ERROR IN SECS COUNT!!!!!!!!";
1244 qWarning() << "BEFORE LAST HIST WAS " << beforeLastHit.timestamp << beforeLastHit.duration;
1245 qWarning() << "LAST HIT WAS " << lastHit.timestamp << lastHit.duration;
1246 assert(secsToEnd < 24 * 3600 * 3600);
1248 beforeLastHit.timestamp = beforeLastHit.timestamp.addSecs( secsToEnd/2.0 );
1249 beforeLastHit.duration = (int)( qRound( secsToEnd / 60.0 ) );
1250 // remove the current very last hit
1251 otHits.pop_back();
1254 hitr++;
1257 // add hit to hit list
1258 // NOTE: we do not try to merge the very last hit with previous ones because we want let
1259 // the user being able to easily recover from a selection error
1260 if (workingOnDeclared) {
1261 t.hits[subTask] << Task::Hit(now.addSecs(3600.0*workingOnDeclared/2), 60*workingOnDeclared);
1262 m_incremental->writePiece(t.title, subTask, now.addSecs(3600.0*workingOnDeclared/2), 60*workingOnDeclared);
1263 } else {
1264 t.hits[subTask] << Task::Hit(now, m_currentInterval);
1265 m_incremental->writePiece(t.title, subTask, now, m_currentInterval);
1267 t.checkConsistency();
1268 QList<QTreeWidgetItem*> items = tasksTree->findItems (t.title, Qt::MatchExactly, 0);
1269 if (!items.isEmpty())
1270 items.first()->setText(1, QString("%1 hours worked till now (overestimated %2)").arg(t.totHours).arg(t.totOverestimation));
1272 // update subtask if new added!
1273 t.updateSubTasks();
1275 // update statistics !!!!
1276 m_editedTasks = m_tasks;
1277 QMetaObject::invokeMethod(this, "selectedStartDate", Qt::QueuedConnection, Q_ARG(QDate, cal1->selectedDate()));
1278 flush();
1284 // attractor = 't' (top), 'b', 'l', 'r'
1285 void layoutInSquare( QList<SakWidget*> sortedWidgets, QRect rect, char attractor)
1287 int w = rect.width();
1288 if (rect.width() < 64) return;
1289 int maxw = qMin(350, w/2);
1290 QSize size(maxw, maxw);
1291 if (sortedWidgets.count() == 0) {
1292 return;
1293 } else if (sortedWidgets.count() == 1) {
1294 sortedWidgets[0]->setGeometry(rect);
1295 } else if (sortedWidgets.count() == 2) {
1296 QPoint off1, off2;
1297 if (attractor == 'C') {
1298 off1 = QPoint(0,w/4);
1299 off2 = QPoint(w/2,w/4);
1300 } else if (attractor == 'T') {
1301 off1 = QPoint(0,0);
1302 off2 = QPoint(w/2,0);
1303 } else if (attractor == 'B') {
1304 off1 = QPoint(0,w/2);
1305 off2 = QPoint(w/2,w/2);
1306 } else if (attractor == 'R') {
1307 off1 = QPoint(w/2,0);
1308 off2 = QPoint(w/2,w/2);
1309 } else if (attractor == 'L') {
1310 off1 = QPoint(0,0);
1311 off2 = QPoint(0,w/2);
1313 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + off1, size));
1314 sortedWidgets[1]->setGeometry(QRect(rect.topLeft() + off2, size));
1315 } else if (sortedWidgets.count() == 3) {
1316 QPoint off1, off2, off3;
1317 if (attractor == 'T' || attractor == 'C') {
1318 off1 = QPoint(0,0);
1319 off2 = QPoint(w/2,0);
1320 off3 = QPoint(w/4,w/2);
1321 } else if (attractor == 'B') {
1322 off1 = QPoint(0,w/2);
1323 off2 = QPoint(w/2,w/2);
1324 off3 = QPoint(w/4,0);
1325 } else if (attractor == 'R') {
1326 off1 = QPoint(w/2,0);
1327 off2 = QPoint(w/2,w/2);
1328 off3 = QPoint(0,w/4);
1329 } else if (attractor == 'L') {
1330 off1 = QPoint(0,0);
1331 off2 = QPoint(0,w/2);
1332 off3 = QPoint(w/2,w/4);
1334 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + off1, size));
1335 sortedWidgets[1]->setGeometry(QRect(rect.topLeft() + off2, size));
1336 sortedWidgets[2]->setGeometry(QRect(rect.topLeft() + off3, size));
1337 } else if (sortedWidgets.count() == 4) {
1338 QPoint off1(0,0);
1339 QPoint off2(0,w/2);
1340 QPoint off3(w/2,0);
1341 QPoint off4(w/2,w/2);
1342 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + off1, size));
1343 sortedWidgets[1]->setGeometry(QRect(rect.topLeft() + off2, size));
1344 sortedWidgets[2]->setGeometry(QRect(rect.topLeft() + off3, size));
1345 sortedWidgets[3]->setGeometry(QRect(rect.topLeft() + off4, size));
1346 } else {
1347 Q_ASSERT(sortedWidgets.count() <= 4);
1351 void layoutInRect( QList<SakWidget*> sortedWidgets, QRect rect, char attractor)
1353 if (sortedWidgets.count() == 0) return;
1354 int h = rect.height();
1355 int w = rect.width();
1356 int maxh = qMin(350, h);
1357 int maxw = qMin(350, w);
1358 if (sortedWidgets.count() == 1) {
1359 if (w>h) {
1360 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + QPoint((w-maxh)/2,(h-maxh)/2), QSize(maxh,maxh)));
1361 } else {
1362 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + QPoint((w-maxw)/2,(h-maxw)/2), QSize(maxw,maxw)));
1364 return;
1365 } else if (sortedWidgets.count() == 2) {
1366 if (w>h) {
1367 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + QPoint(w/2-maxh,(h-maxh)/2), QSize(maxh,maxh)));
1368 sortedWidgets[1]->setGeometry(QRect(rect.topLeft() + QPoint(w/2,(h-maxh)/2), QSize(maxh,maxh)));
1369 } else {
1370 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + QPoint((h-maxw)/2,w-maxw), QSize(w,w)));
1371 sortedWidgets[1]->setGeometry(QRect(rect.topLeft() + QPoint((h-maxw)/2,w/2), QSize(maxw,maxw)));
1373 return;
1375 if (h < 64 || w < 64) return;
1376 QList<SakWidget*> leftList, rightList;
1377 for (int i=4; i<sortedWidgets.count(); i++) {
1378 if (i%2)
1379 rightList << sortedWidgets[i];
1380 else
1381 leftList << sortedWidgets[i];
1383 if (w > h) {
1384 QRect square(rect.topLeft() + QPoint(h/2, 0), QSize(h,h));
1385 layoutInSquare(sortedWidgets.mid(0,4), square, attractor);
1387 QRect leftRect(rect.topLeft(), QSize(h/2, h));
1388 layoutInRect(leftList, leftRect, 'R');
1389 QRect rightRect(rect.topLeft() + QPoint((int)(0.75*w),0), QSize(h/2, h));
1390 layoutInRect(rightList, rightRect, 'L');
1391 } else {
1392 QRect square(rect.topLeft() + QPoint(0,w/2), QSize(w,w));
1393 layoutInSquare(sortedWidgets.mid(0,4), square, attractor);
1395 QRect leftRect(rect.topLeft(), QSize(w, w/2));
1396 layoutInRect(leftList, leftRect, 'B');
1397 QRect rightRect(rect.topLeft() + QPoint(0,(int)(0.75*h)), QSize(w, w/2));
1398 layoutInRect(rightList, rightRect, 'T');
1402 QRect Sak::Layouting( const QList<SakWidget*>& sortedWidgets)
1404 QRect r = m_desktopRect;
1405 int height = (int)(0.75 * r.height());
1406 int width = r.width();
1408 int firstW = width / 2 < height ? width : height * 2;
1409 int firstH = firstW / 2;
1410 QRect firstRect (r.x() + (width - firstW) / 2, r.y() + (height - firstH) / 2 + (int)(r.height() * 0.25), firstW, firstH);
1412 layoutInRect(sortedWidgets, firstRect, 'C');
1413 return QRect(QPoint(r.x(), r.y()), QSize(r.width(), (int)(0.25 * r.height())));
1416 void Sak::grabKeyboard()
1418 #if defined(Q_WS_X11)
1419 if (!grabbed) {
1420 // save current focused application
1421 XGetInputFocus((X11::Display*)QX11Info::display(), &X11::CurrentFocusWindow, &X11::CurrentRevertToReturn);
1422 grabbed=true;
1424 if (X11::CurrentFocusWindow != QX11Info::appRootWindow(QX11Info::appScreen()) ) {
1425 X11::XSetInputFocus((X11::Display*)QX11Info::display(), QX11Info::appRootWindow(QX11Info::appScreen()), RevertToParent, CurrentTime);
1426 X11::XFlush((X11::Display*)QX11Info::display());
1428 #endif
1429 m_view->grabKeyboard();
1432 void Sak::popup()
1434 if (m_stopped) {
1435 trayIcon->showMessage("SAK popup disabled", "SAK triggered a new event but no popup will be shown", QSystemTrayIcon::Information, -1);
1436 return;
1439 // save changes first
1440 if (m_changedTask)
1441 saveTaskChanges();
1442 if (m_changedHit)
1443 saveHitChanges();
1445 if (m_subtaskView) {
1446 // remove subtasks
1447 foreach(SakSubWidget* w, m_subwidgets.values()) {
1448 w->scene()->removeItem(w);
1449 delete w;
1451 m_subwidgets.clear();
1453 m_marker->scene()->removeItem(m_marker);
1454 delete m_marker;
1456 // unhide tasks
1457 foreach(SakWidget* w, m_widgets.values()) {
1458 w->show();
1460 m_subtaskView=false;
1461 return;
1464 m_subtaskView = false;
1466 if (sender() == previewButton) {
1467 m_previewing = true;
1469 QDateTime now = QDateTime::currentDateTime();
1470 QDateTime fromWeek = now;
1471 fromWeek.setDate(now.date().addDays(-now.date().dayOfWeek() + 1));
1472 fromWeek.setTime(QTime(0,0,0));
1473 QDateTime fromMonth = now;
1474 fromMonth.setDate(now.date().addDays(-now.date().day()));
1475 fromMonth.setTime(QTime(0,0,0));
1476 QDateTime fromToday = now;
1477 fromToday.setTime(QTime(0,0,0));
1479 double weekHits = 0;
1480 double dayHits = 0;
1481 double monthHits = 0;
1482 QHash<QString, double> dayStats;
1483 QHash<QString, double> weekStats;
1484 QHash<QString, double> monthStats;
1486 for(QHash<QString, Task>::iterator itr = m_tasks.begin(); itr!=m_tasks.end(); itr++) {
1487 Task& t(itr.value());
1488 t.checkConsistency();
1489 if (t.active) {
1490 double m = t.workedHours(fromMonth, now);
1491 monthStats[t.title] = m;
1492 monthHits += m;
1493 double w = t.workedHours(fromWeek, now);
1494 weekStats[t.title] = w;
1495 weekHits += w;
1496 double d = t.workedHours(fromToday, now);
1497 dayStats[t.title] = d;
1498 dayHits += d;
1503 m_widgets.clear();
1504 foreach(const Task& t, m_tasks.values()) {
1505 if (!t.active || t.title == QString() || t.title == "<away>") continue;
1506 SakWidget* test = new SakWidget(t);
1507 test->setVisible(false);
1508 double d = dayStats[t.title];
1509 double w = weekStats[t.title];
1510 double m = monthStats[t.title];
1511 test->setStatistics(d, w, m, d/dayHits * 100.0, w/weekHits * 100.0, m/monthHits * 100.0);
1512 test->setObjectName(t.title);
1513 connect (test, SIGNAL(clicked(const QString&, const QString&)), this, SLOT(workingOnTask(const QString&, const QString&)));
1514 connect (test, SIGNAL(clicked(const QString&)), this, SLOT(popupSubtasks(const QString&)));
1515 int historyPosition = 1 + m_taskSelectionHistory.indexOf(t.title);
1516 int rank = historyPosition != 0 ? -1000000 * historyPosition : -d;
1517 m_widgets.insertMulti( rank, test);
1520 m_widgetsIterator = m_widgets.begin();
1521 if (m_widgetsIterator != m_widgets.end()) {
1522 m_widgetsIterator.value()->showDetails(true);
1526 const QList<SakWidget*>& values = m_widgets.values();
1527 QRect messageRect = Layouting((const QList<SakWidget*>&)values);
1528 foreach(SakWidget* w, values) {
1529 m_view->scene()->addItem(w);
1530 w->show();
1534 // add the message item
1535 #ifdef CENSORED
1536 QPixmap askingLadyPixmap(":/images/whip_vale.png");
1537 #else
1538 QPixmap askingLadyPixmap(":/images/whip.png");
1539 #endif
1540 if (!askingLady->pixmap().isNull()) {
1541 askingLadyPixmap = askingLady->pixmap();
1544 SakMessageItem* sakMessage = new SakMessageItem(bodyEdit->toPlainText(), askingLadyPixmap);
1545 sakMessage->setGeometry(messageRect);
1546 m_view->scene()->addItem(sakMessage);
1547 sakMessage->show();
1549 // add the exit item
1550 SakExitItem* exitItem = new SakExitItem(QPixmap(":/images/exit.png"));
1551 QRect r = m_desktopRect;
1552 connect(exitItem, SIGNAL(exit()), this, SLOT(clearView()));
1553 exitItem->setPos(r.width() - exitItem->boundingRect().width(), 0);
1554 m_view->scene()->addItem(exitItem);
1555 exitItem->setZValue(1e8);
1556 exitItem->show();
1559 m_view->setGeometry( QRect(m_desktopRect)/*.adjusted(200,200,-200,-200 )*/ );
1560 m_view->backgroundPixmap = viewBackground->pixmap().scaled(r.size(), Qt::KeepAspectRatioByExpanding);
1561 m_view->show();
1562 m_view->raise();
1563 m_view->setFocus();
1564 #if defined(Q_WS_WIN)
1565 SetForegroundWindow(m_view->winId());
1566 #endif
1567 m_getFocusTimer = startTimer( 500 );
1568 qApp->alert(m_view, 5000);
1572 void Sak::popupSubtasks(const QString& _taskname) {
1573 killTimer(m_timeoutPopup); m_timeoutPopup=-1;
1575 QString taskname = _taskname;
1576 if (taskname.isEmpty()) {
1577 if ( m_taskSelectionHistory.isEmpty() ) {
1578 return;
1579 } else {
1580 taskname = m_taskSelectionHistory.back();
1582 QString subtaskName;
1583 if (!m_subtaskSelectionHistory[taskname].isEmpty()) {
1584 subtaskName = m_subtaskSelectionHistory[taskname].back();
1586 workingOnTask(taskname, subtaskName);
1589 m_subtaskView = true;
1590 grabKeyboard();
1591 QRect r = m_desktopRect;
1592 int w = 500;
1593 int h = 40;
1595 // hide tasks to show subtasks
1596 foreach(SakWidget* w, m_widgets.values()) {
1597 w->hide();
1600 QHash<QString, Task>::const_iterator itr = m_tasks.find(taskname);
1601 if (itr == m_tasks.end()) {
1602 workingOnTask(taskname, "");
1603 return;
1605 const Task& t(*itr);
1606 QHash< QString, Task::SubTask >::const_iterator titr = t.subTasks.begin(), tend = t.subTasks.end();
1607 m_subwidgets.clear();
1608 QDateTime now(QDateTime::currentDateTime());
1609 for(; titr != tend; titr++) {
1610 if (!titr->active) continue;
1611 SakSubWidget* sw = new SakSubWidget(t, *titr);
1612 sw->setGeometry(QRectF(0,0,w,h));
1613 connect (sw, SIGNAL(clicked(const QString&, const QString&)), this, SLOT(workingOnTask(const QString&, const QString&)));
1614 connect (sw, SIGNAL(focused()), this, SLOT(focusedSubTask()));
1616 QDateTime rank=now;
1617 QHash< QString, QList< QString > >::const_iterator hhitr = m_subtaskSelectionHistory.find(t.title);
1618 if (hhitr != m_subtaskSelectionHistory.end()) {
1619 int index = hhitr->indexOf(titr->title);
1620 if (index != 0) rank = now.addDays(index+1);
1622 if (rank == now) {
1623 QHash< QString, QList<Task::Hit> >::const_iterator hitr = t.hits.find(titr.key());
1624 if (hitr != t.hits.end()) {
1625 if ( hitr.value().count() && hitr.value().last().timestamp.isValid())
1626 rank = hitr.value().last().timestamp;
1627 else
1628 rank = now.addDays(-999);
1631 m_subwidgets.insertMulti( - rank.toTime_t(), sw);
1634 const QList<SakSubWidget*>& values = m_subwidgets.values();
1636 // create possible completion
1637 QStringList subtaskSortedList;
1638 foreach(SakSubWidget* sub, values) {
1639 subtaskSortedList.push_back(sub->subtask().title);
1641 m_subtaskCompleter = new QCompleter(subtaskSortedList);
1642 m_subtaskCompleter->setCaseSensitivity(Qt::CaseInsensitive);
1644 // QRect messageRect = Layouting((const QList<SakWidget*>&)values);
1646 m_subwidgetsIterator = m_subwidgets.begin();
1647 m_subWidgetRank = 0;
1649 // the one with text
1650 SakSubWidget* tmpSw = new SakSubWidget(t, Task::SubTask(), true);
1651 QCompleter* completer = new QCompleter(subtaskSortedList);
1652 completer->setCaseSensitivity(Qt::CaseInsensitive);
1653 completer->setCompletionMode(QCompleter::InlineCompletion);
1654 ((QLineEdit*)tmpSw->widget())->setCompleter(completer);
1655 m_view->scene()->addItem(tmpSw);
1656 tmpSw->setGeometry(QRectF(0,0,w,h));
1657 connect (tmpSw, SIGNAL(clicked(const QString&, const QString&)), this, SLOT(workingOnTask(const QString&, const QString&)));
1658 connect (tmpSw, SIGNAL(focused()), this, SLOT(focusedSubTask()));
1660 m_subwidgets.insertMulti(-QDateTime::currentDateTime().addDays(999).toTime_t(), tmpSw);
1662 m_subWidgetRank += values.size() != 0;
1664 if (m_subwidgetsIterator != m_subwidgets.end()) {
1665 m_subwidgetsIterator.value()->showDetails(true);
1666 m_view->scene()->setFocusItem(m_subwidgetsIterator.value(), Qt::MouseFocusReason);
1667 m_subwidgetsIterator.value()->widget()->setFocus(Qt::MouseFocusReason);
1668 } else {
1669 m_view->scene()->setFocusItem(tmpSw, Qt::MouseFocusReason);
1670 tmpSw->widget()->setFocus(Qt::MouseFocusReason);
1673 // add a marker to highligh current selection
1674 m_marker = new QGraphicsEllipseItem(r.width()/2 - 280, r.height()/2 - 51, 20,20);
1675 m_marker->setBrush(Qt::red);
1676 m_view->scene()->addItem(m_marker);
1677 m_marker->setVisible(true);
1679 layoutSubTasks(m_subwidgets, m_subWidgetRank);
1681 // Finally add subwidgets
1682 for(int i=0; i<values.size(); i++) {
1683 SakSubWidget* sw = values[i];
1684 m_view->scene()->addItem(sw);
1689 void Sak::scrollTasks(int npos) {
1690 SakWidget* currentShowing = 0;
1691 if (npos < 0) {
1692 for (int i=npos; i<0; i++) {
1693 if (m_widgetsIterator == m_widgets.end()) return;
1694 currentShowing = m_widgetsIterator != m_widgets.end() ? m_widgetsIterator.value() : 0;
1695 if (m_widgetsIterator == m_widgets.begin()) m_widgetsIterator = m_widgets.end();
1696 m_widgetsIterator--;
1698 } else {
1699 for (int i=0; i<npos; i++) {
1700 if (m_widgetsIterator == m_widgets.end()) return;
1701 currentShowing = m_widgetsIterator.value();
1702 m_widgetsIterator++;
1703 if (m_widgetsIterator == m_widgets.end()) m_widgetsIterator = m_widgets.begin();
1706 if (currentShowing && m_widgetsIterator != m_widgets.end()) {
1707 currentShowing->showDetails(false);
1708 m_widgetsIterator.value()->showDetails(true);
1712 void Sak::scrollSubTasks(int npos) {
1713 SakSubWidget* currentShowing = 0;
1714 if (npos < 0) {
1715 for (int i=npos; i<0; i++) {
1716 if (m_subwidgetsIterator == m_subwidgets.end()) return;
1717 currentShowing = m_subwidgetsIterator != m_subwidgets.end() ? m_subwidgetsIterator.value() : 0;
1718 if (m_subwidgetsIterator == m_subwidgets.begin()) {
1719 m_subwidgetsIterator = m_subwidgets.end();
1720 m_subWidgetRank = m_subwidgets.count();
1722 m_subwidgetsIterator--;
1723 m_subWidgetRank--;
1725 } else {
1726 for (int i=0; i<npos; i++) {
1727 if (m_subwidgetsIterator == m_subwidgets.end()) return;
1728 currentShowing = m_subwidgetsIterator.value();
1729 m_subwidgetsIterator++;
1730 m_subWidgetRank++;
1731 if (m_subwidgetsIterator == m_subwidgets.end()) {
1732 m_subwidgetsIterator = m_subwidgets.begin();
1733 m_subWidgetRank = 0;
1737 if (currentShowing && m_subwidgetsIterator != m_subwidgets.end()) {
1738 currentShowing->showDetails(false);
1739 m_subwidgetsIterator.value()->showDetails(true);
1740 m_view->scene()->setFocusItem(m_subwidgetsIterator.value(), Qt::MouseFocusReason);
1741 m_subwidgetsIterator.value()->widget()->setFocus(Qt::MouseFocusReason);
1743 layoutSubTasks(m_subwidgets, m_subWidgetRank);
1746 void Sak::focusedSubTask()
1748 SakSubWidget* w = dynamic_cast<SakSubWidget*>(sender());
1749 if (w) {
1750 QMap<int, SakSubWidget*>::iterator itr = m_subwidgets.begin(), end=m_subwidgets.end();
1751 for(int i=0; itr != end; i++,itr++) {
1752 if (itr.value() == w) {
1753 m_subwidgetsIterator = itr;
1754 m_subWidgetRank = i;
1760 //END Tasks >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
1763 //BEGIN settings ================================
1766 void Sak::setupSettingsWidget()
1768 m_settings = new QMainWindow();
1769 m_settings->setMinimumHeight(650);
1770 m_settings->setMinimumWidth(700);
1771 QWidget* centralWidget = new QWidget;
1772 m_settings->setCentralWidget(centralWidget);
1774 QVBoxLayout* theMainLayout = new QVBoxLayout;
1775 centralWidget->setLayout(theMainLayout);
1777 tabs = new QTabWidget;
1778 theMainLayout->addWidget(tabs);
1779 previewButton = new QPushButton("Preview");
1780 theMainLayout->addWidget(previewButton);
1782 tab1 = new QWidget;
1783 tab2 = new QWidget;
1784 tab3 = new QWidget;
1785 tab4 = new QWidget;
1786 tabs->addTab(tab1, "Tasks");
1787 tabs->addTab(tab2, "General");
1788 tabs->addTab(tab4, "Statistics");
1789 tabs->addTab(tab3, "Advanced");
1791 createActions();
1792 QMenuBar* mainMenu = new QMenuBar;
1793 m_settings->setMenuBar(mainMenu);
1794 QMenu* programMenu = mainMenu->addMenu("Program");
1795 programMenu->addAction(quitAction);
1796 programMenu->addAction(minimizeAction);
1797 QMenu* dbMenu = mainMenu->addMenu("Db");
1798 dbMenu->addAction(flushAction);
1799 dbMenu->addAction(openAction);
1800 // dbMenu->addAction(saveAsDbAction);
1801 dbMenu->addAction(exportDbCsvAction);
1802 #ifdef USEGMAIL
1803 dbMenu->addAction(gmailLoginAction);
1804 dbMenu->addAction(saveToGmailAction);
1805 if (!m_gmail->isValid()) {
1806 gmailLoginAction->setEnabled(false);
1807 saveToGmailAction->setEnabled(false);
1809 #endif
1810 QMenu* actionsMenu = mainMenu->addMenu("Actions");
1811 actionsMenu->addAction(startAction);
1812 actionsMenu->addAction(stopAction);
1813 actionsMenu->addAction(workingOnAction);
1815 QVBoxLayout *mainLayout = new QVBoxLayout;
1816 QGridLayout *messageLayout = new QGridLayout;
1817 durationLabel = new QLabel(tr("Interval:"));
1818 durationLabel1 = new QLabel(tr("(effective only after restart)"));
1820 QSettings settings(QSettings::IniFormat, QSettings::UserScope, "ZanzaSoft", "SAK");
1821 durationSpinBox = new QSpinBox;
1822 durationSpinBox->setSingleStep(1);
1823 durationSpinBox->setRange(1, 1440);
1824 durationSpinBox->setSuffix(" minutes");
1825 durationSpinBox->setValue(qMin(1440, qMax(1, settings.value("Ping interval", 15).toInt())));
1826 durationSpinBox->setCorrectionMode(QAbstractSpinBox::CorrectToNearestValue);
1828 bodyLabel = new QLabel(tr("Message:"));
1829 bodyEdit = new QTextEdit;
1830 bodyEdit->setPlainText( settings.value("Message", "<h1>Enter a message here!</h1>").toString() );
1831 askingLady = new PixmapViewer(0,false);
1832 askingLady->setPixmap(QPixmap(":/images/whip.png"));
1833 viewBackground = new PixmapViewer(0, false);
1835 messageLayout->addWidget(durationLabel, 1, 0);
1836 messageLayout->addWidget(durationSpinBox, 1, 1);
1837 messageLayout->addWidget(durationLabel1, 1, 2);
1838 messageLayout->addWidget(bodyLabel, 3, 0);
1839 messageLayout->addWidget(bodyEdit, 3, 1, 2, 4);
1840 messageLayout->addWidget(new QLabel("Character image:"), 7, 0);
1841 messageLayout->addWidget(askingLady, 7, 1, 2, 4);
1842 messageLayout->addWidget(new QLabel("View background:"), 11, 0);
1843 messageLayout->addWidget(viewBackground, 11, 1, 2, 4);
1844 messageLayout->setColumnStretch(3, 1);
1845 messageLayout->setRowStretch(4, 1);
1846 mainLayout->addLayout(messageLayout);
1847 tab2->setLayout(mainLayout);
1849 mainLayout = new QVBoxLayout;
1850 tab1->setLayout(mainLayout);
1851 // create tree view
1852 tasksTree = new QTreeWidget;
1853 tasksTree->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
1854 tasksTree->setColumnCount(3);
1855 tasksTree->setColumnWidth(0, 300);
1856 tasksTree->setColumnWidth(1, 65);
1857 tasksTree->setIconSize(QSize(32,32));
1858 connect(tasksTree, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(doubleClickedTask(QTreeWidgetItem*,int)));
1861 taskTextEditor = new QTextEdit;
1862 taskTextEditor->setFixedHeight(100);
1863 taskTextEditor->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
1864 taskTextEditor->setToolTip("Task description");
1865 connect(taskTextEditor, SIGNAL(textChanged()), this, SLOT(commitCurrentTask()));
1868 taskTitleEditor = new QLineEdit;
1869 taskTitleEditor->setFixedHeight(20);
1870 taskTitleEditor->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
1871 taskTitleEditor->setValidator(new QRegExpValidator(QRegExp("[\\w]*"), this));
1872 taskTitleEditor->setToolTip("Task title (alphanumeric)");
1873 connect(taskTitleEditor, SIGNAL(editingFinished()), this, SLOT(commitCurrentTask()));
1875 taskTagsEditor = new QLineEdit;
1876 taskTagsEditor->setFixedHeight(20);
1877 taskTagsEditor->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
1878 taskTagsEditor->setValidator(new QRegExpValidator(QRegExp("[\\w,:]*"), this));
1879 taskTagsEditor->setToolTip("Task tags (alphanumeric comma or colon separated)");
1880 connect(taskTagsEditor, SIGNAL(editingFinished()), this, SLOT(commitCurrentTask()));
1882 taskPixmapViewer = new PixmapViewer;
1883 mainLayout->addWidget(tasksTree, 2);
1884 QHBoxLayout* detailsLayout = new QHBoxLayout;
1885 QVBoxLayout* editsLayout = new QVBoxLayout;
1886 detailsLayout->addWidget(taskPixmapViewer);
1887 connect(taskPixmapViewer, SIGNAL(changed()), this, SLOT(commitCurrentTask()));
1888 editsLayout->addWidget(taskTitleEditor);
1889 editsLayout->addWidget(taskTextEditor);
1890 editsLayout->addWidget(taskTagsEditor);
1892 QHBoxLayout* datesLayout = new QHBoxLayout;
1893 datesLayout->addWidget(new QLabel("Due: "));
1894 dueEditor = new QDateEdit;
1895 dueEditor->setMinimumDate(QDate(2000,1,1));
1896 datesLayout->addWidget(dueEditor);
1897 datesLayout->addWidget(new QLabel("Estimated: "));
1898 estimatedHoursEditor = new QSpinBox;
1899 estimatedHoursEditor->setRange(0, 1e5);
1900 estimatedHoursEditor->setSuffix("hours");
1901 datesLayout->addWidget(estimatedHoursEditor);
1902 editsLayout->addLayout(datesLayout);
1903 detailsLayout->addLayout(editsLayout);
1904 connect(dueEditor, SIGNAL(editingFinished()), this, SLOT(commitCurrentTask()));
1905 connect(estimatedHoursEditor,SIGNAL(editingFinished()), this, SLOT(commitCurrentTask()));
1907 QVBoxLayout* colorsLayout = new QVBoxLayout;
1908 bgColorButton = new QPushButton("bg\ncolor");
1909 bgColorButton->setToolTip("Background color");
1910 bgColorButton->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding));
1912 fgColorButton = new QPushButton("fg\ncolor");
1913 fgColorButton->setToolTip("Foreground color");
1914 fgColorButton->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding));
1916 #ifdef Q_WS_WIN
1917 // fix Windows XP "style"
1918 bgColorButton->setStyle(new QWindowsStyle());
1919 fgColorButton->setStyle(new QWindowsStyle());
1920 #endif
1922 colorsLayout->addWidget(bgColorButton);
1923 colorsLayout->addWidget(fgColorButton);
1924 detailsLayout->addLayout(colorsLayout);
1926 mainLayout->addLayout(detailsLayout);
1928 QVBoxLayout* tab4MainLayout = new QVBoxLayout(tab4);
1929 //taskSelector = new QComboBox;
1930 summaryList = newTaskSummaryList();
1931 QTabWidget* tabs = new QTabWidget;
1932 tabs->setTabPosition(QTabWidget::East);
1933 tabs->addTab(summaryList, "List");
1934 summaryView = new QGraphicsView;
1935 summaryView->setScene(new QGraphicsScene);
1936 summaryChart = new TaskSummaryPieChart;
1937 summaryView->scene()->addItem(summaryChart);
1938 summaryView->scene()->setSceneRect(summaryChart->boundingRect());
1939 summaryView->fitInView(summaryView->sceneRect());
1940 summaryView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1941 summaryView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1942 summaryView->installEventFilter(this);
1943 tabs->addTab(summaryView, "Chart");
1944 tab4MainLayout->addWidget(tabs);
1945 cal3 = new QCalendarWidget;
1946 cal3->setMinimumSize(QSize(250,200));
1947 cal4 = new QCalendarWidget;
1948 cal4->setMinimumSize(QSize(250,200));
1949 cal3->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
1950 cal4->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
1951 QHBoxLayout* calsLayout = new QHBoxLayout;
1952 calsLayout->addWidget(cal3);
1953 calsLayout->addWidget(cal4);
1954 cal4->setSelectedDate(QDate::currentDate().addDays(1));
1955 tab4MainLayout->addLayout(calsLayout);
1958 QVBoxLayout* tab3MainLayout = new QVBoxLayout(tab3);
1959 //taskSelector = new QComboBox;
1960 hitsList = newHitsList();
1961 connect(hitsList, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), this, SLOT(hitsSelectedInList(QTreeWidgetItem*,QTreeWidgetItem*)));
1962 hitsTimeline = new Timeline;
1963 connect(hitsTimeline, SIGNAL(hitSelected(HitItem*)), this, SLOT(hitsSelectedInTimeline(HitItem*)));
1964 cal1 = new QCalendarWidget;
1965 cal1->setMinimumSize(QSize(250,200));
1966 cal2 = new QCalendarWidget;
1967 cal2->setMinimumSize(QSize(250,200));
1968 cal1->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
1969 cal2->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
1970 //tab3MainLayout->addWidget(taskSelector);
1972 calsLayout = new QHBoxLayout;
1973 calsLayout->addWidget(cal1);
1974 calsLayout->addWidget(cal2);
1975 cal2->setSelectedDate(QDate::currentDate().addDays(1));
1976 tab3MainLayout->addWidget(hitsList);
1977 tab3MainLayout->addWidget(hitsTimeline);
1978 tab3MainLayout->addLayout(calsLayout);
1982 m_settings->setWindowTitle(tr("SaK"));
1983 m_settings->resize(400, 300);
1987 QTreeWidget* Sak::newHitsList()
1989 QTreeWidget* hitsList = new QTreeWidget;
1990 hitsList->setColumnCount(4);
1991 hitsList->setColumnWidth(0, 200);
1992 hitsList->setColumnWidth(1, 150);
1993 hitsList->setColumnWidth(2, 150);
1994 hitsList->setColumnWidth(3, 150);
1995 hitsList->setColumnWidth(4, 150);
1996 hitsList->setIconSize(QSize(24,24));
1997 hitsList->setSortingEnabled(true);
1998 hitsList->setItemDelegateForColumn(0, new MyDateItemDelegate);
1999 hitsList->setItemDelegateForColumn(1, new TaskItemDelegate(this));
2000 hitsList->setItemDelegateForColumn(2, new SubTaskItemDelegate(this));
2001 QTreeWidgetItem* header = new QTreeWidgetItem;
2002 header->setText(0, "Date/Time");
2003 header->setText(1, "Task");
2004 header->setText(2, "Subtask");
2005 header->setText(3, "Duration (min)");
2006 header->setText(4, "Overestimation");
2007 hitsList->setHeaderItem(header);
2008 hitsList->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
2009 return hitsList;
2012 QTreeWidget* Sak::newTaskSummaryList()
2014 QTreeWidget* taskSummaryList = new QTreeWidget;
2015 taskSummaryList->setColumnCount(4);
2016 taskSummaryList->setColumnWidth(0, 250);
2017 taskSummaryList->setColumnWidth(1, 150);
2018 taskSummaryList->setColumnWidth(1, 150);
2019 taskSummaryList->setIconSize(QSize(24,24));
2020 taskSummaryList->setSortingEnabled(true);
2021 QTreeWidgetItem* header = new QTreeWidgetItem;
2022 header->setText(0, "Task");
2023 header->setText(1, "Hours");
2024 taskSummaryList->setHeaderItem(header);
2025 taskSummaryList->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
2026 taskSummaryList->setEnabled(true);
2027 return taskSummaryList;
2030 void Sak::setVisible(bool visible)
2032 minimizeAction->setEnabled(visible);
2033 maximizeAction->setEnabled(!m_settings->isMaximized());
2034 restoreAction->setEnabled(m_settings->isMaximized() || !visible);
2035 m_settings->setVisible(visible);
2038 void Sak::createActions()
2040 minimizeAction = new QAction(tr("Mi&nimize"), m_settings);
2041 connect(minimizeAction, SIGNAL(triggered()), m_settings, SLOT(hide()));
2043 maximizeAction = new QAction(tr("Ma&ximize"), m_settings);
2044 connect(maximizeAction, SIGNAL(triggered()), m_settings, SLOT(showMaximized()));
2046 restoreAction = new QAction(tr("&Restore"), m_settings);
2047 connect(restoreAction, SIGNAL(triggered()), m_settings, SLOT(showNormal()));
2049 quitAction = new QAction(tr("&Quit"), m_settings);
2050 connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
2052 startAction = new QAction(tr("Start polling"), m_settings);
2053 connect(startAction, SIGNAL(triggered()), this, SLOT(start()));
2055 stopAction = new QAction(tr("Pause polling"), m_settings);
2056 connect(stopAction, SIGNAL(triggered()), this, SLOT(pause()));
2058 workingOnAction = new QAction(tr("Working on.."), m_settings);
2059 connect(workingOnAction, SIGNAL(triggered()), this, SLOT(workingOn()));
2061 flushAction = new QAction(tr("&Flush data/settings to disk"), m_settings);
2062 connect(flushAction, SIGNAL(triggered()), this, SLOT(flush()));
2064 saveAsDbAction = new QAction(tr("Backup as"), m_settings);
2065 // connect(saveAsDbAction, SIGNAL(triggered()), this, SLOT(saveAsDb()));
2067 exportDbCsvAction = new QAction(tr("Export hits in CSV format"), m_settings);
2068 connect(exportDbCsvAction, SIGNAL(triggered()), this, SLOT(exportDbCsv()));
2070 #ifdef USEGMAIL
2071 saveToGmailAction = new QAction(tr("Store in your gmail account free space"), m_settings);
2072 connect(saveToGmailAction, SIGNAL(triggered()), this, SLOT(saveToGmail()));
2074 gmailLoginAction = new QAction(tr("Log in gmail"), m_settings);
2075 connect(gmailLoginAction, SIGNAL(triggered()), this, SLOT(logInGmail()));
2076 #else
2077 saveToGmailAction = NULL;
2078 gmailLoginAction = NULL;
2079 #endif
2081 openAction = new QAction(tr("Import a task from file"), m_settings);
2082 connect(openAction, SIGNAL(triggered()), this, SLOT(open()));
2084 m_addHitAction = new QAction("Add hit", m_settings);
2085 m_addHitMenu = new QMenu(m_settings);
2086 m_addHitAction->setText("Add hit");
2087 m_addHitMenu->addAction(m_addHitAction);
2088 connect(m_addHitAction, SIGNAL(triggered()), this, SLOT(addDefaultHit()));
2090 m_exportDataAction = new QAction("Export data", m_settings);
2091 m_exportDataAction->setText("Export data");
2092 m_addHitMenu->addAction(m_exportDataAction);
2093 connect(m_exportDataAction, SIGNAL(triggered()), this, SLOT(exportHits()));
2095 m_addTaskAction = new QAction("Add task", m_settings);
2096 m_addTaskMenu = new QMenu(m_settings);
2097 m_addTaskAction->setText("Add task");
2098 m_addTaskMenu->addAction(m_addTaskAction);
2099 connect(m_addTaskAction, SIGNAL(triggered()), this, SLOT(addDefaultTask()));
2102 void Sak::trayIconActivated(QSystemTrayIcon::ActivationReason reason)
2104 if (reason == QSystemTrayIcon::DoubleClick) {
2105 setVisible(true);
2109 //END Settings <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<