limit subtask list
[Sak.git] / sak.cpp
blob3bbbf2da01cc992a2d977646989e632673c4f476
1 /***************************************************************************
2 * Copyright (C) 2007 by Arrigo Zanette *
3 * zanettea@gmail.com *
4 ***************************************************************************/
7 #include <QtGui>
8 #include <QCryptographicHash>
9 #include <QSettings>
10 #include <QGraphicsEllipseItem>
11 #include <QProgressDialog>
12 #include <cassert>
14 #include "sak.h"
15 #include "sakwidget.h"
16 #include "saksubwidget.h"
17 #include "sakmessageitem.h"
18 #include "pixmapviewer.h"
19 #include "timeline.h"
20 #include "backupper.h"
21 #include "piechart.h"
22 #ifdef USELIBGMAIL
23 #include "gmailstorage/gmailpyinterface.h"
24 #else
25 #include "gmailstorage/gmailmyinterface.h"
26 #endif
28 //END Task <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
31 // GView
32 #include <QtOpenGL>
34 #if defined(Q_WS_X11)
35 #include <QX11Info>
36 namespace X11
38 #include <X11/Xlib.h>
39 #undef KeyPress
40 #undef KeyRelease
41 static Window CurrentFocusWindow;
42 static int CurrentRevertToReturn;
44 #endif
46 static int grabbed;
49 class GView : public QGraphicsView
51 public:
52 // GView() {
53 // if (QGLFormat::hasOpenGL()) {
54 // qDebug() << "Using OpenGL";
55 // QGLWidget* w = new QGLWidget;
56 // w->setAttribute(Qt::WA_TranslucentBackground, true);
57 // setViewport(w);
58 // }
59 // }
60 // ~GView() {
61 // delete this->viewport();
62 // }
63 void drawBackground(QPainter* p, const QRectF & rect) {
64 viewport()->setAttribute(Qt::WA_TranslucentBackground, true);
65 setAttribute(Qt::WA_NoSystemBackground, true);
66 viewport()->setAttribute(Qt::WA_NoSystemBackground, true);
67 setAttribute(Qt::WA_TranslucentBackground, true);
68 if (backgroundPixmap.isNull()) {
69 QBrush brush(QColor(100,0,0,200));
70 p->setCompositionMode(QPainter::CompositionMode_Source);
71 p->fillRect(rect, brush);
72 } else {
73 p->drawPixmap(rect, backgroundPixmap, rect.translated(backgroundPixmap.rect().center() - viewport()->rect().center()));
76 QPixmap backgroundPixmap;
80 //BEGIN Sak basic >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
82 Sak::Sak(QObject* parent)
83 : QObject(parent)
84 , m_timerId(0)
85 , m_timeoutPopup(0)
86 , m_getFocusTimer(0)
87 , m_stopped(false)
88 , m_settings(0)
89 , m_changedHit(false)
90 , m_changedTask(false)
91 , m_subtaskView(false)
92 , m_yesToBackup(false)
93 , m_fakeClicking(false)
95 workingOnDeclared = 0;
96 workingOnTimer = new QTimer;
98 m_desktopRect = qApp->desktop()->screenGeometry(QPoint(0,0));
100 m_subtaskCompleter = 0;
101 summaryList = hitsList = 0; trayIcon=0;
102 init();
104 if (QCoreApplication::arguments().contains("--clear")) {
105 QHash<QString, Task>::iterator itr = m_tasks.begin();
106 while(itr != m_tasks.end()) {
107 itr->hits.clear();
108 itr++;
112 if (m_tasks.count() <= 0)
113 m_settings->show();
115 m_previewing = false;
116 m_changedHit = false;
117 m_timerId = 0;
118 m_autoSaveTimer = startTimer(1000 * 60 * 45); // every 45 minutes
119 start();
121 // Need to go here, or after plasma reboot the icon will disappear
122 trayIconMenu = new QMenu(m_settings);
123 //trayIconMenu->addAction(minimizeAction);
124 //trayIconMenu->addAction(maximizeAction);
125 //trayIconMenu->addAction(restoreAction);
126 //trayIconMenu->addSeparator();
127 trayIconMenu->addAction(startAction);
128 trayIconMenu->addAction(stopAction);
129 trayIconMenu->addAction(workingOnAction);
130 trayIconMenu->addAction(flushAction);
131 trayIconMenu->addSeparator();
132 trayIconMenu->addAction(quitAction);
133 trayIcon = new QSystemTrayIcon(this);
134 trayIcon->setContextMenu(trayIconMenu);
135 trayIcon->setIcon( QIcon(":/images/icon.png") );
136 trayIcon->setToolTip( tr("Sistema Anti Kazzeggio") );
137 trayIcon->show();
138 connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(trayIconActivated(QSystemTrayIcon::ActivationReason)));
139 trayIcon->installEventFilter(this);
142 m_settings->setWindowIcon( QIcon(":/images/icon.png") );
143 m_settings->setWindowTitle("SaK - Sistema Anti Kazzeggio");
146 void Sak::init()
148 m_backupper = new Backupper;
149 m_incremental = new Incremental;
150 #ifdef USEGMAIL
151 m_gmail = new GmailPyInterface;
152 #else
153 m_gmail = NULL;
154 #endif
156 // load the data model
157 QSettings settings(QSettings::IniFormat, QSettings::UserScope, "ZanzaSoft", "SAK");
158 QByteArray tasksArray = settings.value("tasks").toByteArray();
159 QDataStream stream(&tasksArray, QIODevice::ReadWrite);
160 stream.setVersion(QDataStream::Qt_4_3);
163 { // read locastasks
164 QDir saveDir(QFileInfo(settings.fileName()).dir());
165 saveDir.mkdir("SakTasks");
166 saveDir.cd("SakTasks");
167 QStringList nameFilters;
168 nameFilters << "*.xml";
169 QStringList files = saveDir.entryList( nameFilters, QDir::Files);
170 foreach (QString taskXmlFileName, files) {
171 Task t( loadTaskFromFile(saveDir.filePath(taskXmlFileName)) );
172 m_tasks[t.title] = t;
177 // add subtasks, if missing
179 QHash<QString, Task>::iterator itr = m_tasks.begin();
180 while(itr != m_tasks.end()) {
181 itr->updateSubTasks();
182 itr++;
186 // reset awayTask
187 Task & awayTask = m_tasks["<away>"];
188 awayTask.title = "<away>";
189 awayTask.fgColor = Qt::gray;
190 awayTask.bgColor = Qt::white;
191 awayTask.icon = QPixmap(":/images/away.png");
193 m_editedTasks = m_tasks;
196 hitsTimeline = 0;
197 //merge piecies
198 interactiveMergeHits();
200 m_editedTasks = m_tasks;
202 setupSettingsWidget();
204 QPixmap askingLadyPixmap;
205 QPixmap viewBackgroundPixmap;
206 QVariant askingLadyVariant = settings.value("Lady");
207 if (askingLadyVariant.canConvert(QVariant::Pixmap)) {
208 askingLadyPixmap = askingLadyVariant.value<QPixmap>();
209 } else {
210 QString fileName = QFileInfo(settings.fileName()).absoluteDir().filePath( askingLadyVariant.toString() );
211 askingLadyPixmap = QPixmap(fileName);
213 if(!askingLadyPixmap.isNull()) {
214 askingLady->setPixmap(askingLadyPixmap);
217 QVariant viewBackgroundVariant = settings.value("View background");
218 if (viewBackgroundVariant.canConvert(QVariant::Pixmap)) {
219 viewBackgroundPixmap = viewBackgroundVariant.value<QPixmap>();
220 } else {
221 QString fileName = QFileInfo(settings.fileName()).absoluteDir().filePath( viewBackgroundVariant.toString() );
222 viewBackgroundPixmap = QPixmap(fileName);
225 if(!askingLadyPixmap.isNull()) {
226 askingLady->setPixmap(askingLadyPixmap);\
228 if (!viewBackgroundPixmap.isNull()) {
229 viewBackground->setPixmap(viewBackgroundPixmap);
233 m_settings->installEventFilter(this);
234 hitsList->installEventFilter(this);
235 tasksTree->installEventFilter(this);
236 tasksTree->setUniformRowHeights(false);
237 QTreeWidgetItem* headerItem = new QTreeWidgetItem;
238 headerItem->setSizeHint(0 , QSize(0,0));
239 headerItem->setSizeHint(1 , QSize(0,0));
240 headerItem->setSizeHint(2 , QSize(0,0));
241 tasksTree->setHeaderItem(headerItem);
243 connect(bgColorButton, SIGNAL(clicked()), this, SLOT(selectColor()));
244 connect(fgColorButton, SIGNAL(clicked()), this, SLOT(selectColor()));
245 connect(previewButton, SIGNAL(clicked()), this, SLOT(popup()));
246 connect(tasksTree, SIGNAL(itemSelectionChanged()), this, SLOT(selectedTask()));
247 connect(tasksTree, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(selectedTask()));
248 populateTasks();
250 connect(cal1, SIGNAL(clicked(QDate)), this, SLOT(selectedStartDate(QDate)));
251 connect(cal2, SIGNAL(clicked(QDate)), this, SLOT(selectedEndDate(QDate)));
252 connect(cal3, SIGNAL(clicked(QDate)), this, SLOT(selectedStartDate(QDate)));
253 connect(cal4, SIGNAL(clicked(QDate)), this, SLOT(selectedEndDate(QDate)));
255 connect(today1, SIGNAL(clicked()), this, SLOT(selectTodayDate()));
256 connect(thisWeek1, SIGNAL(clicked()), this, SLOT(selectThisWeeDate()));
257 connect(thisMonth1, SIGNAL(clicked()), this, SLOT(selectThisMonthDate()));
258 connect(lastWeek1, SIGNAL(clicked()), this, SLOT(selectLastWeekDate()));
259 connect(lastMonth1, SIGNAL(clicked()), this, SLOT(selectLastMonthDate()));
261 connect(today2, SIGNAL(clicked()), this, SLOT(selectTodayDate()));
262 connect(thisWeek2, SIGNAL(clicked()), this, SLOT(selectThisWeeDate()));
263 connect(thisMonth2, SIGNAL(clicked()), this, SLOT(selectThisMonthDate()));
264 connect(lastWeek2, SIGNAL(clicked()), this, SLOT(selectLastWeekDate()));
265 connect(lastMonth2, SIGNAL(clicked()), this, SLOT(selectLastMonthDate()));
267 connect(hitsList, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(hitsListItemChanged(QTreeWidgetItem*,int)));
268 selectedTask();
270 m_view = new GView;
271 m_view->setScene(new QGraphicsScene);
272 m_view->scene()->setSceneRect(m_desktopRect);
274 m_view->installEventFilter(this);
275 m_view->setFrameStyle(QFrame::NoFrame);
276 m_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
277 m_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
278 m_view->setWindowFlags(m_view->windowFlags() | Qt::WindowStaysOnTopHint | Qt::ToolTip );
279 //m_view->setWindowModality(Qt::ApplicationModal);
280 m_view->setAttribute(Qt::WA_QuitOnClose, false);
281 // enable transparency with Qt4.5
282 m_view->setAttribute(Qt::WA_TranslucentBackground, true);
283 m_view->setWindowIcon( QIcon(":/images/icon.png") );
284 m_view->setWindowTitle("SaK - Sistema Anti Kazzeggio");
286 m_currentInterval = durationSpinBox->value();
287 m_currentInterval = qMax((int)1, qMin((int)1440, m_currentInterval));
288 qDebug() << "SAK: pinging interval " << m_currentInterval << Task::hours(m_currentInterval) << " hours ";
290 hitsTimeline->setPeriod(QDateTime(cal1->selectedDate()), QDateTime(cal2->selectedDate()));
291 populateHitsList(createHitsList(QDateTime(cal1->selectedDate()), QDateTime(cal2->selectedDate())));
293 connect(workingOnTimer, SIGNAL(timeout()), startAction, SLOT(trigger()));
296 void Sak::start()
298 // ensure the timer is killed
299 if(m_timerId)
300 stop();
301 m_currentInterval = qMax((int)1, m_currentInterval);
302 int msecs = (int)(Task::hours(m_currentInterval)*3600.0*1000.0 / 2);
303 m_timerId = startTimer( msecs );
304 m_nextTimerEvent = QDateTime::currentDateTime().addMSecs(msecs);
305 startAction->setEnabled(false);
306 stopAction->setEnabled(true);
307 workingOnTimer->stop();
308 m_stopped=false;
311 void Sak::stop()
313 if(m_timerId) {
314 killTimer(m_timerId); m_timerId=-1;
316 stopAction->setEnabled(false);
317 startAction->setEnabled(true);
318 m_stopped=true;
321 void Sak::workingOn()
323 // 1. Select the duration
324 QDialog d;
325 QLabel* label = new QLabel("Please select the duration:");
326 QDoubleSpinBox* spinBox = new QDoubleSpinBox;
327 QPushButton* okButton = new QPushButton("Select task/subtask");
328 QPushButton* cancelButton = new QPushButton("Cancel");
329 okButton->hide();
330 connect(spinBox, SIGNAL(valueChanged(double)), okButton,SLOT(show()));
331 connect(cancelButton, SIGNAL(clicked()), &d, SLOT(reject()));
332 connect(okButton, SIGNAL(clicked()), &d, SLOT(accept()));
333 spinBox->setSuffix(" hours");
334 spinBox->setMinimum(0);
335 spinBox->setMaximum(24);
336 spinBox->setValue(0);
337 QVBoxLayout* mainLayout = new QVBoxLayout;
338 QHBoxLayout* buttons = new QHBoxLayout;
339 buttons->addWidget(cancelButton);
340 buttons->addWidget(okButton);
341 mainLayout->addWidget(label);
342 mainLayout->addWidget(spinBox);
343 mainLayout->addLayout(buttons);
344 d.setLayout(mainLayout);
345 d.exec();
346 popup();
347 workingOnDeclared = spinBox->value();
350 void Sak::pause()
352 m_stopped=true;
353 stopAction->setEnabled(false);
354 startAction->setEnabled(true);
357 // check xml is valid
358 static bool checkXml (QFile& file)
360 QByteArray data = file.readLine();
361 QXmlStreamReader stream(data);
362 QXmlStreamReader::TokenType token = stream.readNext(); // skip StartDocument
363 token = stream.readNext();
364 if ( token != QXmlStreamReader::Comment) {
365 qDebug() << "checkXml: missing md5";
366 return false;
369 data = file.readAll();
370 QXmlStreamReader reader(data);
371 while(!reader.atEnd() || !reader.hasError()) {
372 if ( reader.readNext() == QXmlStreamReader::EndDocument)
373 break;
375 qDebug() << "checkXml: ERROR: " << reader.hasError() << reader.errorString() << data.size();
377 return !reader.hasError();
380 Task Sak::recoverTaskFromBackup(QFile& taskXmlFile, const QString& error)
382 int rc;
383 if (!m_yesToBackup && (rc = QMessageBox::warning(0, "Corrupted xml!",
384 QString("Reading xml file " + taskXmlFile.fileName() + " failed\nDo you want to try latest valid backup?" )
385 ,QMessageBox::Yes | QMessageBox::No | QMessageBox::YesToAll)) == QMessageBox::No ) {
386 qDebug() << "Error reading task data from file " << taskXmlFile.fileName() << ":" << error;
387 return Task();
388 } else {
389 if (rc == QMessageBox::YesToAll)
390 m_yesToBackup = true;
391 // read latest backup
392 QString filePath = taskXmlFile.fileName();
393 QFileInfo fInfo(filePath);
394 QString baseFileName = fInfo.fileName();
395 QDir dir = fInfo.dir();
396 dir.cd("../sakbcks");
397 QStringList list = dir.entryList(QStringList() << baseFileName+"*", QDir::Files, QDir::Time );
398 // start from last and validate files
399 qDebug() << filePath << list;
400 for(int i=0; i<list.size(); i++) {
401 QFile f(dir.filePath(list.at(i)));
402 if (!f.open(QIODevice::ReadOnly)) {
403 qDebug() << "error opening file " << dir.filePath(list.at(i));
405 if (checkXml( f ) ) {
406 qDebug() << list.at(i) << "IS OK!!";
407 taskXmlFile.close();
408 taskXmlFile.remove();
409 taskXmlFile.open(QIODevice::ReadOnly);
410 loadTaskFromFile(taskXmlFile.fileName(), false);
411 break;
413 f.close();
416 return Task();
419 Task Sak::loadTaskFromFile(const QString& filePath, bool tryRecover)
421 QFile taskXmlFile(filePath);
422 Task t;
423 qDebug() << "Examine task file " << taskXmlFile.fileName();
424 if (!taskXmlFile.open(QIODevice::ReadOnly)) {
425 qDebug() << "Failed opening xml file " << taskXmlFile.fileName();
427 QByteArray data = taskXmlFile.readLine();
428 QXmlStreamReader stream(data);
429 QXmlStreamReader::TokenType token = stream.readNext(); // skip StartDocument
430 token = stream.readNext();
431 if ( token != QXmlStreamReader::Comment) {
432 if (!m_yesToBackup && (!tryRecover || QMessageBox::No == QMessageBox::warning(0, "Missing md5!",
433 QString("Check of file " + taskXmlFile.fileName() + " failed (missing md5 sum)\nDo you want to load it anyway?" )
434 ,QMessageBox::Yes | QMessageBox::No)) ) {
435 qDebug() << "Skip file " << taskXmlFile.fileName() << " (want a file starting with a comment representing MD5, got" << token << ")";
436 return t;
439 QString md5 = stream.text().toString().trimmed();
440 qDebug() << "md5 = " << md5;
442 // check md5
443 data = taskXmlFile.readAll();
444 if ( md5 != QCryptographicHash::hash(data, QCryptographicHash::Md5).toHex() ) {
445 if (!m_yesToBackup && (!tryRecover || QMessageBox::No == QMessageBox::warning(0, "Checksum mismatch!",
446 QString("Checksum of file " + taskXmlFile.fileName() + " mismatch (maybe it has been edited by hand).\nDo you want to load it anyway?" )
447 ,QMessageBox::Yes | QMessageBox::No)) ) {
448 qDebug() << "Skip file " << taskXmlFile.fileName() << " (bad md5 sum)";
449 return t;
453 // read rest of data
454 stream.clear();
455 stream.addData(data);
457 if ( stream.readNext() != QXmlStreamReader::StartDocument) {
458 if (tryRecover) {
459 return recoverTaskFromBackup(taskXmlFile, "Missing start document");
460 } else {
461 return t;
464 stream >> t;
465 if (stream.error() != QXmlStreamReader::NoError) {
466 if (tryRecover) {
467 return recoverTaskFromBackup(taskXmlFile,stream.errorString());
468 } else {
469 return t;
472 else
473 return t;
476 void Sak::flushSettings()
478 QSettings settings(QSettings::IniFormat, QSettings::UserScope, "ZanzaSoft", "SAK");
479 // QByteArray tasksArray;
480 // QDataStream stream(&tasksArray, QIODevice::ReadWrite);
481 // stream.setVersion(QDataStream::Qt_4_0);
482 // stream << m_tasks;
483 // settings.setValue("tasks", tasksArray);
484 settings.setValue("Ping interval", durationSpinBox->value());
485 settings.setValue("Message", bodyEdit->toPlainText());
486 QDir settingsDir = QFileInfo(settings.fileName()).absoluteDir();
487 QString fileName = settingsDir.filePath( "lady.png");
488 askingLady->pixmap().save(fileName);
489 settings.setValue("Lady", "lady.png");
491 fileName = settingsDir.filePath( "viewbg.png");
492 QString fileNameTmp = fileName+".tmp";
493 viewBackground->pixmap().save(fileNameTmp, "png");
494 QFile::rename(fileName, fileName + ".tmp.tmp");
495 QFile::rename(fileNameTmp, fileName);
496 QFile::remove( fileName + ".tmp.tmp");
497 settings.setValue("View background", "viewbg.png");
498 settings.sync();
501 void Sak::flush()
503 if (m_changedTask)
504 saveTaskChanges();
505 if (m_changedHit)
506 saveHitChanges();
508 if (!m_settings) return;
509 m_backupper->doCyclicBackup();
510 QSettings settings(QSettings::IniFormat, QSettings::UserScope, "ZanzaSoft", "SAK");
513 QDir saveDir(QFileInfo(settings.fileName()).dir());
514 saveDir.mkdir("SakTasks");
515 saveDir.cd("SakTasks");
517 foreach(Task t, m_tasks) {
518 if (t.title.isEmpty()) continue;
519 QFile xmlTaskSave(saveDir.filePath(t.title + ".xml"));
520 QByteArray taskArray;
521 QXmlStreamWriter stream(&taskArray);
522 stream.setAutoFormatting(true);
523 stream.setAutoFormattingIndent(2);
524 stream.writeStartDocument();
525 stream << t;
526 stream.writeEndDocument();
527 xmlTaskSave.open(QIODevice::ReadWrite | QIODevice::Truncate);
528 qDebug() << "Saving xml to file " << xmlTaskSave.fileName();
529 QByteArray hash;
530 hash.append("<!-- ");
531 hash.append( QCryptographicHash::hash(taskArray, QCryptographicHash::Md5).toHex() );
532 hash.append(" -->\n");
533 xmlTaskSave.write(hash);
534 xmlTaskSave.write(taskArray);
535 xmlTaskSave.close();
536 #ifdef Q_OS_WIN32
537 // flush buffers to disk
538 _flushall();
539 #endif
542 // remove files not matching a task
543 QStringList nameFilters;
544 nameFilters << "*.xml";
545 QStringList files = saveDir.entryList( nameFilters, QDir::Files);
546 foreach (QString taskXmlFileName, files) {
547 if (!m_tasks.contains(QFileInfo(taskXmlFileName).baseName())) {
548 qWarning()<< "Remove task " << QFileInfo(taskXmlFileName).baseName() << " from disk";
549 QFile(saveDir.filePath(taskXmlFileName)).remove();
554 m_incremental->clearAddedPieces();
557 //void Sak::saveAsDb()
559 // if (!m_settings) return;
560 // QString fileName = QFileDialog::getSaveFileName();
561 // QFile file(fileName);
562 // file.remove();
563 // flush();
564 // QSettings settingsQSettings::IniFormat, QSettings::UserScope, ("ZanzaSoft", "SAK");
565 // QFile file1(settings.fileName());
566 // if (!file1.copy(fileName)) {
567 // qWarning() << "Error copying " << settings.fileName() << " to " << fileName << file1.errorString();
568 // }
571 void Sak::exportDbCsv()
573 if (!m_settings) return;
574 QString fileName = QFileDialog::getSaveFileName();
575 QFile file(fileName);
576 if (!file.open(QIODevice::ReadWrite|QIODevice::Truncate)) {
577 QMessageBox::warning(0, "Error saving", QString("Error saving to file %1").arg(fileName));
578 return;
580 QTextStream stream(&file);
581 foreach(const Task& t, m_tasks) {
582 QHash< QString, QList< Task::Hit > >::const_iterator itr = t.hits.begin();
583 while(itr != t.hits.end()) {
584 QList< Task::Hit >::const_iterator hitr = itr.value().begin(), hend = itr.value().end();
585 while(hitr != hend) {
586 stream << t.title << ";" << itr.key() << ";" << hitr->timestamp.toString(DATETIMEFORMAT) << ";" << hitr->duration << ";\n";
587 hitr++;
589 itr++;
592 file.close();
596 void Sak::logInGmail()
598 m_gmail->forceLogin();
601 void Sak::saveToGmail()
603 if (!m_settings) return;
604 flush();
605 QSettings settings(QSettings::IniFormat, QSettings::UserScope, "ZanzaSoft", "SAK");
607 QDir saveDir(QFileInfo(settings.fileName()).dir());
608 saveDir.mkdir("SakTasks");
609 saveDir.cd("SakTasks");
610 QStringList nameFilters;
611 nameFilters << "*.xml";
612 QStringList files = saveDir.entryList( nameFilters, QDir::Files);
613 QStringList filePaths;
614 foreach (QString taskXmlFileName, files) {
615 filePaths << saveDir.filePath(taskXmlFileName);
617 m_gmail->storeTaskFiles(filePaths);
620 void Sak::importFromGmail()
622 QStringList filePaths = m_gmail->fetchLatestTasks();
625 void Sak::open(const QStringList& _fileNames)
627 QStringList fileNames = _fileNames.size()?_fileNames:QFileDialog::getOpenFileNames(0, "Open a new task", QString(), "*.xml" );
628 foreach(QString fileName, fileNames) {
629 QFile file(fileName);
630 if (!file.exists()) {
631 QMessageBox::warning(0, "Cannot find task", QString("Cannot find task file %1").arg(fileName));
634 QSettings settings(QSettings::IniFormat, QSettings::UserScope, "ZanzaSoft", "SAK");
635 QDir saveDir(QFileInfo(settings.fileName()).dir());
636 saveDir.mkdir("SakTasks");
637 saveDir.cd("SakTasks");
639 if ( QFile(saveDir.filePath(QFileInfo(fileName).completeBaseName())).exists() ) {
640 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?");
641 QPushButton* overwriteButton = mbox.addButton("Overwrite", QMessageBox::YesRole);
642 QPushButton* mergeButton = mbox.addButton("Merge", QMessageBox::NoRole);
643 QPushButton* cancelButton = mbox.addButton("Cancel", QMessageBox::RejectRole);
644 mbox.setDefaultButton(cancelButton);
645 mbox.exec();
646 QAbstractButton* b = mbox.clickedButton();
647 if (b == cancelButton) { continue; }
648 else {
649 m_backupper->doCyclicBackup();
650 if (b == mergeButton) {
651 Task t = loadTaskFromFile(file.fileName());
652 QHash< QString, QList< Task::Hit > > ::const_iterator itr = t.hits.begin(), end = t.hits.end();
653 while(itr != end) {
654 QString subtask = itr.key();
655 foreach(Task::Hit hit, itr.value())
656 m_incremental->addPiece(t.title, subtask, hit.timestamp, hit.duration);
657 itr++;
659 interactiveMergeHits();
660 } else if (b == overwriteButton) {
661 file.copy(saveDir.filePath(QFileInfo(fileName).completeBaseName()));
667 if (!fileNames.isEmpty()) {
668 m_settings->hide();
669 destroy();
670 init();
671 m_settings->show();
672 start();
676 void Sak::destroy()
678 stop();
679 if (!m_settings) return;
680 flush();
681 flushSettings();
682 m_settings->deleteLater();
683 m_view->scene()->deleteLater();
684 m_view->deleteLater();
685 delete m_backupper;
686 delete m_incremental;
687 delete m_gmail;
688 m_previewing = false;
689 m_changedHit = false;
690 m_timerId = 0;
694 Sak::~Sak()
696 killTimer(m_autoSaveTimer); m_autoSaveTimer=-1;
697 destroy();
701 void Sak::layoutSubTasks( const QMap<int, SakSubWidget*> sortedWidgets, int currentRank) {
702 QMap<int, SakSubWidget*>::const_iterator itr = sortedWidgets.begin(), end = sortedWidgets.end();
703 QRect r = m_desktopRect;
704 for(int i=0; itr != end; i++, itr++) {
705 int h = (*itr)->size().height();
706 int w = (*itr)->size().width();
707 (*itr)->setPos(QPointF((r.width() - w)/2, (r.height()-h)/2 + (i - currentRank - 1) * (h+2)));
712 int Sak::taskCounter = 0;
714 bool Sak::eventFilter(QObject* obj, QEvent* e)
716 // if (obj == m_view) {
717 // qDebug() << "event : " << e->type();
718 // }
719 if (obj == tasksTree) {
720 return taskTreeEventFilter(e);
721 } else if (obj == hitsList || obj == summaryList) {
722 return hitsListEventFilter(e);
723 } else if (obj == summaryView) {
724 if (e->type() == QEvent::Resize || e->type() == QEvent::Show) {
725 summaryView->fitInView(summaryView->sceneRect(), Qt::KeepAspectRatio);
727 return false;
728 } else if (obj == m_settings && e->type() == QEvent::Close) {
729 flush();
730 flushSettings();
731 if (m_changedTask)
732 saveTaskChanges();
733 if (m_changedHit)
734 saveHitChanges();
735 if (trayIcon->isVisible()) {
736 m_settings->hide();
737 e->ignore();
738 return true;
740 } else if (obj == m_view && e->type() == QEvent::Wheel) {
741 QWheelEvent* we = dynamic_cast<QWheelEvent*>(e);
742 if (m_subtaskView) {
743 scrollSubTasks(we->delta() / 120);
744 } else scrollTasks(we->delta() / 120);
745 } else if (obj == m_view && e->type() == QEvent::KeyPress) {
746 QKeyEvent* ke = dynamic_cast<QKeyEvent*>(e);
747 if ((ke->modifiers() & Qt::AltModifier) && (ke->modifiers() & Qt::ControlModifier) ) {
748 clearView();
749 return true;
750 } else if ( ((ke->modifiers() & Qt::ControlModifier) && (ke->key() == Qt::Key_Backspace) )
751 || ((ke->modifiers() & Qt::ControlModifier) && (ke->key() == Qt::Key_Left) )) {
752 if (m_subtaskView) {
753 popup();
754 return true;
756 } else if (m_subtaskView && ke->key() == Qt::Key_Up) {
757 scrollSubTasks(-1);
758 return true;
759 } else if (m_subtaskView && ke->key() == Qt::Key_Down) {
760 scrollSubTasks(+1);
761 return true;
762 } else if (!m_subtaskView && ke->key() == Qt::Key_Left) {
763 scrollTasks(-1);
764 return true;
765 } else if (!m_subtaskView && ke->key() == Qt::Key_Right) {
766 scrollTasks(+1);
767 return true;
768 } else if (!m_subtaskView && ke->key() == Qt::Key_Escape) {
769 clearView();
770 return true;
771 } else { // forward events to current widget
772 if (!m_subtaskView) {
773 if (m_widgetsIterator == m_widgets.end()) return false;
774 SakWidget* currentShowing = m_widgetsIterator.value();
775 currentShowing->keyPressEvent(ke);
776 return true;
777 } else {
778 // autoscroll on text completion!!!
779 if (m_subwidgetsIterator == m_subwidgets.end()) return false;
780 SakSubWidget* currentShowing = m_subwidgetsIterator.value();
781 currentShowing->keyPressEvent(ke);
783 if (m_subWidgetRank != 0 && m_subtaskCompleter) {
784 QString completion(m_subtaskCompleter->completionPrefix());
785 if (ke->text().size() == 1) {
786 if (ke->key() == Qt::Key_Backslash || ke->key() == Qt::Key_Backspace)
787 completion.chop(1);
788 else completion += ke->text();
789 m_subtaskCompleter->setCompletionPrefix(completion);
792 QStringList list( ((QStringListModel*)m_subtaskCompleter->model())->stringList() );
793 int newRank = 1 + ((QStringListModel*)m_subtaskCompleter->model())->stringList().indexOf(m_subtaskCompleter->currentIndex().row() >= 0 && completion.size() ? m_subtaskCompleter->currentCompletion() : completion);
795 if (m_subWidgetRank != newRank) {
796 scrollSubTasks(newRank - m_subWidgetRank);
797 if (newRank == 0) {
798 QLineEdit* editor = dynamic_cast<QLineEdit*>((*m_subwidgets.begin())->widget());
799 if (editor) {
800 editor->setText(completion);
805 } else if (m_subtaskCompleter) {
806 QLineEdit* editor = dynamic_cast<QLineEdit*>((*m_subwidgets.begin())->widget());
807 if (editor) {
808 m_subtaskCompleter->setCompletionPrefix(editor->text());
812 return true;
815 } else if (obj == m_view && e->type() == QEvent::Show) {
816 grabKeyboard();
817 QTimer::singleShot(100, this, SLOT(grabKeyboard()));
818 }else if (obj == m_view && e->type() == QEvent::Close) {
819 if (trayIcon->isVisible()) {
820 return true;
822 } else if (obj && obj == trayIcon && e->type() == QEvent::ToolTip) {
823 QDateTime last = m_incremental->lastTimeStamp;
824 int seconds = QDateTime::currentDateTime().secsTo(m_nextTimerEvent);
825 int hours = seconds / 3600;
826 int minutes = (seconds / 60) % 60;
827 seconds %= 60;
828 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>")))));
829 return false;
831 return false;
834 //END basic >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
837 //BEGIN Tasks >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
838 void Sak::addDefaultTask()
840 QString tentativeName;
841 do {
842 tentativeName = QString("Task %1").arg(taskCounter++);
843 } while(m_editedTasks.contains(tentativeName));
845 Task& t = m_editedTasks[tentativeName];
846 t.title = tentativeName;
847 QTreeWidgetItem* item = new QTreeWidgetItem(QStringList(tentativeName));
848 item->setData(0,Qt::UserRole, QVariant(QMetaType::VoidStar, &t));
849 tasksTree->addTopLevelItem(item);
850 m_changedTask=true;
853 void Sak::populateTasks()
855 tasksTree->clear();
857 QHash<QString, Task>::iterator itr = m_editedTasks.begin(), end=m_editedTasks.end();
858 for(; itr!=end; itr++) {
859 Task& t(itr.value());
860 t.checkConsistency();
862 if (t.title.isEmpty() || t.title == "<away>") continue; // skip away task
863 QTreeWidgetItem* item = new QTreeWidgetItem(QStringList(t.title));
864 item->setData(0, Qt::UserRole, QVariant(QMetaType::VoidStar, &t));
865 QIcon icon;
866 icon.addPixmap(t.icon);
867 item->setSizeHint(0, QSize(32,32));
868 item->setIcon(0, icon);
869 for(int i=0; i<3; i++) {
870 item->setForeground(i,t.fgColor);
871 item->setBackground(i,t.bgColor);
873 //item->setCheckState(1, t.active ? Qt::Checked : Qt::Unchecked);
874 //item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
875 item->setIcon(1,QIcon(t.active ? ":/images/active.png" : ":/images/inactive.png"));
876 item->setText(2,QString("%1 hours worked till now (overestimated %2)").arg(t.totHours, 4, 'f', 2, ' ').arg(t.totOverestimation));
877 foreach(Task::SubTask st, t.subTasks) {
878 if (!st.title.isEmpty()) {
879 QTreeWidgetItem* sitem = new QTreeWidgetItem(item, QStringList(st.title));
880 item->setData(0, Qt::UserRole, QVariant(QMetaType::VoidStar, &st));
881 sitem->setSizeHint(0, QSize(32,32));
882 QColor fgColor = st.fgColor.isValid() ? st.fgColor : t.fgColor;
883 QColor bgColor = st.bgColor.isValid() ? st.bgColor : t.bgColor;
884 for(int i=0; i<3; i++) {
885 sitem->setForeground(i,fgColor);
886 sitem->setBackground(i,bgColor);
888 sitem->setIcon(1,QIcon(st.active ? ":/images/active.png" : ":/images/inactive.png"));
889 sitem->setText(2,QString("%1 hours worked till now").arg(st.totHours,4,'f',2,' '));
892 tasksTree->addTopLevelItem(item);
896 void Sak::saveTaskChanges()
898 if (m_changedTask) {
899 commitCurrentTask();
900 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 ) {
901 m_tasks = m_editedTasks;
902 } else m_editedTasks = m_tasks; //. undo changes
903 m_changedTask=false;
904 selectedStartDate(QDate());
905 populateTasks();
909 void Sak::selectColor() {
910 if (tasksTree->selectedItems().isEmpty()) return;
912 if (sender() == fgColorButton) {
913 QColor c = QColorDialog::getColor(fgColorButton->palette().color(QPalette::ButtonText));
914 if (!c.isValid()) return;
915 QPalette p = fgColorButton->palette();
916 p.setColor(QPalette::ButtonText, c);
917 fgColorButton->setPalette(p);
918 bgColorButton->setPalette(p);
919 } else if (sender() == bgColorButton) {
920 QColor c = QColorDialog::getColor(bgColorButton->palette().color(QPalette::Button));
921 if (!c.isValid()) return;
922 QPalette p = bgColorButton->palette();
923 p.setColor(QPalette::Button, c);
924 fgColorButton->setPalette(p);
925 bgColorButton->setPalette(p);
927 commitCurrentTask();
930 bool Sak::taskTreeEventFilter(QEvent* e)
932 if (e->type() == QEvent::ContextMenu) {
933 QContextMenuEvent* me = dynamic_cast<QContextMenuEvent*>(e);
934 if (!me) return false;
935 m_addTaskMenu->popup(me->globalPos());
936 return true;
937 } else if (e->type() == QEvent::KeyRelease) {
938 QKeyEvent* ke = dynamic_cast<QKeyEvent*>(e);
939 if (!ke) return false;
940 if ( (ke->key() != Qt::Key_Delete && ke->key() != Qt::Key_Backspace) ) return false;
941 if (currentSubtask!="") {
942 QMessageBox whatToDo(QMessageBox::Warning, "Deleting subtask", "Deleting subtask " + currentSubtask + " of task " + currentTask);
943 QPushButton* moveHitsToParentButton = whatToDo.addButton("Move hits to task " + currentTask, QMessageBox::AcceptRole);
944 QPushButton* removeHitsButton = whatToDo.addButton("Remove hits", QMessageBox::AcceptRole);
945 QPushButton* cancelButton = whatToDo.addButton("Cancel", QMessageBox::RejectRole);
946 whatToDo.setDefaultButton(cancelButton);
947 whatToDo.exec();
948 if ( whatToDo.clickedButton() == cancelButton) return true;
949 if (m_editedTasks.find(currentTask) == m_editedTasks.end()) return true;
950 m_changedTask=true;
951 Task& t(m_editedTasks[currentTask]);
952 t.subTasks.take(currentSubtask);
953 if (whatToDo.clickedButton() == removeHitsButton) {
954 t.hits.take(currentSubtask);
955 } else if (whatToDo.clickedButton() == moveHitsToParentButton) {
956 QList<Task::Hit> sorter(t.hits.take(""));
957 sorter << t.hits.take(currentSubtask);
958 qStableSort(sorter.begin(), sorter.end());
959 t.hits[""] = sorter;
961 } else {
962 // remove file from disk
963 m_changedTask=true;
964 m_editedTasks.remove(currentTask);
966 tasksTree->clear();
967 populateTasks();
968 selectedStartDate(QDate());
969 return true;
970 } else if (e->type() == QEvent::Hide) {
971 saveTaskChanges();
973 return false;
977 void Sak::commitCurrentTask()
979 m_changedTask=true;
980 if (currentSubtask.isEmpty()) {
981 QString currentTitle = taskTitleEditor->text();
982 if (!currentTitle.isEmpty()) {
983 if (currentTitle != currentTask) {
984 if (m_editedTasks.contains(currentTitle)) {
985 QMessageBox::warning(0, "Conflict in task names", "Conflict in task names: current task " + currentTask + ", edited title " + currentTitle);
986 taskTitleEditor->setText(currentTask);
987 return;
988 } else if (m_editedTasks.contains(currentTask)) {
989 m_editedTasks[currentTitle] = m_editedTasks.take(currentTask);
990 m_editedTasks[currentTitle].title = currentTitle;
993 } else return;
994 Task& t = m_editedTasks[currentTitle];
995 t.description = taskTextEditor->toPlainText();
996 t.tags = taskTagsEditor->text().split(QRegExp("[,:]"));
997 t.url = taskUrlEditor->text();
998 t.bgColor = bgColorButton->palette().color(QPalette::Button);
999 t.fgColor = fgColorButton->palette().color(QPalette::ButtonText);
1000 t.icon = taskPixmapViewer->pixmap();
1001 QList<QTreeWidgetItem *> items = tasksTree->findItems(currentTask,Qt::MatchExactly,0);
1002 foreach(QTreeWidgetItem* ii, items) {
1003 ii->setText(0, currentTitle);
1004 ii->setIcon(0, taskPixmapViewer->pixmap());
1005 for (int i=0; i<3; i++) {
1006 ii->setForeground(i, QColor(t.fgColor));
1007 ii->setBackground(i, QColor(t.bgColor));
1010 if (dueEditor->date() != dueEditor->minimumDate())
1011 t.dueDate = dueEditor->date();
1012 t.estimatedHours = estimatedHoursEditor->value();
1013 currentTask=currentTitle;
1015 if (tasksTree->selectedItems().size() != 1) return;
1016 QTreeWidgetItem* item = tasksTree->selectedItems()[0];
1017 item->setText(0, taskTitleEditor->text());
1018 QIcon icon;
1019 icon.addPixmap(t.icon);
1020 item->setSizeHint(0, QSize(32,32));
1021 item->setIcon(0, icon);
1022 for(int i=0; i<3; i++) {
1023 item->setForeground(i,t.fgColor);
1024 item->setBackground(i,t.bgColor);
1028 } else { // subtask edited
1029 if (!m_editedTasks.contains(currentTask)) return;
1030 Task& t(m_editedTasks[currentTask]);
1031 QString currentTitle = taskTitleEditor->text();
1032 // backup data
1033 if (!currentTitle.isEmpty()) {
1034 if (currentTitle != currentSubtask) {
1035 if (t.subTasks.contains(currentTitle)) {
1036 QMessageBox::warning(0, "Conflict in subtask names", "Conflict in subtask names");
1037 taskTitleEditor->setText(currentSubtask);
1038 return;
1039 } else if (t.subTasks.contains(currentSubtask)) {
1040 t.subTasks[currentTitle] = t.subTasks.take(currentSubtask);
1041 t.subTasks[currentTitle].title = currentTitle;
1042 t.hits[currentTitle] = t.hits.take(currentSubtask);
1045 } else return;
1046 Task::SubTask& st = t.subTasks[currentTitle];
1047 st.description = taskTextEditor->toPlainText();
1048 st.bgColor = bgColorButton->palette().color(QPalette::Button);
1049 st.fgColor = fgColorButton->palette().color(QPalette::ButtonText);
1050 QList<QTreeWidgetItem *> items = tasksTree->findItems(currentTask,Qt::MatchExactly,0);
1051 foreach(QTreeWidgetItem* jj, items) {
1052 for(int i=0; i<jj->childCount(); i++) {
1053 QTreeWidgetItem* ii = jj->child(i);
1054 if (ii->text(0) != currentSubtask) continue;
1055 ii->setText(0, currentTitle);
1056 for (int i=0; i<3; i++) {
1057 ii->setForeground(i, QColor(st.fgColor));
1058 ii->setBackground(i, QColor(st.bgColor));
1062 currentSubtask = currentTitle;
1064 if (tasksTree->selectedItems().size() != 1) return;
1065 QTreeWidgetItem* item = tasksTree->selectedItems()[0];
1066 item->setText(0, taskTitleEditor->text());
1067 QIcon icon;
1068 icon.addPixmap(t.icon);
1069 item->setSizeHint(0, QSize(32,32));
1070 item->setIcon(0, icon);
1071 for(int i=0; i<3; i++) {
1072 item->setForeground(i,st.fgColor);
1073 item->setBackground(i,st.bgColor);
1078 void Sak::selectedTask()
1080 if (tasksTree->selectedItems().isEmpty()) {
1081 taskPixmapViewer->setEnabled(false);
1082 taskPixmapViewer->setPixmap(QPixmap());
1083 taskTextEditor->setEnabled(false);
1084 taskTitleEditor->setEnabled(false);
1085 taskUrlEditor->setEnabled(false);
1086 bgColorButton->setEnabled(false);
1087 fgColorButton->setEnabled(false);
1088 dueEditor->setEnabled(false);
1089 estimatedHoursEditor->setEnabled(false);
1090 return;
1094 // Disable notifications
1095 taskPixmapViewer->blockSignals(true);
1096 taskTextEditor->blockSignals(true);
1097 taskTitleEditor->blockSignals(true);
1098 taskTagsEditor->blockSignals(true);
1099 taskUrlEditor->blockSignals(true);
1100 bgColorButton->blockSignals(true);
1101 fgColorButton->blockSignals(true);
1102 dueEditor->blockSignals(true);
1103 estimatedHoursEditor->blockSignals(true);
1106 QTreeWidgetItem* selectedItem = tasksTree->selectedItems().first();
1107 QTreeWidgetItem* parentItem = selectedItem->parent();
1108 QString tt = selectedItem->text(0);
1110 if (!parentItem) {
1111 taskPixmapViewer->setEnabled(true);
1112 taskTextEditor->setEnabled(true);
1113 taskTagsEditor->setEnabled(true);
1114 taskUrlEditor->setEnabled(true);
1115 dueEditor->setEnabled(true);
1116 estimatedHoursEditor->setEnabled(true);
1117 bgColorButton->setEnabled(true);
1118 fgColorButton->setEnabled(true);
1119 taskPixmapViewer->setEnabled(true);
1120 } else {
1121 taskPixmapViewer->setEnabled(false);
1122 taskTextEditor->setEnabled(false);
1123 taskTagsEditor->setEnabled(false);
1124 taskUrlEditor->setEnabled(false);
1125 taskPixmapViewer->setPixmap(QPixmap());
1126 dueEditor->setEnabled(false);
1127 estimatedHoursEditor->setEnabled(false);
1128 bgColorButton->setEnabled(false);
1129 fgColorButton->setEnabled(false);
1130 taskPixmapViewer->setEnabled(false);
1132 taskTitleEditor->setEnabled(true);
1135 if (!parentItem) { // editing a task
1136 if (!m_editedTasks.contains(tt)) return;
1137 const Task& t = m_editedTasks[tt];
1138 taskPixmapViewer->setPixmap(t.icon);
1139 taskTextEditor->setPlainText(t.description);
1140 taskUrlEditor->setText(t.url);
1141 taskTitleEditor->setText(t.title);
1142 QPalette p;
1143 p.setColor(QPalette::Button, t.bgColor);
1144 p.setColor(QPalette::ButtonText, t.fgColor);
1145 bgColorButton->setPalette(p);
1146 fgColorButton->setPalette(p);
1147 estimatedHoursEditor->setValue(t.estimatedHours);
1148 dueEditor->setDate(t.dueDate.isValid() ? t.dueDate : dueEditor->minimumDate());
1150 currentTask = t.title;
1151 currentSubtask = "";
1152 } else { // editing a subtask
1153 if (!m_editedTasks.contains(parentItem->text(0))) return;
1154 const Task& t = m_editedTasks[parentItem->text(0)];
1155 if (!t.subTasks.contains(tt)) return;
1156 const Task::SubTask& st = t.subTasks[tt];
1158 taskTextEditor->setPlainText(st.description);
1159 taskTitleEditor->setText(st.title);
1160 taskUrlEditor->setText(t.url);
1161 QPalette p;
1162 p.setColor(QPalette::Button, st.bgColor.isValid() ? st.bgColor : t.bgColor);
1163 p.setColor(QPalette::ButtonText, st.fgColor.isValid() ? st.fgColor : t.fgColor);
1164 bgColorButton->setPalette(p);
1165 fgColorButton->setPalette(p);
1167 currentTask = t.title;
1168 currentSubtask = st.title;
1171 // Re-enable notifications
1172 taskPixmapViewer->blockSignals(false);
1173 taskTextEditor->blockSignals(false);
1174 taskTitleEditor->blockSignals(false);
1175 taskTagsEditor->blockSignals(false);
1176 taskUrlEditor->blockSignals(false);
1177 bgColorButton->blockSignals(false);
1178 fgColorButton->blockSignals(false);
1179 dueEditor->blockSignals(false);
1180 estimatedHoursEditor->blockSignals(false);
1183 void Sak::doubleClickedTask(QTreeWidgetItem* i, int column)
1185 if (column == 1) {
1186 m_changedTask=true;
1187 if (i->parent() == 0) {
1188 QHash<QString, Task>::iterator itr = m_editedTasks.find(i->text(0));
1189 Q_ASSERT(itr != m_editedTasks.end());
1190 bool& active ( itr.value().active );
1191 active = !active;
1192 i->setIcon(column, active ? QIcon(":/images/active.png") : QIcon(":/images/inactive.png"));
1193 } else {
1194 QHash<QString, Task>::iterator itr = m_editedTasks.find(i->parent()->text(0));
1195 Q_ASSERT(itr != m_editedTasks.end());
1196 bool& active ( itr.value().subTasks[i->text(0)].active );
1197 active = !active;
1198 i->setIcon(column, active ? QIcon(":/images/active.png") : QIcon(":/images/inactive.png"));
1200 ((QTreeWidget*)sender())->update();
1206 void Sak::timerEvent(QTimerEvent* e)
1208 if (e->timerId() == m_timerId) {
1209 if (!m_view->isVisible() && !m_settings->isVisible() && m_tasks.count() > 0) {
1210 popup();
1211 // close timer
1212 killTimer(m_timerId); m_timerId=-1;
1213 killTimer(m_timeoutPopup); m_timeoutPopup=-1;
1214 int msecs = (int)(qMax( 30000.0, Task::hours(m_currentInterval)*3600.0*1000.0/10.0));
1215 if (!m_stopped) {
1216 m_timeoutPopup = startTimer(msecs);
1218 // restart timer
1219 m_nextTimerEvent = QDateTime::currentDateTime().addMSecs(msecs);
1220 } else {
1221 if (m_settings && m_settings->isVisible() && !m_settings->isActiveWindow()) {
1222 trayIcon->showMessage("Delayed check point", "Delayed check point due to open settings. Close the setting window!", QSystemTrayIcon::Warning, -1);
1223 m_settings->close();
1225 qDebug() << "SAK: wait 5 seconds";
1226 killTimer(m_timerId); m_timerId=-1;
1227 m_timerId = startTimer(5000);
1228 m_nextTimerEvent = QDateTime::currentDateTime().addMSecs(5000);
1230 } else if (e->timerId() == m_timeoutPopup) {
1231 // ensure the timer is resetted
1232 killTimer(m_timeoutPopup); m_timeoutPopup=-1;
1233 killTimer(m_timerId); m_timerId = -1;
1235 if (!m_subtaskView) {
1236 // if not selecting subtasks clear everything and signal away
1237 workingOnTask("<away>","");
1238 trayIcon->showMessage("New away events", "You have missed a check point. Fix it in the detailed hit list.", QSystemTrayIcon::Information, 999999);
1239 clearView();
1241 } else { // wait 5 seconds
1242 m_nextTimerEvent = QDateTime::currentDateTime().addMSecs(5000);
1243 m_timeoutPopup = startTimer(5000);
1245 } else if (e->timerId() == m_autoSaveTimer) {
1246 flush();
1247 } else if (e->timerId() == m_getFocusTimer) {
1248 grabKeyboard();
1249 } else {
1250 qDebug() << "unknown timer event";
1254 void Sak::clearView()
1256 m_fakeClicking = false;
1257 killTimer(m_timeoutPopup); m_timeoutPopup=-1;
1258 m_subtaskView=false;
1259 m_marker = 0;
1260 delete m_subtaskCompleter; m_subtaskCompleter = 0;
1262 if (m_view)
1263 m_view->releaseKeyboard();
1264 killTimer(m_getFocusTimer); m_getFocusTimer=-1;
1265 #if defined(Q_WS_X11)
1266 // restore focus to previous application
1267 grabbed=false;
1268 X11::XSetInputFocus((X11::Display*)QX11Info::display(), X11::CurrentFocusWindow, X11::CurrentRevertToReturn, CurrentTime);
1269 X11::XFlush((X11::Display*)QX11Info::display());
1270 #endif
1272 // restart normal timer
1273 if (!m_previewing) {
1274 killTimer(m_timerId); m_timerId=-1;
1275 int msecs = (int)(Task::hours(m_currentInterval)*3600.0*1000.0);
1276 m_timerId = startTimer(msecs);
1277 m_nextTimerEvent = QDateTime::currentDateTime().addMSecs(msecs);
1280 if (!m_view) return;
1281 QGraphicsScene* s = m_view->scene();
1282 QList<QGraphicsItem*> items = m_view->items();
1283 m_widgets.clear();
1284 m_widgetsIterator = m_widgets.end();
1285 s->deleteLater();
1286 m_view->close();
1287 m_view->setScene(new QGraphicsScene);
1288 m_view->scene()->setSceneRect(m_desktopRect);
1289 m_previewing = false;
1292 void Sak::workingOnTask(const QString& taskName, const QString& subTask)
1294 clearView();
1295 if (!m_previewing) {
1297 qDebug() << "Working on " << taskName ;
1298 if (m_tasks.contains(taskName)) {
1299 Task& t = m_tasks[taskName];
1301 if (t.title != "<away>" && !t.title.isEmpty()) {
1302 // update history
1303 int historyIndex = m_taskSelectionHistory.indexOf(t.title);
1304 if (historyIndex != -1) {
1305 m_taskSelectionHistory.takeAt(historyIndex);
1307 m_taskSelectionHistory.push_back(t.title);
1309 QList<QString> & subtaskSelectionHistory(m_subtaskSelectionHistory[t.title]);
1310 historyIndex = subtaskSelectionHistory.indexOf(subTask);
1311 if (historyIndex != -1) {
1312 subtaskSelectionHistory.takeAt(historyIndex);
1314 subtaskSelectionHistory.push_back(subTask);
1318 QDateTime now = QDateTime::currentDateTime();
1319 QHash<QString, Task>::iterator itr = m_tasks.begin();
1321 QHash<QString, QList< Task::Hit> >::iterator hitr = t.hits.begin();
1322 // merge last two hits of every subtask if they are close enough
1323 while(hitr != t.hits.end()) {
1324 QList<Task::Hit>& otHits( *hitr );
1325 if (otHits.count() > 1) {
1326 Task::Hit& lastHit(otHits[otHits.size() - 1]);
1327 Task::Hit& beforeLastHit(otHits[otHits.size() - 2]);
1329 // the list might be not sorted (eg. after startup merge)
1330 // so give up merge of last two (unsorted) hits
1331 if (lastHit.timestamp < beforeLastHit.timestamp) { hitr++; continue; }
1333 qDebug() << "Task: " << t.title << " : " << hitr.key();
1334 qDebug() << " Last hit: " << lastHit;
1335 qDebug() << " Before last hit: " << beforeLastHit;
1337 int diff = (lastHit.timestamp.toTime_t() - 30*lastHit.duration) - (beforeLastHit.timestamp.toTime_t() + 30*beforeLastHit.duration);
1338 qDebug() << " diff: " << diff;
1339 if (diff < 120 && diff > -60) { // at most 2 minutes apart and 1 overlapped
1340 beforeLastHit.timestamp = beforeLastHit.timestamp.addSecs(-30*beforeLastHit.duration);
1341 int secsToEnd = beforeLastHit.timestamp.secsTo(lastHit.timestamp.addSecs(30*m_currentInterval));
1342 if (secsToEnd > 24 * 3600 * 3600) {
1343 qWarning() << "TRAPPED ERROR IN SECS COUNT!!!!!!!!";
1344 qWarning() << "BEFORE LAST HIST WAS " << beforeLastHit.timestamp << beforeLastHit.duration;
1345 qWarning() << "LAST HIT WAS " << lastHit.timestamp << lastHit.duration;
1346 assert(secsToEnd < 24 * 3600 * 3600);
1348 beforeLastHit.timestamp = beforeLastHit.timestamp.addSecs( secsToEnd/2.0 );
1349 beforeLastHit.duration = (int)( qRound( secsToEnd / 60.0 ) );
1350 // remove the current very last hit
1351 otHits.pop_back();
1354 hitr++;
1357 // add hit to hit list
1358 // NOTE: we do not try to merge the very last hit with previous ones because we want let
1359 // the user being able to easily recover from a selection error
1360 if (workingOnDeclared) {
1361 t.hits[subTask] << Task::Hit(now.addSecs(3600.0*workingOnDeclared/2), 60*workingOnDeclared);
1362 m_incremental->writePiece(t.title, subTask, now.addSecs(3600.0*workingOnDeclared/2), 60*workingOnDeclared);
1363 stopAction->trigger();
1364 workingOnTimer->setSingleShot(true);
1365 workingOnTimer->setInterval(3600*1000*workingOnDeclared);
1366 workingOnTimer->start();
1367 workingOnDeclared = 0;
1368 } else {
1369 t.hits[subTask] << Task::Hit(now, m_currentInterval);
1370 m_incremental->writePiece(t.title, subTask, now, m_currentInterval);
1372 t.checkConsistency();
1373 QList<QTreeWidgetItem*> items = tasksTree->findItems (t.title, Qt::MatchExactly, 0);
1374 if (!items.isEmpty())
1375 items.first()->setText(1, QString("%1 hours worked till now (overestimated %2)").arg(t.totHours).arg(t.totOverestimation));
1377 // update subtask if new added!
1378 t.updateSubTasks();
1380 // update statistics !!!!
1381 m_editedTasks = m_tasks;
1382 QMetaObject::invokeMethod(this, "selectedStartDate", Qt::QueuedConnection, Q_ARG(QDate, cal1->selectedDate()));
1383 flush();
1389 // attractor = 't' (top), 'b', 'l', 'r'
1390 void layoutInSquare( QList<SakWidget*> sortedWidgets, QRect rect, char attractor)
1392 int w = rect.width();
1393 if (rect.width() < 64) return;
1394 int maxw = qMin(350, w/2);
1395 QSize size(maxw, maxw);
1396 if (sortedWidgets.count() == 0) {
1397 return;
1398 } else if (sortedWidgets.count() == 1) {
1399 sortedWidgets[0]->setGeometry(rect);
1400 } else if (sortedWidgets.count() == 2) {
1401 QPoint off1, off2;
1402 if (attractor == 'C') {
1403 off1 = QPoint(0,w/4);
1404 off2 = QPoint(w/2,w/4);
1405 } else if (attractor == 'T') {
1406 off1 = QPoint(0,0);
1407 off2 = QPoint(w/2,0);
1408 } else if (attractor == 'B') {
1409 off1 = QPoint(0,w/2);
1410 off2 = QPoint(w/2,w/2);
1411 } else if (attractor == 'R') {
1412 off1 = QPoint(w/2,0);
1413 off2 = QPoint(w/2,w/2);
1414 } else if (attractor == 'L') {
1415 off1 = QPoint(0,0);
1416 off2 = QPoint(0,w/2);
1418 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + off1, size));
1419 sortedWidgets[1]->setGeometry(QRect(rect.topLeft() + off2, size));
1420 } else if (sortedWidgets.count() == 3) {
1421 QPoint off1, off2, off3;
1422 if (attractor == 'T' || attractor == 'C') {
1423 off1 = QPoint(0,0);
1424 off2 = QPoint(w/2,0);
1425 off3 = QPoint(w/4,w/2);
1426 } else if (attractor == 'B') {
1427 off1 = QPoint(0,w/2);
1428 off2 = QPoint(w/2,w/2);
1429 off3 = QPoint(w/4,0);
1430 } else if (attractor == 'R') {
1431 off1 = QPoint(w/2,0);
1432 off2 = QPoint(w/2,w/2);
1433 off3 = QPoint(0,w/4);
1434 } else if (attractor == 'L') {
1435 off1 = QPoint(0,0);
1436 off2 = QPoint(0,w/2);
1437 off3 = QPoint(w/2,w/4);
1439 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + off1, size));
1440 sortedWidgets[1]->setGeometry(QRect(rect.topLeft() + off2, size));
1441 sortedWidgets[2]->setGeometry(QRect(rect.topLeft() + off3, size));
1442 } else if (sortedWidgets.count() == 4) {
1443 QPoint off1(0,0);
1444 QPoint off2(0,w/2);
1445 QPoint off3(w/2,0);
1446 QPoint off4(w/2,w/2);
1447 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + off1, size));
1448 sortedWidgets[1]->setGeometry(QRect(rect.topLeft() + off2, size));
1449 sortedWidgets[2]->setGeometry(QRect(rect.topLeft() + off3, size));
1450 sortedWidgets[3]->setGeometry(QRect(rect.topLeft() + off4, size));
1451 } else {
1452 Q_ASSERT(sortedWidgets.count() <= 4);
1456 void layoutInRect( QList<SakWidget*> sortedWidgets, QRect rect, char attractor)
1458 if (sortedWidgets.count() == 0) return;
1459 int h = rect.height();
1460 int w = rect.width();
1461 int maxh = qMin(350, h);
1462 int maxw = qMin(350, w);
1463 if (sortedWidgets.count() == 1) {
1464 if (w>h) {
1465 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + QPoint((w-maxh)/2,(h-maxh)/2), QSize(maxh,maxh)));
1466 } else {
1467 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + QPoint((w-maxw)/2,(h-maxw)/2), QSize(maxw,maxw)));
1469 return;
1470 } else if (sortedWidgets.count() == 2) {
1471 if (w>h) {
1472 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + QPoint(w/2-maxh,(h-maxh)/2), QSize(maxh,maxh)));
1473 sortedWidgets[1]->setGeometry(QRect(rect.topLeft() + QPoint(w/2,(h-maxh)/2), QSize(maxh,maxh)));
1474 } else {
1475 sortedWidgets[0]->setGeometry(QRect(rect.topLeft() + QPoint((h-maxw)/2,w-maxw), QSize(w,w)));
1476 sortedWidgets[1]->setGeometry(QRect(rect.topLeft() + QPoint((h-maxw)/2,w/2), QSize(maxw,maxw)));
1478 return;
1480 if (h < 64 || w < 64) return;
1481 QList<SakWidget*> leftList, rightList;
1482 for (int i=4; i<sortedWidgets.count(); i++) {
1483 if (i%2)
1484 rightList << sortedWidgets[i];
1485 else
1486 leftList << sortedWidgets[i];
1488 if (w > h) {
1489 QRect square(rect.topLeft() + QPoint(h/2, 0), QSize(h,h));
1490 layoutInSquare(sortedWidgets.mid(0,4), square, attractor);
1492 QRect leftRect(rect.topLeft(), QSize(h/2, h));
1493 layoutInRect(leftList, leftRect, 'R');
1494 QRect rightRect(rect.topLeft() + QPoint((int)(0.75*w),0), QSize(h/2, h));
1495 layoutInRect(rightList, rightRect, 'L');
1496 } else {
1497 QRect square(rect.topLeft() + QPoint(0,w/2), QSize(w,w));
1498 layoutInSquare(sortedWidgets.mid(0,4), square, attractor);
1500 QRect leftRect(rect.topLeft(), QSize(w, w/2));
1501 layoutInRect(leftList, leftRect, 'B');
1502 QRect rightRect(rect.topLeft() + QPoint(0,(int)(0.75*h)), QSize(w, w/2));
1503 layoutInRect(rightList, rightRect, 'T');
1507 QRect Sak::Layouting( const QList<SakWidget*>& sortedWidgets)
1509 QRect r = m_desktopRect;
1510 int height = (int)(0.75 * r.height());
1511 int width = r.width();
1513 int firstW = width / 2 < height ? width : height * 2;
1514 int firstH = firstW / 2;
1515 QRect firstRect (r.x() + (width - firstW) / 2, r.y() + (height - firstH) / 2 + (int)(r.height() * 0.25), firstW, firstH);
1517 layoutInRect(sortedWidgets, firstRect, 'C');
1518 return QRect(QPoint(r.x(), r.y()), QSize(r.width(), (int)(0.25 * r.height())));
1521 void Sak::grabKeyboard()
1523 #if defined(Q_WS_X11)
1524 if (!grabbed) {
1525 // save current focused application
1526 XGetInputFocus((X11::Display*)QX11Info::display(), &X11::CurrentFocusWindow, &X11::CurrentRevertToReturn);
1527 grabbed=true;
1529 if (X11::CurrentFocusWindow != QX11Info::appRootWindow(QX11Info::appScreen()) ) {
1530 X11::XSetInputFocus((X11::Display*)QX11Info::display(), QX11Info::appRootWindow(QX11Info::appScreen()), RevertToParent, CurrentTime);
1531 X11::XFlush((X11::Display*)QX11Info::display());
1533 #endif
1534 #if defined(Q_WS_WIN)
1535 SetForegroundWindow(m_view->winId());
1537 if(m_fakeClicking) {
1538 qDebug() << "fake clicking";
1539 INPUT input = {0};
1541 input.mi.dx = 0;
1542 input.mi.dy = 0;
1543 input.mi.mouseData =XBUTTON1;
1544 input.mi.dwFlags = MOUSEEVENTF_MOVE|MOUSEEVENTF_XDOWN|MOUSEEVENTF_XUP;
1545 SendInput(1, &input, sizeof(INPUT));
1548 #endif
1549 m_view->grabKeyboard();
1550 qApp->alert(m_view, 50);
1552 void Sak::popup()
1554 // Reset to 0 working onn hours in case not called from "working on"
1555 workingOnDeclared = 0;
1557 if (m_stopped) {
1558 trayIcon->showMessage("SAK popup disabled", "SAK triggered a new event but no popup will be shown", QSystemTrayIcon::Information, -1);
1559 return;
1562 // save changes first
1563 if (m_changedTask)
1564 saveTaskChanges();
1565 if (m_changedHit)
1566 saveHitChanges();
1568 if (m_subtaskView) {
1569 // remove subtasks
1570 foreach(SakSubWidget* w, m_subwidgets.values()) {
1571 w->scene()->removeItem(w);
1572 delete w;
1574 m_subwidgets.clear();
1576 m_marker->scene()->removeItem(m_marker);
1577 delete m_marker;
1579 // unhide tasks
1580 foreach(SakWidget* w, m_widgets.values()) {
1581 w->show();
1583 m_subtaskView=false;
1584 return;
1587 m_subtaskView = false;
1589 if (sender() == previewButton) {
1590 m_previewing = true;
1592 QDateTime now = QDateTime::currentDateTime();
1593 QDateTime fromWeek = now;
1594 fromWeek.setDate(now.date().addDays(-now.date().dayOfWeek() + 1));
1595 fromWeek.setTime(QTime(0,0,0));
1596 QDateTime fromMonth = now;
1597 fromMonth.setDate(now.date().addDays(-now.date().day()));
1598 fromMonth.setTime(QTime(0,0,0));
1599 QDateTime fromToday = now;
1600 fromToday.setTime(QTime(0,0,0));
1602 double weekHits = 0;
1603 double dayHits = 0;
1604 double monthHits = 0;
1605 QHash<QString, double> dayStats;
1606 QHash<QString, double> weekStats;
1607 QHash<QString, double> monthStats;
1609 for(QHash<QString, Task>::iterator itr = m_tasks.begin(); itr!=m_tasks.end(); itr++) {
1610 Task& t(itr.value());
1611 t.checkConsistency();
1612 if (t.active) {
1613 double m = t.workedHours(fromMonth, now);
1614 monthStats[t.title] = m;
1615 monthHits += m;
1616 double w = t.workedHours(fromWeek, now);
1617 weekStats[t.title] = w;
1618 weekHits += w;
1619 double d = t.workedHours(fromToday, now);
1620 dayStats[t.title] = d;
1621 dayHits += d;
1626 m_widgets.clear();
1627 foreach(const Task& t, m_tasks.values()) {
1628 if (!t.active || t.title == QString() || t.title == "<away>") continue;
1629 SakWidget* test = new SakWidget(t);
1630 test->setVisible(false);
1631 double d = dayStats[t.title];
1632 double w = weekStats[t.title];
1633 double m = monthStats[t.title];
1634 test->setStatistics(d, w, m, d/dayHits * 100.0, w/weekHits * 100.0, m/monthHits * 100.0);
1635 test->setObjectName(t.title);
1636 connect (test, SIGNAL(clicked(const QString&, const QString&)), this, SLOT(workingOnTask(const QString&, const QString&)));
1637 connect (test, SIGNAL(clicked(const QString&)), this, SLOT(popupSubtasks(const QString&)));
1638 int historyPosition = 1 + m_taskSelectionHistory.indexOf(t.title);
1639 int rank = historyPosition != 0 ? -1000000 * historyPosition : -d;
1640 m_widgets.insertMulti( rank, test);
1643 m_widgetsIterator = m_widgets.begin();
1644 if (m_widgetsIterator != m_widgets.end()) {
1645 m_widgetsIterator.value()->showDetails(true);
1649 const QList<SakWidget*>& values = m_widgets.values();
1650 QRect messageRect = Layouting((const QList<SakWidget*>&)values);
1651 foreach(SakWidget* w, values) {
1652 m_view->scene()->addItem(w);
1653 w->show();
1657 // add the message item
1658 #ifdef CENSORED
1659 QPixmap askingLadyPixmap(":/images/whip_vale.png");
1660 #else
1661 QPixmap askingLadyPixmap(":/images/whip.png");
1662 #endif
1663 if (!askingLady->pixmap().isNull()) {
1664 askingLadyPixmap = askingLady->pixmap();
1667 if (!bodyEdit->toPlainText().isEmpty()) {
1668 SakMessageItem* sakMessage = new SakMessageItem(bodyEdit->toPlainText(), askingLadyPixmap);
1669 sakMessage->setGeometry(messageRect);
1670 m_view->scene()->addItem(sakMessage);
1671 sakMessage->show();
1674 // add the exit item
1675 SakExitItem* exitItem = new SakExitItem(QPixmap(":/images/exit.png"));
1676 QRect r = m_desktopRect;
1677 connect(exitItem, SIGNAL(exit()), this, SLOT(clearView()));
1678 exitItem->setPos(r.width() - exitItem->boundingRect().width(), 0);
1679 m_view->scene()->addItem(exitItem);
1680 exitItem->setZValue(1e8);
1681 exitItem->show();
1684 m_view->setGeometry( QRect(m_desktopRect)/*.adjusted(200,200,-200,-200 )*/ );
1685 m_view->backgroundPixmap = viewBackground->pixmap().scaled(r.size(), Qt::KeepAspectRatioByExpanding);
1686 m_view->show();
1687 m_view->raise();
1688 m_view->setFocus();
1689 #if defined(Q_WS_WIN)
1690 SetForegroundWindow(m_view->winId());
1691 #endif
1692 m_getFocusTimer = startTimer( 500 );
1693 qApp->alert(m_view, 100);
1694 m_fakeClicking=true;
1698 void Sak::popupSubtasks(const QString& _taskname) {
1699 m_fakeClicking=false;
1700 killTimer(m_timeoutPopup); m_timeoutPopup=-1;
1702 QString taskname = _taskname;
1703 if (taskname.isEmpty()) {
1704 if ( m_taskSelectionHistory.isEmpty() ) {
1705 return;
1706 } else {
1707 taskname = m_taskSelectionHistory.back();
1709 QString subtaskName;
1710 if (!m_subtaskSelectionHistory[taskname].isEmpty()) {
1711 subtaskName = m_subtaskSelectionHistory[taskname].back();
1713 workingOnTask(taskname, subtaskName);
1716 #if 0
1717 #ifdef Q_OS_WIN32
1718 // This structure will be used to create the keyboard
1719 // input event.
1720 INPUT ip = {0};
1721 // send cltr + alt to exit from vmware
1722 // Set up a generic keyboard event.
1723 ip.type = INPUT_KEYBOARD;
1724 ip.ki.wScan = 0; // hardware scan code for key
1725 ip.ki.time = 0;
1726 ip.ki.dwExtraInfo = 0;
1728 // Press the "CTRL+ALT" key
1729 ip.ki.dwFlags = 0; // 0 for key press
1731 ip.ki.wVk = VK_CONTROL; // virtual-key code for the "CTRL" key
1732 ip.ki.wScan = 0x1d;
1733 qDebug() << SendInput(1, &ip, sizeof(INPUT));
1734 ip.ki.wVk = VK_MENU; // virtual-key code for the "ALT" key
1735 ip.ki.wScan = 0x38;
1736 qDebug() << SendInput(1, &ip, sizeof(INPUT));
1738 // Release the "CTRL+ALT" key
1739 ip.ki.dwFlags = KEYEVENTF_KEYUP; // KEYEVENTF_KEYUP for key release
1741 ip.ki.wVk = VK_CONTROL; // virtual-key code for the "CTRL" key
1742 ip.ki.wScan = 0x1d;
1743 qDebug() << SendInput(1, &ip, sizeof(INPUT));
1744 ip.ki.wVk = VK_MENU; // virtual-key code for the "ALT" key
1745 ip.ki.wScan = 0x38;
1746 qDebug() << SendInput(1, &ip, sizeof(INPUT));
1747 #endif
1748 #endif
1750 m_subtaskView = true;
1751 grabKeyboard();
1752 QRect r = m_desktopRect;
1753 int w = 500;
1754 int h = 40;
1756 // hide tasks to show subtasks
1757 foreach(SakWidget* w, m_widgets.values()) {
1758 w->hide();
1761 QHash<QString, Task>::const_iterator itr = m_tasks.find(taskname);
1762 if (itr == m_tasks.end()) {
1763 workingOnTask(taskname, "");
1764 return;
1767 const Task& t(*itr);
1768 QHash< QString, Task::SubTask >::const_iterator titr = t.subTasks.begin(), tend = t.subTasks.end();
1769 m_subwidgets.clear();
1771 QStringList subtaskSortedList;
1772 QMultiMap<int, Task::SubTask> tmpSubWidgets; // just for ranking -> delay actual creation of max 10 widgets for performance issues
1773 QDateTime now(QDateTime::currentDateTime());
1774 for(; titr != tend; titr++) {
1775 if (!titr->active) continue;
1777 QDateTime rank=now;
1778 QHash< QString, QList< QString > >::const_iterator hhitr = m_subtaskSelectionHistory.find(t.title);
1779 if (hhitr != m_subtaskSelectionHistory.end()) {
1780 int index = hhitr->indexOf(titr->title);
1781 if (index != 0) rank = now.addDays(index+1);
1783 if (rank == now) {
1784 QHash< QString, QList<Task::Hit> >::const_iterator hitr = t.hits.find(titr.key());
1785 if (hitr != t.hits.end()) {
1786 if ( hitr.value().count() && hitr.value().last().timestamp.isValid())
1787 rank = hitr.value().last().timestamp;
1788 else
1789 rank = now.addDays(-999);
1792 tmpSubWidgets.insertMulti( - rank.toTime_t(), *titr);
1793 // create possible completion
1794 subtaskSortedList.push_back((*titr).title);
1797 // Populate only first 10 widgets for performance reason on win32 (native widgets embedded in graphicsview are slow to create)
1798 int sCount=0;
1799 for( QMultiMap<int, Task::SubTask>::const_iterator itr = tmpSubWidgets.begin(); itr !=tmpSubWidgets.end() && sCount<10; itr++, sCount++)
1801 SakSubWidget* sw = new SakSubWidget(t, itr.value());
1802 sw->setGeometry(QRectF(0,0,w,h));
1803 connect (sw, SIGNAL(clicked(const QString&, const QString&)), this, SLOT(workingOnTask(const QString&, const QString&)));
1804 connect (sw, SIGNAL(focused()), this, SLOT(focusedSubTask()));
1805 m_subwidgets.insertMulti(itr.key(), sw);
1807 const QList<SakSubWidget*>& values = m_subwidgets.values();
1810 m_subtaskCompleter = new QCompleter(subtaskSortedList);
1811 m_subtaskCompleter->setCaseSensitivity(Qt::CaseInsensitive);
1813 // QRect messageRect = Layouting((const QList<SakWidget*>&)values);
1815 m_subwidgetsIterator = m_subwidgets.begin();
1816 m_subWidgetRank = 0;
1818 // the one with text
1819 SakSubWidget* tmpSw = new SakSubWidget(t, Task::SubTask(), true);
1820 QCompleter* completer = new QCompleter(subtaskSortedList);
1821 completer->setCaseSensitivity(Qt::CaseInsensitive);
1822 completer->setCompletionMode(QCompleter::InlineCompletion);
1823 ((QLineEdit*)tmpSw->widget())->setCompleter(completer);
1824 m_view->scene()->addItem(tmpSw);
1825 tmpSw->setGeometry(QRectF(0,0,w,h));
1826 connect (tmpSw, SIGNAL(clicked(const QString&, const QString&)), this, SLOT(workingOnTask(const QString&, const QString&)));
1827 connect (tmpSw, SIGNAL(focused()), this, SLOT(focusedSubTask()));
1829 m_subwidgets.insertMulti(-QDateTime::currentDateTime().addDays(999).toTime_t(), tmpSw);
1832 m_subWidgetRank += values.size() != 0;
1834 if (m_subwidgetsIterator != m_subwidgets.end()) {
1835 m_subwidgetsIterator.value()->showDetails(true);
1836 m_view->scene()->setFocusItem(m_subwidgetsIterator.value(), Qt::MouseFocusReason);
1837 m_subwidgetsIterator.value()->widget()->setFocus(Qt::MouseFocusReason);
1838 } else {
1839 m_view->scene()->setFocusItem(tmpSw, Qt::MouseFocusReason);
1840 tmpSw->widget()->setFocus(Qt::MouseFocusReason);
1843 // add a marker to highligh current selection
1844 m_marker = new QGraphicsEllipseItem(r.width()/2 - 280, r.height()/2 - 51, 20,20);
1845 m_marker->setBrush(Qt::red);
1846 m_view->scene()->addItem(m_marker);
1847 m_marker->setVisible(true);
1849 layoutSubTasks(m_subwidgets, m_subWidgetRank);
1851 // Finally add subwidgets
1852 for(int i=0; i<values.size(); i++) {
1853 SakSubWidget* sw = values[i];
1854 m_view->scene()->addItem(sw);
1859 void Sak::scrollTasks(int npos) {
1860 SakWidget* currentShowing = 0;
1861 if (npos < 0) {
1862 for (int i=npos; i<0; i++) {
1863 if (m_widgetsIterator == m_widgets.end()) return;
1864 currentShowing = m_widgetsIterator != m_widgets.end() ? m_widgetsIterator.value() : 0;
1865 if (m_widgetsIterator == m_widgets.begin()) m_widgetsIterator = m_widgets.end();
1866 m_widgetsIterator--;
1868 } else {
1869 for (int i=0; i<npos; i++) {
1870 if (m_widgetsIterator == m_widgets.end()) return;
1871 currentShowing = m_widgetsIterator.value();
1872 m_widgetsIterator++;
1873 if (m_widgetsIterator == m_widgets.end()) m_widgetsIterator = m_widgets.begin();
1876 if (currentShowing && m_widgetsIterator != m_widgets.end()) {
1877 currentShowing->showDetails(false);
1878 m_widgetsIterator.value()->showDetails(true);
1882 void Sak::scrollSubTasks(int npos) {
1883 SakSubWidget* currentShowing = 0;
1884 if (npos < 0) {
1885 for (int i=npos; i<0; i++) {
1886 if (m_subwidgetsIterator == m_subwidgets.end()) return;
1887 currentShowing = m_subwidgetsIterator != m_subwidgets.end() ? m_subwidgetsIterator.value() : 0;
1888 if (m_subwidgetsIterator == m_subwidgets.begin()) {
1889 m_subwidgetsIterator = m_subwidgets.end();
1890 m_subWidgetRank = m_subwidgets.count();
1892 m_subwidgetsIterator--;
1893 m_subWidgetRank--;
1895 } else {
1896 for (int i=0; i<npos; i++) {
1897 if (m_subwidgetsIterator == m_subwidgets.end()) return;
1898 currentShowing = m_subwidgetsIterator.value();
1899 m_subwidgetsIterator++;
1900 m_subWidgetRank++;
1901 if (m_subwidgetsIterator == m_subwidgets.end()) {
1902 m_subwidgetsIterator = m_subwidgets.begin();
1903 m_subWidgetRank = 0;
1907 if (currentShowing && m_subwidgetsIterator != m_subwidgets.end()) {
1908 currentShowing->showDetails(false);
1909 m_subwidgetsIterator.value()->showDetails(true);
1910 m_view->scene()->setFocusItem(m_subwidgetsIterator.value(), Qt::MouseFocusReason);
1911 m_subwidgetsIterator.value()->widget()->setFocus(Qt::MouseFocusReason);
1913 layoutSubTasks(m_subwidgets, m_subWidgetRank);
1916 void Sak::focusedSubTask()
1918 SakSubWidget* w = dynamic_cast<SakSubWidget*>(sender());
1919 if (w) {
1920 QMap<int, SakSubWidget*>::iterator itr = m_subwidgets.begin(), end=m_subwidgets.end();
1921 for(int i=0; itr != end; i++,itr++) {
1922 if (itr.value() == w) {
1923 m_subwidgetsIterator = itr;
1924 m_subWidgetRank = i;
1930 //END Tasks >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
1933 //BEGIN settings ================================
1936 void Sak::setupSettingsWidget()
1938 m_settings = new QMainWindow();
1939 m_settings->setMinimumHeight(650);
1940 m_settings->setMinimumWidth(700);
1941 QWidget* centralWidget = new QWidget;
1942 m_settings->setCentralWidget(centralWidget);
1944 QVBoxLayout* theMainLayout = new QVBoxLayout;
1945 centralWidget->setLayout(theMainLayout);
1947 tabs = new QTabWidget;
1948 theMainLayout->addWidget(tabs);
1949 previewButton = new QPushButton("Preview");
1950 theMainLayout->addWidget(previewButton);
1952 tab1 = new QWidget;
1953 tab2 = new QWidget;
1954 tab3 = new QWidget;
1955 tab4 = new QWidget;
1956 tabs->addTab(tab1, "Tasks");
1957 tabs->addTab(tab2, "General");
1958 tabs->addTab(tab4, "Statistics");
1959 tabs->addTab(tab3, "Advanced");
1961 createActions();
1962 QMenuBar* mainMenu = new QMenuBar;
1963 m_settings->setMenuBar(mainMenu);
1964 QMenu* programMenu = mainMenu->addMenu("Program");
1965 programMenu->addAction(quitAction);
1966 programMenu->addAction(minimizeAction);
1967 QMenu* dbMenu = mainMenu->addMenu("Db");
1968 dbMenu->addAction(flushAction);
1969 dbMenu->addAction(openAction);
1970 // dbMenu->addAction(saveAsDbAction);
1971 dbMenu->addAction(exportDbCsvAction);
1972 #ifdef USEGMAIL
1973 dbMenu->addAction(gmailLoginAction);
1974 dbMenu->addAction(saveToGmailAction);
1975 if (!m_gmail->isValid()) {
1976 gmailLoginAction->setEnabled(false);
1977 saveToGmailAction->setEnabled(false);
1979 #endif
1980 QMenu* actionsMenu = mainMenu->addMenu("Actions");
1981 actionsMenu->addAction(startAction);
1982 actionsMenu->addAction(stopAction);
1983 actionsMenu->addAction(workingOnAction);
1985 QVBoxLayout *mainLayout = new QVBoxLayout;
1986 QGridLayout *messageLayout = new QGridLayout;
1987 durationLabel = new QLabel(tr("Interval:"));
1988 durationLabel1 = new QLabel(tr("(effective only after restart)"));
1990 QSettings settings(QSettings::IniFormat, QSettings::UserScope, "ZanzaSoft", "SAK");
1991 durationSpinBox = new QSpinBox;
1992 durationSpinBox->setSingleStep(1);
1993 durationSpinBox->setRange(1, 1440);
1994 durationSpinBox->setSuffix(" minutes");
1995 durationSpinBox->setValue(qMin(1440, qMax(1, settings.value("Ping interval", 15).toInt())));
1996 durationSpinBox->setCorrectionMode(QAbstractSpinBox::CorrectToNearestValue);
1998 bodyLabel = new QLabel(tr("Message:"));
1999 bodyEdit = new QTextEdit;
2000 bodyEdit->setPlainText( settings.value("Message", "<h1>Enter a message here!</h1>").toString() );
2001 askingLady = new PixmapViewer(0,false);
2002 askingLady->setPixmap(QPixmap(":/images/whip.png"));
2003 viewBackground = new PixmapViewer(0, false);
2005 messageLayout->addWidget(durationLabel, 1, 0);
2006 messageLayout->addWidget(durationSpinBox, 1, 1);
2007 messageLayout->addWidget(durationLabel1, 1, 2);
2008 messageLayout->addWidget(bodyLabel, 3, 0);
2009 messageLayout->addWidget(bodyEdit, 3, 1, 2, 4);
2010 messageLayout->addWidget(new QLabel("Character image:"), 7, 0);
2011 messageLayout->addWidget(askingLady, 7, 1, 2, 4);
2012 messageLayout->addWidget(new QLabel("View background:"), 11, 0);
2013 messageLayout->addWidget(viewBackground, 11, 1, 2, 4);
2014 messageLayout->setColumnStretch(3, 1);
2015 messageLayout->setRowStretch(4, 1);
2016 mainLayout->addLayout(messageLayout);
2017 tab2->setLayout(mainLayout);
2019 mainLayout = new QVBoxLayout;
2020 tab1->setLayout(mainLayout);
2021 // create tree view
2022 tasksTree = new QTreeWidget;
2023 tasksTree->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
2024 tasksTree->setColumnCount(3);
2025 tasksTree->setColumnWidth(0, 300);
2026 tasksTree->setColumnWidth(1, 65);
2027 tasksTree->setIconSize(QSize(32,32));
2028 connect(tasksTree, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(doubleClickedTask(QTreeWidgetItem*,int)));
2031 taskTextEditor = new QTextEdit;
2032 taskTextEditor->setFixedHeight(100);
2033 taskTextEditor->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
2034 taskTextEditor->setToolTip("Task description");
2035 connect(taskTextEditor, SIGNAL(textChanged()), this, SLOT(commitCurrentTask()));
2038 taskTitleEditor = new QLineEdit;
2039 taskTitleEditor->setFixedHeight(20);
2040 taskTitleEditor->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
2041 taskTitleEditor->setValidator(new QRegExpValidator(QRegExp("[\\w]*"), this));
2042 taskTitleEditor->setToolTip("Task title (alphanumeric)");
2043 connect(taskTitleEditor, SIGNAL(editingFinished()), this, SLOT(commitCurrentTask()));
2045 taskTagsEditor = new QLineEdit;
2046 taskTagsEditor->setFixedHeight(20);
2047 taskTagsEditor->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
2048 taskTagsEditor->setValidator(new QRegExpValidator(QRegExp("[\\w,:]*"), this));
2049 taskTagsEditor->setToolTip("Task tags (alphanumeric comma or colon separated)");
2050 connect(taskTagsEditor, SIGNAL(editingFinished()), this, SLOT(commitCurrentTask()));
2052 taskUrlEditor = new QLineEdit;
2053 taskUrlEditor->setFixedHeight(20);
2054 taskUrlEditor->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
2055 taskUrlEditor->setPlaceholderText("http://");
2056 taskUrlEditor->setToolTip("Task url");
2057 connect(taskUrlEditor, SIGNAL(editingFinished()), this, SLOT(commitCurrentTask()));
2059 taskPixmapViewer = new PixmapViewer;
2060 mainLayout->addWidget(tasksTree, 2);
2061 QHBoxLayout* detailsLayout = new QHBoxLayout;
2062 QVBoxLayout* editsLayout = new QVBoxLayout;
2063 detailsLayout->addWidget(taskPixmapViewer);
2064 connect(taskPixmapViewer, SIGNAL(changed()), this, SLOT(commitCurrentTask()));
2065 editsLayout->addWidget(taskTitleEditor);
2066 editsLayout->addWidget(taskTextEditor);
2067 editsLayout->addWidget(taskTagsEditor);
2068 editsLayout->addWidget(taskUrlEditor);
2070 QHBoxLayout* datesLayout = new QHBoxLayout;
2071 datesLayout->addWidget(new QLabel("Due: "));
2072 dueEditor = new QDateEdit;
2073 dueEditor->setMinimumDate(QDate(2000,1,1));
2074 datesLayout->addWidget(dueEditor);
2075 datesLayout->addWidget(new QLabel("Estimated: "));
2076 estimatedHoursEditor = new QSpinBox;
2077 estimatedHoursEditor->setRange(0, 1e5);
2078 estimatedHoursEditor->setSuffix("hours");
2079 datesLayout->addWidget(estimatedHoursEditor);
2080 editsLayout->addLayout(datesLayout);
2081 detailsLayout->addLayout(editsLayout);
2082 connect(dueEditor, SIGNAL(editingFinished()), this, SLOT(commitCurrentTask()));
2083 connect(estimatedHoursEditor,SIGNAL(editingFinished()), this, SLOT(commitCurrentTask()));
2085 QVBoxLayout* colorsLayout = new QVBoxLayout;
2086 bgColorButton = new QPushButton("bg\ncolor");
2087 bgColorButton->setToolTip("Background color");
2088 bgColorButton->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding));
2090 fgColorButton = new QPushButton("fg\ncolor");
2091 fgColorButton->setToolTip("Foreground color");
2092 fgColorButton->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding));
2094 #ifdef Q_WS_WIN
2095 // fix Windows XP "style"
2096 bgColorButton->setStyle(new QWindowsStyle());
2097 fgColorButton->setStyle(new QWindowsStyle());
2098 #endif
2100 colorsLayout->addWidget(bgColorButton);
2101 colorsLayout->addWidget(fgColorButton);
2102 detailsLayout->addLayout(colorsLayout);
2104 mainLayout->addLayout(detailsLayout);
2106 QVBoxLayout* tab4MainLayout = new QVBoxLayout(tab4);
2107 //taskSelector = new QComboBox;
2108 summaryList = newTaskSummaryList();
2109 QTabWidget* tabs = new QTabWidget;
2110 tabs->setTabPosition(QTabWidget::East);
2111 tabs->addTab(summaryList, "List");
2112 summaryView = new QGraphicsView;
2113 summaryView->setScene(new QGraphicsScene);
2114 summaryChart = new TaskSummaryPieChart;
2115 summaryView->scene()->addItem(summaryChart);
2116 summaryView->scene()->setSceneRect(summaryChart->boundingRect());
2117 summaryView->fitInView(summaryView->sceneRect());
2118 summaryView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
2119 summaryView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
2120 summaryView->installEventFilter(this);
2121 tabs->addTab(summaryView, "Chart");
2122 tab4MainLayout->addWidget(tabs);
2124 today1 = new QPushButton("today");
2125 today2 = new QPushButton("today");
2126 thisWeek1 = new QPushButton("this week");
2127 thisWeek2 = new QPushButton("this week");
2128 thisMonth1 = new QPushButton("this month");
2129 thisMonth2 = new QPushButton("this month");
2130 lastWeek1 = new QPushButton("last 7 days");
2131 lastWeek2 = new QPushButton("last 7 days");
2132 lastMonth1 = new QPushButton("last 30 days");
2133 lastMonth2 = new QPushButton("last 30 days");
2135 cal3 = new QCalendarWidget;
2136 cal3->setMinimumSize(QSize(250,200));
2137 cal4 = new QCalendarWidget;
2138 cal4->setMinimumSize(QSize(250,200));
2139 cal3->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
2140 cal4->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
2141 QHBoxLayout* calsLayout = new QHBoxLayout;
2142 calsLayout->addWidget(cal3);
2143 calsLayout->addWidget(cal4);
2144 QHBoxLayout* calButtonsLayout = new QHBoxLayout;
2145 calButtonsLayout->addWidget(today1);
2146 calButtonsLayout->addWidget(thisWeek1);
2147 calButtonsLayout->addWidget(thisMonth1);
2148 calButtonsLayout->addWidget(lastWeek1);
2149 calButtonsLayout->addWidget(lastMonth1);
2150 cal4->setSelectedDate(QDate::currentDate().addDays(1));
2151 tab4MainLayout->addLayout(calsLayout);
2152 tab4MainLayout->addLayout(calButtonsLayout);
2155 QVBoxLayout* tab3MainLayout = new QVBoxLayout(tab3);
2156 //taskSelector = new QComboBox;
2157 hitsList = newHitsList();
2158 connect(hitsList, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), this, SLOT(hitsSelectedInList(QTreeWidgetItem*,QTreeWidgetItem*)));
2159 hitsTimeline = new Timeline;
2160 connect(hitsTimeline, SIGNAL(hitSelected(HitItem*)), this, SLOT(hitsSelectedInTimeline(HitItem*)));
2161 cal1 = new QCalendarWidget;
2162 cal1->setMinimumSize(QSize(250,200));
2163 cal2 = new QCalendarWidget;
2164 cal2->setMinimumSize(QSize(250,200));
2165 cal1->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
2166 cal2->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
2167 //tab3MainLayout->addWidget(taskSelector);
2170 calsLayout = new QHBoxLayout;
2171 calsLayout->addWidget(cal1);
2172 calsLayout->addWidget(cal2);
2173 QHBoxLayout* calButtonsLayout2 = new QHBoxLayout;
2174 calButtonsLayout2->addWidget(today2);
2175 calButtonsLayout2->addWidget(thisWeek2);
2176 calButtonsLayout2->addWidget(thisMonth2);
2177 calButtonsLayout2->addWidget(lastWeek2);
2178 calButtonsLayout2->addWidget(lastMonth2);
2179 cal2->setSelectedDate(QDate::currentDate().addDays(1));
2180 tab3MainLayout->addWidget(hitsList);
2181 tab3MainLayout->addWidget(hitsTimeline);
2182 tab3MainLayout->addLayout(calsLayout);
2183 tab3MainLayout->addLayout(calButtonsLayout2);
2187 m_settings->setWindowTitle(tr("SaK"));
2188 m_settings->resize(400, 300);
2192 QTreeWidget* Sak::newHitsList()
2194 QTreeWidget* hitsList = new QTreeWidget;
2195 hitsList->setColumnCount(3);
2196 hitsList->setColumnWidth(0, 200);
2197 hitsList->setColumnWidth(1, 150);
2198 hitsList->setColumnWidth(2, 150);
2199 hitsList->setColumnWidth(3, 150);
2200 //hitsList->setColumnWidth(4, 150);
2201 hitsList->setIconSize(QSize(24,24));
2202 hitsList->setSortingEnabled(true);
2203 hitsList->setItemDelegateForColumn(0, new MyDateItemDelegate);
2204 hitsList->setItemDelegateForColumn(1, new TaskItemDelegate(this));
2205 hitsList->setItemDelegateForColumn(2, new SubTaskItemDelegate(this));
2206 QTreeWidgetItem* header = new QTreeWidgetItem;
2207 header->setText(0, "Date/Time");
2208 header->setText(1, "Task");
2209 header->setText(2, "Subtask");
2210 header->setText(3, "Duration (min)");
2211 //header->setText(4, "Overestimation");
2212 hitsList->setHeaderItem(header);
2213 hitsList->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
2214 return hitsList;
2217 QTreeWidget* Sak::newTaskSummaryList()
2219 QTreeWidget* taskSummaryList = new QTreeWidget;
2220 taskSummaryList->setColumnCount(4);
2221 taskSummaryList->setColumnWidth(0, 250);
2222 taskSummaryList->setColumnWidth(1, 150);
2223 taskSummaryList->setColumnWidth(1, 150);
2224 taskSummaryList->setIconSize(QSize(24,24));
2225 taskSummaryList->setSortingEnabled(true);
2226 QTreeWidgetItem* header = new QTreeWidgetItem;
2227 header->setText(0, "Task");
2228 header->setText(1, "Hours");
2229 taskSummaryList->setHeaderItem(header);
2230 taskSummaryList->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
2231 taskSummaryList->setEnabled(true);
2232 return taskSummaryList;
2235 void Sak::setVisible(bool visible)
2237 minimizeAction->setEnabled(visible);
2238 maximizeAction->setEnabled(!m_settings->isMaximized());
2239 restoreAction->setEnabled(m_settings->isMaximized() || !visible);
2240 m_settings->setVisible(visible);
2243 void Sak::createActions()
2245 minimizeAction = new QAction(tr("Mi&nimize"), m_settings);
2246 connect(minimizeAction, SIGNAL(triggered()), m_settings, SLOT(hide()));
2248 maximizeAction = new QAction(tr("Ma&ximize"), m_settings);
2249 connect(maximizeAction, SIGNAL(triggered()), m_settings, SLOT(showMaximized()));
2251 restoreAction = new QAction(tr("&Restore"), m_settings);
2252 connect(restoreAction, SIGNAL(triggered()), m_settings, SLOT(showNormal()));
2254 quitAction = new QAction(tr("&Quit"), m_settings);
2255 connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
2257 startAction = new QAction(tr("Start polling"), m_settings);
2258 connect(startAction, SIGNAL(triggered()), this, SLOT(start()));
2260 stopAction = new QAction(tr("Pause polling"), m_settings);
2261 connect(stopAction, SIGNAL(triggered()), this, SLOT(pause()));
2263 workingOnAction = new QAction(tr("Working on.."), m_settings);
2264 connect(workingOnAction, SIGNAL(triggered()), this, SLOT(workingOn()));
2266 flushAction = new QAction(tr("&Flush data/settings to disk"), m_settings);
2267 connect(flushAction, SIGNAL(triggered()), this, SLOT(flush()));
2269 saveAsDbAction = new QAction(tr("Backup as"), m_settings);
2270 // connect(saveAsDbAction, SIGNAL(triggered()), this, SLOT(saveAsDb()));
2272 exportDbCsvAction = new QAction(tr("Export hits in CSV format"), m_settings);
2273 connect(exportDbCsvAction, SIGNAL(triggered()), this, SLOT(exportDbCsv()));
2275 #ifdef USEGMAIL
2276 saveToGmailAction = new QAction(tr("Store in your gmail account free space"), m_settings);
2277 connect(saveToGmailAction, SIGNAL(triggered()), this, SLOT(saveToGmail()));
2279 gmailLoginAction = new QAction(tr("Log in gmail"), m_settings);
2280 connect(gmailLoginAction, SIGNAL(triggered()), this, SLOT(logInGmail()));
2281 #else
2282 saveToGmailAction = NULL;
2283 gmailLoginAction = NULL;
2284 #endif
2286 openAction = new QAction(tr("Import a task from file"), m_settings);
2287 connect(openAction, SIGNAL(triggered()), this, SLOT(open()));
2289 m_addHitAction = new QAction("Add hit", m_settings);
2290 m_addHitMenu = new QMenu(m_settings);
2291 m_addHitAction->setText("Add hit");
2292 m_addHitMenu->addAction(m_addHitAction);
2293 connect(m_addHitAction, SIGNAL(triggered()), this, SLOT(addDefaultHit()));
2295 m_exportDataAction = new QAction("Export data", m_settings);
2296 m_exportDataAction->setText("Export data");
2297 m_addHitMenu->addAction(m_exportDataAction);
2298 connect(m_exportDataAction, SIGNAL(triggered()), this, SLOT(exportHits()));
2300 m_addTaskAction = new QAction("Add task", m_settings);
2301 m_addTaskMenu = new QMenu(m_settings);
2302 m_addTaskAction->setText("Add task");
2303 m_addTaskMenu->addAction(m_addTaskAction);
2304 connect(m_addTaskAction, SIGNAL(triggered()), this, SLOT(addDefaultTask()));
2307 void Sak::trayIconActivated(QSystemTrayIcon::ActivationReason reason)
2309 if (reason == QSystemTrayIcon::DoubleClick) {
2310 setVisible(true);
2314 //END Settings <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<