File indexing completed on 2024-04-21 05:54:04

0001 /*
0002     SPDX-FileCopyrightText: 2006 Bram Schoenmakers <bramschoenmakers@kde.nl>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "rsistats.h"
0008 #include "rsistatitem.h"
0009 
0010 #include <QDateTime>
0011 #include <QLocale>
0012 
0013 #include <KLocalizedString>
0014 
0015 RSIStats::RSIStats()
0016     : m_doUpdates(false)
0017 {
0018     m_statistics.insert(TOTAL_TIME, new RSIStatItem(i18n("Total recorded time")));
0019     m_statistics[TOTAL_TIME]->addDerivedItem(ACTIVITY_PERC);
0020 
0021     m_statistics.insert(ACTIVITY, new RSIStatItem(i18n("Total time of activity")));
0022     m_statistics[ACTIVITY]->addDerivedItem(ACTIVITY_PERC);
0023     m_statistics[ACTIVITY]->addDerivedItem(ACTIVITY_PERC_MINUTE);
0024     m_statistics[ACTIVITY]->addDerivedItem(ACTIVITY_PERC_HOUR);
0025     m_statistics[ACTIVITY]->addDerivedItem(ACTIVITY_PERC_6HOUR);
0026 
0027     m_statistics.insert(IDLENESS, new RSIStatItem(i18n("Total time being idle")));
0028     m_statistics[IDLENESS]->addDerivedItem(ACTIVITY_PERC_MINUTE);
0029     m_statistics[IDLENESS]->addDerivedItem(ACTIVITY_PERC_HOUR);
0030     m_statistics[IDLENESS]->addDerivedItem(ACTIVITY_PERC_6HOUR);
0031 
0032     m_statistics.insert(ACTIVITY_PERC, new RSIStatItem(i18n("Percentage of activity"), 0));
0033 
0034     m_statistics.insert(ACTIVITY_PERC_MINUTE, new RSIStatBitArrayItem(i18n("Percentage of activity last minute"), QVariant(0), 60));
0035     m_statistics.insert(ACTIVITY_PERC_HOUR, new RSIStatBitArrayItem(i18n("Percentage of activity last hour"), QVariant(0), 3600));
0036     m_statistics.insert(ACTIVITY_PERC_6HOUR, new RSIStatBitArrayItem(i18n("Percentage of activity last 6 hours"), QVariant(0), 6 * 3600));
0037 
0038     m_statistics.insert(MAX_IDLENESS, new RSIStatItem(i18n("Maximum idle period")));
0039     m_statistics[MAX_IDLENESS]->addDerivedItem(IDLENESS);
0040 
0041     m_statistics.insert(CURRENT_IDLE_TIME, new RSIStatItem(i18n("Current idle period")));
0042 
0043     m_statistics.insert(IDLENESS_CAUSED_SKIP_TINY, new RSIStatItem(i18n("Number of skipped short breaks (idle)")));
0044 
0045     m_statistics.insert(IDLENESS_CAUSED_SKIP_BIG, new RSIStatItem(i18n("Number of skipped long breaks (idle)")));
0046 
0047     m_statistics.insert(TINY_BREAKS, new RSIStatItem(i18n("Total number of short breaks")));
0048     m_statistics[TINY_BREAKS]->addDerivedItem(PAUSE_SCORE);
0049     m_statistics[TINY_BREAKS]->addDerivedItem(LAST_TINY_BREAK);
0050 
0051     m_statistics.insert(TINY_BREAKS_SKIPPED, new RSIStatItem(i18n("Number of skipped short breaks (user)")));
0052     m_statistics[TINY_BREAKS_SKIPPED]->addDerivedItem(PAUSE_SCORE);
0053 
0054     m_statistics.insert(TINY_BREAKS_POSTPONED, new RSIStatItem(i18n("Number of postponed short breaks (user)")));
0055 
0056     QDateTime dt(QDate(-1, -1, -1), QTime(-1, -1, -1));
0057     m_statistics.insert(LAST_TINY_BREAK, new RSIStatItem(i18n("Last short break"), dt));
0058 
0059     m_statistics.insert(BIG_BREAKS, new RSIStatItem(i18n("Total number of long breaks")));
0060     m_statistics[BIG_BREAKS]->addDerivedItem(PAUSE_SCORE);
0061     m_statistics[BIG_BREAKS]->addDerivedItem(LAST_BIG_BREAK);
0062 
0063     m_statistics.insert(BIG_BREAKS_SKIPPED, new RSIStatItem(i18n("Number of skipped long breaks (user)")));
0064     m_statistics[BIG_BREAKS_SKIPPED]->addDerivedItem(PAUSE_SCORE);
0065 
0066     m_statistics.insert(BIG_BREAKS_POSTPONED, new RSIStatItem(i18n("Number of postponed long breaks (user)")));
0067 
0068     m_statistics.insert(LAST_BIG_BREAK, new RSIStatItem(i18n("Last long break"), dt));
0069 
0070     m_statistics.insert(PAUSE_SCORE, new RSIStatItem(i18n("Pause score"), 100));
0071 
0072     // initialise labels
0073     for (int i = 0; i < STAT_COUNT; ++i) {
0074         QLabel *l = new QLabel(nullptr);
0075         const QString whatsThis = getWhatsThisText(static_cast<RSIStat>(i));
0076         l->setWhatsThis(whatsThis);
0077         m_statistics[i]->getDescription()->setWhatsThis(whatsThis);
0078         m_labels << l;
0079     }
0080 
0081     // initialise statistics
0082     reset();
0083 }
0084 
0085 RSIStats::~RSIStats()
0086 {
0087     qDeleteAll(m_labels);
0088 
0089     for (int i = 0; i < STAT_COUNT; ++i) {
0090         RSIStatItem *item = m_statistics[i];
0091         QLabel *l = item->getDescription();
0092         delete l;
0093         delete item;
0094     }
0095 }
0096 
0097 void RSIStats::reset()
0098 {
0099     for (int i = 0; i < STAT_COUNT; ++i) {
0100         m_statistics[i]->reset();
0101         updateStat(static_cast<RSIStat>(i), /* update derived stats */ false);
0102     }
0103 }
0104 
0105 void RSIStats::increaseStat(RSIStat stat, int delta)
0106 {
0107     QVariant v = m_statistics[stat]->getValue();
0108 
0109     if (v.type() == QVariant::Int)
0110         m_statistics[stat]->setValue(v.toInt() + delta);
0111     else if (v.type() == QVariant::Double)
0112         m_statistics[stat]->setValue(v.toDouble() + (double)delta);
0113     else if (v.type() == QVariant::DateTime)
0114         m_statistics[stat]->setValue(QDateTime(v.toDateTime()).addSecs(delta));
0115 
0116     updateStat(stat);
0117 }
0118 
0119 void RSIStats::setStat(RSIStat stat, const QVariant &val, bool ifmax)
0120 {
0121     QVariant v = m_statistics[stat]->getValue();
0122 
0123     if (!ifmax || (v.type() == QVariant::Int && val.toInt() > v.toInt()) || (v.type() == QVariant::Double && val.toDouble() > v.toDouble())
0124         || (v.type() == QVariant::DateTime && val.toDateTime() > v.toDateTime()))
0125         m_statistics[stat]->setValue(val);
0126 
0127     // WATCH OUT: IDLENESS is derived from MAX_IDLENESS and needs to be
0128     // updated regardless if a new value is set.
0129     updateStat(stat);
0130 }
0131 
0132 void RSIStats::updateDependentStats(RSIStat stat)
0133 {
0134     const QList<RSIStat> &stats = m_statistics[stat]->getDerivedItems();
0135     for (int i = 0; i < stats.count(); ++i) {
0136         RSIStat it = stats.at(i);
0137         switch ((it)) {
0138         case PAUSE_SCORE: {
0139             double a = m_statistics[TINY_BREAKS_SKIPPED]->getValue().toDouble();
0140             double b = m_statistics[BIG_BREAKS_SKIPPED]->getValue().toDouble();
0141             double c = m_statistics[IDLENESS_CAUSED_SKIP_TINY]->getValue().toDouble();
0142             double d = m_statistics[IDLENESS_CAUSED_SKIP_BIG]->getValue().toDouble();
0143 
0144             RSIGlobals *glbl = RSIGlobals::instance();
0145             double ratio = (double)(glbl->intervals()[BIG_BREAK_DURATION]) / (double)(glbl->intervals()[TINY_BREAK_DURATION]);
0146 
0147             double skipped = a - c + ratio * (b - d);
0148             skipped = skipped < 0 ? 0 : skipped;
0149 
0150             double total = m_statistics[TINY_BREAKS]->getValue().toDouble();
0151             total += ratio * m_statistics[BIG_BREAKS]->getValue().toDouble();
0152 
0153             if (total > 0)
0154                 m_statistics[it]->setValue(100 - ((skipped / total) * 100));
0155             else
0156                 m_statistics[it]->setValue(0);
0157 
0158             updateStat(it);
0159             break;
0160         }
0161 
0162         case IDLENESS: {
0163             increaseStat(IDLENESS);
0164             break;
0165         }
0166 
0167         case ACTIVITY_PERC: {
0168             /*
0169                                             seconds of activity
0170                 activity_percentage =  100 - -------------------
0171                                                 total seconds
0172             */
0173 
0174             double activity = m_statistics[ACTIVITY]->getValue().toDouble();
0175             double total = m_statistics[TOTAL_TIME]->getValue().toDouble();
0176 
0177             if (total > 0)
0178                 m_statistics[it]->setValue((activity / total) * 100);
0179             else
0180                 m_statistics[it]->setValue(0);
0181 
0182             updateStat(it);
0183             break;
0184         }
0185 
0186         case ACTIVITY_PERC_MINUTE:
0187         case ACTIVITY_PERC_HOUR:
0188         case ACTIVITY_PERC_6HOUR: {
0189             if (stat == ACTIVITY)
0190                 static_cast<RSIStatBitArrayItem *>(m_statistics[it])->setActivity();
0191             else
0192                 static_cast<RSIStatBitArrayItem *>(m_statistics[it])->setIdle();
0193 
0194             updateStat(it);
0195             break;
0196         }
0197 
0198         case LAST_BIG_BREAK: {
0199             setStat(LAST_BIG_BREAK, QDateTime::currentDateTime());
0200             break;
0201         }
0202 
0203         case LAST_TINY_BREAK: {
0204             setStat(LAST_TINY_BREAK, QDateTime::currentDateTime());
0205             break;
0206         }
0207 
0208         default:; // nada
0209         }
0210     }
0211 }
0212 
0213 void RSIStats::updateStat(RSIStat stat, bool updateDerived)
0214 {
0215     if (updateDerived)
0216         updateDependentStats(stat);
0217 
0218     if (m_doUpdates)
0219         updateLabel(stat);
0220 }
0221 
0222 void RSIStats::updateLabel(RSIStat stat)
0223 {
0224     QLabel *l = m_labels[stat];
0225     double v;
0226 
0227     switch (stat) {
0228         // integer values representing a time
0229     case TOTAL_TIME:
0230     case ACTIVITY:
0231     case IDLENESS:
0232     case MAX_IDLENESS:
0233     case CURRENT_IDLE_TIME:
0234         l->setText(RSIGlobals::instance()->formatSeconds(m_statistics[stat]->getValue().toInt()));
0235         break;
0236 
0237         // plain integer values
0238     case TINY_BREAKS:
0239     case TINY_BREAKS_SKIPPED:
0240     case TINY_BREAKS_POSTPONED:
0241     case IDLENESS_CAUSED_SKIP_TINY:
0242     case BIG_BREAKS:
0243     case BIG_BREAKS_SKIPPED:
0244     case BIG_BREAKS_POSTPONED:
0245     case IDLENESS_CAUSED_SKIP_BIG:
0246         l->setText(QString::number(m_statistics[stat]->getValue().toInt()));
0247         break;
0248 
0249         // doubles
0250     case PAUSE_SCORE:
0251         v = m_statistics[stat]->getValue().toDouble();
0252         setColor(stat, QColor((int)(255 - 2.55 * v), (int)(1.60 * v), 0));
0253         l->setText(QString::number(m_statistics[stat]->getValue().toDouble(), 'f', 1));
0254         break;
0255     case ACTIVITY_PERC:
0256     case ACTIVITY_PERC_MINUTE:
0257     case ACTIVITY_PERC_HOUR:
0258     case ACTIVITY_PERC_6HOUR:
0259         v = m_statistics[stat]->getValue().toDouble();
0260         setColor(stat, QColor((int)(2.55 * v), (int)(160 - 1.60 * v), 0));
0261         l->setText(QString::number(m_statistics[stat]->getValue().toDouble(), 'f', 1));
0262         break;
0263 
0264         // datetimes
0265     case LAST_BIG_BREAK:
0266     case LAST_TINY_BREAK: {
0267         QTime when(m_statistics[stat]->getValue().toTime());
0268         when.isValid() ? l->setText(when.toString()) : l->clear();
0269         break;
0270     }
0271 
0272     default:; // nada
0273     }
0274 
0275     // some stats need a %
0276     if (stat == PAUSE_SCORE || stat == ACTIVITY_PERC || stat == ACTIVITY_PERC_MINUTE || stat == ACTIVITY_PERC_HOUR || stat == ACTIVITY_PERC_6HOUR)
0277         l->setText(l->text() + '%');
0278 }
0279 
0280 void RSIStats::updateLabels()
0281 {
0282     if (!m_doUpdates)
0283         return;
0284 
0285     for (int i = 0; i < STAT_COUNT; ++i) {
0286         updateLabel(static_cast<RSIStat>(i));
0287     }
0288 }
0289 
0290 QVariant RSIStats::getStat(RSIStat stat) const
0291 {
0292     return m_statistics[stat]->getValue();
0293 }
0294 
0295 QLabel *RSIStats::getLabel(RSIStat stat) const
0296 {
0297     return m_labels[stat];
0298 }
0299 
0300 QLabel *RSIStats::getDescription(RSIStat stat) const
0301 {
0302     return m_statistics[stat]->getDescription();
0303 }
0304 
0305 QString RSIStats::getWhatsThisText(RSIStat stat) const
0306 {
0307     switch (stat) {
0308     case TOTAL_TIME:
0309         return i18n("This is the total time RSIBreak has been running.");
0310     case ACTIVITY:
0311         return i18n(
0312             "This is the total amount of time you used the "
0313             "keyboard or mouse.");
0314     case IDLENESS:
0315         return i18n(
0316             "This is the total amount of time you did not use "
0317             "the keyboard or mouse.");
0318     case ACTIVITY_PERC:
0319         return i18n(
0320             "This is a percentage of activity, based on the "
0321             "periods of activity vs. the total time RSIBreak has been running. "
0322             "The color indicates the level of your activity. When the color is "
0323             "close to full red it is recommended you lower your work pace.");
0324     case MAX_IDLENESS:
0325         return i18n(
0326             "This is the longest period of inactivity measured "
0327             "while RSIBreak has been running.");
0328     case TINY_BREAKS:
0329         return i18n("This is the total number of short breaks");
0330     case LAST_TINY_BREAK:
0331         return i18n(
0332             "This is the time of the last finished short break. "
0333             "The color of this text gradually turns from green to red, "
0334             "indicating when you can expect the next tiny break.");
0335     case TINY_BREAKS_SKIPPED:
0336         return i18n(
0337             "This is the total number of short breaks "
0338             "which you skipped.");
0339     case TINY_BREAKS_POSTPONED:
0340         return i18n(
0341             "This is the total number of short breaks "
0342             "which you postponed.");
0343     case IDLENESS_CAUSED_SKIP_TINY:
0344         return i18n(
0345             "This is the total number of short breaks "
0346             "which were skipped because you were idle.");
0347     case BIG_BREAKS:
0348         return i18n("This is the total number of long breaks.");
0349     case LAST_BIG_BREAK:
0350         return i18n(
0351             "This is the time of the last finished long break."
0352             "The color of this text gradually turns from green to red,"
0353             "indicating when you can expect the next big break.");
0354     case BIG_BREAKS_SKIPPED:
0355         return i18n(
0356             "This is the total number of long breaks "
0357             "which you skipped.");
0358     case BIG_BREAKS_POSTPONED:
0359         return i18n(
0360             "This is the total number of long breaks "
0361             "which you postponed.");
0362     case IDLENESS_CAUSED_SKIP_BIG:
0363         return i18n(
0364             "This is the total number of long breaks "
0365             "which were skipped because you were idle.");
0366     case PAUSE_SCORE:
0367         return i18n(
0368             "This is an indication of how well you behaved "
0369             "with the breaks. It decreases every time you skip a break.");
0370     case CURRENT_IDLE_TIME:
0371         return i18n("This is the current idle time.");
0372     case ACTIVITY_PERC_MINUTE:
0373         return i18n(
0374             "This is a percentage of activity during the last minute. "
0375             "The color indicates the level of your activity. When the color is "
0376             "close to full red it is recommended you lower your work pace.");
0377     case ACTIVITY_PERC_HOUR:
0378         return i18n(
0379             "This is a percentage of activity during the last hour. "
0380             "The color indicates the level of your activity. When the color is "
0381             "close to full red it is recommended you lower your work pace.");
0382     case ACTIVITY_PERC_6HOUR:
0383         return i18n(
0384             "This is a percentage of activity during the last 6 hours. "
0385             "The color indicates the level of your activity. When the color is "
0386             "close to full red it is recommended you lower your work pace.");
0387     default:;
0388     }
0389 
0390     return QString();
0391 }
0392 
0393 void RSIStats::setColor(RSIStat stat, const QColor &color)
0394 {
0395     QPalette normal;
0396     normal.setColor(QPalette::Active, QPalette::WindowText, color);
0397     m_statistics[stat]->getDescription()->setPalette(normal);
0398     m_labels[stat]->setPalette(normal);
0399 }
0400 
0401 void RSIStats::doUpdates(bool b)
0402 {
0403     m_doUpdates = b;
0404     if (m_doUpdates)
0405         updateLabels();
0406 }