Call to QDesktopWidget breaks the systray icon (it will disappear on plasma-desktop...
[Sak.git] / sak.cpp
blob43224dfbf3878ac862daf05e86b8b10bbcbdd688
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_settings(0)
77 , m_changedHit(false)
78 , m_changedTask(false)
79 , m_subtaskView(false)
81 m_desktopRect = qApp->desktop()->rect();
83 m_subtaskCompleter = 0;
84 summaryList = hitsList = 0;
85 init();
87 if (QCoreApplication::arguments().contains("--clear")) {
88 QHash<QString, Task>::iterator itr = m_tasks.begin();
89 while(itr != m_tasks.end()) {
90 itr->hits.clear();
91 itr++;
95 if (m_tasks.count() <= 0)
96 m_settings->show();
98 m_previewing = false;
99 m_changedHit = false;
100 m_timerId = 0;
101 m_autoSaveTimer = startTimer(1000 * 60 * 45); // every 45 minutes
102 start();
104 // Need to go here, or after plasma reboot the icon will disappear
105 trayIconMenu = new QMenu(m_settings);
106 //trayIconMenu->addAction(minimizeAction);
107 //trayIconMenu->addAction(maximizeAction);
108 //trayIconMenu->addAction(restoreAction);
109 //trayIconMenu->addSeparator();
110 trayIconMenu->addAction(startAction);
111 trayIconMenu->addAction(stopAction);
112 trayIconMenu->addAction(flushAction);
113 trayIconMenu->addSeparator();
114 trayIconMenu->addAction(quitAction);
115 trayIcon = new QSystemTrayIcon(this);
116 trayIcon->setContextMenu(trayIconMenu);
117 trayIcon->setIcon( QIcon(":/images/icon.png") );
118 trayIcon->setToolTip( tr("Sistema Anti Kazzeggio") );
119 trayIcon->show();
120 connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(trayIconActivated(QSystemTrayIcon::ActivationReason)));
121 trayIcon->installEventFilter(this);
124 m_settings->setWindowIcon( QIcon(":/images/icon.png") );
125 m_settings->setWindowTitle("SaK - Sistema Anti Kazzeggio");
128 void Sak::init()
130 m_backupper = new Backupper;
131 m_incremental = new Incremental;
132 #ifdef USEGMAIL
133 m_gmail = new GmailPyInterface;
134 #else
135 m_gmail = NULL;
136 #endif
138 // load the data model
139 QSettings settings(QSettings::IniFormat, QSettings::UserScope, "ZanzaSoft", "SAK");
140 QByteArray tasksArray = settings.value("tasks").toByteArray();
141 QDataStream stream(&tasksArray, QIODevice::ReadWrite);
142 stream.setVersion(QDataStream::Qt_4_3);
145 { // read locastasks
146 QDir saveDir(QFileInfo(settings.fileName()).dir());
147 saveDir.mkdir("SakTasks");
148 saveDir.cd("SakTasks");
149 QStringList nameFilters;
150 nameFilters << "*.xml";
151 QStringList files = saveDir.entryList( nameFilters, QDir::Files);
152 foreach (QString taskXmlFileName, files) {
153 Task t( loadTaskFromFile(saveDir.filePath(taskXmlFileName)) );
154 m_tasks[t.title] = t;
159 // add subtasks, if missing
161 QHash<QString, Task>::iterator itr = m_tasks.begin();
162 while(itr != m_tasks.end()) {
163 itr->updateSubTasks();
164 itr++;
168 // reset awayTask
169 Task & awayTask = m_tasks["<away>"];
170 awayTask.title = "<away>";
171 awayTask.fgColor = Qt::gray;
172 awayTask.bgColor = Qt::white;
173 awayTask.icon = QPixmap(":/images/away.png");
175 m_editedTasks = m_tasks;
178 hitsTimeline = 0;
179 //merge piecies
180 interactiveMergeHits();
182 m_editedTasks = m_tasks;
184 setupSettingsWidget();
187 m_settings->installEventFilter(this);
188 hitsList->installEventFilter(this);
189 tasksTree->installEventFilter(this);
190 tasksTree->setUniformRowHeights(false);
191 QTreeWidgetItem* headerItem = new QTreeWidgetItem;
192 headerItem->setSizeHint(0 , QSize(0,0));
193 headerItem->setSizeHint(1 , QSize(0,0));
194 headerItem->setSizeHint(2 , QSize(0,0));
195 tasksTree->setHeaderItem(headerItem);
197 connect(bgColorButton, SIGNAL(clicked()), this, SLOT(selectColor()));
198 connect(fgColorButton, SIGNAL(clicked()), this, SLOT(selectColor()));
199 connect(previewButton, SIGNAL(clicked()), this, SLOT(popup()));
200 connect(tasksTree, SIGNAL(itemSelectionChanged()), this, SLOT(selectedTask()));
201 connect(tasksTree, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(selectedTask()));
202 populateTasks();
204 connect(cal1, SIGNAL(clicked(QDate)), this, SLOT(selectedStartDate(QDate)));
205 connect(cal2, SIGNAL(clicked(QDate)), this, SLOT(selectedEndDate(QDate)));
206 connect(cal3, SIGNAL(clicked(QDate)), this, SLOT(selectedStartDate(QDate)));
207 connect(cal4, SIGNAL(clicked(QDate)), this, SLOT(selectedEndDate(QDate)));
208 connect(hitsList, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(hitsListItemChanged(QTreeWidgetItem*,int)));
209 selectedTask();
211 m_view = new GView;
212 m_view->setScene(new QGraphicsScene);
213 m_view->scene()->setSceneRect(m_desktopRect);
215 m_view->installEventFilter(this);
216 m_view->setFrameStyle(QFrame::NoFrame);
217 m_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
218 m_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
219 m_view->setWindowFlags(m_view->windowFlags() | Qt::WindowStaysOnTopHint | Qt::ToolTip );
220 //m_view->setWindowModality(Qt::ApplicationModal);
221 m_view->setAttribute(Qt::WA_QuitOnClose, false);
222 // enable transparency with Qt4.5
223 m_view->setAttribute(Qt::WA_TranslucentBackground, true);
224 m_view->setWindowIcon( QIcon(":/images/icon.png") );
225 m_view->setWindowTitle("SaK - Sistema Anti Kazzeggio");
227 m_currentInterval = durationSpinBox->value();
228 m_currentInterval = qMax((int)1, qMin((int)1440, m_currentInterval));
229 qDebug() << "SAK: pinging interval " << m_currentInterval << Task::hours(m_currentInterval) << " hours ";
231 hitsTimeline->setPeriod(QDateTime(cal1->selectedDate()), QDateTime(cal2->selectedDate()));
232 populateHitsList(createHitsList(QDateTime(cal1->selectedDate()), QDateTime(cal2->selectedDate())));
235 void Sak::start()
237 m_currentInterval = qMax((int)1, m_currentInterval);
238 if (!m_timerId) {
239 int msecs = (int)(Task::hours(m_currentInterval)*3600.0*1000.0 / 2);
240 m_timerId = startTimer( msecs );
241 m_nextTimerEvent = QDateTime::currentDateTime().addMSecs(msecs);
242 startAction->setEnabled(false);
243 stopAction->setEnabled(true);
244 } else {
245 startAction->setEnabled(true);
246 stopAction->setEnabled(false);
250 void Sak::stop()
252 if (m_timerId) {
253 killTimer(m_timerId);
254 m_timerId = 0;
255 stopAction->setEnabled(false);
256 startAction->setEnabled(true);
257 } else {
259 stopAction->setEnabled(true);
260 startAction->setEnabled(false);
264 Task Sak::loadTaskFromFile(const QString& filePath)
266 QFile taskXmlFile(filePath);
267 Task t;
268 qDebug() << "Examine task file " << taskXmlFile.fileName();
269 if (!taskXmlFile.open(QIODevice::ReadOnly)) {
270 qDebug() << "Failed opening xml file " << taskXmlFile.fileName();
272 QByteArray data = taskXmlFile.readLine();
273 QXmlStreamReader stream(data);
274 QXmlStreamReader::TokenType token = stream.readNext(); // skip StartDocument
275 token = stream.readNext();
276 if ( token != QXmlStreamReader::Comment) {
277 qDebug() << "Skip file " << taskXmlFile.fileName() << " (want a file starting with a comment representing MD5, got" << token << ")";
278 return t;
280 QString md5 = stream.text().toString().trimmed();
281 qDebug() << "md5 = " << md5;
283 // check md5
284 data = taskXmlFile.readAll();
285 if ( md5 != QCryptographicHash::hash(data, QCryptographicHash::Md5).toHex() ) {
286 if (QMessageBox::No == QMessageBox::warning(0, "Corrupted file!",
287 QString("Check of file " + taskXmlFile.fileName() + " failed (maybe it has been edited by hand).\nDo you want to load it anyway?" )
288 ,QMessageBox::Yes | QMessageBox::No) ) {
289 qDebug() << "Skip file " << taskXmlFile.fileName() << " (bad md5 sum)";
290 return t;
294 // read rest of data
295 stream.clear();
296 stream.addData(data);
298 if ( stream.readNext() != QXmlStreamReader::StartDocument) {
299 qDebug() << "Skip file " << taskXmlFile.fileName() << " (want start document)";
300 return t;
302 stream >> t;
303 if (stream.error() != QXmlStreamReader::NoError) {
304 qDebug() << "Error reading task data from file " << taskXmlFile.fileName() << ":" << stream.errorString();
305 return Task();
307 // QFile tmp("/tmp/" + t.title + ".xml");
308 // tmp.open(QIODevice::ReadWrite);
309 // QXmlStreamWriter ss(&tmp);
310 // ss.setAutoFormatting(true);
311 // ss.setAutoFormattingIndent(2);
312 // ss.writeStartDocument();
313 // ss << t;
314 // ss.writeEndDocument();
315 // tmp.close();
316 else
317 return t;
320 void Sak::flush()
322 if (m_changedTask)
323 saveTaskChanges();
324 if (m_changedHit)
325 saveHitChanges();
327 if (!m_settings) return;
328 m_backupper->doCyclicBackup();
329 QSettings settings(QSettings::IniFormat, QSettings::UserScope, "ZanzaSoft", "SAK");
330 // QByteArray tasksArray;
331 // QDataStream stream(&tasksArray, QIODevice::ReadWrite);
332 // stream.setVersion(QDataStream::Qt_4_0);
333 // stream << m_tasks;
334 // settings.setValue("tasks", tasksArray);
335 settings.setValue("Ping interval", durationSpinBox->value());
336 settings.setValue("Message", bodyEdit->toPlainText());
337 settings.sync();
339 QDir saveDir(QFileInfo(settings.fileName()).dir());
340 saveDir.mkdir("SakTasks");
341 saveDir.cd("SakTasks");
343 foreach(Task t, m_tasks) {
344 if (t.title.isEmpty()) continue;
345 QFile xmlTaskSave(saveDir.filePath(t.title + ".xml"));
346 QByteArray taskArray;
347 QXmlStreamWriter stream(&taskArray);
348 stream.setAutoFormatting(true);
349 stream.setAutoFormattingIndent(2);
350 stream.writeStartDocument();
351 stream << t;
352 stream.writeEndDocument();
353 xmlTaskSave.open(QIODevice::ReadWrite | QIODevice::Truncate);
354 qDebug() << "Saving xml to file " << xmlTaskSave.fileName();
355 QByteArray hash;
356 hash.append("<!-- ");
357 hash.append( QCryptographicHash::hash(taskArray, QCryptographicHash::Md5).toHex() );
358 hash.append(" -->\n");
359 xmlTaskSave.write(hash);
360 xmlTaskSave.write(taskArray);
361 xmlTaskSave.close();
364 // remove files not matching a task
365 QStringList nameFilters;
366 nameFilters << "*.xml";
367 QStringList files = saveDir.entryList( nameFilters, QDir::Files);
368 foreach (QString taskXmlFileName, files) {
369 if (!m_tasks.contains(QFileInfo(taskXmlFileName).baseName())) {
370 qWarning()<< "Remove task " << QFileInfo(taskXmlFileName).baseName() << " from disk";
371 QFile(saveDir.filePath(taskXmlFileName)).remove();
376 m_incremental->clearAddedPieces();
379 //void Sak::saveAsDb()
381 // if (!m_settings) return;
382 // QString fileName = QFileDialog::getSaveFileName();
383 // QFile file(fileName);
384 // file.remove();
385 // flush();
386 // QSettings settingsQSettings::IniFormat, QSettings::UserScope, ("ZanzaSoft", "SAK");
387 // QFile file1(settings.fileName());
388 // if (!file1.copy(fileName)) {
389 // qWarning() << "Error copying " << settings.fileName() << " to " << fileName << file1.errorString();
390 // }
393 void Sak::exportDbCsv()
395 if (!m_settings) return;
396 QString fileName = QFileDialog::getSaveFileName();
397 QFile file(fileName);
398 if (!file.open(QIODevice::ReadWrite|QIODevice::Truncate)) {
399 QMessageBox::warning(0, "Error saving", QString("Error saving to file %1").arg(fileName));
400 return;
402 QTextStream stream(&file);
403 foreach(const Task& t, m_tasks) {
404 QHash< QString, QList< Task::Hit > >::const_iterator itr = t.hits.begin();
405 while(itr != t.hits.end()) {
406 QList< Task::Hit >::const_iterator hitr = itr.value().begin(), hend = itr.value().end();
407 while(hitr != hend) {
408 stream << t.title << ";" << itr.key() << ";" << hitr->timestamp.toString(DATETIMEFORMAT) << ";" << hitr->duration << ";\n";
409 hitr++;
411 itr++;
414 file.close();
418 void Sak::logInGmail()
420 m_gmail->forceLogin();
423 void Sak::saveToGmail()
425 if (!m_settings) return;
426 flush();
427 QSettings settings(QSettings::IniFormat, QSettings::UserScope, "ZanzaSoft", "SAK");
429 QDir saveDir(QFileInfo(settings.fileName()).dir());
430 saveDir.mkdir("SakTasks");
431 saveDir.cd("SakTasks");
432 QStringList nameFilters;
433 nameFilters << "*.xml";
434 QStringList files = saveDir.entryList( nameFilters, QDir::Files);
435 QStringList filePaths;
436 foreach (QString taskXmlFileName, files) {
437 filePaths << saveDir.filePath(taskXmlFileName);
439 m_gmail->storeTaskFiles(filePaths);
442 void Sak::importFromGmail()
444 QStringList filePaths = m_gmail->fetchLatestTasks();
447 void Sak::open(const QStringList& _fileNames)
449 QStringList fileNames = _fileNames.size()?_fileNames:QFileDialog::getOpenFileNames(0, "Open a new task", QString(), "*.xml" );
450 foreach(QString fileName, fileNames) {
451 QFile file(fileName);
452 if (!file.exists()) {
453 QMessageBox::warning(0, "Cannot find task", QString("Cannot find task file %1").arg(fileName));
456 QSettings settings(QSettings::IniFormat, QSettings::UserScope, "ZanzaSoft", "SAK");
457 QDir saveDir(QFileInfo(settings.fileName()).dir());
458 saveDir.mkdir("SakTasks");
459 saveDir.cd("SakTasks");
461 if ( QFile(saveDir.filePath(QFileInfo(fileName).completeBaseName())).exists() ) {
462 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?");
463 QPushButton* overwriteButton = mbox.addButton("Overwrite", QMessageBox::YesRole);
464 QPushButton* mergeButton = mbox.addButton("Merge", QMessageBox::NoRole);
465 QPushButton* cancelButton = mbox.addButton("Cancel", QMessageBox::RejectRole);
466 mbox.setDefaultButton(cancelButton);
467 mbox.exec();
468 QAbstractButton* b = mbox.clickedButton();
469 if (b == cancelButton) { continue; }
470 else {
471 m_backupper->doCyclicBackup();
472 if (b == mergeButton) {
473 Task t = loadTaskFromFile(file.fileName());
474 QHash< QString, QList< Task::Hit > > ::const_iterator itr = t.hits.begin(), end = t.hits.end();
475 while(itr != end) {
476 QString subtask = itr.key();
477 foreach(Task::Hit hit, itr.value())
478 m_incremental->addPiece(t.title, subtask, hit.timestamp, hit.duration);
479 itr++;
481 interactiveMergeHits();
482 } else if (b == overwriteButton) {
483 file.copy(saveDir.filePath(QFileInfo(fileName).completeBaseName()));
489 if (!fileNames.isEmpty()) {
490 m_settings->hide();
491 destroy();
492 init();
493 m_settings->show();
494 start();
498 void Sak::destroy()
500 stop();
501 if (!m_settings) return;
502 flush();
503 m_settings->deleteLater();
504 m_view->scene()->deleteLater();
505 m_view->deleteLater();
506 delete m_backupper;
507 delete m_incremental;
508 delete m_gmail;
509 m_previewing = false;
510 m_changedHit = false;
511 m_timerId = 0;
515 Sak::~Sak()
517 killTimer(m_autoSaveTimer);
518 destroy();
522 void Sak::layoutSubTasks( const QMap<int, SakSubWidget*> sortedWidgets, int currentRank) {
523 QMap<int, SakSubWidget*>::const_iterator itr = sortedWidgets.begin(), end = sortedWidgets.end();
524 QRect r = m_desktopRect;
525 for(int i=0; itr != end; i++, itr++) {
526 int h = (*itr)->size().height();
527 int w = (*itr)->size().width();
528 (*itr)->setPos(QPointF((r.width() - w)/2, (r.height()-h)/2 + (i - currentRank - 1) * (h+2)));
533 int Sak::taskCounter = 0;
535 bool Sak::eventFilter(QObject* obj, QEvent* e)
537 // if (obj == m_view) {
538 // qDebug() << "event : " << e->type();
539 // }
540 if (obj == tasksTree) {
541 return taskTreeEventFilter(e);
542 } else if (obj == hitsList || obj == summaryList) {
543 return hitsListEventFilter(e);
544 } else if (obj == m_settings && e->type() == QEvent::Close) {
545 if (m_changedTask)
546 saveTaskChanges();
547 if (m_changedHit)
548 saveHitChanges();
549 if (trayIcon->isVisible()) {
550 m_settings->hide();
551 e->ignore();
552 return true;
554 } else if (obj == m_view && e->type() == QEvent::Wheel) {
555 QWheelEvent* we = dynamic_cast<QWheelEvent*>(e);
556 if (m_subtaskView) {
557 scrollSubTasks(we->delta() / 120);
558 } else scrollTasks(we->delta() / 120);
559 } else if (obj == m_view && e->type() == QEvent::KeyPress) {
560 QKeyEvent* ke = dynamic_cast<QKeyEvent*>(e);
561 if ((ke->modifiers() & Qt::AltModifier) && (ke->modifiers() & Qt::ControlModifier) ) {
562 clearView();
563 return true;
564 } else if ( ((ke->modifiers() & Qt::ControlModifier) && (ke->key() == Qt::Key_Backspace) )
565 || ((ke->modifiers() & Qt::ControlModifier) && (ke->key() == Qt::Key_Left) )) {
566 if (m_subtaskView) {
567 popup();
568 return true;
570 } else if (m_subtaskView && ke->key() == Qt::Key_Up) {
571 scrollSubTasks(-1);
572 return true;
573 } else if (m_subtaskView && ke->key() == Qt::Key_Down) {
574 scrollSubTasks(+1);
575 return true;
576 } else if (!m_subtaskView && ke->key() == Qt::Key_Left) {
577 scrollTasks(-1);
578 return true;
579 } else if (!m_subtaskView && ke->key() == Qt::Key_Right) {
580 scrollTasks(+1);
581 return true;
582 } else if (!m_subtaskView && ke->key() == Qt::Key_Escape) {
583 clearView();
584 return true;
585 } else { // forward events to current widget
586 if (!m_subtaskView) {
587 if (m_widgetsIterator == m_widgets.end()) return false;
588 SakWidget* currentShowing = m_widgetsIterator.value();
589 currentShowing->keyPressEvent(ke);
590 return true;
591 } else {
592 // autoscroll on text completion!!!
593 if (m_subwidgetsIterator == m_subwidgets.end()) return false;
594 SakSubWidget* currentShowing = m_subwidgetsIterator.value();
595 currentShowing->keyPressEvent(ke);
597 if (m_subWidgetRank != 0 && m_subtaskCompleter) {
598 QString completion(m_subtaskCompleter->completionPrefix());
599 if (ke->text().size() == 1) {
600 if (ke->key() == Qt::Key_Backslash || ke->key() == Qt::Key_Backspace)
601 completion.chop(1);
602 else completion += ke->text();
603 m_subtaskCompleter->setCompletionPrefix(completion);
606 QStringList list( ((QStringListModel*)m_subtaskCompleter->model())->stringList() );
607 int newRank = 1 + ((QStringListModel*)m_subtaskCompleter->model())->stringList().indexOf(m_subtaskCompleter->currentIndex().row() >= 0 && completion.size() ? m_subtaskCompleter->currentCompletion() : completion);
609 if (m_subWidgetRank != newRank) {
610 scrollSubTasks(newRank - m_subWidgetRank);
611 if (newRank == 0) {
612 QLineEdit* editor = dynamic_cast<QLineEdit*>((*m_subwidgets.begin())->widget());
613 if (editor) {
614 editor->setText(completion);
619 } else if (m_subtaskCompleter) {
620 QLineEdit* editor = dynamic_cast<QLineEdit*>((*m_subwidgets.begin())->widget());
621 if (editor) {
622 m_subtaskCompleter->setCompletionPrefix(editor->text());
626 return true;
629 } else if (obj == m_view && e->type() == QEvent::Show) {
630 grabKeyboard();
631 QTimer::singleShot(500, this, SLOT(grabKeyboard()));
632 } else if (obj == m_view && e->type() == QEvent::Close) {
633 if (trayIcon->isVisible()) {
634 return true;
636 } else if (obj && obj == trayIcon && e->type() == QEvent::ToolTip) {
637 QDateTime last = m_incremental->lastTimeStamp;
638 int seconds = QDateTime::currentDateTime().secsTo(m_nextTimerEvent);
639 int hours = seconds / 3600;
640 int minutes = (seconds / 60) % 60;
641 seconds %= 60;
642 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>")))));
643 return false;
645 return false;
648 //END basic >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
651 //BEGIN Tasks >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
652 void Sak::addDefaultTask()
654 QString tentativeName;
655 do {
656 tentativeName = QString("Task %1").arg(taskCounter++);
657 } while(m_editedTasks.contains(tentativeName));
659 Task& t = m_editedTasks[tentativeName];
660 t.title = tentativeName;
661 QTreeWidgetItem* item = new QTreeWidgetItem(QStringList(tentativeName));
662 item->setData(0,Qt::UserRole, QVariant(QMetaType::VoidStar, &t));
663 tasksTree->addTopLevelItem(item);
664 m_changedTask=true;
667 void Sak::populateTasks()
669 tasksTree->clear();
671 QHash<QString, Task>::iterator itr = m_editedTasks.begin(), end=m_editedTasks.end();
672 for(; itr!=end; itr++) {
673 Task& t(itr.value());
674 t.checkConsistency();
676 if (t.title.isEmpty() || t.title == "<away>") continue; // skip away task
677 QTreeWidgetItem* item = new QTreeWidgetItem(QStringList(t.title));
678 item->setData(0, Qt::UserRole, QVariant(QMetaType::VoidStar, &t));
679 QIcon icon;
680 icon.addPixmap(t.icon);
681 item->setSizeHint(0, QSize(32,32));
682 item->setIcon(0, icon);
683 for(int i=0; i<3; i++) {
684 item->setForeground(i,t.fgColor);
685 item->setBackground(i,t.bgColor);
687 //item->setCheckState(1, t.active ? Qt::Checked : Qt::Unchecked);
688 //item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
689 item->setIcon(1,QIcon(t.active ? ":/images/active.png" : ":/images/inactive.png"));
690 item->setText(2,QString("%1 hours worked till now (overestimated %2)").arg(t.totHours, 4, 'f', 2, ' ').arg(t.totOverestimation));
691 foreach(Task::SubTask st, t.subTasks) {
692 if (!st.title.isEmpty()) {
693 QTreeWidgetItem* sitem = new QTreeWidgetItem(item, QStringList(st.title));
694 item->setData(0, Qt::UserRole, QVariant(QMetaType::VoidStar, &st));
695 sitem->setSizeHint(0, QSize(32,32));
696 QColor fgColor = st.fgColor.isValid() ? st.fgColor : t.fgColor;
697 QColor bgColor = st.bgColor.isValid() ? st.bgColor : t.bgColor;
698 for(int i=0; i<3; i++) {
699 sitem->setForeground(i,fgColor);
700 sitem->setBackground(i,bgColor);
702 sitem->setIcon(1,QIcon(st.active ? ":/images/active.png" : ":/images/inactive.png"));
703 sitem->setText(2,QString("%1 hours worked till now").arg(st.totHours,4,'f',2,' '));
706 tasksTree->addTopLevelItem(item);
710 void Sak::saveTaskChanges()
712 if (m_changedTask) {
713 commitCurrentTask();
714 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 ) {
715 m_tasks = m_editedTasks;
716 } else m_editedTasks = m_tasks; //. undo changes
717 m_changedTask=false;
718 selectedStartDate(QDate());
719 populateTasks();
723 void Sak::selectColor() {
724 if (tasksTree->selectedItems().isEmpty()) return;
726 if (sender() == fgColorButton) {
727 QColor c = QColorDialog::getColor(fgColorButton->palette().color(QPalette::ButtonText));
728 if (!c.isValid()) return;
729 QPalette p = fgColorButton->palette();
730 p.setColor(QPalette::ButtonText, c);
731 fgColorButton->setPalette(p);
732 bgColorButton->setPalette(p);
733 } else if (sender() == bgColorButton) {
734 QColor c = QColorDialog::getColor(bgColorButton->palette().color(QPalette::Button));
735 if (!c.isValid()) return;
736 QPalette p = bgColorButton->palette();
737 p.setColor(QPalette::Button, c);
738 fgColorButton->setPalette(p);
739 bgColorButton->setPalette(p);
741 commitCurrentTask();
744 bool Sak::taskTreeEventFilter(QEvent* e)
746 if (e->type() == QEvent::ContextMenu) {
747 QContextMenuEvent* me = dynamic_cast<QContextMenuEvent*>(e);
748 if (!me) return false;
749 m_addTaskMenu->popup(me->globalPos());
750 return true;
751 } else if (e->type() == QEvent::KeyRelease) {
752 QKeyEvent* ke = dynamic_cast<QKeyEvent*>(e);
753 if (!ke) return false;
754 if ( (ke->key() != Qt::Key_Delete && ke->key() != Qt::Key_Backspace) ) return false;
755 if (currentSubtask!="") {
756 QMessageBox whatToDo(QMessageBox::Warning, "Deleting subtask", "Deleting subtask " + currentSubtask + " of task " + currentTask);
757 QPushButton* moveHitsToParentButton = whatToDo.addButton("Move hits to task " + currentTask, QMessageBox::AcceptRole);
758 QPushButton* removeHitsButton = whatToDo.addButton("Remove hits", QMessageBox::AcceptRole);
759 QPushButton* cancelButton = whatToDo.addButton("Cancel", QMessageBox::RejectRole);
760 whatToDo.setDefaultButton(cancelButton);
761 whatToDo.exec();
762 if ( whatToDo.clickedButton() == cancelButton) return true;
763 if (m_editedTasks.find(currentTask) == m_editedTasks.end()) return true;
764 m_changedTask=true;
765 Task& t(m_editedTasks[currentTask]);
766 t.subTasks.take(currentSubtask);
767 if (whatToDo.clickedButton() == removeHitsButton) {
768 t.hits.take(currentSubtask);
769 } else if (whatToDo.clickedButton() == moveHitsToParentButton) {
770 QList<Task::Hit> sorter(t.hits.take(""));
771 sorter << t.hits.take(currentSubtask);
772 qStableSort(sorter.begin(), sorter.end());
773 t.hits[""] = sorter;
775 } else {
776 // remove file from disk
777 m_changedTask=true;
778 m_editedTasks.remove(currentTask);
780 tasksTree->clear();
781 populateTasks();
782 selectedStartDate(QDate());
783 return true;
784 } else if (e->type() == QEvent::Hide) {
785 saveTaskChanges();
787 return false;
791 void Sak::commitCurrentTask()
793 m_changedTask=true;
794 if (currentSubtask.isEmpty()) {
795 QString currentTitle = taskTitleEditor->text();
796 if (!currentTitle.isEmpty()) {
797 if (currentTitle != currentTask) {
798 if (m_editedTasks.contains(currentTitle)) {
799 QMessageBox::warning(0, "Conflict in task names", "Conflict in task names: current task " + currentTask + ", edited title " + currentTitle);
800 taskTitleEditor->setText(currentTask);
801 return;
802 } else if (m_editedTasks.contains(currentTask)) {
803 m_editedTasks[currentTitle] = m_editedTasks.take(currentTask);
804 m_editedTasks[currentTitle].title = currentTitle;
807 } else return;
808 Task& t = m_editedTasks[currentTitle];
809 t.bgColor = bgColorButton->palette().color(QPalette::Button);
810 t.fgColor = fgColorButton->palette().color(QPalette::ButtonText);
811 t.icon = taskPixmapViewer->pixmap();
812 QList<QTreeWidgetItem *> items = tasksTree->findItems(currentTask,Qt::MatchExactly,0);
813 foreach(QTreeWidgetItem* ii, items) {
814 ii->setText(0, currentTitle);
815 ii->setIcon(0, taskPixmapViewer->pixmap());
816 for (int i=0; i<3; i++) {
817 ii->setForeground(i, QColor(t.fgColor));
818 ii->setBackground(i, QColor(t.bgColor));
821 if (dueEditor->date() != dueEditor->minimumDate())
822 t.dueDate = dueEditor->date();
823 t.estimatedHours = estimatedHoursEditor->value();
824 currentTask=currentTitle;
826 if (tasksTree->selectedItems().size() != 1) return;
827 QTreeWidgetItem* item = tasksTree->selectedItems()[0];
828 item->setText(0, taskTitleEditor->text());
829 QIcon icon;
830 icon.addPixmap(t.icon);
831 item->setSizeHint(0, QSize(32,32));
832 item->setIcon(0, icon);
833 for(int i=0; i<3; i++) {
834 item->setForeground(i,t.fgColor);
835 item->setBackground(i,t.bgColor);
839 } else { // subtask edited
840 if (!m_editedTasks.contains(currentTask)) return;
841 Task& t(m_editedTasks[currentTask]);
842 QString currentTitle = taskTitleEditor->text();
843 // backup data
844 if (!currentTitle.isEmpty()) {
845 if (currentTitle != currentSubtask) {
846 if (t.subTasks.contains(currentTitle)) {
847 QMessageBox::warning(0, "Conflict in subtask names", "Conflict in subtask names");
848 taskTitleEditor->setText(currentSubtask);
849 return;
850 } else if (t.subTasks.contains(currentSubtask)) {
851 t.subTasks[currentTitle] = t.subTasks.take(currentSubtask);
852 t.subTasks[currentTitle].title = currentTitle;
853 t.hits[currentTitle] = t.hits.take(currentSubtask);
856 } else return;
857 Task::SubTask& st = t.subTasks[currentTitle];
858 st.bgColor = bgColorButton->palette().color(QPalette::Button);
859 st.fgColor = fgColorButton->palette().color(QPalette::ButtonText);
860 QList<QTreeWidgetItem *> items = tasksTree->findItems(currentTask,Qt::MatchExactly,0);
861 foreach(QTreeWidgetItem* jj, items) {
862 for(int i=0; i<jj->childCount(); i++) {
863 QTreeWidgetItem* ii = jj->child(i);
864 if (ii->text(0) != currentSubtask) continue;
865 ii->setText(0, currentTitle);
866 for (int i=0; i<3; i++) {
867 ii->setForeground(i, QColor(st.fgColor));
868 ii->setBackground(i, QColor(st.bgColor));
872 currentSubtask = currentTitle;
874 if (tasksTree->selectedItems().size() != 1) return;
875 QTreeWidgetItem* item = tasksTree->selectedItems()[0];
876 item->setText(0, taskTitleEditor->text());
877 QIcon icon;
878 icon.addPixmap(t.icon);
879 item->setSizeHint(0, QSize(32,32));
880 item->setIcon(0, icon);
881 for(int i=0; i<3; i++) {
882 item->setForeground(i,st.fgColor);
883 item->setBackground(i,st.bgColor);
888 void Sak::selectedTask()
890 if (tasksTree->selectedItems().isEmpty()) {
891 taskPixmapViewer->setEnabled(false);
892 taskPixmapViewer->setPixmap(QPixmap());
893 taskTextEditor->setEnabled(false);
894 taskTitleEditor->setEnabled(false);
895 bgColorButton->setEnabled(false);
896 fgColorButton->setEnabled(false);
897 dueEditor->setEnabled(false);
898 estimatedHoursEditor->setEnabled(false);
899 return;
902 QTreeWidgetItem* selectedItem = tasksTree->selectedItems().first();
903 QTreeWidgetItem* parentItem = selectedItem->parent();
904 QString tt = selectedItem->text(0);
906 if (!parentItem) {
907 taskPixmapViewer->setEnabled(true);
908 dueEditor->setEnabled(true);
909 estimatedHoursEditor->setEnabled(true);
910 } else {
911 taskPixmapViewer->setEnabled(false);
912 taskPixmapViewer->setPixmap(QPixmap());
913 dueEditor->setEnabled(false);
914 estimatedHoursEditor->setEnabled(false);
916 taskTextEditor->setEnabled(true);
917 taskTitleEditor->setEnabled(true);
918 bgColorButton->setEnabled(true);
919 fgColorButton->setEnabled(true);
922 if (!parentItem) { // editing a task
923 if (!m_editedTasks.contains(tt)) return;
924 const Task& t = m_editedTasks[tt];
925 taskPixmapViewer->setPixmap(t.icon);
926 taskTextEditor->blockSignals(true);
927 taskTextEditor->setPlainText(t.description);
928 taskTextEditor->blockSignals(false);
929 taskTitleEditor->setText(t.title);
930 QPalette p;
931 p.setColor(QPalette::Button, t.bgColor);
932 p.setColor(QPalette::ButtonText, t.fgColor);
933 bgColorButton->setPalette(p);
934 fgColorButton->setPalette(p);
935 estimatedHoursEditor->setValue(t.estimatedHours);
936 dueEditor->setDate(t.dueDate.isValid() ? t.dueDate : dueEditor->minimumDate());
938 currentTask = t.title;
939 currentSubtask = "";
940 } else {
941 if (!m_editedTasks.contains(parentItem->text(0))) return;
942 const Task& t = m_editedTasks[parentItem->text(0)];
943 if (!t.subTasks.contains(tt)) return;
944 const Task::SubTask& st = t.subTasks[tt];
945 taskTextEditor->setPlainText(st.description);
946 taskTitleEditor->setText(st.title);
947 QPalette p;
948 p.setColor(QPalette::Button, st.bgColor.isValid() ? st.bgColor : t.bgColor);
949 p.setColor(QPalette::ButtonText, st.fgColor.isValid() ? st.fgColor : t.fgColor);
950 bgColorButton->setPalette(p);
951 fgColorButton->setPalette(p);
953 currentTask = t.title;
954 currentSubtask = st.title;
958 void Sak::doubleClickedTask(QTreeWidgetItem* i, int column)
960 if (column == 1) {
961 m_changedTask=true;
962 if (i->parent() == 0) {
963 QHash<QString, Task>::iterator itr = m_editedTasks.find(i->text(0));
964 Q_ASSERT(itr != m_editedTasks.end());
965 bool& active ( itr.value().active );
966 active = !active;
967 i->setIcon(column, active ? QIcon(":/images/active.png") : QIcon(":/images/inactive.png"));
968 } else {
969 QHash<QString, Task>::iterator itr = m_editedTasks.find(i->parent()->text(0));
970 Q_ASSERT(itr != m_editedTasks.end());
971 bool& active ( itr.value().subTasks[i->text(0)].active );
972 active = !active;
973 i->setIcon(column, active ? QIcon(":/images/active.png") : QIcon(":/images/inactive.png"));
975 ((QTreeWidget*)sender())->update();
981 void Sak::timerEvent(QTimerEvent* e)
983 if (e->timerId() == m_timerId) {
984 if (!m_view->isVisible() && !m_settings->isVisible() && m_tasks.count() > 0) {
985 popup();
986 // close timer
987 killTimer(m_timeoutPopup);
988 m_timeoutPopup = startTimer((int)(qMax( 30000.0, Task::hours(m_currentInterval)*3600.0*1000.0/10.0))); // 5 secmin
989 // restart timer
990 killTimer(m_timerId);
991 int msecs = (int)(Task::hours(m_currentInterval)*3600.0*1000.0);
992 m_timerId = startTimer(msecs);
993 m_nextTimerEvent = QDateTime::currentDateTime().addMSecs(msecs);
994 } else {
995 if (m_settings && m_settings->isVisible() && !m_settings->isActiveWindow()) {
996 trayIcon->showMessage("Delayed check point", "Delayed check point due to open settings. Close the setting window!", QSystemTrayIcon::Warning, -1);
997 m_settings->close();
999 qDebug() << "SAK: wait 5 seconds";
1000 killTimer(m_timerId);
1001 m_timerId = startTimer(5000);
1002 m_nextTimerEvent = QDateTime::currentDateTime().addMSecs(5000);
1004 } else if (e->timerId() == m_timeoutPopup) {
1005 // ensure the timer is resetted
1006 killTimer(m_timeoutPopup);
1008 if (!m_subtaskView) {
1009 // if not selecting subtasks clear everything and signal away
1010 workingOnTask("<away>","");
1011 trayIcon->showMessage("New away events", "You have missed a check point. Fix it in the detailed hit list.", QSystemTrayIcon::Information, 999999);
1012 clearView();
1014 } else if (e->timerId() == m_autoSaveTimer) {
1015 flush();
1016 } else {
1017 qDebug() << "unknown timer event";
1021 void Sak::clearView()
1023 killTimer(m_timeoutPopup);
1024 m_subtaskView=false;
1025 m_marker = 0;
1026 delete m_subtaskCompleter; m_subtaskCompleter = 0;
1028 if (!m_view) return;
1029 QGraphicsScene* s = m_view->scene();
1030 QList<QGraphicsItem*> items = m_view->items();
1031 s->deleteLater();
1032 m_view->close();
1033 m_view->setScene(new QGraphicsScene);
1034 m_view->scene()->setSceneRect(m_desktopRect);
1035 m_previewing = false;
1036 m_view->releaseKeyboard();
1038 #if defined(Q_WS_X11)
1039 // restore focus to previous application
1040 grabbed=false;
1041 X11::XSetInputFocus((X11::Display*)QX11Info::display(), X11::CurrentFocusWindow, X11::CurrentRevertToReturn, CurrentTime);
1042 X11::XFlush((X11::Display*)QX11Info::display());
1043 #endif
1046 void Sak::workingOnTask(const QString& taskName, const QString& subTask)
1048 if (!m_previewing) {
1050 qDebug() << "Working on " << taskName ;
1051 if (m_tasks.contains(taskName)) {
1052 Task& t = m_tasks[taskName];
1054 if (t.title != "<away>" && !t.title.isEmpty()) {
1055 // update history
1056 int historyIndex = m_taskSelectionHistory.indexOf(t.title);
1057 if (historyIndex != -1) {
1058 m_taskSelectionHistory.takeAt(historyIndex);
1060 m_taskSelectionHistory.push_back(t.title);
1062 QList<QString> & subtaskSelectionHistory(m_subtaskSelectionHistory[t.title]);
1063 historyIndex = subtaskSelectionHistory.indexOf(subTask);
1064 if (historyIndex != -1) {
1065 subtaskSelectionHistory.takeAt(historyIndex);
1067 subtaskSelectionHistory.push_back(subTask);
1071 QDateTime now = QDateTime::currentDateTime();
1072 QHash<QString, Task>::iterator itr = m_tasks.begin();
1074 QHash<QString, QList< Task::Hit> >::iterator hitr = t.hits.begin();
1075 // merge last two hits of every subtask if they are close enough
1076 while(hitr != t.hits.end()) {
1077 QList<Task::Hit>& otHits( *hitr );
1078 if (otHits.count() > 1) {
1079 Task::Hit& lastHit(otHits[otHits.size() - 1]);
1080 Task::Hit& beforeLastHit(otHits[otHits.size() - 2]);
1082 int diff = (lastHit.timestamp.toTime_t() - 30*lastHit.duration) - (beforeLastHit.timestamp.toTime_t() + 30*beforeLastHit.duration);
1083 if (diff < 120) { // at most 2 minutes apart
1084 beforeLastHit.timestamp = beforeLastHit.timestamp.addSecs(-30*beforeLastHit.duration);
1085 int secsToEnd = beforeLastHit.timestamp.secsTo(lastHit.timestamp.addSecs(30*m_currentInterval));
1086 if (secsToEnd > 24 * 3600 * 3600) {
1087 qWarning() << "TRAPPED ERROR IN SECS COUNT!!!!!!!!";
1088 qWarning() << "BEFORE LAST HIST WAS " << beforeLastHit.timestamp << beforeLastHit.duration;
1089 qWarning() << "LAST HIT WAS " << lastHit.timestamp << lastHit.duration;
1090 assert(secsToEnd < 24 * 3600 * 3600);
1092 beforeLastHit.timestamp = beforeLastHit.timestamp.addSecs( secsToEnd/2.0 );
1093 beforeLastHit.duration = (int)( round( secsToEnd / 60.0 ) );
1094 // remove the current very last hit
1095 otHits.pop_back();
1098 hitr++;
1101 // add hit to hit list
1102 // NOTE: we do not try to merge the very last hit with previous ones because we want let
1103 // the user being able to easily recover from a selection error
1104 t.hits[subTask] << Task::Hit(now, m_currentInterval);
1105 m_incremental->writePiece(t.title, subTask, now, m_currentInterval);
1106 t.checkConsistency();
1107 QList<QTreeWidgetItem*> items = tasksTree->findItems (t.title, Qt::MatchExactly, 0);
1108 if (!items.isEmpty())
1109 items.first()->setText(1, QString("%1 hours worked till now (overestimated %2)").arg(t.totHours).arg(t.totOverestimation));
1111 // update subtask if new added!
1112 t.updateSubTasks();
1114 // update statistics !!!!
1115 m_editedTasks = m_tasks;
1116 QMetaObject::invokeMethod(this, "selectedStartDate", Qt::QueuedConnection, Q_ARG(QDate, cal1->selectedDate()));
1119 clearView();
1123 // attractor = 't' (top), 'b', 'l', 'r'
1124 void layoutInSquare( QList<SakWidget*> sortedWidgets, QRect rect, char attractor)
1126 int w = rect.width();
1127 if (rect.width() < 64) return;
1128 int maxw = qMin(350, w/2);
1129 QSize size(maxw, maxw);
1130 if (sortedWidgets.count() == 0) {
1131 return;
1132 } else if (sortedWidgets.count() == 1) {
1133 sortedWidgets[0]->setGeometry(rect);
1134 } else if (sortedWidgets.count() == 2) {
1135 QPoint off1, off2;
1136 if (attractor == 'C') {
1137 off1 = QPoint(0,w/4);
1138 off2 = QPoint(w/2,w/4);
1139 } else if (attractor == 'T') {
1140 off1 = QPoint(0,0);
1141 off2 = QPoint(w/2,0);
1142 } else if (attractor == 'B') {
1143 off1 = QPoint(0,w/2);
1144 off2 = QPoint(w/2,w/2);
1145 } else if (attractor == 'R') {
1146 off1 = QPoint(w/2,0);
1147 off2 = QPoint(w/2,w/2);
1148 } else if (attractor == 'L') {
1149 off1 = QPoint(0,0);
1150 off2 = QPoint(0,w/2);
1152 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + off1, size));
1153 sortedWidgets[1]->setGeometry(QRect(rect.topLeft() + off2, size));
1154 } else if (sortedWidgets.count() == 3) {
1155 QPoint off1, off2, off3;
1156 if (attractor == 'T' || attractor == 'C') {
1157 off1 = QPoint(0,0);
1158 off2 = QPoint(w/2,0);
1159 off3 = QPoint(w/4,w/2);
1160 } else if (attractor == 'B') {
1161 off1 = QPoint(0,w/2);
1162 off2 = QPoint(w/2,w/2);
1163 off3 = QPoint(w/4,0);
1164 } else if (attractor == 'R') {
1165 off1 = QPoint(w/2,0);
1166 off2 = QPoint(w/2,w/2);
1167 off3 = QPoint(0,w/4);
1168 } else if (attractor == 'L') {
1169 off1 = QPoint(0,0);
1170 off2 = QPoint(0,w/2);
1171 off3 = QPoint(w/2,w/4);
1173 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + off1, size));
1174 sortedWidgets[1]->setGeometry(QRect(rect.topLeft() + off2, size));
1175 sortedWidgets[2]->setGeometry(QRect(rect.topLeft() + off3, size));
1176 } else if (sortedWidgets.count() == 4) {
1177 QPoint off1(0,0);
1178 QPoint off2(0,w/2);
1179 QPoint off3(w/2,0);
1180 QPoint off4(w/2,w/2);
1181 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + off1, size));
1182 sortedWidgets[1]->setGeometry(QRect(rect.topLeft() + off2, size));
1183 sortedWidgets[2]->setGeometry(QRect(rect.topLeft() + off3, size));
1184 sortedWidgets[3]->setGeometry(QRect(rect.topLeft() + off4, size));
1185 } else {
1186 Q_ASSERT(sortedWidgets.count() <= 4);
1190 void layoutInRect( QList<SakWidget*> sortedWidgets, QRect rect, char attractor)
1192 if (sortedWidgets.count() == 0) return;
1193 int h = rect.height();
1194 int w = rect.width();
1195 int maxh = qMin(350, h);
1196 int maxw = qMin(350, w);
1197 if (sortedWidgets.count() == 1) {
1198 if (w>h) {
1199 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + QPoint((w-maxh)/2,(h-maxh)/2), QSize(maxh,maxh)));
1200 } else {
1201 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + QPoint((w-maxw)/2,(h-maxw)/2), QSize(maxw,maxw)));
1203 return;
1204 } else if (sortedWidgets.count() == 2) {
1205 if (w>h) {
1206 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + QPoint(w/2-maxh,(h-maxh)/2), QSize(maxh,maxh)));
1207 sortedWidgets[1]->setGeometry(QRect(rect.topLeft() + QPoint(w/2,(h-maxh)/2), QSize(maxh,maxh)));
1208 } else {
1209 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + QPoint((h-maxw)/2,w-maxw), QSize(w,w)));
1210 sortedWidgets[1]->setGeometry(QRect(rect.topLeft() + QPoint((h-maxw)/2,w/2), QSize(maxw,maxw)));
1212 return;
1214 if (h < 64 || w < 64) return;
1215 QList<SakWidget*> leftList, rightList;
1216 for (int i=4; i<sortedWidgets.count(); i++) {
1217 if (i%2)
1218 rightList << sortedWidgets[i];
1219 else
1220 leftList << sortedWidgets[i];
1222 if (w > h) {
1223 QRect square(rect.topLeft() + QPoint(h/2, 0), QSize(h,h));
1224 layoutInSquare(sortedWidgets.mid(0,4), square, attractor);
1226 QRect leftRect(rect.topLeft(), QSize(h/2, h));
1227 layoutInRect(leftList, leftRect, 'R');
1228 QRect rightRect(rect.topLeft() + QPoint((int)(0.75*w),0), QSize(h/2, h));
1229 layoutInRect(rightList, rightRect, 'L');
1230 } else {
1231 QRect square(rect.topLeft() + QPoint(0,w/2), QSize(w,w));
1232 layoutInSquare(sortedWidgets.mid(0,4), square, attractor);
1234 QRect leftRect(rect.topLeft(), QSize(w, w/2));
1235 layoutInRect(leftList, leftRect, 'B');
1236 QRect rightRect(rect.topLeft() + QPoint(0,(int)(0.75*h)), QSize(w, w/2));
1237 layoutInRect(rightList, rightRect, 'T');
1241 QRect Sak::Layouting( const QList<SakWidget*>& sortedWidgets)
1243 QRect r = m_desktopRect;
1244 int height = (int)(0.75 * r.height());
1245 int width = r.width();
1247 int firstW = width / 2 < height ? width : height * 2;
1248 int firstH = firstW / 2;
1249 QRect firstRect (r.x() + (width - firstW) / 2, r.y() + (height - firstH) / 2 + (int)(r.height() * 0.25), firstW, firstH);
1251 layoutInRect(sortedWidgets, firstRect, 'C');
1252 return QRect(QPoint(r.x(), r.y()), QSize(r.width(), (int)(0.25 * r.height())));
1255 void Sak::grabKeyboard()
1257 #if defined(Q_WS_X11)
1258 if (!grabbed) {
1259 // save current focused application
1260 XGetInputFocus((X11::Display*)QX11Info::display(), &X11::CurrentFocusWindow, &X11::CurrentRevertToReturn);
1261 grabbed=true;
1263 X11::XSetInputFocus((X11::Display*)QX11Info::display(), QX11Info::appRootWindow(QX11Info::appScreen()), RevertToParent, CurrentTime);
1264 X11::XFlush((X11::Display*)QX11Info::display());
1265 #endif
1266 m_view->grabKeyboard();
1269 void Sak::popup()
1271 // save changes first
1272 if (m_changedTask)
1273 saveTaskChanges();
1274 if (m_changedHit)
1275 saveHitChanges();
1277 if (m_subtaskView) {
1278 // remove subtasks
1279 foreach(SakSubWidget* w, m_subwidgets.values()) {
1280 w->scene()->removeItem(w);
1281 delete w;
1282 m_subwidgets.clear();
1284 m_marker->scene()->removeItem(m_marker);
1285 delete m_marker;
1287 // unhide tasks
1288 foreach(SakWidget* w, m_widgets.values()) {
1289 w->show();
1291 m_subtaskView=false;
1292 return;
1295 m_subtaskView = false;
1297 if (sender() == previewButton) {
1298 m_previewing = true;
1300 QDateTime now = QDateTime::currentDateTime();
1301 QDateTime fromWeek = now;
1302 fromWeek.setDate(now.date().addDays(-now.date().dayOfWeek() + 1));
1303 fromWeek.setTime(QTime(0,0,0));
1304 QDateTime fromMonth = now;
1305 fromMonth.setDate(now.date().addDays(-now.date().day()));
1306 fromMonth.setTime(QTime(0,0,0));
1307 QDateTime fromToday = now;
1308 fromToday.setTime(QTime(0,0,0));
1310 double weekHits = 0;
1311 double dayHits = 0;
1312 double monthHits = 0;
1313 QHash<QString, double> dayStats;
1314 QHash<QString, double> weekStats;
1315 QHash<QString, double> monthStats;
1317 foreach(const Task& t, m_tasks.values()) {
1318 if (t.active) {
1319 double m = t.workedHours(fromMonth, now);
1320 monthStats[t.title] = m;
1321 monthHits += m;
1322 double w = t.workedHours(fromWeek, now);
1323 weekStats[t.title] = w;
1324 weekHits += w;
1325 double d = t.workedHours(fromToday, now);
1326 dayStats[t.title] = d;
1327 dayHits += d;
1332 m_widgets.clear();
1333 foreach(const Task& t, m_tasks.values()) {
1334 if (!t.active || t.title == QString() || t.title == "<away>") continue;
1335 SakWidget* test = new SakWidget(t);
1336 test->setVisible(false);
1337 double d = dayStats[t.title];
1338 double w = weekStats[t.title];
1339 double m = monthStats[t.title];
1340 test->setStatistics(d, w, m, d/dayHits * 100.0, w/weekHits * 100.0, m/monthHits * 100.0);
1341 test->setObjectName(t.title);
1342 connect (test, SIGNAL(clicked(const QString&, const QString&)), this, SLOT(workingOnTask(const QString&, const QString&)));
1343 connect (test, SIGNAL(clicked(const QString&)), this, SLOT(popupSubtasks(const QString&)));
1344 int historyPosition = 1 + m_taskSelectionHistory.indexOf(t.title);
1345 int rank = historyPosition != 0 ? -1000000 * historyPosition : -d;
1346 m_widgets.insertMulti( rank, test);
1349 m_widgetsIterator = m_widgets.begin();
1350 if (m_widgetsIterator != m_widgets.end()) {
1351 m_widgetsIterator.value()->showDetails(true);
1355 const QList<SakWidget*>& values = m_widgets.values();
1356 QRect messageRect = Layouting((const QList<SakWidget*>&)values);
1357 foreach(SakWidget* w, values) {
1358 m_view->scene()->addItem(w);
1359 w->show();
1363 // add the message item
1364 SakMessageItem* sakMessage = new SakMessageItem(bodyEdit->toPlainText());
1365 sakMessage->setGeometry(messageRect);
1366 m_view->scene()->addItem(sakMessage);
1367 sakMessage->show();
1369 // add the exit item
1370 SakExitItem* exitItem = new SakExitItem(QPixmap(":/images/exit.png"));
1371 QRect r = m_desktopRect;
1372 connect(exitItem, SIGNAL(exit()), this, SLOT(clearView()));
1373 exitItem->setPos(r.width() - exitItem->boundingRect().width(), 0);
1374 m_view->scene()->addItem(exitItem);
1375 exitItem->setZValue(1e8);
1376 exitItem->show();
1379 m_view->setGeometry( QRect(m_desktopRect)/*.adjusted(200,200,-200,-200 )*/ );
1380 m_view->show();
1381 m_view->raise();
1382 m_view->setFocus();
1383 #if defined(Q_WS_WIN)
1384 SetForegroundWindow(m_view->winId());
1385 #endif
1386 qApp->alert(m_view, 5000);
1390 void Sak::popupSubtasks(const QString& _taskname) {
1392 killTimer(m_timeoutPopup);
1394 QString taskname = _taskname;
1395 if (taskname.isEmpty()) {
1396 if ( m_taskSelectionHistory.isEmpty() ) {
1397 return;
1398 } else {
1399 taskname = m_taskSelectionHistory.back();
1401 QString subtaskName;
1402 if (!m_subtaskSelectionHistory[taskname].isEmpty()) {
1403 subtaskName = m_subtaskSelectionHistory[taskname].back();
1405 workingOnTask(taskname, subtaskName);
1408 m_subtaskView = true;
1409 grabKeyboard();
1410 QRect r = m_desktopRect;
1411 int w = 500;
1412 int h = 40;
1414 // hide tasks to show subtasks
1415 foreach(SakWidget* w, m_widgets.values()) {
1416 w->hide();
1419 QHash<QString, Task>::const_iterator itr = m_tasks.find(taskname);
1420 if (itr == m_tasks.end()) {
1421 workingOnTask(taskname, "");
1422 return;
1424 const Task& t(*itr);
1425 QHash< QString, Task::SubTask >::const_iterator titr = t.subTasks.begin(), tend = t.subTasks.end();
1426 m_subwidgets.clear();
1427 QDateTime now(QDateTime::currentDateTime());
1428 for(; titr != tend; titr++) {
1429 if (!titr->active) continue;
1430 SakSubWidget* sw = new SakSubWidget(t, *titr);
1431 sw->setGeometry(QRectF(0,0,w,h));
1432 connect (sw, SIGNAL(clicked(const QString&, const QString&)), this, SLOT(workingOnTask(const QString&, const QString&)));
1433 connect (sw, SIGNAL(focused()), this, SLOT(focusedSubTask()));
1435 QDateTime rank=now;
1436 QHash< QString, QList< QString > >::const_iterator hhitr = m_subtaskSelectionHistory.find(t.title);
1437 if (hhitr != m_subtaskSelectionHistory.end()) {
1438 int index = hhitr->indexOf(titr->title);
1439 if (index != 0) rank = now.addDays(index+1);
1441 if (rank == now) {
1442 QHash< QString, QList<Task::Hit> >::const_iterator hitr = t.hits.find(titr.key());
1443 if (hitr != t.hits.end()) {
1444 if ( hitr.value().count() && hitr.value().last().timestamp.isValid())
1445 rank = hitr.value().last().timestamp;
1446 else
1447 rank = now.addDays(-999);
1450 m_subwidgets.insertMulti( - rank.toTime_t(), sw);
1453 const QList<SakSubWidget*>& values = m_subwidgets.values();
1455 // create possible completion
1456 QStringList subtaskSortedList;
1457 foreach(SakSubWidget* sub, values) {
1458 subtaskSortedList.push_back(sub->subtask().title);
1460 m_subtaskCompleter = new QCompleter(subtaskSortedList);
1461 m_subtaskCompleter->setCaseSensitivity(Qt::CaseInsensitive);
1463 // QRect messageRect = Layouting((const QList<SakWidget*>&)values);
1465 m_subwidgetsIterator = m_subwidgets.begin();
1466 m_subWidgetRank = 0;
1468 // the one with text
1469 SakSubWidget* tmpSw = new SakSubWidget(t, Task::SubTask(), true);
1470 QCompleter* completer = new QCompleter(subtaskSortedList);
1471 completer->setCaseSensitivity(Qt::CaseInsensitive);
1472 completer->setCompletionMode(QCompleter::InlineCompletion);
1473 ((QLineEdit*)tmpSw->widget())->setCompleter(completer);
1474 m_view->scene()->addItem(tmpSw);
1475 tmpSw->setGeometry(QRectF(0,0,w,h));
1476 connect (tmpSw, SIGNAL(clicked(const QString&, const QString&)), this, SLOT(workingOnTask(const QString&, const QString&)));
1477 connect (tmpSw, SIGNAL(focused()), this, SLOT(focusedSubTask()));
1479 m_subwidgets.insertMulti(-QDateTime::currentDateTime().addDays(999).toTime_t(), tmpSw);
1481 m_subWidgetRank += values.size() != 0;
1483 if (m_subwidgetsIterator != m_subwidgets.end()) {
1484 m_subwidgetsIterator.value()->showDetails(true);
1485 m_view->scene()->setFocusItem(m_subwidgetsIterator.value(), Qt::MouseFocusReason);
1486 m_subwidgetsIterator.value()->widget()->setFocus(Qt::MouseFocusReason);
1487 } else {
1488 m_view->scene()->setFocusItem(tmpSw, Qt::MouseFocusReason);
1489 tmpSw->widget()->setFocus(Qt::MouseFocusReason);
1492 // add a marker to highligh current selection
1493 m_marker = new QGraphicsEllipseItem(r.width()/2 - 280, r.height()/2 - 51, 20,20);
1494 m_marker->setBrush(Qt::red);
1495 m_view->scene()->addItem(m_marker);
1496 m_marker->setVisible(true);
1498 layoutSubTasks(m_subwidgets, m_subWidgetRank);
1500 // Finally add subwidgets
1501 for(int i=0; i<values.size(); i++) {
1502 SakSubWidget* sw = values[i];
1503 m_view->scene()->addItem(sw);
1508 void Sak::scrollTasks(int npos) {
1509 SakWidget* currentShowing = 0;
1510 if (npos < 0) {
1511 for (int i=npos; i<0; i++) {
1512 if (m_widgetsIterator == m_widgets.end()) return;
1513 currentShowing = m_widgetsIterator != m_widgets.end() ? m_widgetsIterator.value() : 0;
1514 if (m_widgetsIterator == m_widgets.begin()) m_widgetsIterator = m_widgets.end();
1515 m_widgetsIterator--;
1517 } else {
1518 for (int i=0; i<npos; i++) {
1519 if (m_widgetsIterator == m_widgets.end()) return;
1520 currentShowing = m_widgetsIterator.value();
1521 m_widgetsIterator++;
1522 if (m_widgetsIterator == m_widgets.end()) m_widgetsIterator = m_widgets.begin();
1525 if (currentShowing && m_widgetsIterator != m_widgets.end()) {
1526 currentShowing->showDetails(false);
1527 m_widgetsIterator.value()->showDetails(true);
1531 void Sak::scrollSubTasks(int npos) {
1532 SakSubWidget* currentShowing = 0;
1533 if (npos < 0) {
1534 for (int i=npos; i<0; i++) {
1535 if (m_subwidgetsIterator == m_subwidgets.end()) return;
1536 currentShowing = m_subwidgetsIterator != m_subwidgets.end() ? m_subwidgetsIterator.value() : 0;
1537 if (m_subwidgetsIterator == m_subwidgets.begin()) {
1538 m_subwidgetsIterator = m_subwidgets.end();
1539 m_subWidgetRank = m_subwidgets.count();
1541 m_subwidgetsIterator--;
1542 m_subWidgetRank--;
1544 } else {
1545 for (int i=0; i<npos; i++) {
1546 if (m_subwidgetsIterator == m_subwidgets.end()) return;
1547 currentShowing = m_subwidgetsIterator.value();
1548 m_subwidgetsIterator++;
1549 m_subWidgetRank++;
1550 if (m_subwidgetsIterator == m_subwidgets.end()) {
1551 m_subwidgetsIterator = m_subwidgets.begin();
1552 m_subWidgetRank = 0;
1556 if (currentShowing && m_subwidgetsIterator != m_subwidgets.end()) {
1557 currentShowing->showDetails(false);
1558 m_subwidgetsIterator.value()->showDetails(true);
1559 m_view->scene()->setFocusItem(m_subwidgetsIterator.value(), Qt::MouseFocusReason);
1560 m_subwidgetsIterator.value()->widget()->setFocus(Qt::MouseFocusReason);
1562 layoutSubTasks(m_subwidgets, m_subWidgetRank);
1565 void Sak::focusedSubTask()
1567 SakSubWidget* w = dynamic_cast<SakSubWidget*>(sender());
1568 if (w) {
1569 QMap<int, SakSubWidget*>::iterator itr = m_subwidgets.begin(), end=m_subwidgets.end();
1570 for(int i=0; itr != end; i++,itr++) {
1571 if (itr.value() == w) {
1572 m_subwidgetsIterator = itr;
1573 m_subWidgetRank = i;
1579 //END Tasks >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
1582 //BEGIN settings ================================
1585 void Sak::setupSettingsWidget()
1587 m_settings = new QMainWindow();
1588 m_settings->setMinimumHeight(650);
1589 m_settings->setMinimumWidth(700);
1590 QWidget* centralWidget = new QWidget;
1591 m_settings->setCentralWidget(centralWidget);
1593 QVBoxLayout* theMainLayout = new QVBoxLayout;
1594 centralWidget->setLayout(theMainLayout);
1596 tabs = new QTabWidget;
1597 theMainLayout->addWidget(tabs);
1598 previewButton = new QPushButton("Preview");
1599 theMainLayout->addWidget(previewButton);
1601 tab1 = new QWidget;
1602 tab2 = new QWidget;
1603 tab3 = new QWidget;
1604 tab4 = new QWidget;
1605 tabs->addTab(tab1, "Tasks");
1606 tabs->addTab(tab2, "General");
1607 tabs->addTab(tab4, "Statistics");
1608 tabs->addTab(tab3, "Advanced");
1610 createActions();
1611 QMenuBar* mainMenu = new QMenuBar;
1612 m_settings->setMenuBar(mainMenu);
1613 QMenu* programMenu = mainMenu->addMenu("Program");
1614 programMenu->addAction(quitAction);
1615 programMenu->addAction(minimizeAction);
1616 QMenu* dbMenu = mainMenu->addMenu("Db");
1617 dbMenu->addAction(flushAction);
1618 dbMenu->addAction(openAction);
1619 // dbMenu->addAction(saveAsDbAction);
1620 dbMenu->addAction(exportDbCsvAction);
1621 #ifdef USEGMAIL
1622 dbMenu->addAction(gmailLoginAction);
1623 dbMenu->addAction(saveToGmailAction);
1624 if (!m_gmail->isValid()) {
1625 gmailLoginAction->setEnabled(false);
1626 saveToGmailAction->setEnabled(false);
1628 #endif
1629 QMenu* actionsMenu = mainMenu->addMenu("Actions");
1630 actionsMenu->addAction(startAction);
1631 actionsMenu->addAction(stopAction);
1633 QVBoxLayout *mainLayout = new QVBoxLayout;
1634 QGridLayout *messageLayout = new QGridLayout;
1635 durationLabel = new QLabel(tr("Interval:"));
1636 durationLabel1 = new QLabel(tr("(effective only after restart)"));
1638 QSettings settings(QSettings::IniFormat, QSettings::UserScope, "ZanzaSoft", "SAK");
1639 durationSpinBox = new QSpinBox;
1640 durationSpinBox->setSingleStep(1);
1641 durationSpinBox->setRange(1, 1440);
1642 durationSpinBox->setSuffix(" minutes");
1643 durationSpinBox->setValue(qMin(1440, qMax(1, settings.value("Ping interval", 15).toInt())));
1644 durationSpinBox->setCorrectionMode(QAbstractSpinBox::CorrectToNearestValue);
1646 bodyLabel = new QLabel(tr("Message:"));
1647 bodyEdit = new QTextEdit;
1648 bodyEdit->setPlainText( settings.value("Message", "<h1>Enter a message here!</h1>").toString() );
1650 messageLayout->addWidget(durationLabel, 1, 0);
1651 messageLayout->addWidget(durationSpinBox, 1, 1);
1652 messageLayout->addWidget(durationLabel1, 1, 2);
1653 messageLayout->addWidget(bodyLabel, 3, 0);
1654 messageLayout->addWidget(bodyEdit, 3, 1, 2, 4);
1655 messageLayout->setColumnStretch(3, 1);
1656 messageLayout->setRowStretch(4, 1);
1657 mainLayout->addLayout(messageLayout);
1658 tab2->setLayout(mainLayout);
1660 mainLayout = new QVBoxLayout;
1661 tab1->setLayout(mainLayout);
1662 // create tree view
1663 tasksTree = new QTreeWidget;
1664 tasksTree->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
1665 tasksTree->setColumnCount(3);
1666 tasksTree->setColumnWidth(0, 300);
1667 tasksTree->setColumnWidth(1, 65);
1668 tasksTree->setIconSize(QSize(32,32));
1669 connect(tasksTree, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(doubleClickedTask(QTreeWidgetItem*,int)));
1672 taskTextEditor = new QTextEdit;
1673 taskTextEditor->setFixedHeight(100);
1674 taskTextEditor->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
1675 connect(taskTextEditor, SIGNAL(textChanged()), this, SLOT(commitCurrentTask()));
1678 taskTitleEditor = new QLineEdit;
1679 taskTitleEditor->setFixedHeight(20);
1680 taskTitleEditor->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
1681 connect(taskTitleEditor, SIGNAL(editingFinished()), this, SLOT(commitCurrentTask()));
1683 taskPixmapViewer = new PixmapViewer;
1684 mainLayout->addWidget(tasksTree, 2);
1685 QHBoxLayout* detailsLayout = new QHBoxLayout;
1686 QVBoxLayout* editsLayout = new QVBoxLayout;
1687 detailsLayout->addWidget(taskPixmapViewer);
1688 connect(taskPixmapViewer, SIGNAL(changed()), this, SLOT(commitCurrentTask()));
1689 editsLayout->addWidget(taskTitleEditor);
1690 editsLayout->addWidget(taskTextEditor);
1691 QHBoxLayout* datesLayout = new QHBoxLayout;
1692 datesLayout->addWidget(new QLabel("Due: "));
1693 dueEditor = new QDateEdit;
1694 dueEditor->setMinimumDate(QDate(2000,1,1));
1695 datesLayout->addWidget(dueEditor);
1696 datesLayout->addWidget(new QLabel("Estimated: "));
1697 estimatedHoursEditor = new QSpinBox;
1698 estimatedHoursEditor->setRange(0, 1e5);
1699 estimatedHoursEditor->setSuffix("hours");
1700 datesLayout->addWidget(estimatedHoursEditor);
1701 editsLayout->addLayout(datesLayout);
1702 detailsLayout->addLayout(editsLayout);
1703 connect(dueEditor, SIGNAL(editingFinished()), this, SLOT(commitCurrentTask()));
1704 connect(estimatedHoursEditor,SIGNAL(editingFinished()), this, SLOT(commitCurrentTask()));
1706 QVBoxLayout* colorsLayout = new QVBoxLayout;
1707 bgColorButton = new QPushButton("bg\ncolor");
1708 bgColorButton->setToolTip("Background color");
1709 bgColorButton->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding));
1711 fgColorButton = new QPushButton("fg\ncolor");
1712 fgColorButton->setToolTip("Foreground color");
1713 fgColorButton->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding));
1715 #ifdef Q_WS_WIN
1716 // fix Windows XP "style"
1717 bgColorButton->setStyle(new QWindowsStyle());
1718 fgColorButton->setStyle(new QWindowsStyle());
1719 #endif
1721 colorsLayout->addWidget(bgColorButton);
1722 colorsLayout->addWidget(fgColorButton);
1723 detailsLayout->addLayout(colorsLayout);
1725 mainLayout->addLayout(detailsLayout);
1727 QVBoxLayout* tab4MainLayout = new QVBoxLayout(tab4);
1728 //taskSelector = new QComboBox;
1729 summaryList = newTaskSummaryList();
1730 QTabWidget* tabs = new QTabWidget;
1731 tabs->setTabPosition(QTabWidget::East);
1732 tabs->addTab(summaryList, "List");
1733 QGraphicsView* summaryView = new QGraphicsView;
1734 summaryView->setScene(new QGraphicsScene);
1735 TaskSummaryPieChart* chart = new TaskSummaryPieChart;
1736 summaryView->scene()->addItem(new TaskSummaryPieChart);
1737 summaryView->centerOn(chart);
1738 tabs->addTab(summaryView, "Chart");
1739 tab4MainLayout->addWidget(tabs);
1740 cal3 = new QCalendarWidget;
1741 cal3->setMinimumSize(QSize(250,200));
1742 cal4 = new QCalendarWidget;
1743 cal4->setMinimumSize(QSize(250,200));
1744 cal3->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
1745 cal4->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
1746 QHBoxLayout* calsLayout = new QHBoxLayout;
1747 calsLayout->addWidget(cal3);
1748 calsLayout->addWidget(cal4);
1749 cal4->setSelectedDate(QDate::currentDate().addDays(1));
1750 tab4MainLayout->addLayout(calsLayout);
1753 QVBoxLayout* tab3MainLayout = new QVBoxLayout(tab3);
1754 //taskSelector = new QComboBox;
1755 hitsList = newHitsList();
1756 connect(hitsList, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), this, SLOT(hitsSelectedInList(QTreeWidgetItem*,QTreeWidgetItem*)));
1757 hitsTimeline = new Timeline;
1758 connect(hitsTimeline, SIGNAL(hitSelected(HitItem*)), this, SLOT(hitsSelectedInTimeline(HitItem*)));
1759 cal1 = new QCalendarWidget;
1760 cal1->setMinimumSize(QSize(250,200));
1761 cal2 = new QCalendarWidget;
1762 cal2->setMinimumSize(QSize(250,200));
1763 cal1->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
1764 cal2->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
1765 //tab3MainLayout->addWidget(taskSelector);
1767 calsLayout = new QHBoxLayout;
1768 calsLayout->addWidget(cal1);
1769 calsLayout->addWidget(cal2);
1770 cal2->setSelectedDate(QDate::currentDate().addDays(1));
1771 tab3MainLayout->addWidget(hitsList);
1772 tab3MainLayout->addWidget(hitsTimeline);
1773 tab3MainLayout->addLayout(calsLayout);
1777 m_settings->setWindowTitle(tr("SaK"));
1778 m_settings->resize(400, 300);
1782 QTreeWidget* Sak::newHitsList()
1784 QTreeWidget* hitsList = new QTreeWidget;
1785 hitsList->setColumnCount(4);
1786 hitsList->setColumnWidth(0, 200);
1787 hitsList->setColumnWidth(1, 150);
1788 hitsList->setColumnWidth(2, 150);
1789 hitsList->setColumnWidth(3, 150);
1790 hitsList->setColumnWidth(4, 150);
1791 hitsList->setIconSize(QSize(24,24));
1792 hitsList->setSortingEnabled(true);
1793 hitsList->setItemDelegateForColumn(0, new MyDateItemDelegate);
1794 hitsList->setItemDelegateForColumn(1, new TaskItemDelegate(this));
1795 hitsList->setItemDelegateForColumn(2, new SubTaskItemDelegate(this));
1796 QTreeWidgetItem* header = new QTreeWidgetItem;
1797 header->setText(0, "Date/Time");
1798 header->setText(1, "Task");
1799 header->setText(2, "Subtask");
1800 header->setText(3, "Duration (min)");
1801 header->setText(4, "Overestimation");
1802 hitsList->setHeaderItem(header);
1803 hitsList->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
1804 return hitsList;
1807 QTreeWidget* Sak::newTaskSummaryList()
1809 QTreeWidget* taskSummaryList = new QTreeWidget;
1810 taskSummaryList->setColumnCount(4);
1811 taskSummaryList->setColumnWidth(0, 250);
1812 taskSummaryList->setColumnWidth(1, 150);
1813 taskSummaryList->setColumnWidth(1, 150);
1814 taskSummaryList->setIconSize(QSize(24,24));
1815 taskSummaryList->setSortingEnabled(true);
1816 QTreeWidgetItem* header = new QTreeWidgetItem;
1817 header->setText(0, "Task");
1818 header->setText(1, "Hours");
1819 taskSummaryList->setHeaderItem(header);
1820 taskSummaryList->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
1821 taskSummaryList->setEnabled(true);
1822 return taskSummaryList;
1825 void Sak::setVisible(bool visible)
1827 minimizeAction->setEnabled(visible);
1828 maximizeAction->setEnabled(!m_settings->isMaximized());
1829 restoreAction->setEnabled(m_settings->isMaximized() || !visible);
1830 m_settings->setVisible(visible);
1833 void Sak::createActions()
1835 minimizeAction = new QAction(tr("Mi&nimize"), m_settings);
1836 connect(minimizeAction, SIGNAL(triggered()), m_settings, SLOT(hide()));
1838 maximizeAction = new QAction(tr("Ma&ximize"), m_settings);
1839 connect(maximizeAction, SIGNAL(triggered()), m_settings, SLOT(showMaximized()));
1841 restoreAction = new QAction(tr("&Restore"), m_settings);
1842 connect(restoreAction, SIGNAL(triggered()), m_settings, SLOT(showNormal()));
1844 quitAction = new QAction(tr("&Quit"), m_settings);
1845 connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
1847 startAction = new QAction(tr("Start polling"), m_settings);
1848 connect(startAction, SIGNAL(triggered()), this, SLOT(start()));
1850 stopAction = new QAction(tr("Stop polling"), m_settings);
1851 connect(stopAction, SIGNAL(triggered()), this, SLOT(stop()));
1853 flushAction = new QAction(tr("&Flush data/settings to disk"), m_settings);
1854 connect(flushAction, SIGNAL(triggered()), this, SLOT(flush()));
1856 saveAsDbAction = new QAction(tr("Backup as"), m_settings);
1857 // connect(saveAsDbAction, SIGNAL(triggered()), this, SLOT(saveAsDb()));
1859 exportDbCsvAction = new QAction(tr("Export hits in CSV format"), m_settings);
1860 connect(exportDbCsvAction, SIGNAL(triggered()), this, SLOT(exportDbCsv()));
1862 #ifdef USEGMAIL
1863 saveToGmailAction = new QAction(tr("Store in your gmail account free space"), m_settings);
1864 connect(saveToGmailAction, SIGNAL(triggered()), this, SLOT(saveToGmail()));
1866 gmailLoginAction = new QAction(tr("Log in gmail"), m_settings);
1867 connect(gmailLoginAction, SIGNAL(triggered()), this, SLOT(logInGmail()));
1868 #else
1869 saveToGmailAction = NULL;
1870 gmailLoginAction = NULL;
1871 #endif
1873 openAction = new QAction(tr("Import a task from file"), m_settings);
1874 connect(openAction, SIGNAL(triggered()), this, SLOT(open()));
1876 m_addHitAction = new QAction("Add hit", m_settings);
1877 m_addHitMenu = new QMenu(m_settings);
1878 m_addHitAction->setText("Add hit");
1879 m_addHitMenu->addAction(m_addHitAction);
1880 connect(m_addHitAction, SIGNAL(triggered()), this, SLOT(addDefaultHit()));
1882 m_exportDataAction = new QAction("Export data", m_settings);
1883 m_exportDataAction->setText("Export data");
1884 m_addHitMenu->addAction(m_exportDataAction);
1885 connect(m_exportDataAction, SIGNAL(triggered()), this, SLOT(exportHits()));
1887 m_addTaskAction = new QAction("Add task", m_settings);
1888 m_addTaskMenu = new QMenu(m_settings);
1889 m_addTaskAction->setText("Add task");
1890 m_addTaskMenu->addAction(m_addTaskAction);
1891 connect(m_addTaskAction, SIGNAL(triggered()), this, SLOT(addDefaultTask()));
1894 void Sak::trayIconActivated(QSystemTrayIcon::ActivationReason reason)
1896 if (reason == QSystemTrayIcon::DoubleClick) {
1897 setVisible(true);
1901 //END Settings <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<