fix start/stop/pause
[Sak.git] / sak.cpp
blob68fca87251811b2071345588ac482daf5c8d2897
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 // ensure the timer is killed
239 if(m_timerId)
240 stop();
241 m_currentInterval = qMax((int)1, m_currentInterval);
242 int msecs = (int)(Task::hours(m_currentInterval)*3600.0*1000.0 / 2);
243 m_timerId = startTimer( msecs );
244 m_nextTimerEvent = QDateTime::currentDateTime().addMSecs(msecs);
245 startAction->setEnabled(false);
246 stopAction->setEnabled(true);
247 m_stopped=false;
250 void Sak::stop()
252 if(m_timerId) {
253 killTimer(m_timerId);
254 m_timerId = 0;
256 stopAction->setEnabled(false);
257 startAction->setEnabled(true);
258 m_stopped=true;
261 void Sak::pause()
263 m_stopped=true;
264 stopAction->setEnabled(false);
265 startAction->setEnabled(true);
268 Task Sak::loadTaskFromFile(const QString& filePath)
270 QFile taskXmlFile(filePath);
271 Task t;
272 qDebug() << "Examine task file " << taskXmlFile.fileName();
273 if (!taskXmlFile.open(QIODevice::ReadOnly)) {
274 qDebug() << "Failed opening xml file " << taskXmlFile.fileName();
276 QByteArray data = taskXmlFile.readLine();
277 QXmlStreamReader stream(data);
278 QXmlStreamReader::TokenType token = stream.readNext(); // skip StartDocument
279 token = stream.readNext();
280 if ( token != QXmlStreamReader::Comment) {
281 qDebug() << "Skip file " << taskXmlFile.fileName() << " (want a file starting with a comment representing MD5, got" << token << ")";
282 return t;
284 QString md5 = stream.text().toString().trimmed();
285 qDebug() << "md5 = " << md5;
287 // check md5
288 data = taskXmlFile.readAll();
289 if ( md5 != QCryptographicHash::hash(data, QCryptographicHash::Md5).toHex() ) {
290 if (QMessageBox::No == QMessageBox::warning(0, "Corrupted file!",
291 QString("Check of file " + taskXmlFile.fileName() + " failed (maybe it has been edited by hand).\nDo you want to load it anyway?" )
292 ,QMessageBox::Yes | QMessageBox::No) ) {
293 qDebug() << "Skip file " << taskXmlFile.fileName() << " (bad md5 sum)";
294 return t;
298 // read rest of data
299 stream.clear();
300 stream.addData(data);
302 if ( stream.readNext() != QXmlStreamReader::StartDocument) {
303 qDebug() << "Skip file " << taskXmlFile.fileName() << " (want start document)";
304 return t;
306 stream >> t;
307 if (stream.error() != QXmlStreamReader::NoError) {
308 qDebug() << "Error reading task data from file " << taskXmlFile.fileName() << ":" << stream.errorString();
309 return Task();
311 // QFile tmp("/tmp/" + t.title + ".xml");
312 // tmp.open(QIODevice::ReadWrite);
313 // QXmlStreamWriter ss(&tmp);
314 // ss.setAutoFormatting(true);
315 // ss.setAutoFormattingIndent(2);
316 // ss.writeStartDocument();
317 // ss << t;
318 // ss.writeEndDocument();
319 // tmp.close();
320 else
321 return t;
324 void Sak::flush()
326 if (m_changedTask)
327 saveTaskChanges();
328 if (m_changedHit)
329 saveHitChanges();
331 if (!m_settings) return;
332 m_backupper->doCyclicBackup();
333 QSettings settings(QSettings::IniFormat, QSettings::UserScope, "ZanzaSoft", "SAK");
334 // QByteArray tasksArray;
335 // QDataStream stream(&tasksArray, QIODevice::ReadWrite);
336 // stream.setVersion(QDataStream::Qt_4_0);
337 // stream << m_tasks;
338 // settings.setValue("tasks", tasksArray);
339 settings.setValue("Ping interval", durationSpinBox->value());
340 settings.setValue("Message", bodyEdit->toPlainText());
341 settings.sync();
343 QDir saveDir(QFileInfo(settings.fileName()).dir());
344 saveDir.mkdir("SakTasks");
345 saveDir.cd("SakTasks");
347 foreach(Task t, m_tasks) {
348 if (t.title.isEmpty()) continue;
349 QFile xmlTaskSave(saveDir.filePath(t.title + ".xml"));
350 QByteArray taskArray;
351 QXmlStreamWriter stream(&taskArray);
352 stream.setAutoFormatting(true);
353 stream.setAutoFormattingIndent(2);
354 stream.writeStartDocument();
355 stream << t;
356 stream.writeEndDocument();
357 xmlTaskSave.open(QIODevice::ReadWrite | QIODevice::Truncate);
358 qDebug() << "Saving xml to file " << xmlTaskSave.fileName();
359 QByteArray hash;
360 hash.append("<!-- ");
361 hash.append( QCryptographicHash::hash(taskArray, QCryptographicHash::Md5).toHex() );
362 hash.append(" -->\n");
363 xmlTaskSave.write(hash);
364 xmlTaskSave.write(taskArray);
365 xmlTaskSave.close();
368 // remove files not matching a task
369 QStringList nameFilters;
370 nameFilters << "*.xml";
371 QStringList files = saveDir.entryList( nameFilters, QDir::Files);
372 foreach (QString taskXmlFileName, files) {
373 if (!m_tasks.contains(QFileInfo(taskXmlFileName).baseName())) {
374 qWarning()<< "Remove task " << QFileInfo(taskXmlFileName).baseName() << " from disk";
375 QFile(saveDir.filePath(taskXmlFileName)).remove();
380 m_incremental->clearAddedPieces();
383 //void Sak::saveAsDb()
385 // if (!m_settings) return;
386 // QString fileName = QFileDialog::getSaveFileName();
387 // QFile file(fileName);
388 // file.remove();
389 // flush();
390 // QSettings settingsQSettings::IniFormat, QSettings::UserScope, ("ZanzaSoft", "SAK");
391 // QFile file1(settings.fileName());
392 // if (!file1.copy(fileName)) {
393 // qWarning() << "Error copying " << settings.fileName() << " to " << fileName << file1.errorString();
394 // }
397 void Sak::exportDbCsv()
399 if (!m_settings) return;
400 QString fileName = QFileDialog::getSaveFileName();
401 QFile file(fileName);
402 if (!file.open(QIODevice::ReadWrite|QIODevice::Truncate)) {
403 QMessageBox::warning(0, "Error saving", QString("Error saving to file %1").arg(fileName));
404 return;
406 QTextStream stream(&file);
407 foreach(const Task& t, m_tasks) {
408 QHash< QString, QList< Task::Hit > >::const_iterator itr = t.hits.begin();
409 while(itr != t.hits.end()) {
410 QList< Task::Hit >::const_iterator hitr = itr.value().begin(), hend = itr.value().end();
411 while(hitr != hend) {
412 stream << t.title << ";" << itr.key() << ";" << hitr->timestamp.toString(DATETIMEFORMAT) << ";" << hitr->duration << ";\n";
413 hitr++;
415 itr++;
418 file.close();
422 void Sak::logInGmail()
424 m_gmail->forceLogin();
427 void Sak::saveToGmail()
429 if (!m_settings) return;
430 flush();
431 QSettings settings(QSettings::IniFormat, QSettings::UserScope, "ZanzaSoft", "SAK");
433 QDir saveDir(QFileInfo(settings.fileName()).dir());
434 saveDir.mkdir("SakTasks");
435 saveDir.cd("SakTasks");
436 QStringList nameFilters;
437 nameFilters << "*.xml";
438 QStringList files = saveDir.entryList( nameFilters, QDir::Files);
439 QStringList filePaths;
440 foreach (QString taskXmlFileName, files) {
441 filePaths << saveDir.filePath(taskXmlFileName);
443 m_gmail->storeTaskFiles(filePaths);
446 void Sak::importFromGmail()
448 QStringList filePaths = m_gmail->fetchLatestTasks();
451 void Sak::open(const QStringList& _fileNames)
453 QStringList fileNames = _fileNames.size()?_fileNames:QFileDialog::getOpenFileNames(0, "Open a new task", QString(), "*.xml" );
454 foreach(QString fileName, fileNames) {
455 QFile file(fileName);
456 if (!file.exists()) {
457 QMessageBox::warning(0, "Cannot find task", QString("Cannot find task file %1").arg(fileName));
460 QSettings settings(QSettings::IniFormat, QSettings::UserScope, "ZanzaSoft", "SAK");
461 QDir saveDir(QFileInfo(settings.fileName()).dir());
462 saveDir.mkdir("SakTasks");
463 saveDir.cd("SakTasks");
465 if ( QFile(saveDir.filePath(QFileInfo(fileName).completeBaseName())).exists() ) {
466 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?");
467 QPushButton* overwriteButton = mbox.addButton("Overwrite", QMessageBox::YesRole);
468 QPushButton* mergeButton = mbox.addButton("Merge", QMessageBox::NoRole);
469 QPushButton* cancelButton = mbox.addButton("Cancel", QMessageBox::RejectRole);
470 mbox.setDefaultButton(cancelButton);
471 mbox.exec();
472 QAbstractButton* b = mbox.clickedButton();
473 if (b == cancelButton) { continue; }
474 else {
475 m_backupper->doCyclicBackup();
476 if (b == mergeButton) {
477 Task t = loadTaskFromFile(file.fileName());
478 QHash< QString, QList< Task::Hit > > ::const_iterator itr = t.hits.begin(), end = t.hits.end();
479 while(itr != end) {
480 QString subtask = itr.key();
481 foreach(Task::Hit hit, itr.value())
482 m_incremental->addPiece(t.title, subtask, hit.timestamp, hit.duration);
483 itr++;
485 interactiveMergeHits();
486 } else if (b == overwriteButton) {
487 file.copy(saveDir.filePath(QFileInfo(fileName).completeBaseName()));
493 if (!fileNames.isEmpty()) {
494 m_settings->hide();
495 destroy();
496 init();
497 m_settings->show();
498 start();
502 void Sak::destroy()
504 stop();
505 if (!m_settings) return;
506 flush();
507 m_settings->deleteLater();
508 m_view->scene()->deleteLater();
509 m_view->deleteLater();
510 delete m_backupper;
511 delete m_incremental;
512 delete m_gmail;
513 m_previewing = false;
514 m_changedHit = false;
515 m_timerId = 0;
519 Sak::~Sak()
521 killTimer(m_autoSaveTimer);
522 destroy();
526 void Sak::layoutSubTasks( const QMap<int, SakSubWidget*> sortedWidgets, int currentRank) {
527 QMap<int, SakSubWidget*>::const_iterator itr = sortedWidgets.begin(), end = sortedWidgets.end();
528 QRect r = m_desktopRect;
529 for(int i=0; itr != end; i++, itr++) {
530 int h = (*itr)->size().height();
531 int w = (*itr)->size().width();
532 (*itr)->setPos(QPointF((r.width() - w)/2, (r.height()-h)/2 + (i - currentRank - 1) * (h+2)));
537 int Sak::taskCounter = 0;
539 bool Sak::eventFilter(QObject* obj, QEvent* e)
541 // if (obj == m_view) {
542 // qDebug() << "event : " << e->type();
543 // }
544 if (obj == tasksTree) {
545 return taskTreeEventFilter(e);
546 } else if (obj == hitsList || obj == summaryList) {
547 return hitsListEventFilter(e);
548 } else if (obj == m_settings && e->type() == QEvent::Close) {
549 if (m_changedTask)
550 saveTaskChanges();
551 if (m_changedHit)
552 saveHitChanges();
553 if (trayIcon->isVisible()) {
554 m_settings->hide();
555 e->ignore();
556 return true;
558 } else if (obj == m_view && e->type() == QEvent::Wheel) {
559 QWheelEvent* we = dynamic_cast<QWheelEvent*>(e);
560 if (m_subtaskView) {
561 scrollSubTasks(we->delta() / 120);
562 } else scrollTasks(we->delta() / 120);
563 } else if (obj == m_view && e->type() == QEvent::KeyPress) {
564 QKeyEvent* ke = dynamic_cast<QKeyEvent*>(e);
565 if ((ke->modifiers() & Qt::AltModifier) && (ke->modifiers() & Qt::ControlModifier) ) {
566 clearView();
567 return true;
568 } else if ( ((ke->modifiers() & Qt::ControlModifier) && (ke->key() == Qt::Key_Backspace) )
569 || ((ke->modifiers() & Qt::ControlModifier) && (ke->key() == Qt::Key_Left) )) {
570 if (m_subtaskView) {
571 popup();
572 return true;
574 } else if (m_subtaskView && ke->key() == Qt::Key_Up) {
575 scrollSubTasks(-1);
576 return true;
577 } else if (m_subtaskView && ke->key() == Qt::Key_Down) {
578 scrollSubTasks(+1);
579 return true;
580 } else if (!m_subtaskView && ke->key() == Qt::Key_Left) {
581 scrollTasks(-1);
582 return true;
583 } else if (!m_subtaskView && ke->key() == Qt::Key_Right) {
584 scrollTasks(+1);
585 return true;
586 } else if (!m_subtaskView && ke->key() == Qt::Key_Escape) {
587 clearView();
588 return true;
589 } else { // forward events to current widget
590 if (!m_subtaskView) {
591 if (m_widgetsIterator == m_widgets.end()) return false;
592 SakWidget* currentShowing = m_widgetsIterator.value();
593 currentShowing->keyPressEvent(ke);
594 return true;
595 } else {
596 // autoscroll on text completion!!!
597 if (m_subwidgetsIterator == m_subwidgets.end()) return false;
598 SakSubWidget* currentShowing = m_subwidgetsIterator.value();
599 currentShowing->keyPressEvent(ke);
601 if (m_subWidgetRank != 0 && m_subtaskCompleter) {
602 QString completion(m_subtaskCompleter->completionPrefix());
603 if (ke->text().size() == 1) {
604 if (ke->key() == Qt::Key_Backslash || ke->key() == Qt::Key_Backspace)
605 completion.chop(1);
606 else completion += ke->text();
607 m_subtaskCompleter->setCompletionPrefix(completion);
610 QStringList list( ((QStringListModel*)m_subtaskCompleter->model())->stringList() );
611 int newRank = 1 + ((QStringListModel*)m_subtaskCompleter->model())->stringList().indexOf(m_subtaskCompleter->currentIndex().row() >= 0 && completion.size() ? m_subtaskCompleter->currentCompletion() : completion);
613 if (m_subWidgetRank != newRank) {
614 scrollSubTasks(newRank - m_subWidgetRank);
615 if (newRank == 0) {
616 QLineEdit* editor = dynamic_cast<QLineEdit*>((*m_subwidgets.begin())->widget());
617 if (editor) {
618 editor->setText(completion);
623 } else if (m_subtaskCompleter) {
624 QLineEdit* editor = dynamic_cast<QLineEdit*>((*m_subwidgets.begin())->widget());
625 if (editor) {
626 m_subtaskCompleter->setCompletionPrefix(editor->text());
630 return true;
633 } else if (obj == m_view && e->type() == QEvent::Show) {
634 grabKeyboard();
635 QTimer::singleShot(500, this, SLOT(grabKeyboard()));
636 } else if (obj == m_view && e->type() == QEvent::Close) {
637 if (trayIcon->isVisible()) {
638 return true;
640 } else if (obj && obj == trayIcon && e->type() == QEvent::ToolTip) {
641 QDateTime last = m_incremental->lastTimeStamp;
642 int seconds = QDateTime::currentDateTime().secsTo(m_nextTimerEvent);
643 int hours = seconds / 3600;
644 int minutes = (seconds / 60) % 60;
645 seconds %= 60;
646 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>")))));
647 return false;
649 return false;
652 //END basic >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
655 //BEGIN Tasks >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
656 void Sak::addDefaultTask()
658 QString tentativeName;
659 do {
660 tentativeName = QString("Task %1").arg(taskCounter++);
661 } while(m_editedTasks.contains(tentativeName));
663 Task& t = m_editedTasks[tentativeName];
664 t.title = tentativeName;
665 QTreeWidgetItem* item = new QTreeWidgetItem(QStringList(tentativeName));
666 item->setData(0,Qt::UserRole, QVariant(QMetaType::VoidStar, &t));
667 tasksTree->addTopLevelItem(item);
668 m_changedTask=true;
671 void Sak::populateTasks()
673 tasksTree->clear();
675 QHash<QString, Task>::iterator itr = m_editedTasks.begin(), end=m_editedTasks.end();
676 for(; itr!=end; itr++) {
677 Task& t(itr.value());
678 t.checkConsistency();
680 if (t.title.isEmpty() || t.title == "<away>") continue; // skip away task
681 QTreeWidgetItem* item = new QTreeWidgetItem(QStringList(t.title));
682 item->setData(0, Qt::UserRole, QVariant(QMetaType::VoidStar, &t));
683 QIcon icon;
684 icon.addPixmap(t.icon);
685 item->setSizeHint(0, QSize(32,32));
686 item->setIcon(0, icon);
687 for(int i=0; i<3; i++) {
688 item->setForeground(i,t.fgColor);
689 item->setBackground(i,t.bgColor);
691 //item->setCheckState(1, t.active ? Qt::Checked : Qt::Unchecked);
692 //item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
693 item->setIcon(1,QIcon(t.active ? ":/images/active.png" : ":/images/inactive.png"));
694 item->setText(2,QString("%1 hours worked till now (overestimated %2)").arg(t.totHours, 4, 'f', 2, ' ').arg(t.totOverestimation));
695 foreach(Task::SubTask st, t.subTasks) {
696 if (!st.title.isEmpty()) {
697 QTreeWidgetItem* sitem = new QTreeWidgetItem(item, QStringList(st.title));
698 item->setData(0, Qt::UserRole, QVariant(QMetaType::VoidStar, &st));
699 sitem->setSizeHint(0, QSize(32,32));
700 QColor fgColor = st.fgColor.isValid() ? st.fgColor : t.fgColor;
701 QColor bgColor = st.bgColor.isValid() ? st.bgColor : t.bgColor;
702 for(int i=0; i<3; i++) {
703 sitem->setForeground(i,fgColor);
704 sitem->setBackground(i,bgColor);
706 sitem->setIcon(1,QIcon(st.active ? ":/images/active.png" : ":/images/inactive.png"));
707 sitem->setText(2,QString("%1 hours worked till now").arg(st.totHours,4,'f',2,' '));
710 tasksTree->addTopLevelItem(item);
714 void Sak::saveTaskChanges()
716 if (m_changedTask) {
717 commitCurrentTask();
718 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 ) {
719 m_tasks = m_editedTasks;
720 } else m_editedTasks = m_tasks; //. undo changes
721 m_changedTask=false;
722 selectedStartDate(QDate());
723 populateTasks();
727 void Sak::selectColor() {
728 if (tasksTree->selectedItems().isEmpty()) return;
730 if (sender() == fgColorButton) {
731 QColor c = QColorDialog::getColor(fgColorButton->palette().color(QPalette::ButtonText));
732 if (!c.isValid()) return;
733 QPalette p = fgColorButton->palette();
734 p.setColor(QPalette::ButtonText, c);
735 fgColorButton->setPalette(p);
736 bgColorButton->setPalette(p);
737 } else if (sender() == bgColorButton) {
738 QColor c = QColorDialog::getColor(bgColorButton->palette().color(QPalette::Button));
739 if (!c.isValid()) return;
740 QPalette p = bgColorButton->palette();
741 p.setColor(QPalette::Button, c);
742 fgColorButton->setPalette(p);
743 bgColorButton->setPalette(p);
745 commitCurrentTask();
748 bool Sak::taskTreeEventFilter(QEvent* e)
750 if (e->type() == QEvent::ContextMenu) {
751 QContextMenuEvent* me = dynamic_cast<QContextMenuEvent*>(e);
752 if (!me) return false;
753 m_addTaskMenu->popup(me->globalPos());
754 return true;
755 } else if (e->type() == QEvent::KeyRelease) {
756 QKeyEvent* ke = dynamic_cast<QKeyEvent*>(e);
757 if (!ke) return false;
758 if ( (ke->key() != Qt::Key_Delete && ke->key() != Qt::Key_Backspace) ) return false;
759 if (currentSubtask!="") {
760 QMessageBox whatToDo(QMessageBox::Warning, "Deleting subtask", "Deleting subtask " + currentSubtask + " of task " + currentTask);
761 QPushButton* moveHitsToParentButton = whatToDo.addButton("Move hits to task " + currentTask, QMessageBox::AcceptRole);
762 QPushButton* removeHitsButton = whatToDo.addButton("Remove hits", QMessageBox::AcceptRole);
763 QPushButton* cancelButton = whatToDo.addButton("Cancel", QMessageBox::RejectRole);
764 whatToDo.setDefaultButton(cancelButton);
765 whatToDo.exec();
766 if ( whatToDo.clickedButton() == cancelButton) return true;
767 if (m_editedTasks.find(currentTask) == m_editedTasks.end()) return true;
768 m_changedTask=true;
769 Task& t(m_editedTasks[currentTask]);
770 t.subTasks.take(currentSubtask);
771 if (whatToDo.clickedButton() == removeHitsButton) {
772 t.hits.take(currentSubtask);
773 } else if (whatToDo.clickedButton() == moveHitsToParentButton) {
774 QList<Task::Hit> sorter(t.hits.take(""));
775 sorter << t.hits.take(currentSubtask);
776 qStableSort(sorter.begin(), sorter.end());
777 t.hits[""] = sorter;
779 } else {
780 // remove file from disk
781 m_changedTask=true;
782 m_editedTasks.remove(currentTask);
784 tasksTree->clear();
785 populateTasks();
786 selectedStartDate(QDate());
787 return true;
788 } else if (e->type() == QEvent::Hide) {
789 saveTaskChanges();
791 return false;
795 void Sak::commitCurrentTask()
797 m_changedTask=true;
798 if (currentSubtask.isEmpty()) {
799 QString currentTitle = taskTitleEditor->text();
800 if (!currentTitle.isEmpty()) {
801 if (currentTitle != currentTask) {
802 if (m_editedTasks.contains(currentTitle)) {
803 QMessageBox::warning(0, "Conflict in task names", "Conflict in task names: current task " + currentTask + ", edited title " + currentTitle);
804 taskTitleEditor->setText(currentTask);
805 return;
806 } else if (m_editedTasks.contains(currentTask)) {
807 m_editedTasks[currentTitle] = m_editedTasks.take(currentTask);
808 m_editedTasks[currentTitle].title = currentTitle;
811 } else return;
812 Task& t = m_editedTasks[currentTitle];
813 t.bgColor = bgColorButton->palette().color(QPalette::Button);
814 t.fgColor = fgColorButton->palette().color(QPalette::ButtonText);
815 t.icon = taskPixmapViewer->pixmap();
816 QList<QTreeWidgetItem *> items = tasksTree->findItems(currentTask,Qt::MatchExactly,0);
817 foreach(QTreeWidgetItem* ii, items) {
818 ii->setText(0, currentTitle);
819 ii->setIcon(0, taskPixmapViewer->pixmap());
820 for (int i=0; i<3; i++) {
821 ii->setForeground(i, QColor(t.fgColor));
822 ii->setBackground(i, QColor(t.bgColor));
825 if (dueEditor->date() != dueEditor->minimumDate())
826 t.dueDate = dueEditor->date();
827 t.estimatedHours = estimatedHoursEditor->value();
828 currentTask=currentTitle;
830 if (tasksTree->selectedItems().size() != 1) return;
831 QTreeWidgetItem* item = tasksTree->selectedItems()[0];
832 item->setText(0, taskTitleEditor->text());
833 QIcon icon;
834 icon.addPixmap(t.icon);
835 item->setSizeHint(0, QSize(32,32));
836 item->setIcon(0, icon);
837 for(int i=0; i<3; i++) {
838 item->setForeground(i,t.fgColor);
839 item->setBackground(i,t.bgColor);
843 } else { // subtask edited
844 if (!m_editedTasks.contains(currentTask)) return;
845 Task& t(m_editedTasks[currentTask]);
846 QString currentTitle = taskTitleEditor->text();
847 // backup data
848 if (!currentTitle.isEmpty()) {
849 if (currentTitle != currentSubtask) {
850 if (t.subTasks.contains(currentTitle)) {
851 QMessageBox::warning(0, "Conflict in subtask names", "Conflict in subtask names");
852 taskTitleEditor->setText(currentSubtask);
853 return;
854 } else if (t.subTasks.contains(currentSubtask)) {
855 t.subTasks[currentTitle] = t.subTasks.take(currentSubtask);
856 t.subTasks[currentTitle].title = currentTitle;
857 t.hits[currentTitle] = t.hits.take(currentSubtask);
860 } else return;
861 Task::SubTask& st = t.subTasks[currentTitle];
862 st.bgColor = bgColorButton->palette().color(QPalette::Button);
863 st.fgColor = fgColorButton->palette().color(QPalette::ButtonText);
864 QList<QTreeWidgetItem *> items = tasksTree->findItems(currentTask,Qt::MatchExactly,0);
865 foreach(QTreeWidgetItem* jj, items) {
866 for(int i=0; i<jj->childCount(); i++) {
867 QTreeWidgetItem* ii = jj->child(i);
868 if (ii->text(0) != currentSubtask) continue;
869 ii->setText(0, currentTitle);
870 for (int i=0; i<3; i++) {
871 ii->setForeground(i, QColor(st.fgColor));
872 ii->setBackground(i, QColor(st.bgColor));
876 currentSubtask = currentTitle;
878 if (tasksTree->selectedItems().size() != 1) return;
879 QTreeWidgetItem* item = tasksTree->selectedItems()[0];
880 item->setText(0, taskTitleEditor->text());
881 QIcon icon;
882 icon.addPixmap(t.icon);
883 item->setSizeHint(0, QSize(32,32));
884 item->setIcon(0, icon);
885 for(int i=0; i<3; i++) {
886 item->setForeground(i,st.fgColor);
887 item->setBackground(i,st.bgColor);
892 void Sak::selectedTask()
894 if (tasksTree->selectedItems().isEmpty()) {
895 taskPixmapViewer->setEnabled(false);
896 taskPixmapViewer->setPixmap(QPixmap());
897 taskTextEditor->setEnabled(false);
898 taskTitleEditor->setEnabled(false);
899 bgColorButton->setEnabled(false);
900 fgColorButton->setEnabled(false);
901 dueEditor->setEnabled(false);
902 estimatedHoursEditor->setEnabled(false);
903 return;
906 QTreeWidgetItem* selectedItem = tasksTree->selectedItems().first();
907 QTreeWidgetItem* parentItem = selectedItem->parent();
908 QString tt = selectedItem->text(0);
910 if (!parentItem) {
911 taskPixmapViewer->setEnabled(true);
912 dueEditor->setEnabled(true);
913 estimatedHoursEditor->setEnabled(true);
914 } else {
915 taskPixmapViewer->setEnabled(false);
916 taskPixmapViewer->setPixmap(QPixmap());
917 dueEditor->setEnabled(false);
918 estimatedHoursEditor->setEnabled(false);
920 taskTextEditor->setEnabled(true);
921 taskTitleEditor->setEnabled(true);
922 bgColorButton->setEnabled(true);
923 fgColorButton->setEnabled(true);
926 if (!parentItem) { // editing a task
927 if (!m_editedTasks.contains(tt)) return;
928 const Task& t = m_editedTasks[tt];
929 taskPixmapViewer->setPixmap(t.icon);
930 taskTextEditor->blockSignals(true);
931 taskTextEditor->setPlainText(t.description);
932 taskTextEditor->blockSignals(false);
933 taskTitleEditor->setText(t.title);
934 QPalette p;
935 p.setColor(QPalette::Button, t.bgColor);
936 p.setColor(QPalette::ButtonText, t.fgColor);
937 bgColorButton->setPalette(p);
938 fgColorButton->setPalette(p);
939 estimatedHoursEditor->setValue(t.estimatedHours);
940 dueEditor->setDate(t.dueDate.isValid() ? t.dueDate : dueEditor->minimumDate());
942 currentTask = t.title;
943 currentSubtask = "";
944 } else {
945 if (!m_editedTasks.contains(parentItem->text(0))) return;
946 const Task& t = m_editedTasks[parentItem->text(0)];
947 if (!t.subTasks.contains(tt)) return;
948 const Task::SubTask& st = t.subTasks[tt];
949 taskTextEditor->setPlainText(st.description);
950 taskTitleEditor->setText(st.title);
951 QPalette p;
952 p.setColor(QPalette::Button, st.bgColor.isValid() ? st.bgColor : t.bgColor);
953 p.setColor(QPalette::ButtonText, st.fgColor.isValid() ? st.fgColor : t.fgColor);
954 bgColorButton->setPalette(p);
955 fgColorButton->setPalette(p);
957 currentTask = t.title;
958 currentSubtask = st.title;
962 void Sak::doubleClickedTask(QTreeWidgetItem* i, int column)
964 if (column == 1) {
965 m_changedTask=true;
966 if (i->parent() == 0) {
967 QHash<QString, Task>::iterator itr = m_editedTasks.find(i->text(0));
968 Q_ASSERT(itr != m_editedTasks.end());
969 bool& active ( itr.value().active );
970 active = !active;
971 i->setIcon(column, active ? QIcon(":/images/active.png") : QIcon(":/images/inactive.png"));
972 } else {
973 QHash<QString, Task>::iterator itr = m_editedTasks.find(i->parent()->text(0));
974 Q_ASSERT(itr != m_editedTasks.end());
975 bool& active ( itr.value().subTasks[i->text(0)].active );
976 active = !active;
977 i->setIcon(column, active ? QIcon(":/images/active.png") : QIcon(":/images/inactive.png"));
979 ((QTreeWidget*)sender())->update();
985 void Sak::timerEvent(QTimerEvent* e)
987 if (e->timerId() == m_timerId) {
988 if (!m_view->isVisible() && !m_settings->isVisible() && m_tasks.count() > 0) {
989 popup();
990 // close timer
991 killTimer(m_timeoutPopup);
992 m_timeoutPopup = startTimer((int)(qMax( 30000.0, Task::hours(m_currentInterval)*3600.0*1000.0/10.0))); // 5 secmin
993 // restart timer
994 killTimer(m_timerId);
995 int msecs = (int)(Task::hours(m_currentInterval)*3600.0*1000.0);
996 m_timerId = startTimer(msecs);
997 m_nextTimerEvent = QDateTime::currentDateTime().addMSecs(msecs);
998 } else {
999 if (m_settings && m_settings->isVisible() && !m_settings->isActiveWindow()) {
1000 trayIcon->showMessage("Delayed check point", "Delayed check point due to open settings. Close the setting window!", QSystemTrayIcon::Warning, -1);
1001 m_settings->close();
1003 qDebug() << "SAK: wait 5 seconds";
1004 killTimer(m_timerId);
1005 m_timerId = startTimer(5000);
1006 m_nextTimerEvent = QDateTime::currentDateTime().addMSecs(5000);
1008 } else if (e->timerId() == m_timeoutPopup) {
1009 // ensure the timer is resetted
1010 killTimer(m_timeoutPopup);
1012 if (!m_subtaskView) {
1013 // if not selecting subtasks clear everything and signal away
1014 workingOnTask("<away>","");
1015 trayIcon->showMessage("New away events", "You have missed a check point. Fix it in the detailed hit list.", QSystemTrayIcon::Information, 999999);
1016 clearView();
1018 } else if (e->timerId() == m_autoSaveTimer) {
1019 flush();
1020 } else {
1021 qDebug() << "unknown timer event";
1025 void Sak::clearView()
1027 killTimer(m_timeoutPopup);
1028 m_subtaskView=false;
1029 m_marker = 0;
1030 delete m_subtaskCompleter; m_subtaskCompleter = 0;
1032 if (!m_view) return;
1033 QGraphicsScene* s = m_view->scene();
1034 QList<QGraphicsItem*> items = m_view->items();
1035 s->deleteLater();
1036 m_view->close();
1037 m_view->setScene(new QGraphicsScene);
1038 m_view->scene()->setSceneRect(m_desktopRect);
1039 m_previewing = false;
1040 m_view->releaseKeyboard();
1042 #if defined(Q_WS_X11)
1043 // restore focus to previous application
1044 grabbed=false;
1045 X11::XSetInputFocus((X11::Display*)QX11Info::display(), X11::CurrentFocusWindow, X11::CurrentRevertToReturn, CurrentTime);
1046 X11::XFlush((X11::Display*)QX11Info::display());
1047 #endif
1050 void Sak::workingOnTask(const QString& taskName, const QString& subTask)
1052 if (!m_previewing) {
1054 qDebug() << "Working on " << taskName ;
1055 if (m_tasks.contains(taskName)) {
1056 Task& t = m_tasks[taskName];
1058 if (t.title != "<away>" && !t.title.isEmpty()) {
1059 // update history
1060 int historyIndex = m_taskSelectionHistory.indexOf(t.title);
1061 if (historyIndex != -1) {
1062 m_taskSelectionHistory.takeAt(historyIndex);
1064 m_taskSelectionHistory.push_back(t.title);
1066 QList<QString> & subtaskSelectionHistory(m_subtaskSelectionHistory[t.title]);
1067 historyIndex = subtaskSelectionHistory.indexOf(subTask);
1068 if (historyIndex != -1) {
1069 subtaskSelectionHistory.takeAt(historyIndex);
1071 subtaskSelectionHistory.push_back(subTask);
1075 QDateTime now = QDateTime::currentDateTime();
1076 QHash<QString, Task>::iterator itr = m_tasks.begin();
1078 QHash<QString, QList< Task::Hit> >::iterator hitr = t.hits.begin();
1079 // merge last two hits of every subtask if they are close enough
1080 while(hitr != t.hits.end()) {
1081 QList<Task::Hit>& otHits( *hitr );
1082 if (otHits.count() > 1) {
1083 Task::Hit& lastHit(otHits[otHits.size() - 1]);
1084 Task::Hit& beforeLastHit(otHits[otHits.size() - 2]);
1086 int diff = (lastHit.timestamp.toTime_t() - 30*lastHit.duration) - (beforeLastHit.timestamp.toTime_t() + 30*beforeLastHit.duration);
1087 if (diff < 120) { // at most 2 minutes apart
1088 beforeLastHit.timestamp = beforeLastHit.timestamp.addSecs(-30*beforeLastHit.duration);
1089 int secsToEnd = beforeLastHit.timestamp.secsTo(lastHit.timestamp.addSecs(30*m_currentInterval));
1090 if (secsToEnd > 24 * 3600 * 3600) {
1091 qWarning() << "TRAPPED ERROR IN SECS COUNT!!!!!!!!";
1092 qWarning() << "BEFORE LAST HIST WAS " << beforeLastHit.timestamp << beforeLastHit.duration;
1093 qWarning() << "LAST HIT WAS " << lastHit.timestamp << lastHit.duration;
1094 assert(secsToEnd < 24 * 3600 * 3600);
1096 beforeLastHit.timestamp = beforeLastHit.timestamp.addSecs( secsToEnd/2.0 );
1097 beforeLastHit.duration = (int)( round( secsToEnd / 60.0 ) );
1098 // remove the current very last hit
1099 otHits.pop_back();
1102 hitr++;
1105 // add hit to hit list
1106 // NOTE: we do not try to merge the very last hit with previous ones because we want let
1107 // the user being able to easily recover from a selection error
1108 t.hits[subTask] << Task::Hit(now, m_currentInterval);
1109 m_incremental->writePiece(t.title, subTask, now, m_currentInterval);
1110 t.checkConsistency();
1111 QList<QTreeWidgetItem*> items = tasksTree->findItems (t.title, Qt::MatchExactly, 0);
1112 if (!items.isEmpty())
1113 items.first()->setText(1, QString("%1 hours worked till now (overestimated %2)").arg(t.totHours).arg(t.totOverestimation));
1115 // update subtask if new added!
1116 t.updateSubTasks();
1118 // update statistics !!!!
1119 m_editedTasks = m_tasks;
1120 QMetaObject::invokeMethod(this, "selectedStartDate", Qt::QueuedConnection, Q_ARG(QDate, cal1->selectedDate()));
1123 clearView();
1127 // attractor = 't' (top), 'b', 'l', 'r'
1128 void layoutInSquare( QList<SakWidget*> sortedWidgets, QRect rect, char attractor)
1130 int w = rect.width();
1131 if (rect.width() < 64) return;
1132 int maxw = qMin(350, w/2);
1133 QSize size(maxw, maxw);
1134 if (sortedWidgets.count() == 0) {
1135 return;
1136 } else if (sortedWidgets.count() == 1) {
1137 sortedWidgets[0]->setGeometry(rect);
1138 } else if (sortedWidgets.count() == 2) {
1139 QPoint off1, off2;
1140 if (attractor == 'C') {
1141 off1 = QPoint(0,w/4);
1142 off2 = QPoint(w/2,w/4);
1143 } else if (attractor == 'T') {
1144 off1 = QPoint(0,0);
1145 off2 = QPoint(w/2,0);
1146 } else if (attractor == 'B') {
1147 off1 = QPoint(0,w/2);
1148 off2 = QPoint(w/2,w/2);
1149 } else if (attractor == 'R') {
1150 off1 = QPoint(w/2,0);
1151 off2 = QPoint(w/2,w/2);
1152 } else if (attractor == 'L') {
1153 off1 = QPoint(0,0);
1154 off2 = QPoint(0,w/2);
1156 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + off1, size));
1157 sortedWidgets[1]->setGeometry(QRect(rect.topLeft() + off2, size));
1158 } else if (sortedWidgets.count() == 3) {
1159 QPoint off1, off2, off3;
1160 if (attractor == 'T' || attractor == 'C') {
1161 off1 = QPoint(0,0);
1162 off2 = QPoint(w/2,0);
1163 off3 = QPoint(w/4,w/2);
1164 } else if (attractor == 'B') {
1165 off1 = QPoint(0,w/2);
1166 off2 = QPoint(w/2,w/2);
1167 off3 = QPoint(w/4,0);
1168 } else if (attractor == 'R') {
1169 off1 = QPoint(w/2,0);
1170 off2 = QPoint(w/2,w/2);
1171 off3 = QPoint(0,w/4);
1172 } else if (attractor == 'L') {
1173 off1 = QPoint(0,0);
1174 off2 = QPoint(0,w/2);
1175 off3 = QPoint(w/2,w/4);
1177 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + off1, size));
1178 sortedWidgets[1]->setGeometry(QRect(rect.topLeft() + off2, size));
1179 sortedWidgets[2]->setGeometry(QRect(rect.topLeft() + off3, size));
1180 } else if (sortedWidgets.count() == 4) {
1181 QPoint off1(0,0);
1182 QPoint off2(0,w/2);
1183 QPoint off3(w/2,0);
1184 QPoint off4(w/2,w/2);
1185 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + off1, size));
1186 sortedWidgets[1]->setGeometry(QRect(rect.topLeft() + off2, size));
1187 sortedWidgets[2]->setGeometry(QRect(rect.topLeft() + off3, size));
1188 sortedWidgets[3]->setGeometry(QRect(rect.topLeft() + off4, size));
1189 } else {
1190 Q_ASSERT(sortedWidgets.count() <= 4);
1194 void layoutInRect( QList<SakWidget*> sortedWidgets, QRect rect, char attractor)
1196 if (sortedWidgets.count() == 0) return;
1197 int h = rect.height();
1198 int w = rect.width();
1199 int maxh = qMin(350, h);
1200 int maxw = qMin(350, w);
1201 if (sortedWidgets.count() == 1) {
1202 if (w>h) {
1203 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + QPoint((w-maxh)/2,(h-maxh)/2), QSize(maxh,maxh)));
1204 } else {
1205 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + QPoint((w-maxw)/2,(h-maxw)/2), QSize(maxw,maxw)));
1207 return;
1208 } else if (sortedWidgets.count() == 2) {
1209 if (w>h) {
1210 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + QPoint(w/2-maxh,(h-maxh)/2), QSize(maxh,maxh)));
1211 sortedWidgets[1]->setGeometry(QRect(rect.topLeft() + QPoint(w/2,(h-maxh)/2), QSize(maxh,maxh)));
1212 } else {
1213 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + QPoint((h-maxw)/2,w-maxw), QSize(w,w)));
1214 sortedWidgets[1]->setGeometry(QRect(rect.topLeft() + QPoint((h-maxw)/2,w/2), QSize(maxw,maxw)));
1216 return;
1218 if (h < 64 || w < 64) return;
1219 QList<SakWidget*> leftList, rightList;
1220 for (int i=4; i<sortedWidgets.count(); i++) {
1221 if (i%2)
1222 rightList << sortedWidgets[i];
1223 else
1224 leftList << sortedWidgets[i];
1226 if (w > h) {
1227 QRect square(rect.topLeft() + QPoint(h/2, 0), QSize(h,h));
1228 layoutInSquare(sortedWidgets.mid(0,4), square, attractor);
1230 QRect leftRect(rect.topLeft(), QSize(h/2, h));
1231 layoutInRect(leftList, leftRect, 'R');
1232 QRect rightRect(rect.topLeft() + QPoint((int)(0.75*w),0), QSize(h/2, h));
1233 layoutInRect(rightList, rightRect, 'L');
1234 } else {
1235 QRect square(rect.topLeft() + QPoint(0,w/2), QSize(w,w));
1236 layoutInSquare(sortedWidgets.mid(0,4), square, attractor);
1238 QRect leftRect(rect.topLeft(), QSize(w, w/2));
1239 layoutInRect(leftList, leftRect, 'B');
1240 QRect rightRect(rect.topLeft() + QPoint(0,(int)(0.75*h)), QSize(w, w/2));
1241 layoutInRect(rightList, rightRect, 'T');
1245 QRect Sak::Layouting( const QList<SakWidget*>& sortedWidgets)
1247 QRect r = m_desktopRect;
1248 int height = (int)(0.75 * r.height());
1249 int width = r.width();
1251 int firstW = width / 2 < height ? width : height * 2;
1252 int firstH = firstW / 2;
1253 QRect firstRect (r.x() + (width - firstW) / 2, r.y() + (height - firstH) / 2 + (int)(r.height() * 0.25), firstW, firstH);
1255 layoutInRect(sortedWidgets, firstRect, 'C');
1256 return QRect(QPoint(r.x(), r.y()), QSize(r.width(), (int)(0.25 * r.height())));
1259 void Sak::grabKeyboard()
1261 #if defined(Q_WS_X11)
1262 if (!grabbed) {
1263 // save current focused application
1264 XGetInputFocus((X11::Display*)QX11Info::display(), &X11::CurrentFocusWindow, &X11::CurrentRevertToReturn);
1265 grabbed=true;
1267 X11::XSetInputFocus((X11::Display*)QX11Info::display(), QX11Info::appRootWindow(QX11Info::appScreen()), RevertToParent, CurrentTime);
1268 X11::XFlush((X11::Display*)QX11Info::display());
1269 #endif
1270 m_view->grabKeyboard();
1273 void Sak::popup()
1275 if (m_stopped) {
1276 trayIcon->showMessage("SAK popup disabled", "SAK triggered a new event but no popup will be shown", QSystemTrayIcon::Information, -1);
1277 return;
1280 // save changes first
1281 if (m_changedTask)
1282 saveTaskChanges();
1283 if (m_changedHit)
1284 saveHitChanges();
1286 if (m_subtaskView) {
1287 // remove subtasks
1288 foreach(SakSubWidget* w, m_subwidgets.values()) {
1289 w->scene()->removeItem(w);
1290 delete w;
1291 m_subwidgets.clear();
1293 m_marker->scene()->removeItem(m_marker);
1294 delete m_marker;
1296 // unhide tasks
1297 foreach(SakWidget* w, m_widgets.values()) {
1298 w->show();
1300 m_subtaskView=false;
1301 return;
1304 m_subtaskView = false;
1306 if (sender() == previewButton) {
1307 m_previewing = true;
1309 QDateTime now = QDateTime::currentDateTime();
1310 QDateTime fromWeek = now;
1311 fromWeek.setDate(now.date().addDays(-now.date().dayOfWeek() + 1));
1312 fromWeek.setTime(QTime(0,0,0));
1313 QDateTime fromMonth = now;
1314 fromMonth.setDate(now.date().addDays(-now.date().day()));
1315 fromMonth.setTime(QTime(0,0,0));
1316 QDateTime fromToday = now;
1317 fromToday.setTime(QTime(0,0,0));
1319 double weekHits = 0;
1320 double dayHits = 0;
1321 double monthHits = 0;
1322 QHash<QString, double> dayStats;
1323 QHash<QString, double> weekStats;
1324 QHash<QString, double> monthStats;
1326 foreach(const Task& t, m_tasks.values()) {
1327 if (t.active) {
1328 double m = t.workedHours(fromMonth, now);
1329 monthStats[t.title] = m;
1330 monthHits += m;
1331 double w = t.workedHours(fromWeek, now);
1332 weekStats[t.title] = w;
1333 weekHits += w;
1334 double d = t.workedHours(fromToday, now);
1335 dayStats[t.title] = d;
1336 dayHits += d;
1341 m_widgets.clear();
1342 foreach(const Task& t, m_tasks.values()) {
1343 if (!t.active || t.title == QString() || t.title == "<away>") continue;
1344 SakWidget* test = new SakWidget(t);
1345 test->setVisible(false);
1346 double d = dayStats[t.title];
1347 double w = weekStats[t.title];
1348 double m = monthStats[t.title];
1349 test->setStatistics(d, w, m, d/dayHits * 100.0, w/weekHits * 100.0, m/monthHits * 100.0);
1350 test->setObjectName(t.title);
1351 connect (test, SIGNAL(clicked(const QString&, const QString&)), this, SLOT(workingOnTask(const QString&, const QString&)));
1352 connect (test, SIGNAL(clicked(const QString&)), this, SLOT(popupSubtasks(const QString&)));
1353 int historyPosition = 1 + m_taskSelectionHistory.indexOf(t.title);
1354 int rank = historyPosition != 0 ? -1000000 * historyPosition : -d;
1355 m_widgets.insertMulti( rank, test);
1358 m_widgetsIterator = m_widgets.begin();
1359 if (m_widgetsIterator != m_widgets.end()) {
1360 m_widgetsIterator.value()->showDetails(true);
1364 const QList<SakWidget*>& values = m_widgets.values();
1365 QRect messageRect = Layouting((const QList<SakWidget*>&)values);
1366 foreach(SakWidget* w, values) {
1367 m_view->scene()->addItem(w);
1368 w->show();
1372 // add the message item
1373 SakMessageItem* sakMessage = new SakMessageItem(bodyEdit->toPlainText());
1374 sakMessage->setGeometry(messageRect);
1375 m_view->scene()->addItem(sakMessage);
1376 sakMessage->show();
1378 // add the exit item
1379 SakExitItem* exitItem = new SakExitItem(QPixmap(":/images/exit.png"));
1380 QRect r = m_desktopRect;
1381 connect(exitItem, SIGNAL(exit()), this, SLOT(clearView()));
1382 exitItem->setPos(r.width() - exitItem->boundingRect().width(), 0);
1383 m_view->scene()->addItem(exitItem);
1384 exitItem->setZValue(1e8);
1385 exitItem->show();
1388 m_view->setGeometry( QRect(m_desktopRect)/*.adjusted(200,200,-200,-200 )*/ );
1389 m_view->show();
1390 m_view->raise();
1391 m_view->setFocus();
1392 #if defined(Q_WS_WIN)
1393 SetForegroundWindow(m_view->winId());
1394 #endif
1395 qApp->alert(m_view, 5000);
1399 void Sak::popupSubtasks(const QString& _taskname) {
1401 killTimer(m_timeoutPopup);
1403 QString taskname = _taskname;
1404 if (taskname.isEmpty()) {
1405 if ( m_taskSelectionHistory.isEmpty() ) {
1406 return;
1407 } else {
1408 taskname = m_taskSelectionHistory.back();
1410 QString subtaskName;
1411 if (!m_subtaskSelectionHistory[taskname].isEmpty()) {
1412 subtaskName = m_subtaskSelectionHistory[taskname].back();
1414 workingOnTask(taskname, subtaskName);
1417 m_subtaskView = true;
1418 grabKeyboard();
1419 QRect r = m_desktopRect;
1420 int w = 500;
1421 int h = 40;
1423 // hide tasks to show subtasks
1424 foreach(SakWidget* w, m_widgets.values()) {
1425 w->hide();
1428 QHash<QString, Task>::const_iterator itr = m_tasks.find(taskname);
1429 if (itr == m_tasks.end()) {
1430 workingOnTask(taskname, "");
1431 return;
1433 const Task& t(*itr);
1434 QHash< QString, Task::SubTask >::const_iterator titr = t.subTasks.begin(), tend = t.subTasks.end();
1435 m_subwidgets.clear();
1436 QDateTime now(QDateTime::currentDateTime());
1437 for(; titr != tend; titr++) {
1438 if (!titr->active) continue;
1439 SakSubWidget* sw = new SakSubWidget(t, *titr);
1440 sw->setGeometry(QRectF(0,0,w,h));
1441 connect (sw, SIGNAL(clicked(const QString&, const QString&)), this, SLOT(workingOnTask(const QString&, const QString&)));
1442 connect (sw, SIGNAL(focused()), this, SLOT(focusedSubTask()));
1444 QDateTime rank=now;
1445 QHash< QString, QList< QString > >::const_iterator hhitr = m_subtaskSelectionHistory.find(t.title);
1446 if (hhitr != m_subtaskSelectionHistory.end()) {
1447 int index = hhitr->indexOf(titr->title);
1448 if (index != 0) rank = now.addDays(index+1);
1450 if (rank == now) {
1451 QHash< QString, QList<Task::Hit> >::const_iterator hitr = t.hits.find(titr.key());
1452 if (hitr != t.hits.end()) {
1453 if ( hitr.value().count() && hitr.value().last().timestamp.isValid())
1454 rank = hitr.value().last().timestamp;
1455 else
1456 rank = now.addDays(-999);
1459 m_subwidgets.insertMulti( - rank.toTime_t(), sw);
1462 const QList<SakSubWidget*>& values = m_subwidgets.values();
1464 // create possible completion
1465 QStringList subtaskSortedList;
1466 foreach(SakSubWidget* sub, values) {
1467 subtaskSortedList.push_back(sub->subtask().title);
1469 m_subtaskCompleter = new QCompleter(subtaskSortedList);
1470 m_subtaskCompleter->setCaseSensitivity(Qt::CaseInsensitive);
1472 // QRect messageRect = Layouting((const QList<SakWidget*>&)values);
1474 m_subwidgetsIterator = m_subwidgets.begin();
1475 m_subWidgetRank = 0;
1477 // the one with text
1478 SakSubWidget* tmpSw = new SakSubWidget(t, Task::SubTask(), true);
1479 QCompleter* completer = new QCompleter(subtaskSortedList);
1480 completer->setCaseSensitivity(Qt::CaseInsensitive);
1481 completer->setCompletionMode(QCompleter::InlineCompletion);
1482 ((QLineEdit*)tmpSw->widget())->setCompleter(completer);
1483 m_view->scene()->addItem(tmpSw);
1484 tmpSw->setGeometry(QRectF(0,0,w,h));
1485 connect (tmpSw, SIGNAL(clicked(const QString&, const QString&)), this, SLOT(workingOnTask(const QString&, const QString&)));
1486 connect (tmpSw, SIGNAL(focused()), this, SLOT(focusedSubTask()));
1488 m_subwidgets.insertMulti(-QDateTime::currentDateTime().addDays(999).toTime_t(), tmpSw);
1490 m_subWidgetRank += values.size() != 0;
1492 if (m_subwidgetsIterator != m_subwidgets.end()) {
1493 m_subwidgetsIterator.value()->showDetails(true);
1494 m_view->scene()->setFocusItem(m_subwidgetsIterator.value(), Qt::MouseFocusReason);
1495 m_subwidgetsIterator.value()->widget()->setFocus(Qt::MouseFocusReason);
1496 } else {
1497 m_view->scene()->setFocusItem(tmpSw, Qt::MouseFocusReason);
1498 tmpSw->widget()->setFocus(Qt::MouseFocusReason);
1501 // add a marker to highligh current selection
1502 m_marker = new QGraphicsEllipseItem(r.width()/2 - 280, r.height()/2 - 51, 20,20);
1503 m_marker->setBrush(Qt::red);
1504 m_view->scene()->addItem(m_marker);
1505 m_marker->setVisible(true);
1507 layoutSubTasks(m_subwidgets, m_subWidgetRank);
1509 // Finally add subwidgets
1510 for(int i=0; i<values.size(); i++) {
1511 SakSubWidget* sw = values[i];
1512 m_view->scene()->addItem(sw);
1517 void Sak::scrollTasks(int npos) {
1518 SakWidget* currentShowing = 0;
1519 if (npos < 0) {
1520 for (int i=npos; i<0; i++) {
1521 if (m_widgetsIterator == m_widgets.end()) return;
1522 currentShowing = m_widgetsIterator != m_widgets.end() ? m_widgetsIterator.value() : 0;
1523 if (m_widgetsIterator == m_widgets.begin()) m_widgetsIterator = m_widgets.end();
1524 m_widgetsIterator--;
1526 } else {
1527 for (int i=0; i<npos; i++) {
1528 if (m_widgetsIterator == m_widgets.end()) return;
1529 currentShowing = m_widgetsIterator.value();
1530 m_widgetsIterator++;
1531 if (m_widgetsIterator == m_widgets.end()) m_widgetsIterator = m_widgets.begin();
1534 if (currentShowing && m_widgetsIterator != m_widgets.end()) {
1535 currentShowing->showDetails(false);
1536 m_widgetsIterator.value()->showDetails(true);
1540 void Sak::scrollSubTasks(int npos) {
1541 SakSubWidget* currentShowing = 0;
1542 if (npos < 0) {
1543 for (int i=npos; i<0; i++) {
1544 if (m_subwidgetsIterator == m_subwidgets.end()) return;
1545 currentShowing = m_subwidgetsIterator != m_subwidgets.end() ? m_subwidgetsIterator.value() : 0;
1546 if (m_subwidgetsIterator == m_subwidgets.begin()) {
1547 m_subwidgetsIterator = m_subwidgets.end();
1548 m_subWidgetRank = m_subwidgets.count();
1550 m_subwidgetsIterator--;
1551 m_subWidgetRank--;
1553 } else {
1554 for (int i=0; i<npos; i++) {
1555 if (m_subwidgetsIterator == m_subwidgets.end()) return;
1556 currentShowing = m_subwidgetsIterator.value();
1557 m_subwidgetsIterator++;
1558 m_subWidgetRank++;
1559 if (m_subwidgetsIterator == m_subwidgets.end()) {
1560 m_subwidgetsIterator = m_subwidgets.begin();
1561 m_subWidgetRank = 0;
1565 if (currentShowing && m_subwidgetsIterator != m_subwidgets.end()) {
1566 currentShowing->showDetails(false);
1567 m_subwidgetsIterator.value()->showDetails(true);
1568 m_view->scene()->setFocusItem(m_subwidgetsIterator.value(), Qt::MouseFocusReason);
1569 m_subwidgetsIterator.value()->widget()->setFocus(Qt::MouseFocusReason);
1571 layoutSubTasks(m_subwidgets, m_subWidgetRank);
1574 void Sak::focusedSubTask()
1576 SakSubWidget* w = dynamic_cast<SakSubWidget*>(sender());
1577 if (w) {
1578 QMap<int, SakSubWidget*>::iterator itr = m_subwidgets.begin(), end=m_subwidgets.end();
1579 for(int i=0; itr != end; i++,itr++) {
1580 if (itr.value() == w) {
1581 m_subwidgetsIterator = itr;
1582 m_subWidgetRank = i;
1588 //END Tasks >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
1591 //BEGIN settings ================================
1594 void Sak::setupSettingsWidget()
1596 m_settings = new QMainWindow();
1597 m_settings->setMinimumHeight(650);
1598 m_settings->setMinimumWidth(700);
1599 QWidget* centralWidget = new QWidget;
1600 m_settings->setCentralWidget(centralWidget);
1602 QVBoxLayout* theMainLayout = new QVBoxLayout;
1603 centralWidget->setLayout(theMainLayout);
1605 tabs = new QTabWidget;
1606 theMainLayout->addWidget(tabs);
1607 previewButton = new QPushButton("Preview");
1608 theMainLayout->addWidget(previewButton);
1610 tab1 = new QWidget;
1611 tab2 = new QWidget;
1612 tab3 = new QWidget;
1613 tab4 = new QWidget;
1614 tabs->addTab(tab1, "Tasks");
1615 tabs->addTab(tab2, "General");
1616 tabs->addTab(tab4, "Statistics");
1617 tabs->addTab(tab3, "Advanced");
1619 createActions();
1620 QMenuBar* mainMenu = new QMenuBar;
1621 m_settings->setMenuBar(mainMenu);
1622 QMenu* programMenu = mainMenu->addMenu("Program");
1623 programMenu->addAction(quitAction);
1624 programMenu->addAction(minimizeAction);
1625 QMenu* dbMenu = mainMenu->addMenu("Db");
1626 dbMenu->addAction(flushAction);
1627 dbMenu->addAction(openAction);
1628 // dbMenu->addAction(saveAsDbAction);
1629 dbMenu->addAction(exportDbCsvAction);
1630 #ifdef USEGMAIL
1631 dbMenu->addAction(gmailLoginAction);
1632 dbMenu->addAction(saveToGmailAction);
1633 if (!m_gmail->isValid()) {
1634 gmailLoginAction->setEnabled(false);
1635 saveToGmailAction->setEnabled(false);
1637 #endif
1638 QMenu* actionsMenu = mainMenu->addMenu("Actions");
1639 actionsMenu->addAction(startAction);
1640 actionsMenu->addAction(stopAction);
1642 QVBoxLayout *mainLayout = new QVBoxLayout;
1643 QGridLayout *messageLayout = new QGridLayout;
1644 durationLabel = new QLabel(tr("Interval:"));
1645 durationLabel1 = new QLabel(tr("(effective only after restart)"));
1647 QSettings settings(QSettings::IniFormat, QSettings::UserScope, "ZanzaSoft", "SAK");
1648 durationSpinBox = new QSpinBox;
1649 durationSpinBox->setSingleStep(1);
1650 durationSpinBox->setRange(1, 1440);
1651 durationSpinBox->setSuffix(" minutes");
1652 durationSpinBox->setValue(qMin(1440, qMax(1, settings.value("Ping interval", 15).toInt())));
1653 durationSpinBox->setCorrectionMode(QAbstractSpinBox::CorrectToNearestValue);
1655 bodyLabel = new QLabel(tr("Message:"));
1656 bodyEdit = new QTextEdit;
1657 bodyEdit->setPlainText( settings.value("Message", "<h1>Enter a message here!</h1>").toString() );
1659 messageLayout->addWidget(durationLabel, 1, 0);
1660 messageLayout->addWidget(durationSpinBox, 1, 1);
1661 messageLayout->addWidget(durationLabel1, 1, 2);
1662 messageLayout->addWidget(bodyLabel, 3, 0);
1663 messageLayout->addWidget(bodyEdit, 3, 1, 2, 4);
1664 messageLayout->setColumnStretch(3, 1);
1665 messageLayout->setRowStretch(4, 1);
1666 mainLayout->addLayout(messageLayout);
1667 tab2->setLayout(mainLayout);
1669 mainLayout = new QVBoxLayout;
1670 tab1->setLayout(mainLayout);
1671 // create tree view
1672 tasksTree = new QTreeWidget;
1673 tasksTree->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
1674 tasksTree->setColumnCount(3);
1675 tasksTree->setColumnWidth(0, 300);
1676 tasksTree->setColumnWidth(1, 65);
1677 tasksTree->setIconSize(QSize(32,32));
1678 connect(tasksTree, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(doubleClickedTask(QTreeWidgetItem*,int)));
1681 taskTextEditor = new QTextEdit;
1682 taskTextEditor->setFixedHeight(100);
1683 taskTextEditor->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
1684 connect(taskTextEditor, SIGNAL(textChanged()), this, SLOT(commitCurrentTask()));
1687 taskTitleEditor = new QLineEdit;
1688 taskTitleEditor->setFixedHeight(20);
1689 taskTitleEditor->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
1690 connect(taskTitleEditor, SIGNAL(editingFinished()), this, SLOT(commitCurrentTask()));
1692 taskPixmapViewer = new PixmapViewer;
1693 mainLayout->addWidget(tasksTree, 2);
1694 QHBoxLayout* detailsLayout = new QHBoxLayout;
1695 QVBoxLayout* editsLayout = new QVBoxLayout;
1696 detailsLayout->addWidget(taskPixmapViewer);
1697 connect(taskPixmapViewer, SIGNAL(changed()), this, SLOT(commitCurrentTask()));
1698 editsLayout->addWidget(taskTitleEditor);
1699 editsLayout->addWidget(taskTextEditor);
1700 QHBoxLayout* datesLayout = new QHBoxLayout;
1701 datesLayout->addWidget(new QLabel("Due: "));
1702 dueEditor = new QDateEdit;
1703 dueEditor->setMinimumDate(QDate(2000,1,1));
1704 datesLayout->addWidget(dueEditor);
1705 datesLayout->addWidget(new QLabel("Estimated: "));
1706 estimatedHoursEditor = new QSpinBox;
1707 estimatedHoursEditor->setRange(0, 1e5);
1708 estimatedHoursEditor->setSuffix("hours");
1709 datesLayout->addWidget(estimatedHoursEditor);
1710 editsLayout->addLayout(datesLayout);
1711 detailsLayout->addLayout(editsLayout);
1712 connect(dueEditor, SIGNAL(editingFinished()), this, SLOT(commitCurrentTask()));
1713 connect(estimatedHoursEditor,SIGNAL(editingFinished()), this, SLOT(commitCurrentTask()));
1715 QVBoxLayout* colorsLayout = new QVBoxLayout;
1716 bgColorButton = new QPushButton("bg\ncolor");
1717 bgColorButton->setToolTip("Background color");
1718 bgColorButton->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding));
1720 fgColorButton = new QPushButton("fg\ncolor");
1721 fgColorButton->setToolTip("Foreground color");
1722 fgColorButton->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding));
1724 #ifdef Q_WS_WIN
1725 // fix Windows XP "style"
1726 bgColorButton->setStyle(new QWindowsStyle());
1727 fgColorButton->setStyle(new QWindowsStyle());
1728 #endif
1730 colorsLayout->addWidget(bgColorButton);
1731 colorsLayout->addWidget(fgColorButton);
1732 detailsLayout->addLayout(colorsLayout);
1734 mainLayout->addLayout(detailsLayout);
1736 QVBoxLayout* tab4MainLayout = new QVBoxLayout(tab4);
1737 //taskSelector = new QComboBox;
1738 summaryList = newTaskSummaryList();
1739 QTabWidget* tabs = new QTabWidget;
1740 tabs->setTabPosition(QTabWidget::East);
1741 tabs->addTab(summaryList, "List");
1742 QGraphicsView* summaryView = new QGraphicsView;
1743 summaryView->setScene(new QGraphicsScene);
1744 TaskSummaryPieChart* chart = new TaskSummaryPieChart;
1745 summaryView->scene()->addItem(new TaskSummaryPieChart);
1746 summaryView->centerOn(chart);
1747 tabs->addTab(summaryView, "Chart");
1748 tab4MainLayout->addWidget(tabs);
1749 cal3 = new QCalendarWidget;
1750 cal3->setMinimumSize(QSize(250,200));
1751 cal4 = new QCalendarWidget;
1752 cal4->setMinimumSize(QSize(250,200));
1753 cal3->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
1754 cal4->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
1755 QHBoxLayout* calsLayout = new QHBoxLayout;
1756 calsLayout->addWidget(cal3);
1757 calsLayout->addWidget(cal4);
1758 cal4->setSelectedDate(QDate::currentDate().addDays(1));
1759 tab4MainLayout->addLayout(calsLayout);
1762 QVBoxLayout* tab3MainLayout = new QVBoxLayout(tab3);
1763 //taskSelector = new QComboBox;
1764 hitsList = newHitsList();
1765 connect(hitsList, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), this, SLOT(hitsSelectedInList(QTreeWidgetItem*,QTreeWidgetItem*)));
1766 hitsTimeline = new Timeline;
1767 connect(hitsTimeline, SIGNAL(hitSelected(HitItem*)), this, SLOT(hitsSelectedInTimeline(HitItem*)));
1768 cal1 = new QCalendarWidget;
1769 cal1->setMinimumSize(QSize(250,200));
1770 cal2 = new QCalendarWidget;
1771 cal2->setMinimumSize(QSize(250,200));
1772 cal1->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
1773 cal2->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
1774 //tab3MainLayout->addWidget(taskSelector);
1776 calsLayout = new QHBoxLayout;
1777 calsLayout->addWidget(cal1);
1778 calsLayout->addWidget(cal2);
1779 cal2->setSelectedDate(QDate::currentDate().addDays(1));
1780 tab3MainLayout->addWidget(hitsList);
1781 tab3MainLayout->addWidget(hitsTimeline);
1782 tab3MainLayout->addLayout(calsLayout);
1786 m_settings->setWindowTitle(tr("SaK"));
1787 m_settings->resize(400, 300);
1791 QTreeWidget* Sak::newHitsList()
1793 QTreeWidget* hitsList = new QTreeWidget;
1794 hitsList->setColumnCount(4);
1795 hitsList->setColumnWidth(0, 200);
1796 hitsList->setColumnWidth(1, 150);
1797 hitsList->setColumnWidth(2, 150);
1798 hitsList->setColumnWidth(3, 150);
1799 hitsList->setColumnWidth(4, 150);
1800 hitsList->setIconSize(QSize(24,24));
1801 hitsList->setSortingEnabled(true);
1802 hitsList->setItemDelegateForColumn(0, new MyDateItemDelegate);
1803 hitsList->setItemDelegateForColumn(1, new TaskItemDelegate(this));
1804 hitsList->setItemDelegateForColumn(2, new SubTaskItemDelegate(this));
1805 QTreeWidgetItem* header = new QTreeWidgetItem;
1806 header->setText(0, "Date/Time");
1807 header->setText(1, "Task");
1808 header->setText(2, "Subtask");
1809 header->setText(3, "Duration (min)");
1810 header->setText(4, "Overestimation");
1811 hitsList->setHeaderItem(header);
1812 hitsList->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
1813 return hitsList;
1816 QTreeWidget* Sak::newTaskSummaryList()
1818 QTreeWidget* taskSummaryList = new QTreeWidget;
1819 taskSummaryList->setColumnCount(4);
1820 taskSummaryList->setColumnWidth(0, 250);
1821 taskSummaryList->setColumnWidth(1, 150);
1822 taskSummaryList->setColumnWidth(1, 150);
1823 taskSummaryList->setIconSize(QSize(24,24));
1824 taskSummaryList->setSortingEnabled(true);
1825 QTreeWidgetItem* header = new QTreeWidgetItem;
1826 header->setText(0, "Task");
1827 header->setText(1, "Hours");
1828 taskSummaryList->setHeaderItem(header);
1829 taskSummaryList->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
1830 taskSummaryList->setEnabled(true);
1831 return taskSummaryList;
1834 void Sak::setVisible(bool visible)
1836 minimizeAction->setEnabled(visible);
1837 maximizeAction->setEnabled(!m_settings->isMaximized());
1838 restoreAction->setEnabled(m_settings->isMaximized() || !visible);
1839 m_settings->setVisible(visible);
1842 void Sak::createActions()
1844 minimizeAction = new QAction(tr("Mi&nimize"), m_settings);
1845 connect(minimizeAction, SIGNAL(triggered()), m_settings, SLOT(hide()));
1847 maximizeAction = new QAction(tr("Ma&ximize"), m_settings);
1848 connect(maximizeAction, SIGNAL(triggered()), m_settings, SLOT(showMaximized()));
1850 restoreAction = new QAction(tr("&Restore"), m_settings);
1851 connect(restoreAction, SIGNAL(triggered()), m_settings, SLOT(showNormal()));
1853 quitAction = new QAction(tr("&Quit"), m_settings);
1854 connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
1856 startAction = new QAction(tr("Start polling"), m_settings);
1857 connect(startAction, SIGNAL(triggered()), this, SLOT(start()));
1859 stopAction = new QAction(tr("Pause polling"), m_settings);
1860 connect(stopAction, SIGNAL(triggered()), this, SLOT(pause()));
1862 flushAction = new QAction(tr("&Flush data/settings to disk"), m_settings);
1863 connect(flushAction, SIGNAL(triggered()), this, SLOT(flush()));
1865 saveAsDbAction = new QAction(tr("Backup as"), m_settings);
1866 // connect(saveAsDbAction, SIGNAL(triggered()), this, SLOT(saveAsDb()));
1868 exportDbCsvAction = new QAction(tr("Export hits in CSV format"), m_settings);
1869 connect(exportDbCsvAction, SIGNAL(triggered()), this, SLOT(exportDbCsv()));
1871 #ifdef USEGMAIL
1872 saveToGmailAction = new QAction(tr("Store in your gmail account free space"), m_settings);
1873 connect(saveToGmailAction, SIGNAL(triggered()), this, SLOT(saveToGmail()));
1875 gmailLoginAction = new QAction(tr("Log in gmail"), m_settings);
1876 connect(gmailLoginAction, SIGNAL(triggered()), this, SLOT(logInGmail()));
1877 #else
1878 saveToGmailAction = NULL;
1879 gmailLoginAction = NULL;
1880 #endif
1882 openAction = new QAction(tr("Import a task from file"), m_settings);
1883 connect(openAction, SIGNAL(triggered()), this, SLOT(open()));
1885 m_addHitAction = new QAction("Add hit", m_settings);
1886 m_addHitMenu = new QMenu(m_settings);
1887 m_addHitAction->setText("Add hit");
1888 m_addHitMenu->addAction(m_addHitAction);
1889 connect(m_addHitAction, SIGNAL(triggered()), this, SLOT(addDefaultHit()));
1891 m_exportDataAction = new QAction("Export data", m_settings);
1892 m_exportDataAction->setText("Export data");
1893 m_addHitMenu->addAction(m_exportDataAction);
1894 connect(m_exportDataAction, SIGNAL(triggered()), this, SLOT(exportHits()));
1896 m_addTaskAction = new QAction("Add task", m_settings);
1897 m_addTaskMenu = new QMenu(m_settings);
1898 m_addTaskAction->setText("Add task");
1899 m_addTaskMenu->addAction(m_addTaskAction);
1900 connect(m_addTaskAction, SIGNAL(triggered()), this, SLOT(addDefaultTask()));
1903 void Sak::trayIconActivated(QSystemTrayIcon::ActivationReason reason)
1905 if (reason == QSystemTrayIcon::DoubleClick) {
1906 setVisible(true);
1910 //END Settings <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<