test
[push-it] / src / mainwindow.cpp
1 /*
2     Copyright (C) <2010>  <Markus Scharnowski markus.scharnowski@gmail.com>
3
4     This program is free software: you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation, either version 3 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 */
17 #include <QFileDialog>
18 #include <QMessageBox>
19 #include <QDataStream>
20 #include <QApplication>
21 #include <QLayout>
22 #include <QInputDialog>
23 #include <QTextCodec>
24
25 #include <assert.h>
26 #include <sstream>
27 #include <iomanip>
28 #include <iostream>
29
30 #include "mainwindow.h"
31 #include "aboutdialog.hpp"
32 #include "showfulllistdialog.hpp"
33
34 #define DEBUG_OUTPUT
35 #undef DEBUG_OUTPUT
36
37 MainWindow::MainWindow(QWidget *parent) :
38     QMainWindow(parent)
39 {
40   snprintf(mainButtonStartText,BUFSIZ-1,
41            "&Start\n\n"
42            "If this program is used uncautiously\n"
43            "you could break your device physically.\n"
44            "Always be sure that you use this\n"
45            "software and the corresponding\n"
46            "hardware in a way that the hardware\n"
47            "won't get damaged."
48            );
49   this->setupUi();
50   fsExitButton = new FullScreenExitButton(this);
51   fsExitButton->hide();
52
53   timer = new QTimer(this);
54   connect(timer, SIGNAL(timeout()), this, SLOT(mainButtonTimeoutUpdate()));
55   timerRefreshTimeMs = 100;
56
57   connect(actionReset, SIGNAL(triggered()), this , SLOT(resetProgram()));
58   connect(actionResetFs, SIGNAL(triggered()), this , SLOT(resetProgramAndFullscreen()));
59 //  connect(actionResetFs, SIGNAL(triggered()), this , SLOT(actionReset->Trigger));
60   connect(actionSimulated_click, SIGNAL(triggered()), this , SLOT(mainButtonRefresh()));
61   connect(actionPause, SIGNAL(triggered()), this , SLOT(pauseCounting()));
62   connect(actionSave_full_list, SIGNAL(triggered()), this , SLOT(saveToFile()));
63   connect(actionEnable_full_date, SIGNAL(triggered()), this , SLOT(toggleFullDate()));
64   connect(actionEnable_time_and_date, SIGNAL(triggered()), this , SLOT(toggleTimeDate()));
65   connect(actionAbout, SIGNAL(triggered()), this , SLOT(about()));
66   connect(actionShow_full_list, SIGNAL(triggered()), this , SLOT(showFullList()));
67   connect(action_Fullscreen, SIGNAL(triggered()), this, SLOT(showFullScreenHandler()));
68   connect(actionChangeNumberOfLines, SIGNAL(triggered()), this, SLOT(changeNumberOfLines()));
69   connect(actionChangeFontSize, SIGNAL(triggered()), this, SLOT(changeFontSize()));
70
71   connect(this->mainButton, SIGNAL(clicked()), this, SLOT(mainButtonRefresh()));
72
73   changeNumberOfLines(8);
74   changeFontSize(30);
75   resetProgram();
76 }
77
78 MainWindow::~MainWindow()
79 {
80 }
81
82 int MainWindow::updateTime()
83 {
84   hitCount++;
85   if(1 == hitCount)
86   {
87     overallTime.start();
88   }
89   overallTime.update();
90
91   return EXIT_SUCCESS;
92 }
93
94 void MainWindow::mainButtonRefresh()
95 {
96   updateTime();
97   QString latest = generateHistoryString();
98   QString string = completeString.remove(mainButtonStartText);
99   QStringList lines;
100   QString header = getHeaderLine();
101
102   /* timer handling */
103   if(NULL != timer)
104   {
105     if(1 == hitCount)
106     {
107       timer->start(timerRefreshTimeMs);
108     }
109     else if(hitCount > 1)
110     {
111       if(!timer->isActive())
112       {
113         timer->start(timerRefreshTimeMs);
114       }
115     }
116   }
117
118   if(1 == hitCount)
119   {
120     mainButtonRefreshTextOnly();
121     return;
122   }
123   lines = string.split("\n");
124
125   /* add new entry information directly behind the current header
126        wihtout start information if there is any.
127     */
128   if(lines.length() > 0)
129   {
130     if(lines[0].indexOf("#0") > 0)
131     {
132       lines[0].truncate(lines[0].indexOf("#0"));
133     }
134     lines[0].append(latest);
135   }
136
137   string = lines.join("\n").prepend("\n").prepend(header);
138   completeString = string;
139   lines = string.split("\n");
140   /* adjust the total number of lines of the button */
141   if(lines.length() > maximumNumberOfLines)
142   {
143     while(lines.length() > maximumNumberOfLines
144           && lines.length() > 0)
145     {
146       lines.removeLast();
147     }
148   }
149   else if(lines.length() < maximumNumberOfLines)
150   {
151     while(lines.length() < maximumNumberOfLines)
152     {
153       lines.append(" ");
154     }
155   }
156   string = lines.join("\n");
157   this->mainButton->setText(tr(string.toStdString().c_str()));
158   toggleActionPause();
159 #ifdef DEBUG_OUTPUT
160   std::cout << "---" << std::endl << string.toStdString() << std::endl << "-" << string.split("\n").length() << "-" << std::endl;
161 #endif
162 }
163
164 QString MainWindow::generateHistoryString()
165 {
166   QString result;
167   char tmp[BUFSIZ];
168   std::string previousStr, lastStr, firstStr;
169
170   previousStr = overallTime.getPreviousTimeString();
171   lastStr = overallTime.getLastTimeString();
172   firstStr = overallTime.getStartTimeString();
173
174    snprintf(tmp,BUFSIZ-1,
175       "\n"
176 //      "Δ Delta %.3lfs "
177 //      "⅀∑ Total %.3lfs "
178       "Δ %.3lfs "
179       "∑ %.3lfs "
180       "Avg %.3lfs"
181       ,
182       overallTime.getDeltaLastToPrev(),
183       overallTime.getDeltaLastToStart(),
184       overallTime.getDeltaLastToStart()/(hitCount-1)
185       );
186
187   result = tr(tmp);
188
189   if(true == timeOutput)
190   {
191     result.prepend(lastStr.c_str());
192   }
193
194   return result;
195 }
196
197 int MainWindow::resetCounters()
198 {
199   hitCount=-1;
200   updateTime();
201   return EXIT_SUCCESS;
202 }
203
204 int MainWindow::resetUi()
205 {
206   this->mainButton->setText(tr(mainButtonStartText));
207   this->actionPause->setText(tr("&Pause"));
208   this->actionEnable_full_date->setDisabled(true);
209
210   completeString = this->mainButton->text();
211   timeOutput = false;
212
213   return EXIT_SUCCESS;
214 }
215
216 void MainWindow::resetProgram()
217 {
218   resetCounters();
219   resetUi();
220   if(timer->isActive())
221   {
222     timer->stop();
223   }
224 }
225
226 void MainWindow::pauseCounting()
227 {
228   overallTime.pause();
229   toggleTimer();
230   toggleActionPause();
231 }
232
233 int MainWindow::toggleTimer()
234 {
235   if(timer->isActive())
236   {
237     timer->stop();;
238   }
239   else
240   {
241     timer->start(timerRefreshTimeMs);
242   }
243   return EXIT_SUCCESS;
244 }
245
246 void MainWindow::mainButtonTimeoutUpdate()
247 {
248   if(!timer->isActive())
249   {
250     assert(0 && "Unintended use");
251     return;
252   }
253   else if(overallTime.isPaused())
254   {
255     timer->stop();
256     return;
257   }
258
259   QString string = this->mainButton->text();
260   QStringList lines = string.split("\n");
261   if(lines.length() >= 1)
262   {
263     lines[0] = getHeaderLine();
264   }
265   else
266   {
267     assert(0 && "Lines are missing");
268   }
269   this->mainButton->setText(tr(lines.join("\n").toStdString().c_str()));
270
271   //update stored string
272   lines = completeString.split("\n");
273   if(lines.length() >= 1)
274   {
275     lines[0] = getHeaderLine();
276   }
277   completeString = lines.join("\n");
278 }
279
280 QString MainWindow::getHeaderLine()
281 {
282   QString headerLine;
283   std::stringstream sstr;
284
285   double runTime = overallTime.getDeltaNowToStart();
286   double time = 0;
287   int hours = 0;
288   int min = 0;
289
290   sstr << "#" << hitCount << " ";
291   sstr << std::setfill('0');
292   hours = (int)(runTime/3600);
293   sstr << hours << ":"; //hour
294   time = runTime - 3600*hours;
295   min = (int)(time/60);
296   sstr << std::setw(2) << min << ":"; //minute
297   time = runTime - 3600*hours - 60*min;
298   sstr.setf(std::ios::fixed);
299   sstr.setf(std::ios::showpoint);
300   sstr.precision(1);
301   sstr << time << " "; //seconds
302   if(true == timeOutput)
303   {
304     sstr << "#0 " << overallTime.getStartTimeString();
305   }
306
307   headerLine = sstr.str().c_str();
308   return headerLine;
309 }
310
311 void MainWindow::saveToFile()
312 {
313   ShowFullListDialog *listHander;
314   listHander = new ShowFullListDialog(0,completeString);
315   listHander->save();
316 }
317
318 int MainWindow::toggleActionPause()
319 {
320   QString str = this->actionPause->text();
321   if(str == "&Pause" && overallTime.isPaused())
322   {
323     this->actionPause->setText(tr("R&esume"));
324   }
325   else
326   {
327     this->actionPause->setText(tr("&Pause"));
328   }
329   return EXIT_SUCCESS;
330 }
331
332 int MainWindow::updateUiText()
333 {
334   toggleActionPause();
335   return EXIT_SUCCESS;
336 }
337
338 void MainWindow::toggleFullDate()
339 {
340   if(overallTime.isDateForStringsEnabled())
341   {
342     overallTime.disableDateForStrings();
343   }
344   else
345   {
346     overallTime.enableDateForStrings();
347   }
348 }
349
350 void MainWindow::toggleTimeDate()
351 {
352   if(this->actionEnable_time_and_date->isChecked())
353   {
354     this->actionEnable_full_date->setEnabled(true);
355     timeOutput = true;
356   }
357   else
358   {
359     this->actionEnable_full_date->setDisabled(true);
360     timeOutput = false;
361   }
362 }
363
364 void MainWindow::about()
365 {
366   AboutDialog *pa = new AboutDialog;
367   QTextBrowser *examples = new QTextBrowser;
368   examples->setOpenExternalLinks(true);
369   examples->setHtml(
370       "<p><b><a href =\"http://sites.google.com/site/markusscharnowski/n/pushit/real-life-examples\">Examples website with videos and pictures.</a></b><p>"
371
372       "<p>Excerpt:</p>"
373
374       "<p><b>Pull ups</b></p>"
375       "<p>Place the phone add the position where you are in the highest position, so you can hit it with your nose. In my case I mount the Nokia N900 above a door with the help of some tesa-strips. I also tried it without tesa-strips but after some pushes on the N900 I was too anxious that it could fall down on the floor. So I recommend some safety mechanism to have fun with your device now and in the future. Every time you pulling yourself up, you touch the screen of the N900 with your nose. So you will always know how many pull ups you have done.</p>"
376
377       "<p><b>Push ups</b></p>"
378       "<p>Place the smartphone just below your nose so you can hit it with the nose when in the lowest position. And then start pushing up.</p>"
379
380       "<p><b>Crunches</b></p>"
381       "Doing crunches is a tiny bit more tricky. You have to go into your crunching position (laying on your back, feet up) and place your phone behind your head. Positioned well, you will be able to touch your phone with one of your thumbs every time you are going down (assuming you fold your hands behind your head when doing crunches)."
382
383       "<p><b>Lower back exercise</b></p>"
384       "<p>Place the phone just below your nose so you can hit it with the nose. I don't know how this exercise is called - if you know it, please let me know it too.<br>"
385       "To execute this exercise I lay myself on some pillows on a bench. My legs will get support by a table. The exercise itself is moving you upper body down to the phone and up again till your back is strait like when your are standing on your feet.</p>"
386       );
387
388   pa->setProgramName(windowTitle());
389   pa->setProgramUrl("http://sites.google.com/site/markusscharnowski/n/pushit");
390   pa->tabs->insertTab(5,examples,tr("&Examples"));
391   pa->setMinimumHeight(400);
392
393   pa->show();
394 }
395
396 void MainWindow::showFullList()
397 {
398   ShowFullListDialog *ps = new ShowFullListDialog(0,completeString);
399   ps->show();
400 }
401
402 void MainWindow::showFullScreenHandler()
403 {
404   bool isFullScreen = windowState() & Qt::WindowFullScreen;
405   if (isFullScreen)
406   {
407     showNormal();
408 //    this->action_Fullscreen->setText(tr("&Fullscreen"));
409   }
410   else
411   {
412     showFullScreen();
413     fsExitButton->show();
414 //    this->action_Fullscreen->setText(tr("&Disable Fullscreen"));
415   }
416 }
417
418 void MainWindow::slotFullscreenExit()
419 {
420   action_Fullscreen->trigger();
421 }
422
423 void MainWindow::setupUi()
424 {
425   if (this->objectName().isEmpty())
426   {
427     this->setObjectName(QString::fromUtf8("MainWindow"));
428   }
429   setBaseSize(800,480);
430   resize(800,480);
431
432   /* main button */
433   mainButton = new QPushButton;
434   mainButton->setObjectName(QString::fromUtf8("mainButton"));
435   QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
436   mainButton->setSizePolicy(sizePolicy);
437
438   QFont font1;
439   font1.setPointSize(26);
440   font1.setBold(true);
441   font1.setWeight(75);
442
443   mainButton->setFont(font1);
444   mainButton->setAutoFillBackground(true);
445   mainButton->setDefault(true);
446   mainButton->setFlat(true);
447   mainButton->setStyleSheet("text-align:center;");;
448
449   /* default font */
450   QFont font;
451   font.setPointSize(12);
452   this->setFont(font);
453
454   /* central widget */
455   centralWidget = new QWidget(this);
456   centralWidget->setObjectName(QString::fromUtf8("centralWidget"));
457   this->setCentralWidget(centralWidget);
458
459   QHBoxLayout *mainLayout = new QHBoxLayout;
460   mainLayout->addWidget(mainButton);
461   centralWidget->setLayout(mainLayout);
462
463   /* actions for te menu */
464   actionReset = new QAction(this);
465   actionReset->setObjectName(QString::fromUtf8("actionReset"));
466
467   actionResetFs = new QAction(this);
468   actionResetFs->setObjectName(QString::fromUtf8("actionResetFs"));
469
470   actionSimulated_click = new QAction(this);
471   actionSimulated_click->setObjectName(QString::fromUtf8("actionSimulated_click"));
472
473   actionPause = new QAction(this);
474   actionPause->setObjectName(QString::fromUtf8("actionPause"));
475
476   actionShow_full_list = new QAction(this);
477   actionShow_full_list->setObjectName(QString::fromUtf8("actionShow_full_list"));
478
479   actionSave_full_list = new QAction(this);
480   actionSave_full_list->setObjectName(QString::fromUtf8("actionSave_full_list"));
481
482   actionAbout = new QAction(this);
483   actionAbout->setObjectName(QString::fromUtf8("actionAbout"));
484
485   actionEnable_full_date = new QAction(this);
486   actionEnable_full_date->setObjectName(QString::fromUtf8("actionEnable_full_date"));
487   actionEnable_full_date->setCheckable(true);
488
489   actionEnable_time_and_date = new QAction(this);
490   actionEnable_time_and_date->setObjectName(QString::fromUtf8("actionEnable_time_and_date"));
491   actionEnable_time_and_date->setCheckable(true);
492
493   action_Fullscreen = new QAction(this);
494   action_Fullscreen->setObjectName(QString::fromUtf8("action_Fullscreen"));
495   action_Fullscreen->setCheckable(true);
496
497   actionChangeNumberOfLines = new QAction(this);
498   actionChangeNumberOfLines->setObjectName(QString::fromUtf8("actionChangeNumberOfLines"));
499
500   actionChangeFontSize = new QAction(this);
501   actionChangeFontSize->setObjectName(QString::fromUtf8("actionChangeFontSize"));
502
503   /* menu */
504   menuBar = new QMenuBar(this);
505   menuBar->setObjectName(QString::fromUtf8("menuBar"));
506   menuBar->setGeometry(QRect(0, 0, 800, 25));
507   menuPush_It_Menu = new QMenu(menuBar);
508   menuPush_It_Menu->setObjectName(QString::fromUtf8("menuPush_It_Menu"));
509   menuHelp = new QMenu(menuBar);
510   menuHelp->setObjectName(QString::fromUtf8("menuHelp"));
511   menuData = new QMenu(menuBar);
512   menuData->setObjectName(QString::fromUtf8("menuData"));
513   menuView = new QMenu(menuBar);
514   menuView->setObjectName(QString::fromUtf8("menuView"));
515   this->setMenuBar(menuBar);
516
517   menuBar->addAction(menuPush_It_Menu->menuAction());
518   menuBar->addAction(menuView->menuAction());
519   menuBar->addAction(menuData->menuAction());
520   menuBar->addAction(menuHelp->menuAction());
521   menuPush_It_Menu->addAction(actionReset);
522   menuPush_It_Menu->addAction(actionPause);
523   menuPush_It_Menu->addAction(actionSimulated_click);
524   menuHelp->addAction(actionAbout);
525   menuData->addAction(actionEnable_time_and_date);
526   menuData->addAction(actionEnable_full_date);
527   menuData->addSeparator();
528   menuData->addAction(actionSave_full_list);
529   menuData->addAction(actionShow_full_list);
530   menuView->addAction(action_Fullscreen);
531   menuView->addAction(actionResetFs);
532   menuView->addAction(actionChangeNumberOfLines);
533   menuView->addAction(actionChangeFontSize);
534
535   retranslateUi();
536
537   QMetaObject::connectSlotsByName(this);
538 } // setupUi
539
540 void MainWindow::retranslateUi()
541 {
542   this->setWindowTitle(QApplication::translate("MainWindow", "Push It!", 0, QApplication::UnicodeUTF8));
543   actionReset->setText(QApplication::translate("MainWindow", "&Reset", 0, QApplication::UnicodeUTF8));
544   actionResetFs->setText(QApplication::translate("MainWindow", "&Reset+Fullscreen", 0, QApplication::UnicodeUTF8));
545   actionSimulated_click->setText(QApplication::translate("MainWindow", "Push &it!", 0, QApplication::UnicodeUTF8));
546   actionPause->setText(QApplication::translate("MainWindow", "&Pause", 0, QApplication::UnicodeUTF8));
547   actionShow_full_list->setText(QApplication::translate("MainWindow", "Show full &list", 0, QApplication::UnicodeUTF8));
548   actionSave_full_list->setText(QApplication::translate("MainWindow", "&Save full list", 0, QApplication::UnicodeUTF8));
549   actionAbout->setText(QApplication::translate("MainWindow", "&About", 0, QApplication::UnicodeUTF8));
550   actionEnable_full_date->setText(QApplication::translate("MainWindow", "Enable &full date", 0, QApplication::UnicodeUTF8));
551   actionEnable_time_and_date->setText(QApplication::translate("MainWindow", "Enable &time and date", 0, QApplication::UnicodeUTF8));
552   action_Fullscreen->setText(QApplication::translate("MainWindow", "&Fullscreen", 0, QApplication::UnicodeUTF8));
553   actionChangeNumberOfLines->setText(QApplication::translate("MainWindow", "&Lines", 0, QApplication::UnicodeUTF8));
554   actionChangeFontSize->setText(QApplication::translate("MainWindow", "Font &size", 0, QApplication::UnicodeUTF8));
555   mainButton->setText(QApplication::translate("MainWindow", "Start", 0, QApplication::UnicodeUTF8));
556   menuPush_It_Menu->setTitle(QApplication::translate("MainWindow", "&Counter", 0, QApplication::UnicodeUTF8));
557   menuHelp->setTitle(QApplication::translate("MainWindow", "&Help", 0, QApplication::UnicodeUTF8));
558   menuData->setTitle(QApplication::translate("MainWindow", "&Data", 0, QApplication::UnicodeUTF8));
559   menuView->setTitle(QApplication::translate("MainWindow", "&View", 0, QApplication::UnicodeUTF8));
560 } // retranslateUi
561
562 void MainWindow::changeNumberOfLines(int number)
563 {
564   int maxLines = 1000;
565   bool ok = true;
566   if(number == 0)
567   {
568     number = QInputDialog::getInt(this, tr("Lines"),
569                                   tr("Number of lines to be displayed:"),
570                                   maximumNumberOfLines, 0, maxLines, 1,
571                                   &ok);
572   }
573
574   if(false == ok)
575   {
576     return;
577   }
578   else if (number > 0 && number < maxLines)
579   {
580     maximumNumberOfLines = number;
581   }
582   else
583   {
584     QString text;
585     text.sprintf("Invalid entry. Try something like \"4\" (max %i).",maxLines);
586     QMessageBox::information(this, tr("Invalid entry"),
587                              tr(text.toStdString().c_str()));
588     return;
589   }
590
591   QString actionText = actionChangeNumberOfLines->text();
592   actionText = "&Lines (";
593   actionText += QString::number(maximumNumberOfLines);
594   actionText += ")";
595   actionChangeNumberOfLines->setText(actionText);
596   mainButtonRefreshTextOnly();
597 }
598
599 void MainWindow::changeFontSize(int size)
600 {
601   int maxSize = 100;
602   bool ok = false;
603   QFont font = mainButton->font();
604   if(size == 0)
605   {
606     size = QInputDialog::getInt(this, tr("Font size"),
607                                   tr("Font size:"),
608                                   font.pointSize(), 0, maxSize, 1,
609                                   &ok);
610   }
611   else
612   {
613     ok = true;
614   }
615
616   if(false == ok)
617   {
618     return;
619   }
620   else if (size > 0 && size < maxSize)
621   {
622     font.setPointSize(size);
623     mainButton->setFont(font);
624   }
625   else
626   {
627     QString text;
628     text.sprintf("Invalid entry. Try something like \"28\" (max %i).",maxSize);
629     QMessageBox::information(this, tr("Invalid entry"),
630                              tr(text.toStdString().c_str()));
631     return;
632   }
633
634   QString actionText = actionChangeFontSize->text();
635   actionText = "Font &size (";
636   actionText += QString::number(size);
637   actionText += ")";
638   actionChangeFontSize->setText(actionText);
639   mainButtonRefreshTextOnly();
640 }
641
642 /*
643 function to use when the text itself is not changed but the number of lines or
644 the formatting of the text
645 */
646 void MainWindow::mainButtonRefreshTextOnly()
647 {
648   QString string = completeString;
649   QStringList lines;
650
651   if(1 == hitCount)
652   {
653     string = getHeaderLine();
654   }
655
656   lines = string.split("\n");
657
658   /* adjust the total number of lines of the button */
659   if(lines.length() > maximumNumberOfLines)
660   {
661     while(lines.length() > maximumNumberOfLines
662           && lines.length() > 0)
663     {
664       lines.removeLast();
665     }
666   }
667   else if(lines.length() < maximumNumberOfLines)
668   {
669     while(lines.length() < maximumNumberOfLines)
670     {
671       lines.append(" ");
672     }
673   }
674
675   string = lines.join("\n");
676   this->mainButton->setText(tr(string.toStdString().c_str()));
677 }
678
679 void MainWindow::resetProgramAndFullscreen()
680 {
681   resetProgram();
682   action_Fullscreen->trigger();
683 }