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