notifies from systray when stopped
[Sak.git] / sak.cpp
blob36cc077c539f845bf9a5702171946498a61435e4
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 <cassert>
13 #include "sak.h"
14 #include "sakwidget.h"
15 #include "saksubwidget.h"
16 #include "sakmessageitem.h"
17 #include "pixmapviewer.h"
18 #include "timeline.h"
19 #include "backupper.h"
20 #include "piechart.h"
21 #ifdef USELIBGMAIL
22 #include "gmailstorage/gmailpyinterface.h"
23 #else
24 #include "gmailstorage/gmailmyinterface.h"
25 #endif
27 //END Task <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
30 // GView
31 #include <QtOpenGL>
33 #if defined(Q_WS_X11)
34 #include <QX11Info>
35 namespace X11
37 #include <X11/Xlib.h>
38 #undef KeyPress
39 #undef KeyRelease
40 static Window CurrentFocusWindow;
41 static int CurrentRevertToReturn;
43 #endif
45 static int grabbed;
48 class GView : public QGraphicsView
50 public:
51 // GView() {
52 // if (QGLFormat::hasOpenGL()) {
53 // qDebug() << "Using OpenGL";
54 // QGLWidget* w = new QGLWidget;
55 // w->setAttribute(Qt::WA_TranslucentBackground, true);
56 // setViewport(w);
57 // }
58 // }
59 // ~GView() {
60 // delete this->viewport();
61 // }
62 void drawBackground(QPainter* p, const QRectF & rect) {
63 QBrush brush(QColor(0,0,0,200));
64 p->setCompositionMode(QPainter::CompositionMode_Source);
65 p->fillRect(rect, brush);
70 //BEGIN Sak basic >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
72 Sak::Sak(QObject* parent)
73 : QObject(parent)
74 , m_timerId(0)
75 , m_timeoutPopup(0)
76 , m_stopped(false)
77 , m_settings(0)
78 , m_changedHit(false)
79 , m_changedTask(false)
80 , m_subtaskView(false)
82 m_desktopRect = qApp->desktop()->rect();
84 m_subtaskCompleter = 0;
85 summaryList = hitsList = 0;
86 init();
88 if (QCoreApplication::arguments().contains("--clear")) {
89 QHash<QString, Task>::iterator itr = m_tasks.begin();
90 while(itr != m_tasks.end()) {
91 itr->hits.clear();
92 itr++;
96 if (m_tasks.count() <= 0)
97 m_settings->show();
99 m_previewing = false;
100 m_changedHit = false;
101 m_timerId = 0;
102 m_autoSaveTimer = startTimer(1000 * 60 * 45); // every 45 minutes
103 start();
105 // Need to go here, or after plasma reboot the icon will disappear
106 trayIconMenu = new QMenu(m_settings);
107 //trayIconMenu->addAction(minimizeAction);
108 //trayIconMenu->addAction(maximizeAction);
109 //trayIconMenu->addAction(restoreAction);
110 //trayIconMenu->addSeparator();
111 trayIconMenu->addAction(startAction);
112 trayIconMenu->addAction(stopAction);
113 trayIconMenu->addAction(flushAction);
114 trayIconMenu->addSeparator();
115 trayIconMenu->addAction(quitAction);
116 trayIcon = new QSystemTrayIcon(this);
117 trayIcon->setContextMenu(trayIconMenu);
118 trayIcon->setIcon( QIcon(":/images/icon.png") );
119 trayIcon->setToolTip( tr("Sistema Anti Kazzeggio") );
120 trayIcon->show();
121 connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(trayIconActivated(QSystemTrayIcon::ActivationReason)));
122 trayIcon->installEventFilter(this);
125 m_settings->setWindowIcon( QIcon(":/images/icon.png") );
126 m_settings->setWindowTitle("SaK - Sistema Anti Kazzeggio");
129 void Sak::init()
131 m_backupper = new Backupper;
132 m_incremental = new Incremental;
133 #ifdef USEGMAIL
134 m_gmail = new GmailPyInterface;
135 #else
136 m_gmail = NULL;
137 #endif
139 // load the data model
140 QSettings settings(QSettings::IniFormat, QSettings::UserScope, "ZanzaSoft", "SAK");
141 QByteArray tasksArray = settings.value("tasks").toByteArray();
142 QDataStream stream(&tasksArray, QIODevice::ReadWrite);
143 stream.setVersion(QDataStream::Qt_4_3);
146 { // read locastasks
147 QDir saveDir(QFileInfo(settings.fileName()).dir());
148 saveDir.mkdir("SakTasks");
149 saveDir.cd("SakTasks");
150 QStringList nameFilters;
151 nameFilters << "*.xml";
152 QStringList files = saveDir.entryList( nameFilters, QDir::Files);
153 foreach (QString taskXmlFileName, files) {
154 Task t( loadTaskFromFile(saveDir.filePath(taskXmlFileName)) );
155 m_tasks[t.title] = t;
160 // add subtasks, if missing
162 QHash<QString, Task>::iterator itr = m_tasks.begin();
163 while(itr != m_tasks.end()) {
164 itr->updateSubTasks();
165 itr++;
169 // reset awayTask
170 Task & awayTask = m_tasks["<away>"];
171 awayTask.title = "<away>";
172 awayTask.fgColor = Qt::gray;
173 awayTask.bgColor = Qt::white;
174 awayTask.icon = QPixmap(":/images/away.png");
176 m_editedTasks = m_tasks;
179 hitsTimeline = 0;
180 //merge piecies
181 interactiveMergeHits();
183 m_editedTasks = m_tasks;
185 setupSettingsWidget();
188 m_settings->installEventFilter(this);
189 hitsList->installEventFilter(this);
190 tasksTree->installEventFilter(this);
191 tasksTree->setUniformRowHeights(false);
192 QTreeWidgetItem* headerItem = new QTreeWidgetItem;
193 headerItem->setSizeHint(0 , QSize(0,0));
194 headerItem->setSizeHint(1 , QSize(0,0));
195 headerItem->setSizeHint(2 , QSize(0,0));
196 tasksTree->setHeaderItem(headerItem);
198 connect(bgColorButton, SIGNAL(clicked()), this, SLOT(selectColor()));
199 connect(fgColorButton, SIGNAL(clicked()), this, SLOT(selectColor()));
200 connect(previewButton, SIGNAL(clicked()), this, SLOT(popup()));
201 connect(tasksTree, SIGNAL(itemSelectionChanged()), this, SLOT(selectedTask()));
202 connect(tasksTree, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(selectedTask()));
203 populateTasks();
205 connect(cal1, SIGNAL(clicked(QDate)), this, SLOT(selectedStartDate(QDate)));
206 connect(cal2, SIGNAL(clicked(QDate)), this, SLOT(selectedEndDate(QDate)));
207 connect(cal3, SIGNAL(clicked(QDate)), this, SLOT(selectedStartDate(QDate)));
208 connect(cal4, SIGNAL(clicked(QDate)), this, SLOT(selectedEndDate(QDate)));
209 connect(hitsList, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(hitsListItemChanged(QTreeWidgetItem*,int)));
210 selectedTask();
212 m_view = new GView;
213 m_view->setScene(new QGraphicsScene);
214 m_view->scene()->setSceneRect(m_desktopRect);
216 m_view->installEventFilter(this);
217 m_view->setFrameStyle(QFrame::NoFrame);
218 m_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
219 m_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
220 m_view->setWindowFlags(m_view->windowFlags() | Qt::WindowStaysOnTopHint | Qt::ToolTip );
221 //m_view->setWindowModality(Qt::ApplicationModal);
222 m_view->setAttribute(Qt::WA_QuitOnClose, false);
223 // enable transparency with Qt4.5
224 m_view->setAttribute(Qt::WA_TranslucentBackground, true);
225 m_view->setWindowIcon( QIcon(":/images/icon.png") );
226 m_view->setWindowTitle("SaK - Sistema Anti Kazzeggio");
228 m_currentInterval = durationSpinBox->value();
229 m_currentInterval = qMax((int)1, qMin((int)1440, m_currentInterval));
230 qDebug() << "SAK: pinging interval " << m_currentInterval << Task::hours(m_currentInterval) << " hours ";
232 hitsTimeline->setPeriod(QDateTime(cal1->selectedDate()), QDateTime(cal2->selectedDate()));
233 populateHitsList(createHitsList(QDateTime(cal1->selectedDate()), QDateTime(cal2->selectedDate())));
236 void Sak::start()
238 m_stopped=false;
239 m_currentInterval = qMax((int)1, m_currentInterval);
240 if (!m_timerId) {
241 int msecs = (int)(Task::hours(m_currentInterval)*3600.0*1000.0 / 2);
242 m_timerId = startTimer( msecs );
243 m_nextTimerEvent = QDateTime::currentDateTime().addMSecs(msecs);
244 startAction->setEnabled(false);
245 stopAction->setEnabled(true);
246 } else {
247 startAction->setEnabled(true);
248 stopAction->setEnabled(false);
252 void Sak::stop()
254 m_stopped=true;
255 if (m_timerId) {
256 //killTimer(m_timerId);
257 //m_timerId = 0;
258 stopAction->setEnabled(false);
259 startAction->setEnabled(true);
260 } else {
261 stopAction->setEnabled(true);
262 startAction->setEnabled(false);
266 Task Sak::loadTaskFromFile(const QString& filePath)
268 QFile taskXmlFile(filePath);
269 Task t;
270 qDebug() << "Examine task file " << taskXmlFile.fileName();
271 if (!taskXmlFile.open(QIODevice::ReadOnly)) {
272 qDebug() << "Failed opening xml file " << taskXmlFile.fileName();
274 QByteArray data = taskXmlFile.readLine();
275 QXmlStreamReader stream(data);
276 QXmlStreamReader::TokenType token = stream.readNext(); // skip StartDocument
277 token = stream.readNext();
278 if ( token != QXmlStreamReader::Comment) {
279 qDebug() << "Skip file " << taskXmlFile.fileName() << " (want a file starting with a comment representing MD5, got" << token << ")";
280 return t;
282 QString md5 = stream.text().toString().trimmed();
283 qDebug() << "md5 = " << md5;
285 // check md5
286 data = taskXmlFile.readAll();
287 if ( md5 != QCryptographicHash::hash(data, QCryptographicHash::Md5).toHex() ) {
288 if (QMessageBox::No == QMessageBox::warning(0, "Corrupted file!",
289 QString("Check of file " + taskXmlFile.fileName() + " failed (maybe it has been edited by hand).\nDo you want to load it anyway?" )
290 ,QMessageBox::Yes | QMessageBox::No) ) {
291 qDebug() << "Skip file " << taskXmlFile.fileName() << " (bad md5 sum)";
292 return t;
296 // read rest of data
297 stream.clear();
298 stream.addData(data);
300 if ( stream.readNext() != QXmlStreamReader::StartDocument) {
301 qDebug() << "Skip file " << taskXmlFile.fileName() << " (want start document)";
302 return t;
304 stream >> t;
305 if (stream.error() != QXmlStreamReader::NoError) {
306 qDebug() << "Error reading task data from file " << taskXmlFile.fileName() << ":" << stream.errorString();
307 return Task();
309 // QFile tmp("/tmp/" + t.title + ".xml");
310 // tmp.open(QIODevice::ReadWrite);
311 // QXmlStreamWriter ss(&tmp);
312 // ss.setAutoFormatting(true);
313 // ss.setAutoFormattingIndent(2);
314 // ss.writeStartDocument();
315 // ss << t;
316 // ss.writeEndDocument();
317 // tmp.close();
318 else
319 return t;
322 void Sak::flush()
324 if (m_changedTask)
325 saveTaskChanges();
326 if (m_changedHit)
327 saveHitChanges();
329 if (!m_settings) return;
330 m_backupper->doCyclicBackup();
331 QSettings settings(QSettings::IniFormat, QSettings::UserScope, "ZanzaSoft", "SAK");
332 // QByteArray tasksArray;
333 // QDataStream stream(&tasksArray, QIODevice::ReadWrite);
334 // stream.setVersion(QDataStream::Qt_4_0);
335 // stream << m_tasks;
336 // settings.setValue("tasks", tasksArray);
337 settings.setValue("Ping interval", durationSpinBox->value());
338 settings.setValue("Message", bodyEdit->toPlainText());
339 settings.sync();
341 QDir saveDir(QFileInfo(settings.fileName()).dir());
342 saveDir.mkdir("SakTasks");
343 saveDir.cd("SakTasks");
345 foreach(Task t, m_tasks) {
346 if (t.title.isEmpty()) continue;
347 QFile xmlTaskSave(saveDir.filePath(t.title + ".xml"));
348 QByteArray taskArray;
349 QXmlStreamWriter stream(&taskArray);
350 stream.setAutoFormatting(true);
351 stream.setAutoFormattingIndent(2);
352 stream.writeStartDocument();
353 stream << t;
354 stream.writeEndDocument();
355 xmlTaskSave.open(QIODevice::ReadWrite | QIODevice::Truncate);
356 qDebug() << "Saving xml to file " << xmlTaskSave.fileName();
357 QByteArray hash;
358 hash.append("<!-- ");
359 hash.append( QCryptographicHash::hash(taskArray, QCryptographicHash::Md5).toHex() );
360 hash.append(" -->\n");
361 xmlTaskSave.write(hash);
362 xmlTaskSave.write(taskArray);
363 xmlTaskSave.close();
366 // remove files not matching a task
367 QStringList nameFilters;
368 nameFilters << "*.xml";
369 QStringList files = saveDir.entryList( nameFilters, QDir::Files);
370 foreach (QString taskXmlFileName, files) {
371 if (!m_tasks.contains(QFileInfo(taskXmlFileName).baseName())) {
372 qWarning()<< "Remove task " << QFileInfo(taskXmlFileName).baseName() << " from disk";
373 QFile(saveDir.filePath(taskXmlFileName)).remove();
378 m_incremental->clearAddedPieces();
381 //void Sak::saveAsDb()
383 // if (!m_settings) return;
384 // QString fileName = QFileDialog::getSaveFileName();
385 // QFile file(fileName);
386 // file.remove();
387 // flush();
388 // QSettings settingsQSettings::IniFormat, QSettings::UserScope, ("ZanzaSoft", "SAK");
389 // QFile file1(settings.fileName());
390 // if (!file1.copy(fileName)) {
391 // qWarning() << "Error copying " << settings.fileName() << " to " << fileName << file1.errorString();
392 // }
395 void Sak::exportDbCsv()
397 if (!m_settings) return;
398 QString fileName = QFileDialog::getSaveFileName();
399 QFile file(fileName);
400 if (!file.open(QIODevice::ReadWrite|QIODevice::Truncate)) {
401 QMessageBox::warning(0, "Error saving", QString("Error saving to file %1").arg(fileName));
402 return;
404 QTextStream stream(&file);
405 foreach(const Task& t, m_tasks) {
406 QHash< QString, QList< Task::Hit > >::const_iterator itr = t.hits.begin();
407 while(itr != t.hits.end()) {
408 QList< Task::Hit >::const_iterator hitr = itr.value().begin(), hend = itr.value().end();
409 while(hitr != hend) {
410 stream << t.title << ";" << itr.key() << ";" << hitr->timestamp.toString(DATETIMEFORMAT) << ";" << hitr->duration << ";\n";
411 hitr++;
413 itr++;
416 file.close();
420 void Sak::logInGmail()
422 m_gmail->forceLogin();
425 void Sak::saveToGmail()
427 if (!m_settings) return;
428 flush();
429 QSettings settings(QSettings::IniFormat, QSettings::UserScope, "ZanzaSoft", "SAK");
431 QDir saveDir(QFileInfo(settings.fileName()).dir());
432 saveDir.mkdir("SakTasks");
433 saveDir.cd("SakTasks");
434 QStringList nameFilters;
435 nameFilters << "*.xml";
436 QStringList files = saveDir.entryList( nameFilters, QDir::Files);
437 QStringList filePaths;
438 foreach (QString taskXmlFileName, files) {
439 filePaths << saveDir.filePath(taskXmlFileName);
441 m_gmail->storeTaskFiles(filePaths);
444 void Sak::importFromGmail()
446 QStringList filePaths = m_gmail->fetchLatestTasks();
449 void Sak::open(const QStringList& _fileNames)
451 QStringList fileNames = _fileNames.size()?_fileNames:QFileDialog::getOpenFileNames(0, "Open a new task", QString(), "*.xml" );
452 foreach(QString fileName, fileNames) {
453 QFile file(fileName);
454 if (!file.exists()) {
455 QMessageBox::warning(0, "Cannot find task", QString("Cannot find task file %1").arg(fileName));
458 QSettings settings(QSettings::IniFormat, QSettings::UserScope, "ZanzaSoft", "SAK");
459 QDir saveDir(QFileInfo(settings.fileName()).dir());
460 saveDir.mkdir("SakTasks");
461 saveDir.cd("SakTasks");
463 if ( QFile(saveDir.filePath(QFileInfo(fileName).completeBaseName())).exists() ) {
464 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?");
465 QPushButton* overwriteButton = mbox.addButton("Overwrite", QMessageBox::YesRole);
466 QPushButton* mergeButton = mbox.addButton("Merge", QMessageBox::NoRole);
467 QPushButton* cancelButton = mbox.addButton("Cancel", QMessageBox::RejectRole);
468 mbox.setDefaultButton(cancelButton);
469 mbox.exec();
470 QAbstractButton* b = mbox.clickedButton();
471 if (b == cancelButton) { continue; }
472 else {
473 m_backupper->doCyclicBackup();
474 if (b == mergeButton) {
475 Task t = loadTaskFromFile(file.fileName());
476 QHash< QString, QList< Task::Hit > > ::const_iterator itr = t.hits.begin(), end = t.hits.end();
477 while(itr != end) {
478 QString subtask = itr.key();
479 foreach(Task::Hit hit, itr.value())
480 m_incremental->addPiece(t.title, subtask, hit.timestamp, hit.duration);
481 itr++;
483 interactiveMergeHits();
484 } else if (b == overwriteButton) {
485 file.copy(saveDir.filePath(QFileInfo(fileName).completeBaseName()));
491 if (!fileNames.isEmpty()) {
492 m_settings->hide();
493 destroy();
494 init();
495 m_settings->show();
496 start();
500 void Sak::destroy()
502 stop();
503 if (!m_settings) return;
504 flush();
505 m_settings->deleteLater();
506 m_view->scene()->deleteLater();
507 m_view->deleteLater();
508 delete m_backupper;
509 delete m_incremental;
510 delete m_gmail;
511 m_previewing = false;
512 m_changedHit = false;
513 m_timerId = 0;
517 Sak::~Sak()
519 killTimer(m_autoSaveTimer);
520 destroy();
524 void Sak::layoutSubTasks( const QMap<int, SakSubWidget*> sortedWidgets, int currentRank) {
525 QMap<int, SakSubWidget*>::const_iterator itr = sortedWidgets.begin(), end = sortedWidgets.end();
526 QRect r = m_desktopRect;
527 for(int i=0; itr != end; i++, itr++) {
528 int h = (*itr)->size().height();
529 int w = (*itr)->size().width();
530 (*itr)->setPos(QPointF((r.width() - w)/2, (r.height()-h)/2 + (i - currentRank - 1) * (h+2)));
535 int Sak::taskCounter = 0;
537 bool Sak::eventFilter(QObject* obj, QEvent* e)
539 // if (obj == m_view) {
540 // qDebug() << "event : " << e->type();
541 // }
542 if (obj == tasksTree) {
543 return taskTreeEventFilter(e);
544 } else if (obj == hitsList || obj == summaryList) {
545 return hitsListEventFilter(e);
546 } else if (obj == m_settings && e->type() == QEvent::Close) {
547 if (m_changedTask)
548 saveTaskChanges();
549 if (m_changedHit)
550 saveHitChanges();
551 if (trayIcon->isVisible()) {
552 m_settings->hide();
553 e->ignore();
554 return true;
556 } else if (obj == m_view && e->type() == QEvent::Wheel) {
557 QWheelEvent* we = dynamic_cast<QWheelEvent*>(e);
558 if (m_subtaskView) {
559 scrollSubTasks(we->delta() / 120);
560 } else scrollTasks(we->delta() / 120);
561 } else if (obj == m_view && e->type() == QEvent::KeyPress) {
562 QKeyEvent* ke = dynamic_cast<QKeyEvent*>(e);
563 if ((ke->modifiers() & Qt::AltModifier) && (ke->modifiers() & Qt::ControlModifier) ) {
564 clearView();
565 return true;
566 } else if ( ((ke->modifiers() & Qt::ControlModifier) && (ke->key() == Qt::Key_Backspace) )
567 || ((ke->modifiers() & Qt::ControlModifier) && (ke->key() == Qt::Key_Left) )) {
568 if (m_subtaskView) {
569 popup();
570 return true;
572 } else if (m_subtaskView && ke->key() == Qt::Key_Up) {
573 scrollSubTasks(-1);
574 return true;
575 } else if (m_subtaskView && ke->key() == Qt::Key_Down) {
576 scrollSubTasks(+1);
577 return true;
578 } else if (!m_subtaskView && ke->key() == Qt::Key_Left) {
579 scrollTasks(-1);
580 return true;
581 } else if (!m_subtaskView && ke->key() == Qt::Key_Right) {
582 scrollTasks(+1);
583 return true;
584 } else if (!m_subtaskView && ke->key() == Qt::Key_Escape) {
585 clearView();
586 return true;
587 } else { // forward events to current widget
588 if (!m_subtaskView) {
589 if (m_widgetsIterator == m_widgets.end()) return false;
590 SakWidget* currentShowing = m_widgetsIterator.value();
591 currentShowing->keyPressEvent(ke);
592 return true;
593 } else {
594 // autoscroll on text completion!!!
595 if (m_subwidgetsIterator == m_subwidgets.end()) return false;
596 SakSubWidget* currentShowing = m_subwidgetsIterator.value();
597 currentShowing->keyPressEvent(ke);
599 if (m_subWidgetRank != 0 && m_subtaskCompleter) {
600 QString completion(m_subtaskCompleter->completionPrefix());
601 if (ke->text().size() == 1) {
602 if (ke->key() == Qt::Key_Backslash || ke->key() == Qt::Key_Backspace)
603 completion.chop(1);
604 else completion += ke->text();
605 m_subtaskCompleter->setCompletionPrefix(completion);
608 QStringList list( ((QStringListModel*)m_subtaskCompleter->model())->stringList() );
609 int newRank = 1 + ((QStringListModel*)m_subtaskCompleter->model())->stringList().indexOf(m_subtaskCompleter->currentIndex().row() >= 0 && completion.size() ? m_subtaskCompleter->currentCompletion() : completion);
611 if (m_subWidgetRank != newRank) {
612 scrollSubTasks(newRank - m_subWidgetRank);
613 if (newRank == 0) {
614 QLineEdit* editor = dynamic_cast<QLineEdit*>((*m_subwidgets.begin())->widget());
615 if (editor) {
616 editor->setText(completion);
621 } else if (m_subtaskCompleter) {
622 QLineEdit* editor = dynamic_cast<QLineEdit*>((*m_subwidgets.begin())->widget());
623 if (editor) {
624 m_subtaskCompleter->setCompletionPrefix(editor->text());
628 return true;
631 } else if (obj == m_view && e->type() == QEvent::Show) {
632 grabKeyboard();
633 QTimer::singleShot(500, this, SLOT(grabKeyboard()));
634 } else if (obj == m_view && e->type() == QEvent::Close) {
635 if (trayIcon->isVisible()) {
636 return true;
638 } else if (obj && obj == trayIcon && e->type() == QEvent::ToolTip) {
639 QDateTime last = m_incremental->lastTimeStamp;
640 int seconds = QDateTime::currentDateTime().secsTo(m_nextTimerEvent);
641 int hours = seconds / 3600;
642 int minutes = (seconds / 60) % 60;
643 seconds %= 60;
644 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>")))));
645 return false;
647 return false;
650 //END basic >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
653 //BEGIN Tasks >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
654 void Sak::addDefaultTask()
656 QString tentativeName;
657 do {
658 tentativeName = QString("Task %1").arg(taskCounter++);
659 } while(m_editedTasks.contains(tentativeName));
661 Task& t = m_editedTasks[tentativeName];
662 t.title = tentativeName;
663 QTreeWidgetItem* item = new QTreeWidgetItem(QStringList(tentativeName));
664 item->setData(0,Qt::UserRole, QVariant(QMetaType::VoidStar, &t));
665 tasksTree->addTopLevelItem(item);
666 m_changedTask=true;
669 void Sak::populateTasks()
671 tasksTree->clear();
673 QHash<QString, Task>::iterator itr = m_editedTasks.begin(), end=m_editedTasks.end();
674 for(; itr!=end; itr++) {
675 Task& t(itr.value());
676 t.checkConsistency();
678 if (t.title.isEmpty() || t.title == "<away>") continue; // skip away task
679 QTreeWidgetItem* item = new QTreeWidgetItem(QStringList(t.title));
680 item->setData(0, Qt::UserRole, QVariant(QMetaType::VoidStar, &t));
681 QIcon icon;
682 icon.addPixmap(t.icon);
683 item->setSizeHint(0, QSize(32,32));
684 item->setIcon(0, icon);
685 for(int i=0; i<3; i++) {
686 item->setForeground(i,t.fgColor);
687 item->setBackground(i,t.bgColor);
689 //item->setCheckState(1, t.active ? Qt::Checked : Qt::Unchecked);
690 //item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
691 item->setIcon(1,QIcon(t.active ? ":/images/active.png" : ":/images/inactive.png"));
692 item->setText(2,QString("%1 hours worked till now (overestimated %2)").arg(t.totHours, 4, 'f', 2, ' ').arg(t.totOverestimation));
693 foreach(Task::SubTask st, t.subTasks) {
694 if (!st.title.isEmpty()) {
695 QTreeWidgetItem* sitem = new QTreeWidgetItem(item, QStringList(st.title));
696 item->setData(0, Qt::UserRole, QVariant(QMetaType::VoidStar, &st));
697 sitem->setSizeHint(0, QSize(32,32));
698 QColor fgColor = st.fgColor.isValid() ? st.fgColor : t.fgColor;
699 QColor bgColor = st.bgColor.isValid() ? st.bgColor : t.bgColor;
700 for(int i=0; i<3; i++) {
701 sitem->setForeground(i,fgColor);
702 sitem->setBackground(i,bgColor);
704 sitem->setIcon(1,QIcon(st.active ? ":/images/active.png" : ":/images/inactive.png"));
705 sitem->setText(2,QString("%1 hours worked till now").arg(st.totHours,4,'f',2,' '));
708 tasksTree->addTopLevelItem(item);
712 void Sak::saveTaskChanges()
714 if (m_changedTask) {
715 commitCurrentTask();
716 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 ) {
717 m_tasks = m_editedTasks;
718 } else m_editedTasks = m_tasks; //. undo changes
719 m_changedTask=false;
720 selectedStartDate(QDate());
721 populateTasks();
725 void Sak::selectColor() {
726 if (tasksTree->selectedItems().isEmpty()) return;
728 if (sender() == fgColorButton) {
729 QColor c = QColorDialog::getColor(fgColorButton->palette().color(QPalette::ButtonText));
730 if (!c.isValid()) return;
731 QPalette p = fgColorButton->palette();
732 p.setColor(QPalette::ButtonText, c);
733 fgColorButton->setPalette(p);
734 bgColorButton->setPalette(p);
735 } else if (sender() == bgColorButton) {
736 QColor c = QColorDialog::getColor(bgColorButton->palette().color(QPalette::Button));
737 if (!c.isValid()) return;
738 QPalette p = bgColorButton->palette();
739 p.setColor(QPalette::Button, c);
740 fgColorButton->setPalette(p);
741 bgColorButton->setPalette(p);
743 commitCurrentTask();
746 bool Sak::taskTreeEventFilter(QEvent* e)
748 if (e->type() == QEvent::ContextMenu) {
749 QContextMenuEvent* me = dynamic_cast<QContextMenuEvent*>(e);
750 if (!me) return false;
751 m_addTaskMenu->popup(me->globalPos());
752 return true;
753 } else if (e->type() == QEvent::KeyRelease) {
754 QKeyEvent* ke = dynamic_cast<QKeyEvent*>(e);
755 if (!ke) return false;
756 if ( (ke->key() != Qt::Key_Delete && ke->key() != Qt::Key_Backspace) ) return false;
757 if (currentSubtask!="") {
758 QMessageBox whatToDo(QMessageBox::Warning, "Deleting subtask", "Deleting subtask " + currentSubtask + " of task " + currentTask);
759 QPushButton* moveHitsToParentButton = whatToDo.addButton("Move hits to task " + currentTask, QMessageBox::AcceptRole);
760 QPushButton* removeHitsButton = whatToDo.addButton("Remove hits", QMessageBox::AcceptRole);
761 QPushButton* cancelButton = whatToDo.addButton("Cancel", QMessageBox::RejectRole);
762 whatToDo.setDefaultButton(cancelButton);
763 whatToDo.exec();
764 if ( whatToDo.clickedButton() == cancelButton) return true;
765 if (m_editedTasks.find(currentTask) == m_editedTasks.end()) return true;
766 m_changedTask=true;
767 Task& t(m_editedTasks[currentTask]);
768 t.subTasks.take(currentSubtask);
769 if (whatToDo.clickedButton() == removeHitsButton) {
770 t.hits.take(currentSubtask);
771 } else if (whatToDo.clickedButton() == moveHitsToParentButton) {
772 QList<Task::Hit> sorter(t.hits.take(""));
773 sorter << t.hits.take(currentSubtask);
774 qStableSort(sorter.begin(), sorter.end());
775 t.hits[""] = sorter;
777 } else {
778 // remove file from disk
779 m_changedTask=true;
780 m_editedTasks.remove(currentTask);
782 tasksTree->clear();
783 populateTasks();
784 selectedStartDate(QDate());
785 return true;
786 } else if (e->type() == QEvent::Hide) {
787 saveTaskChanges();
789 return false;
793 void Sak::commitCurrentTask()
795 m_changedTask=true;
796 if (currentSubtask.isEmpty()) {
797 QString currentTitle = taskTitleEditor->text();
798 if (!currentTitle.isEmpty()) {
799 if (currentTitle != currentTask) {
800 if (m_editedTasks.contains(currentTitle)) {
801 QMessageBox::warning(0, "Conflict in task names", "Conflict in task names: current task " + currentTask + ", edited title " + currentTitle);
802 taskTitleEditor->setText(currentTask);
803 return;
804 } else if (m_editedTasks.contains(currentTask)) {
805 m_editedTasks[currentTitle] = m_editedTasks.take(currentTask);
806 m_editedTasks[currentTitle].title = currentTitle;
809 } else return;
810 Task& t = m_editedTasks[currentTitle];
811 t.bgColor = bgColorButton->palette().color(QPalette::Button);
812 t.fgColor = fgColorButton->palette().color(QPalette::ButtonText);
813 t.icon = taskPixmapViewer->pixmap();
814 QList<QTreeWidgetItem *> items = tasksTree->findItems(currentTask,Qt::MatchExactly,0);
815 foreach(QTreeWidgetItem* ii, items) {
816 ii->setText(0, currentTitle);
817 ii->setIcon(0, taskPixmapViewer->pixmap());
818 for (int i=0; i<3; i++) {
819 ii->setForeground(i, QColor(t.fgColor));
820 ii->setBackground(i, QColor(t.bgColor));
823 if (dueEditor->date() != dueEditor->minimumDate())
824 t.dueDate = dueEditor->date();
825 t.estimatedHours = estimatedHoursEditor->value();
826 currentTask=currentTitle;
828 if (tasksTree->selectedItems().size() != 1) return;
829 QTreeWidgetItem* item = tasksTree->selectedItems()[0];
830 item->setText(0, taskTitleEditor->text());
831 QIcon icon;
832 icon.addPixmap(t.icon);
833 item->setSizeHint(0, QSize(32,32));
834 item->setIcon(0, icon);
835 for(int i=0; i<3; i++) {
836 item->setForeground(i,t.fgColor);
837 item->setBackground(i,t.bgColor);
841 } else { // subtask edited
842 if (!m_editedTasks.contains(currentTask)) return;
843 Task& t(m_editedTasks[currentTask]);
844 QString currentTitle = taskTitleEditor->text();
845 // backup data
846 if (!currentTitle.isEmpty()) {
847 if (currentTitle != currentSubtask) {
848 if (t.subTasks.contains(currentTitle)) {
849 QMessageBox::warning(0, "Conflict in subtask names", "Conflict in subtask names");
850 taskTitleEditor->setText(currentSubtask);
851 return;
852 } else if (t.subTasks.contains(currentSubtask)) {
853 t.subTasks[currentTitle] = t.subTasks.take(currentSubtask);
854 t.subTasks[currentTitle].title = currentTitle;
855 t.hits[currentTitle] = t.hits.take(currentSubtask);
858 } else return;
859 Task::SubTask& st = t.subTasks[currentTitle];
860 st.bgColor = bgColorButton->palette().color(QPalette::Button);
861 st.fgColor = fgColorButton->palette().color(QPalette::ButtonText);
862 QList<QTreeWidgetItem *> items = tasksTree->findItems(currentTask,Qt::MatchExactly,0);
863 foreach(QTreeWidgetItem* jj, items) {
864 for(int i=0; i<jj->childCount(); i++) {
865 QTreeWidgetItem* ii = jj->child(i);
866 if (ii->text(0) != currentSubtask) continue;
867 ii->setText(0, currentTitle);
868 for (int i=0; i<3; i++) {
869 ii->setForeground(i, QColor(st.fgColor));
870 ii->setBackground(i, QColor(st.bgColor));
874 currentSubtask = currentTitle;
876 if (tasksTree->selectedItems().size() != 1) return;
877 QTreeWidgetItem* item = tasksTree->selectedItems()[0];
878 item->setText(0, taskTitleEditor->text());
879 QIcon icon;
880 icon.addPixmap(t.icon);
881 item->setSizeHint(0, QSize(32,32));
882 item->setIcon(0, icon);
883 for(int i=0; i<3; i++) {
884 item->setForeground(i,st.fgColor);
885 item->setBackground(i,st.bgColor);
890 void Sak::selectedTask()
892 if (tasksTree->selectedItems().isEmpty()) {
893 taskPixmapViewer->setEnabled(false);
894 taskPixmapViewer->setPixmap(QPixmap());
895 taskTextEditor->setEnabled(false);
896 taskTitleEditor->setEnabled(false);
897 bgColorButton->setEnabled(false);
898 fgColorButton->setEnabled(false);
899 dueEditor->setEnabled(false);
900 estimatedHoursEditor->setEnabled(false);
901 return;
904 QTreeWidgetItem* selectedItem = tasksTree->selectedItems().first();
905 QTreeWidgetItem* parentItem = selectedItem->parent();
906 QString tt = selectedItem->text(0);
908 if (!parentItem) {
909 taskPixmapViewer->setEnabled(true);
910 dueEditor->setEnabled(true);
911 estimatedHoursEditor->setEnabled(true);
912 } else {
913 taskPixmapViewer->setEnabled(false);
914 taskPixmapViewer->setPixmap(QPixmap());
915 dueEditor->setEnabled(false);
916 estimatedHoursEditor->setEnabled(false);
918 taskTextEditor->setEnabled(true);
919 taskTitleEditor->setEnabled(true);
920 bgColorButton->setEnabled(true);
921 fgColorButton->setEnabled(true);
924 if (!parentItem) { // editing a task
925 if (!m_editedTasks.contains(tt)) return;
926 const Task& t = m_editedTasks[tt];
927 taskPixmapViewer->setPixmap(t.icon);
928 taskTextEditor->blockSignals(true);
929 taskTextEditor->setPlainText(t.description);
930 taskTextEditor->blockSignals(false);
931 taskTitleEditor->setText(t.title);
932 QPalette p;
933 p.setColor(QPalette::Button, t.bgColor);
934 p.setColor(QPalette::ButtonText, t.fgColor);
935 bgColorButton->setPalette(p);
936 fgColorButton->setPalette(p);
937 estimatedHoursEditor->setValue(t.estimatedHours);
938 dueEditor->setDate(t.dueDate.isValid() ? t.dueDate : dueEditor->minimumDate());
940 currentTask = t.title;
941 currentSubtask = "";
942 } else {
943 if (!m_editedTasks.contains(parentItem->text(0))) return;
944 const Task& t = m_editedTasks[parentItem->text(0)];
945 if (!t.subTasks.contains(tt)) return;
946 const Task::SubTask& st = t.subTasks[tt];
947 taskTextEditor->setPlainText(st.description);
948 taskTitleEditor->setText(st.title);
949 QPalette p;
950 p.setColor(QPalette::Button, st.bgColor.isValid() ? st.bgColor : t.bgColor);
951 p.setColor(QPalette::ButtonText, st.fgColor.isValid() ? st.fgColor : t.fgColor);
952 bgColorButton->setPalette(p);
953 fgColorButton->setPalette(p);
955 currentTask = t.title;
956 currentSubtask = st.title;
960 void Sak::doubleClickedTask(QTreeWidgetItem* i, int column)
962 if (column == 1) {
963 m_changedTask=true;
964 if (i->parent() == 0) {
965 QHash<QString, Task>::iterator itr = m_editedTasks.find(i->text(0));
966 Q_ASSERT(itr != m_editedTasks.end());
967 bool& active ( itr.value().active );
968 active = !active;
969 i->setIcon(column, active ? QIcon(":/images/active.png") : QIcon(":/images/inactive.png"));
970 } else {
971 QHash<QString, Task>::iterator itr = m_editedTasks.find(i->parent()->text(0));
972 Q_ASSERT(itr != m_editedTasks.end());
973 bool& active ( itr.value().subTasks[i->text(0)].active );
974 active = !active;
975 i->setIcon(column, active ? QIcon(":/images/active.png") : QIcon(":/images/inactive.png"));
977 ((QTreeWidget*)sender())->update();
983 void Sak::timerEvent(QTimerEvent* e)
985 if (e->timerId() == m_timerId) {
986 if (!m_view->isVisible() && !m_settings->isVisible() && m_tasks.count() > 0) {
987 popup();
988 // close timer
989 killTimer(m_timeoutPopup);
990 m_timeoutPopup = startTimer((int)(qMax( 30000.0, Task::hours(m_currentInterval)*3600.0*1000.0/10.0))); // 5 secmin
991 // restart timer
992 killTimer(m_timerId);
993 int msecs = (int)(Task::hours(m_currentInterval)*3600.0*1000.0);
994 m_timerId = startTimer(msecs);
995 m_nextTimerEvent = QDateTime::currentDateTime().addMSecs(msecs);
996 } else {
997 if (m_settings && m_settings->isVisible() && !m_settings->isActiveWindow()) {
998 trayIcon->showMessage("Delayed check point", "Delayed check point due to open settings. Close the setting window!", QSystemTrayIcon::Warning, -1);
999 m_settings->close();
1001 qDebug() << "SAK: wait 5 seconds";
1002 killTimer(m_timerId);
1003 m_timerId = startTimer(5000);
1004 m_nextTimerEvent = QDateTime::currentDateTime().addMSecs(5000);
1006 } else if (e->timerId() == m_timeoutPopup) {
1007 // ensure the timer is resetted
1008 killTimer(m_timeoutPopup);
1010 if (!m_subtaskView) {
1011 // if not selecting subtasks clear everything and signal away
1012 workingOnTask("<away>","");
1013 trayIcon->showMessage("New away events", "You have missed a check point. Fix it in the detailed hit list.", QSystemTrayIcon::Information, 999999);
1014 clearView();
1016 } else if (e->timerId() == m_autoSaveTimer) {
1017 flush();
1018 } else {
1019 qDebug() << "unknown timer event";
1023 void Sak::clearView()
1025 killTimer(m_timeoutPopup);
1026 m_subtaskView=false;
1027 m_marker = 0;
1028 delete m_subtaskCompleter; m_subtaskCompleter = 0;
1030 if (!m_view) return;
1031 QGraphicsScene* s = m_view->scene();
1032 QList<QGraphicsItem*> items = m_view->items();
1033 s->deleteLater();
1034 m_view->close();
1035 m_view->setScene(new QGraphicsScene);
1036 m_view->scene()->setSceneRect(m_desktopRect);
1037 m_previewing = false;
1038 m_view->releaseKeyboard();
1040 #if defined(Q_WS_X11)
1041 // restore focus to previous application
1042 grabbed=false;
1043 X11::XSetInputFocus((X11::Display*)QX11Info::display(), X11::CurrentFocusWindow, X11::CurrentRevertToReturn, CurrentTime);
1044 X11::XFlush((X11::Display*)QX11Info::display());
1045 #endif
1048 void Sak::workingOnTask(const QString& taskName, const QString& subTask)
1050 if (!m_previewing) {
1052 qDebug() << "Working on " << taskName ;
1053 if (m_tasks.contains(taskName)) {
1054 Task& t = m_tasks[taskName];
1056 if (t.title != "<away>" && !t.title.isEmpty()) {
1057 // update history
1058 int historyIndex = m_taskSelectionHistory.indexOf(t.title);
1059 if (historyIndex != -1) {
1060 m_taskSelectionHistory.takeAt(historyIndex);
1062 m_taskSelectionHistory.push_back(t.title);
1064 QList<QString> & subtaskSelectionHistory(m_subtaskSelectionHistory[t.title]);
1065 historyIndex = subtaskSelectionHistory.indexOf(subTask);
1066 if (historyIndex != -1) {
1067 subtaskSelectionHistory.takeAt(historyIndex);
1069 subtaskSelectionHistory.push_back(subTask);
1073 QDateTime now = QDateTime::currentDateTime();
1074 QHash<QString, Task>::iterator itr = m_tasks.begin();
1076 QHash<QString, QList< Task::Hit> >::iterator hitr = t.hits.begin();
1077 // merge last two hits of every subtask if they are close enough
1078 while(hitr != t.hits.end()) {
1079 QList<Task::Hit>& otHits( *hitr );
1080 if (otHits.count() > 1) {
1081 Task::Hit& lastHit(otHits[otHits.size() - 1]);
1082 Task::Hit& beforeLastHit(otHits[otHits.size() - 2]);
1084 int diff = (lastHit.timestamp.toTime_t() - 30*lastHit.duration) - (beforeLastHit.timestamp.toTime_t() + 30*beforeLastHit.duration);
1085 if (diff < 120) { // at most 2 minutes apart
1086 beforeLastHit.timestamp = beforeLastHit.timestamp.addSecs(-30*beforeLastHit.duration);
1087 int secsToEnd = beforeLastHit.timestamp.secsTo(lastHit.timestamp.addSecs(30*m_currentInterval));
1088 if (secsToEnd > 24 * 3600 * 3600) {
1089 qWarning() << "TRAPPED ERROR IN SECS COUNT!!!!!!!!";
1090 qWarning() << "BEFORE LAST HIST WAS " << beforeLastHit.timestamp << beforeLastHit.duration;
1091 qWarning() << "LAST HIT WAS " << lastHit.timestamp << lastHit.duration;
1092 assert(secsToEnd < 24 * 3600 * 3600);
1094 beforeLastHit.timestamp = beforeLastHit.timestamp.addSecs( secsToEnd/2.0 );
1095 beforeLastHit.duration = (int)( round( secsToEnd / 60.0 ) );
1096 // remove the current very last hit
1097 otHits.pop_back();
1100 hitr++;
1103 // add hit to hit list
1104 // NOTE: we do not try to merge the very last hit with previous ones because we want let
1105 // the user being able to easily recover from a selection error
1106 t.hits[subTask] << Task::Hit(now, m_currentInterval);
1107 m_incremental->writePiece(t.title, subTask, now, m_currentInterval);
1108 t.checkConsistency();
1109 QList<QTreeWidgetItem*> items = tasksTree->findItems (t.title, Qt::MatchExactly, 0);
1110 if (!items.isEmpty())
1111 items.first()->setText(1, QString("%1 hours worked till now (overestimated %2)").arg(t.totHours).arg(t.totOverestimation));
1113 // update subtask if new added!
1114 t.updateSubTasks();
1116 // update statistics !!!!
1117 m_editedTasks = m_tasks;
1118 QMetaObject::invokeMethod(this, "selectedStartDate", Qt::QueuedConnection, Q_ARG(QDate, cal1->selectedDate()));
1121 clearView();
1125 // attractor = 't' (top), 'b', 'l', 'r'
1126 void layoutInSquare( QList<SakWidget*> sortedWidgets, QRect rect, char attractor)
1128 int w = rect.width();
1129 if (rect.width() < 64) return;
1130 int maxw = qMin(350, w/2);
1131 QSize size(maxw, maxw);
1132 if (sortedWidgets.count() == 0) {
1133 return;
1134 } else if (sortedWidgets.count() == 1) {
1135 sortedWidgets[0]->setGeometry(rect);
1136 } else if (sortedWidgets.count() == 2) {
1137 QPoint off1, off2;
1138 if (attractor == 'C') {
1139 off1 = QPoint(0,w/4);
1140 off2 = QPoint(w/2,w/4);
1141 } else if (attractor == 'T') {
1142 off1 = QPoint(0,0);
1143 off2 = QPoint(w/2,0);
1144 } else if (attractor == 'B') {
1145 off1 = QPoint(0,w/2);
1146 off2 = QPoint(w/2,w/2);
1147 } else if (attractor == 'R') {
1148 off1 = QPoint(w/2,0);
1149 off2 = QPoint(w/2,w/2);
1150 } else if (attractor == 'L') {
1151 off1 = QPoint(0,0);
1152 off2 = QPoint(0,w/2);
1154 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + off1, size));
1155 sortedWidgets[1]->setGeometry(QRect(rect.topLeft() + off2, size));
1156 } else if (sortedWidgets.count() == 3) {
1157 QPoint off1, off2, off3;
1158 if (attractor == 'T' || attractor == 'C') {
1159 off1 = QPoint(0,0);
1160 off2 = QPoint(w/2,0);
1161 off3 = QPoint(w/4,w/2);
1162 } else if (attractor == 'B') {
1163 off1 = QPoint(0,w/2);
1164 off2 = QPoint(w/2,w/2);
1165 off3 = QPoint(w/4,0);
1166 } else if (attractor == 'R') {
1167 off1 = QPoint(w/2,0);
1168 off2 = QPoint(w/2,w/2);
1169 off3 = QPoint(0,w/4);
1170 } else if (attractor == 'L') {
1171 off1 = QPoint(0,0);
1172 off2 = QPoint(0,w/2);
1173 off3 = QPoint(w/2,w/4);
1175 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + off1, size));
1176 sortedWidgets[1]->setGeometry(QRect(rect.topLeft() + off2, size));
1177 sortedWidgets[2]->setGeometry(QRect(rect.topLeft() + off3, size));
1178 } else if (sortedWidgets.count() == 4) {
1179 QPoint off1(0,0);
1180 QPoint off2(0,w/2);
1181 QPoint off3(w/2,0);
1182 QPoint off4(w/2,w/2);
1183 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + off1, size));
1184 sortedWidgets[1]->setGeometry(QRect(rect.topLeft() + off2, size));
1185 sortedWidgets[2]->setGeometry(QRect(rect.topLeft() + off3, size));
1186 sortedWidgets[3]->setGeometry(QRect(rect.topLeft() + off4, size));
1187 } else {
1188 Q_ASSERT(sortedWidgets.count() <= 4);
1192 void layoutInRect( QList<SakWidget*> sortedWidgets, QRect rect, char attractor)
1194 if (sortedWidgets.count() == 0) return;
1195 int h = rect.height();
1196 int w = rect.width();
1197 int maxh = qMin(350, h);
1198 int maxw = qMin(350, w);
1199 if (sortedWidgets.count() == 1) {
1200 if (w>h) {
1201 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + QPoint((w-maxh)/2,(h-maxh)/2), QSize(maxh,maxh)));
1202 } else {
1203 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + QPoint((w-maxw)/2,(h-maxw)/2), QSize(maxw,maxw)));
1205 return;
1206 } else if (sortedWidgets.count() == 2) {
1207 if (w>h) {
1208 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + QPoint(w/2-maxh,(h-maxh)/2), QSize(maxh,maxh)));
1209 sortedWidgets[1]->setGeometry(QRect(rect.topLeft() + QPoint(w/2,(h-maxh)/2), QSize(maxh,maxh)));
1210 } else {
1211 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + QPoint((h-maxw)/2,w-maxw), QSize(w,w)));
1212 sortedWidgets[1]->setGeometry(QRect(rect.topLeft() + QPoint((h-maxw)/2,w/2), QSize(maxw,maxw)));
1214 return;
1216 if (h < 64 || w < 64) return;
1217 QList<SakWidget*> leftList, rightList;
1218 for (int i=4; i<sortedWidgets.count(); i++) {
1219 if (i%2)
1220 rightList << sortedWidgets[i];
1221 else
1222 leftList << sortedWidgets[i];
1224 if (w > h) {
1225 QRect square(rect.topLeft() + QPoint(h/2, 0), QSize(h,h));
1226 layoutInSquare(sortedWidgets.mid(0,4), square, attractor);
1228 QRect leftRect(rect.topLeft(), QSize(h/2, h));
1229 layoutInRect(leftList, leftRect, 'R');
1230 QRect rightRect(rect.topLeft() + QPoint((int)(0.75*w),0), QSize(h/2, h));
1231 layoutInRect(rightList, rightRect, 'L');
1232 } else {
1233 QRect square(rect.topLeft() + QPoint(0,w/2), QSize(w,w));
1234 layoutInSquare(sortedWidgets.mid(0,4), square, attractor);
1236 QRect leftRect(rect.topLeft(), QSize(w, w/2));
1237 layoutInRect(leftList, leftRect, 'B');
1238 QRect rightRect(rect.topLeft() + QPoint(0,(int)(0.75*h)), QSize(w, w/2));
1239 layoutInRect(rightList, rightRect, 'T');
1243 QRect Sak::Layouting( const QList<SakWidget*>& sortedWidgets)
1245 QRect r = m_desktopRect;
1246 int height = (int)(0.75 * r.height());
1247 int width = r.width();
1249 int firstW = width / 2 < height ? width : height * 2;
1250 int firstH = firstW / 2;
1251 QRect firstRect (r.x() + (width - firstW) / 2, r.y() + (height - firstH) / 2 + (int)(r.height() * 0.25), firstW, firstH);
1253 layoutInRect(sortedWidgets, firstRect, 'C');
1254 return QRect(QPoint(r.x(), r.y()), QSize(r.width(), (int)(0.25 * r.height())));
1257 void Sak::grabKeyboard()
1259 #if defined(Q_WS_X11)
1260 if (!grabbed) {
1261 // save current focused application
1262 XGetInputFocus((X11::Display*)QX11Info::display(), &X11::CurrentFocusWindow, &X11::CurrentRevertToReturn);
1263 grabbed=true;
1265 X11::XSetInputFocus((X11::Display*)QX11Info::display(), QX11Info::appRootWindow(QX11Info::appScreen()), RevertToParent, CurrentTime);
1266 X11::XFlush((X11::Display*)QX11Info::display());
1267 #endif
1268 m_view->grabKeyboard();
1271 void Sak::popup()
1273 if (m_stopped) {
1274 trayIcon->showMessage("SAK popup disabled", "SAK triggered a new event but no popup will be shown", QSystemTrayIcon::Info, -1);
1277 // save changes first
1278 if (m_changedTask)
1279 saveTaskChanges();
1280 if (m_changedHit)
1281 saveHitChanges();
1283 if (m_subtaskView) {
1284 // remove subtasks
1285 foreach(SakSubWidget* w, m_subwidgets.values()) {
1286 w->scene()->removeItem(w);
1287 delete w;
1288 m_subwidgets.clear();
1290 m_marker->scene()->removeItem(m_marker);
1291 delete m_marker;
1293 // unhide tasks
1294 foreach(SakWidget* w, m_widgets.values()) {
1295 w->show();
1297 m_subtaskView=false;
1298 return;
1301 m_subtaskView = false;
1303 if (sender() == previewButton) {
1304 m_previewing = true;
1306 QDateTime now = QDateTime::currentDateTime();
1307 QDateTime fromWeek = now;
1308 fromWeek.setDate(now.date().addDays(-now.date().dayOfWeek() + 1));
1309 fromWeek.setTime(QTime(0,0,0));
1310 QDateTime fromMonth = now;
1311 fromMonth.setDate(now.date().addDays(-now.date().day()));
1312 fromMonth.setTime(QTime(0,0,0));
1313 QDateTime fromToday = now;
1314 fromToday.setTime(QTime(0,0,0));
1316 double weekHits = 0;
1317 double dayHits = 0;
1318 double monthHits = 0;
1319 QHash<QString, double> dayStats;
1320 QHash<QString, double> weekStats;
1321 QHash<QString, double> monthStats;
1323 foreach(const Task& t, m_tasks.values()) {
1324 if (t.active) {
1325 double m = t.workedHours(fromMonth, now);
1326 monthStats[t.title] = m;
1327 monthHits += m;
1328 double w = t.workedHours(fromWeek, now);
1329 weekStats[t.title] = w;
1330 weekHits += w;
1331 double d = t.workedHours(fromToday, now);
1332 dayStats[t.title] = d;
1333 dayHits += d;
1338 m_widgets.clear();
1339 foreach(const Task& t, m_tasks.values()) {
1340 if (!t.active || t.title == QString() || t.title == "<away>") continue;
1341 SakWidget* test = new SakWidget(t);
1342 test->setVisible(false);
1343 double d = dayStats[t.title];
1344 double w = weekStats[t.title];
1345 double m = monthStats[t.title];
1346 test->setStatistics(d, w, m, d/dayHits * 100.0, w/weekHits * 100.0, m/monthHits * 100.0);
1347 test->setObjectName(t.title);
1348 connect (test, SIGNAL(clicked(const QString&, const QString&)), this, SLOT(workingOnTask(const QString&, const QString&)));
1349 connect (test, SIGNAL(clicked(const QString&)), this, SLOT(popupSubtasks(const QString&)));
1350 int historyPosition = 1 + m_taskSelectionHistory.indexOf(t.title);
1351 int rank = historyPosition != 0 ? -1000000 * historyPosition : -d;
1352 m_widgets.insertMulti( rank, test);
1355 m_widgetsIterator = m_widgets.begin();
1356 if (m_widgetsIterator != m_widgets.end()) {
1357 m_widgetsIterator.value()->showDetails(true);
1361 const QList<SakWidget*>& values = m_widgets.values();
1362 QRect messageRect = Layouting((const QList<SakWidget*>&)values);
1363 foreach(SakWidget* w, values) {
1364 m_view->scene()->addItem(w);
1365 w->show();
1369 // add the message item
1370 SakMessageItem* sakMessage = new SakMessageItem(bodyEdit->toPlainText());
1371 sakMessage->setGeometry(messageRect);
1372 m_view->scene()->addItem(sakMessage);
1373 sakMessage->show();
1375 // add the exit item
1376 SakExitItem* exitItem = new SakExitItem(QPixmap(":/images/exit.png"));
1377 QRect r = m_desktopRect;
1378 connect(exitItem, SIGNAL(exit()), this, SLOT(clearView()));
1379 exitItem->setPos(r.width() - exitItem->boundingRect().width(), 0);
1380 m_view->scene()->addItem(exitItem);
1381 exitItem->setZValue(1e8);
1382 exitItem->show();
1385 m_view->setGeometry( QRect(m_desktopRect)/*.adjusted(200,200,-200,-200 )*/ );
1386 m_view->show();
1387 m_view->raise();
1388 m_view->setFocus();
1389 #if defined(Q_WS_WIN)
1390 SetForegroundWindow(m_view->winId());
1391 #endif
1392 qApp->alert(m_view, 5000);
1396 void Sak::popupSubtasks(const QString& _taskname) {
1398 killTimer(m_timeoutPopup);
1400 QString taskname = _taskname;
1401 if (taskname.isEmpty()) {
1402 if ( m_taskSelectionHistory.isEmpty() ) {
1403 return;
1404 } else {
1405 taskname = m_taskSelectionHistory.back();
1407 QString subtaskName;
1408 if (!m_subtaskSelectionHistory[taskname].isEmpty()) {
1409 subtaskName = m_subtaskSelectionHistory[taskname].back();
1411 workingOnTask(taskname, subtaskName);
1414 m_subtaskView = true;
1415 grabKeyboard();
1416 QRect r = m_desktopRect;
1417 int w = 500;
1418 int h = 40;
1420 // hide tasks to show subtasks
1421 foreach(SakWidget* w, m_widgets.values()) {
1422 w->hide();
1425 QHash<QString, Task>::const_iterator itr = m_tasks.find(taskname);
1426 if (itr == m_tasks.end()) {
1427 workingOnTask(taskname, "");
1428 return;
1430 const Task& t(*itr);
1431 QHash< QString, Task::SubTask >::const_iterator titr = t.subTasks.begin(), tend = t.subTasks.end();
1432 m_subwidgets.clear();
1433 QDateTime now(QDateTime::currentDateTime());
1434 for(; titr != tend; titr++) {
1435 if (!titr->active) continue;
1436 SakSubWidget* sw = new SakSubWidget(t, *titr);
1437 sw->setGeometry(QRectF(0,0,w,h));
1438 connect (sw, SIGNAL(clicked(const QString&, const QString&)), this, SLOT(workingOnTask(const QString&, const QString&)));
1439 connect (sw, SIGNAL(focused()), this, SLOT(focusedSubTask()));
1441 QDateTime rank=now;
1442 QHash< QString, QList< QString > >::const_iterator hhitr = m_subtaskSelectionHistory.find(t.title);
1443 if (hhitr != m_subtaskSelectionHistory.end()) {
1444 int index = hhitr->indexOf(titr->title);
1445 if (index != 0) rank = now.addDays(index+1);
1447 if (rank == now) {
1448 QHash< QString, QList<Task::Hit> >::const_iterator hitr = t.hits.find(titr.key());
1449 if (hitr != t.hits.end()) {
1450 if ( hitr.value().count() && hitr.value().last().timestamp.isValid())
1451 rank = hitr.value().last().timestamp;
1452 else
1453 rank = now.addDays(-999);
1456 m_subwidgets.insertMulti( - rank.toTime_t(), sw);
1459 const QList<SakSubWidget*>& values = m_subwidgets.values();
1461 // create possible completion
1462 QStringList subtaskSortedList;
1463 foreach(SakSubWidget* sub, values) {
1464 subtaskSortedList.push_back(sub->subtask().title);
1466 m_subtaskCompleter = new QCompleter(subtaskSortedList);
1467 m_subtaskCompleter->setCaseSensitivity(Qt::CaseInsensitive);
1469 // QRect messageRect = Layouting((const QList<SakWidget*>&)values);
1471 m_subwidgetsIterator = m_subwidgets.begin();
1472 m_subWidgetRank = 0;
1474 // the one with text
1475 SakSubWidget* tmpSw = new SakSubWidget(t, Task::SubTask(), true);
1476 QCompleter* completer = new QCompleter(subtaskSortedList);
1477 completer->setCaseSensitivity(Qt::CaseInsensitive);
1478 completer->setCompletionMode(QCompleter::InlineCompletion);
1479 ((QLineEdit*)tmpSw->widget())->setCompleter(completer);
1480 m_view->scene()->addItem(tmpSw);
1481 tmpSw->setGeometry(QRectF(0,0,w,h));
1482 connect (tmpSw, SIGNAL(clicked(const QString&, const QString&)), this, SLOT(workingOnTask(const QString&, const QString&)));
1483 connect (tmpSw, SIGNAL(focused()), this, SLOT(focusedSubTask()));
1485 m_subwidgets.insertMulti(-QDateTime::currentDateTime().addDays(999).toTime_t(), tmpSw);
1487 m_subWidgetRank += values.size() != 0;
1489 if (m_subwidgetsIterator != m_subwidgets.end()) {
1490 m_subwidgetsIterator.value()->showDetails(true);
1491 m_view->scene()->setFocusItem(m_subwidgetsIterator.value(), Qt::MouseFocusReason);
1492 m_subwidgetsIterator.value()->widget()->setFocus(Qt::MouseFocusReason);
1493 } else {
1494 m_view->scene()->setFocusItem(tmpSw, Qt::MouseFocusReason);
1495 tmpSw->widget()->setFocus(Qt::MouseFocusReason);
1498 // add a marker to highligh current selection
1499 m_marker = new QGraphicsEllipseItem(r.width()/2 - 280, r.height()/2 - 51, 20,20);
1500 m_marker->setBrush(Qt::red);
1501 m_view->scene()->addItem(m_marker);
1502 m_marker->setVisible(true);
1504 layoutSubTasks(m_subwidgets, m_subWidgetRank);
1506 // Finally add subwidgets
1507 for(int i=0; i<values.size(); i++) {
1508 SakSubWidget* sw = values[i];
1509 m_view->scene()->addItem(sw);
1514 void Sak::scrollTasks(int npos) {
1515 SakWidget* currentShowing = 0;
1516 if (npos < 0) {
1517 for (int i=npos; i<0; i++) {
1518 if (m_widgetsIterator == m_widgets.end()) return;
1519 currentShowing = m_widgetsIterator != m_widgets.end() ? m_widgetsIterator.value() : 0;
1520 if (m_widgetsIterator == m_widgets.begin()) m_widgetsIterator = m_widgets.end();
1521 m_widgetsIterator--;
1523 } else {
1524 for (int i=0; i<npos; i++) {
1525 if (m_widgetsIterator == m_widgets.end()) return;
1526 currentShowing = m_widgetsIterator.value();
1527 m_widgetsIterator++;
1528 if (m_widgetsIterator == m_widgets.end()) m_widgetsIterator = m_widgets.begin();
1531 if (currentShowing && m_widgetsIterator != m_widgets.end()) {
1532 currentShowing->showDetails(false);
1533 m_widgetsIterator.value()->showDetails(true);
1537 void Sak::scrollSubTasks(int npos) {
1538 SakSubWidget* currentShowing = 0;
1539 if (npos < 0) {
1540 for (int i=npos; i<0; i++) {
1541 if (m_subwidgetsIterator == m_subwidgets.end()) return;
1542 currentShowing = m_subwidgetsIterator != m_subwidgets.end() ? m_subwidgetsIterator.value() : 0;
1543 if (m_subwidgetsIterator == m_subwidgets.begin()) {
1544 m_subwidgetsIterator = m_subwidgets.end();
1545 m_subWidgetRank = m_subwidgets.count();
1547 m_subwidgetsIterator--;
1548 m_subWidgetRank--;
1550 } else {
1551 for (int i=0; i<npos; i++) {
1552 if (m_subwidgetsIterator == m_subwidgets.end()) return;
1553 currentShowing = m_subwidgetsIterator.value();
1554 m_subwidgetsIterator++;
1555 m_subWidgetRank++;
1556 if (m_subwidgetsIterator == m_subwidgets.end()) {
1557 m_subwidgetsIterator = m_subwidgets.begin();
1558 m_subWidgetRank = 0;
1562 if (currentShowing && m_subwidgetsIterator != m_subwidgets.end()) {
1563 currentShowing->showDetails(false);
1564 m_subwidgetsIterator.value()->showDetails(true);
1565 m_view->scene()->setFocusItem(m_subwidgetsIterator.value(), Qt::MouseFocusReason);
1566 m_subwidgetsIterator.value()->widget()->setFocus(Qt::MouseFocusReason);
1568 layoutSubTasks(m_subwidgets, m_subWidgetRank);
1571 void Sak::focusedSubTask()
1573 SakSubWidget* w = dynamic_cast<SakSubWidget*>(sender());
1574 if (w) {
1575 QMap<int, SakSubWidget*>::iterator itr = m_subwidgets.begin(), end=m_subwidgets.end();
1576 for(int i=0; itr != end; i++,itr++) {
1577 if (itr.value() == w) {
1578 m_subwidgetsIterator = itr;
1579 m_subWidgetRank = i;
1585 //END Tasks >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
1588 //BEGIN settings ================================
1591 void Sak::setupSettingsWidget()
1593 m_settings = new QMainWindow();
1594 m_settings->setMinimumHeight(650);
1595 m_settings->setMinimumWidth(700);
1596 QWidget* centralWidget = new QWidget;
1597 m_settings->setCentralWidget(centralWidget);
1599 QVBoxLayout* theMainLayout = new QVBoxLayout;
1600 centralWidget->setLayout(theMainLayout);
1602 tabs = new QTabWidget;
1603 theMainLayout->addWidget(tabs);
1604 previewButton = new QPushButton("Preview");
1605 theMainLayout->addWidget(previewButton);
1607 tab1 = new QWidget;
1608 tab2 = new QWidget;
1609 tab3 = new QWidget;
1610 tab4 = new QWidget;
1611 tabs->addTab(tab1, "Tasks");
1612 tabs->addTab(tab2, "General");
1613 tabs->addTab(tab4, "Statistics");
1614 tabs->addTab(tab3, "Advanced");
1616 createActions();
1617 QMenuBar* mainMenu = new QMenuBar;
1618 m_settings->setMenuBar(mainMenu);
1619 QMenu* programMenu = mainMenu->addMenu("Program");
1620 programMenu->addAction(quitAction);
1621 programMenu->addAction(minimizeAction);
1622 QMenu* dbMenu = mainMenu->addMenu("Db");
1623 dbMenu->addAction(flushAction);
1624 dbMenu->addAction(openAction);
1625 // dbMenu->addAction(saveAsDbAction);
1626 dbMenu->addAction(exportDbCsvAction);
1627 #ifdef USEGMAIL
1628 dbMenu->addAction(gmailLoginAction);
1629 dbMenu->addAction(saveToGmailAction);
1630 if (!m_gmail->isValid()) {
1631 gmailLoginAction->setEnabled(false);
1632 saveToGmailAction->setEnabled(false);
1634 #endif
1635 QMenu* actionsMenu = mainMenu->addMenu("Actions");
1636 actionsMenu->addAction(startAction);
1637 actionsMenu->addAction(stopAction);
1639 QVBoxLayout *mainLayout = new QVBoxLayout;
1640 QGridLayout *messageLayout = new QGridLayout;
1641 durationLabel = new QLabel(tr("Interval:"));
1642 durationLabel1 = new QLabel(tr("(effective only after restart)"));
1644 QSettings settings(QSettings::IniFormat, QSettings::UserScope, "ZanzaSoft", "SAK");
1645 durationSpinBox = new QSpinBox;
1646 durationSpinBox->setSingleStep(1);
1647 durationSpinBox->setRange(1, 1440);
1648 durationSpinBox->setSuffix(" minutes");
1649 durationSpinBox->setValue(qMin(1440, qMax(1, settings.value("Ping interval", 15).toInt())));
1650 durationSpinBox->setCorrectionMode(QAbstractSpinBox::CorrectToNearestValue);
1652 bodyLabel = new QLabel(tr("Message:"));
1653 bodyEdit = new QTextEdit;
1654 bodyEdit->setPlainText( settings.value("Message", "<h1>Enter a message here!</h1>").toString() );
1656 messageLayout->addWidget(durationLabel, 1, 0);
1657 messageLayout->addWidget(durationSpinBox, 1, 1);
1658 messageLayout->addWidget(durationLabel1, 1, 2);
1659 messageLayout->addWidget(bodyLabel, 3, 0);
1660 messageLayout->addWidget(bodyEdit, 3, 1, 2, 4);
1661 messageLayout->setColumnStretch(3, 1);
1662 messageLayout->setRowStretch(4, 1);
1663 mainLayout->addLayout(messageLayout);
1664 tab2->setLayout(mainLayout);
1666 mainLayout = new QVBoxLayout;
1667 tab1->setLayout(mainLayout);
1668 // create tree view
1669 tasksTree = new QTreeWidget;
1670 tasksTree->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
1671 tasksTree->setColumnCount(3);
1672 tasksTree->setColumnWidth(0, 300);
1673 tasksTree->setColumnWidth(1, 65);
1674 tasksTree->setIconSize(QSize(32,32));
1675 connect(tasksTree, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(doubleClickedTask(QTreeWidgetItem*,int)));
1678 taskTextEditor = new QTextEdit;
1679 taskTextEditor->setFixedHeight(100);
1680 taskTextEditor->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
1681 connect(taskTextEditor, SIGNAL(textChanged()), this, SLOT(commitCurrentTask()));
1684 taskTitleEditor = new QLineEdit;
1685 taskTitleEditor->setFixedHeight(20);
1686 taskTitleEditor->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
1687 connect(taskTitleEditor, SIGNAL(editingFinished()), this, SLOT(commitCurrentTask()));
1689 taskPixmapViewer = new PixmapViewer;
1690 mainLayout->addWidget(tasksTree, 2);
1691 QHBoxLayout* detailsLayout = new QHBoxLayout;
1692 QVBoxLayout* editsLayout = new QVBoxLayout;
1693 detailsLayout->addWidget(taskPixmapViewer);
1694 connect(taskPixmapViewer, SIGNAL(changed()), this, SLOT(commitCurrentTask()));
1695 editsLayout->addWidget(taskTitleEditor);
1696 editsLayout->addWidget(taskTextEditor);
1697 QHBoxLayout* datesLayout = new QHBoxLayout;
1698 datesLayout->addWidget(new QLabel("Due: "));
1699 dueEditor = new QDateEdit;
1700 dueEditor->setMinimumDate(QDate(2000,1,1));
1701 datesLayout->addWidget(dueEditor);
1702 datesLayout->addWidget(new QLabel("Estimated: "));
1703 estimatedHoursEditor = new QSpinBox;
1704 estimatedHoursEditor->setRange(0, 1e5);
1705 estimatedHoursEditor->setSuffix("hours");
1706 datesLayout->addWidget(estimatedHoursEditor);
1707 editsLayout->addLayout(datesLayout);
1708 detailsLayout->addLayout(editsLayout);
1709 connect(dueEditor, SIGNAL(editingFinished()), this, SLOT(commitCurrentTask()));
1710 connect(estimatedHoursEditor,SIGNAL(editingFinished()), this, SLOT(commitCurrentTask()));
1712 QVBoxLayout* colorsLayout = new QVBoxLayout;
1713 bgColorButton = new QPushButton("bg\ncolor");
1714 bgColorButton->setToolTip("Background color");
1715 bgColorButton->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding));
1717 fgColorButton = new QPushButton("fg\ncolor");
1718 fgColorButton->setToolTip("Foreground color");
1719 fgColorButton->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding));
1721 #ifdef Q_WS_WIN
1722 // fix Windows XP "style"
1723 bgColorButton->setStyle(new QWindowsStyle());
1724 fgColorButton->setStyle(new QWindowsStyle());
1725 #endif
1727 colorsLayout->addWidget(bgColorButton);
1728 colorsLayout->addWidget(fgColorButton);
1729 detailsLayout->addLayout(colorsLayout);
1731 mainLayout->addLayout(detailsLayout);
1733 QVBoxLayout* tab4MainLayout = new QVBoxLayout(tab4);
1734 //taskSelector = new QComboBox;
1735 summaryList = newTaskSummaryList();
1736 QTabWidget* tabs = new QTabWidget;
1737 tabs->setTabPosition(QTabWidget::East);
1738 tabs->addTab(summaryList, "List");
1739 QGraphicsView* summaryView = new QGraphicsView;
1740 summaryView->setScene(new QGraphicsScene);
1741 TaskSummaryPieChart* chart = new TaskSummaryPieChart;
1742 summaryView->scene()->addItem(new TaskSummaryPieChart);
1743 summaryView->centerOn(chart);
1744 tabs->addTab(summaryView, "Chart");
1745 tab4MainLayout->addWidget(tabs);
1746 cal3 = new QCalendarWidget;
1747 cal3->setMinimumSize(QSize(250,200));
1748 cal4 = new QCalendarWidget;
1749 cal4->setMinimumSize(QSize(250,200));
1750 cal3->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
1751 cal4->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
1752 QHBoxLayout* calsLayout = new QHBoxLayout;
1753 calsLayout->addWidget(cal3);
1754 calsLayout->addWidget(cal4);
1755 cal4->setSelectedDate(QDate::currentDate().addDays(1));
1756 tab4MainLayout->addLayout(calsLayout);
1759 QVBoxLayout* tab3MainLayout = new QVBoxLayout(tab3);
1760 //taskSelector = new QComboBox;
1761 hitsList = newHitsList();
1762 connect(hitsList, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), this, SLOT(hitsSelectedInList(QTreeWidgetItem*,QTreeWidgetItem*)));
1763 hitsTimeline = new Timeline;
1764 connect(hitsTimeline, SIGNAL(hitSelected(HitItem*)), this, SLOT(hitsSelectedInTimeline(HitItem*)));
1765 cal1 = new QCalendarWidget;
1766 cal1->setMinimumSize(QSize(250,200));
1767 cal2 = new QCalendarWidget;
1768 cal2->setMinimumSize(QSize(250,200));
1769 cal1->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
1770 cal2->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
1771 //tab3MainLayout->addWidget(taskSelector);
1773 calsLayout = new QHBoxLayout;
1774 calsLayout->addWidget(cal1);
1775 calsLayout->addWidget(cal2);
1776 cal2->setSelectedDate(QDate::currentDate().addDays(1));
1777 tab3MainLayout->addWidget(hitsList);
1778 tab3MainLayout->addWidget(hitsTimeline);
1779 tab3MainLayout->addLayout(calsLayout);
1783 m_settings->setWindowTitle(tr("SaK"));
1784 m_settings->resize(400, 300);
1788 QTreeWidget* Sak::newHitsList()
1790 QTreeWidget* hitsList = new QTreeWidget;
1791 hitsList->setColumnCount(4);
1792 hitsList->setColumnWidth(0, 200);
1793 hitsList->setColumnWidth(1, 150);
1794 hitsList->setColumnWidth(2, 150);
1795 hitsList->setColumnWidth(3, 150);
1796 hitsList->setColumnWidth(4, 150);
1797 hitsList->setIconSize(QSize(24,24));
1798 hitsList->setSortingEnabled(true);
1799 hitsList->setItemDelegateForColumn(0, new MyDateItemDelegate);
1800 hitsList->setItemDelegateForColumn(1, new TaskItemDelegate(this));
1801 hitsList->setItemDelegateForColumn(2, new SubTaskItemDelegate(this));
1802 QTreeWidgetItem* header = new QTreeWidgetItem;
1803 header->setText(0, "Date/Time");
1804 header->setText(1, "Task");
1805 header->setText(2, "Subtask");
1806 header->setText(3, "Duration (min)");
1807 header->setText(4, "Overestimation");
1808 hitsList->setHeaderItem(header);
1809 hitsList->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
1810 return hitsList;
1813 QTreeWidget* Sak::newTaskSummaryList()
1815 QTreeWidget* taskSummaryList = new QTreeWidget;
1816 taskSummaryList->setColumnCount(4);
1817 taskSummaryList->setColumnWidth(0, 250);
1818 taskSummaryList->setColumnWidth(1, 150);
1819 taskSummaryList->setColumnWidth(1, 150);
1820 taskSummaryList->setIconSize(QSize(24,24));
1821 taskSummaryList->setSortingEnabled(true);
1822 QTreeWidgetItem* header = new QTreeWidgetItem;
1823 header->setText(0, "Task");
1824 header->setText(1, "Hours");
1825 taskSummaryList->setHeaderItem(header);
1826 taskSummaryList->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
1827 taskSummaryList->setEnabled(true);
1828 return taskSummaryList;
1831 void Sak::setVisible(bool visible)
1833 minimizeAction->setEnabled(visible);
1834 maximizeAction->setEnabled(!m_settings->isMaximized());
1835 restoreAction->setEnabled(m_settings->isMaximized() || !visible);
1836 m_settings->setVisible(visible);
1839 void Sak::createActions()
1841 minimizeAction = new QAction(tr("Mi&nimize"), m_settings);
1842 connect(minimizeAction, SIGNAL(triggered()), m_settings, SLOT(hide()));
1844 maximizeAction = new QAction(tr("Ma&ximize"), m_settings);
1845 connect(maximizeAction, SIGNAL(triggered()), m_settings, SLOT(showMaximized()));
1847 restoreAction = new QAction(tr("&Restore"), m_settings);
1848 connect(restoreAction, SIGNAL(triggered()), m_settings, SLOT(showNormal()));
1850 quitAction = new QAction(tr("&Quit"), m_settings);
1851 connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
1853 startAction = new QAction(tr("Start polling"), m_settings);
1854 connect(startAction, SIGNAL(triggered()), this, SLOT(start()));
1856 stopAction = new QAction(tr("Stop polling"), m_settings);
1857 connect(stopAction, SIGNAL(triggered()), this, SLOT(stop()));
1859 flushAction = new QAction(tr("&Flush data/settings to disk"), m_settings);
1860 connect(flushAction, SIGNAL(triggered()), this, SLOT(flush()));
1862 saveAsDbAction = new QAction(tr("Backup as"), m_settings);
1863 // connect(saveAsDbAction, SIGNAL(triggered()), this, SLOT(saveAsDb()));
1865 exportDbCsvAction = new QAction(tr("Export hits in CSV format"), m_settings);
1866 connect(exportDbCsvAction, SIGNAL(triggered()), this, SLOT(exportDbCsv()));
1868 #ifdef USEGMAIL
1869 saveToGmailAction = new QAction(tr("Store in your gmail account free space"), m_settings);
1870 connect(saveToGmailAction, SIGNAL(triggered()), this, SLOT(saveToGmail()));
1872 gmailLoginAction = new QAction(tr("Log in gmail"), m_settings);
1873 connect(gmailLoginAction, SIGNAL(triggered()), this, SLOT(logInGmail()));
1874 #else
1875 saveToGmailAction = NULL;
1876 gmailLoginAction = NULL;
1877 #endif
1879 openAction = new QAction(tr("Import a task from file"), m_settings);
1880 connect(openAction, SIGNAL(triggered()), this, SLOT(open()));
1882 m_addHitAction = new QAction("Add hit", m_settings);
1883 m_addHitMenu = new QMenu(m_settings);
1884 m_addHitAction->setText("Add hit");
1885 m_addHitMenu->addAction(m_addHitAction);
1886 connect(m_addHitAction, SIGNAL(triggered()), this, SLOT(addDefaultHit()));
1888 m_exportDataAction = new QAction("Export data", m_settings);
1889 m_exportDataAction->setText("Export data");
1890 m_addHitMenu->addAction(m_exportDataAction);
1891 connect(m_exportDataAction, SIGNAL(triggered()), this, SLOT(exportHits()));
1893 m_addTaskAction = new QAction("Add task", m_settings);
1894 m_addTaskMenu = new QMenu(m_settings);
1895 m_addTaskAction->setText("Add task");
1896 m_addTaskMenu->addAction(m_addTaskAction);
1897 connect(m_addTaskAction, SIGNAL(triggered()), this, SLOT(addDefaultTask()));
1900 void Sak::trayIconActivated(QSystemTrayIcon::ActivationReason reason)
1902 if (reason == QSystemTrayIcon::DoubleClick) {
1903 setVisible(true);
1907 //END Settings <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<