diff --git a/src/rsiglobals.cpp b/src/rsiglobals.cpp index 487f3a7..7ec2042 100644 --- a/src/rsiglobals.cpp +++ b/src/rsiglobals.cpp @@ -1,112 +1,113 @@ /* Copyright (C) 2006,2010 Tom Albers Copyright (C) 2010 Juan Luis Baptiste This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "rsiglobals.h" #include #include #include #include #include #include #include #include "rsistats.h" RSIGlobals *RSIGlobals::m_instance = 0; RSIStats *RSIGlobals::m_stats = 0; RSIGlobals::RSIGlobals( QObject *parent ) : QObject( parent ) { resetUsage(); slotReadConfig(); } RSIGlobals::~RSIGlobals() { delete m_stats; m_stats = 0L; } RSIGlobals *RSIGlobals::instance() { if ( !m_instance ) { m_instance = new RSIGlobals(); m_stats = new RSIStats(); } return m_instance; } QString RSIGlobals::formatSeconds( const int seconds ) { return m_format.formatSpelloutDuration( seconds * 1000 ); } void RSIGlobals::slotReadConfig() { KConfigGroup config = KSharedConfig::openConfig()->group( "General Settings" ); m_intervals.resize(INTERVAL_COUNT); m_intervals[TINY_BREAK_INTERVAL] = config.readEntry( "TinyInterval", 10 ) * 60; m_intervals[TINY_BREAK_DURATION] = config.readEntry( "TinyDuration", 20 ); m_intervals[TINY_BREAK_THRESHOLD] = config.readEntry( "TinyThreshold", 20 ); m_intervals[BIG_BREAK_INTERVAL] = config.readEntry( "BigInterval", 60 ) * 60; m_intervals[BIG_BREAK_DURATION] = config.readEntry( "BigDuration", 1 ) * 60; m_intervals[BIG_BREAK_THRESHOLD] = config.readEntry( "BigThreshold", 1 ) * 60; m_intervals[POSTPONE_BREAK_INTERVAL] = config.readEntry( "PostponeBreakDuration", 5 ) * 60; m_intervals[PATIENCE_INTERVAL] = config.readEntry( "Patience", 30 ); + m_intervals[SHORT_INPUT_INTERVAL] = config.readEntry( "ShortInputInterval", 2 ); if ( config.readEntry( "DEBUG", 0 ) > 0 ) { qDebug() << "Debug mode activated"; m_intervals[TINY_BREAK_INTERVAL] = m_intervals[TINY_BREAK_INTERVAL] / 60; m_intervals[BIG_BREAK_INTERVAL] = m_intervals[BIG_BREAK_INTERVAL] / 60; m_intervals[BIG_BREAK_DURATION] = m_intervals[BIG_BREAK_DURATION] / 60; m_intervals[POSTPONE_BREAK_INTERVAL] = m_intervals[POSTPONE_BREAK_INTERVAL] / 60; } } QColor RSIGlobals::getTinyBreakColor( int secsToBreak ) const { int minimized = m_intervals[TINY_BREAK_INTERVAL]; double v = 100 * secsToBreak / ( double )minimized; v = v > 100 ? 100 : v; v = v < 0 ? 0 : v; return QColor(( int )( 255 - 2.55 * v ), ( int )( 1.60 * v ), 0 ); } QColor RSIGlobals::getBigBreakColor( int secsToBreak ) const { int minimized = m_intervals[BIG_BREAK_INTERVAL]; double v = 100 * secsToBreak / ( double )minimized; v = v > 100 ? 100 : v; v = v < 0 ? 0 : v; return QColor(( int )( 255 - 2.55 * v ), ( int )( 1.60 * v ), 0 ); } void RSIGlobals::resetUsage() { m_usageArray.fill( false, 60 * 60 * 24 ); } diff --git a/src/rsiglobals.h b/src/rsiglobals.h index 0c6d4d0..9895b7b 100644 --- a/src/rsiglobals.h +++ b/src/rsiglobals.h @@ -1,167 +1,168 @@ /* Copyright (C) 2006 Tom Albers This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef RSIGLOBALS_H #define RSIGLOBALS_H #include #include #include #include #include #include class RSIStats; enum RSIStat { TOTAL_TIME = 0, ACTIVITY, IDLENESS, ACTIVITY_PERC, ACTIVITY_PERC_MINUTE, ACTIVITY_PERC_HOUR, ACTIVITY_PERC_6HOUR, MAX_IDLENESS, CURRENT_IDLE_TIME, IDLENESS_CAUSED_SKIP_TINY, IDLENESS_CAUSED_SKIP_BIG, TINY_BREAKS, TINY_BREAKS_SKIPPED, TINY_BREAKS_POSTPONED, LAST_TINY_BREAK, BIG_BREAKS, BIG_BREAKS_SKIPPED, BIG_BREAKS_POSTPONED, LAST_BIG_BREAK, PAUSE_SCORE, STAT_COUNT }; enum RSIInterval { TINY_BREAK_INTERVAL = 0, TINY_BREAK_DURATION, TINY_BREAK_THRESHOLD, BIG_BREAK_INTERVAL, BIG_BREAK_DURATION, BIG_BREAK_THRESHOLD, POSTPONE_BREAK_INTERVAL, PATIENCE_INTERVAL, + SHORT_INPUT_INTERVAL, INTERVAL_COUNT }; /** * @class RSIGlobals * This class consists of a few commonly used routines and values. * @author Tom Albers */ class RSIGlobals : public QObject { Q_OBJECT public: /** Default constructor. */ explicit RSIGlobals( QObject *parent = 0 ); /** Default destructor. */ ~RSIGlobals(); /** * Returns an instance of RSIGlobals. Never create your own instance * of RSIGlobals, but use this method instead to get the one and only * instance. */ static RSIGlobals *instance(); /** * Returns the one and only statistics component. * * @see RSIStats */ static RSIStats *stats() { return m_stats; } /** * Converts @p seconds to a reasonable string. * @param seconds the amount of seconds * @returns a formatted string. */ QString formatSeconds( const int seconds ); /** * Returns a reference to the mapping containing all intervals. * These intervals define when a tiny or big break should occur and for how * long. */ const QVector &intervals() const { return m_intervals; } /** * This function returns a color ranging from green to red. * The more red, the more the user needs a tiny break. */ QColor getTinyBreakColor( int secsToBreak ) const; /** * This function returns a color ranging from green to red. * The more red, the more the user needs a tiny break. */ QColor getBigBreakColor( int secsToBreak ) const; /** * Returns the array which keeps track per second for 24 hours when the * user was active or idle. Activity = 1, idle = 0. * The RSIStatBitArrayItem can read and write a certain interval of this * array, for example to measure the activity in 60 seconds or 1 hour. * * @see RSIStatBitArrayItem */ QBitArray *usageArray() { return &m_usageArray; } /** * Resets the usage array, with all values to 0. */ void resetUsage(); /** * * Hook to KDE's Notifying system at start/end of a break. * @param start when true the start commands are executed, false executes * the ones at the end of a break. * @param big true for big breaks, false for short ones. */ void NotifyBreak( bool start, bool big ); public slots: /** * Reads the configuration. */ void slotReadConfig(); private: static RSIGlobals *m_instance; static RSIStats *m_stats; QVector m_intervals; QBitArray m_usageArray; KFormat m_format; }; #endif // RSIGLOBALS_H diff --git a/src/rsitimer.cpp b/src/rsitimer.cpp index 548bc4a..582260c 100644 --- a/src/rsitimer.cpp +++ b/src/rsitimer.cpp @@ -1,325 +1,326 @@ /* Copyright (C) 2005-2006,2008-2010 Tom Albers Copyright (C) 2005-2006 Bram Schoenmakers Copyright (C) 2010 Juan Luis Baptiste The parts for idle detection is based on kdepim's karm idletimedetector.cpp/.h This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "rsitimer.h" #include #include #include #include #include #include "rsiglobals.h" #include "rsistats.h" RSITimer::RSITimer( QObject *parent ) : QThread( parent ) , m_idleTimeInstance( new RSIIdleTimeImpl() ) , m_intervals( RSIGlobals::instance()->intervals() ) , m_state ( TimerState::Monitoring ) { updateConfig( true ); } RSITimer::RSITimer( std::unique_ptr &&_idleTime, const QVector _intervals, const bool _usePopup, const bool _useIdleTimers ) : QThread( nullptr ) , m_idleTimeInstance( std::move(_idleTime) ) , m_usePopup( _usePopup ) , m_useIdleTimers( _useIdleTimers ) , m_intervals( _intervals ) , m_state( TimerState::Monitoring ) { createTimers(); } void RSITimer::createTimers() { int bigThreshold = m_useIdleTimers ? m_intervals[BIG_BREAK_THRESHOLD] : INT_MAX; int tinyThreshold = m_useIdleTimers ? m_intervals[TINY_BREAK_THRESHOLD] : INT_MAX; m_bigBreakCounter = std::unique_ptr { new RSITimerCounter( m_intervals[BIG_BREAK_INTERVAL], m_intervals[BIG_BREAK_DURATION], bigThreshold ) }; m_tinyBreakCounter = std::unique_ptr { new RSITimerCounter( m_intervals[TINY_BREAK_INTERVAL], m_intervals[TINY_BREAK_DURATION], tinyThreshold ) }; } void RSITimer::run() { QTimer timer; connect( &timer, &QTimer::timeout, this, &RSITimer::timeout ); timer.setTimerType( Qt::TimerType::CoarseTimer ); timer.start( 1000 ); exec(); // start event loop to make timers work. } void RSITimer::hibernationDetector( const int totalIdle ) { // poor mans hibernation detector.... static QDateTime last = QDateTime::currentDateTime(); QDateTime current = QDateTime::currentDateTime(); if ( last.secsTo( current ) > 60 ) { qDebug() << "Not been checking idleTime for more than 60 seconds, " << "assuming the computer hibernated, resetting timers" << "Last: " << last << "Current: " << current << "Idle, s: " << totalIdle; resetAfterBreak(); } last = current; } int RSITimer::idleTime() { int totalIdle = m_idleTimeInstance->getIdleTime() / 1000; hibernationDetector( totalIdle ); // TODO Find a modern-desktop way to check if the screensaver is inhibited // and disable the timer because we assume you're doing for example a presentation and // don't want rsibreak to annoy you return totalIdle; } void RSITimer::doBreakNow( const int breakTime, const bool nextBreakIsBig ) { m_state = TimerState::Resting; m_pauseCounter = std::unique_ptr { new RSITimerCounter( breakTime, breakTime, INT_MAX ) }; m_popupCounter = nullptr; + m_shortInputCounter = std::unique_ptr { new RSITimerCounter( m_intervals[SHORT_INPUT_INTERVAL], 1, 1 ) }; if ( nextBreakIsBig ) { emit startLongBreak(); } else { emit startShortBreak(); } emit updateWidget( breakTime ); emit breakNow(); } void RSITimer::resetAfterBreak() { m_state = TimerState::Monitoring; m_pauseCounter = nullptr; m_popupCounter = nullptr; m_shortInputCounter = nullptr; defaultUpdateToolTip(); emit updateIdleAvg( 0.0 ); emit relax( -1, false ); emit minimize(); if ( m_bigBreakCounter->isReset() ) { emit endLongBreak(); } else { emit endShortBreak(); } } // -------------------------- SLOTS ------------------------// void RSITimer::slotStart() { m_state = TimerState::Monitoring; } void RSITimer::slotStop() { m_state = TimerState::Suspended; emit updateIdleAvg( 0.0 ); emit updateToolTip( 0, 0 ); } void RSITimer::slotSuspended( bool suspend ) { suspend ? slotStop() : slotStart(); } void RSITimer::slotLock() { resetAfterBreak(); } void RSITimer::skipBreak() { if ( m_bigBreakCounter->isReset() ) { RSIGlobals::instance()->stats()->increaseStat( BIG_BREAKS_SKIPPED ); emit bigBreakSkipped(); } else { RSIGlobals::instance()->stats()->increaseStat( TINY_BREAKS_SKIPPED ); emit tinyBreakSkipped(); } resetAfterBreak(); } void RSITimer::postponeBreak() { if ( m_bigBreakCounter->isReset() ) { m_bigBreakCounter->postpone( m_intervals[POSTPONE_BREAK_INTERVAL] ); RSIGlobals::instance()->stats()->increaseStat( BIG_BREAKS_POSTPONED ); } else { m_tinyBreakCounter->postpone( m_intervals[POSTPONE_BREAK_INTERVAL] ); RSIGlobals::instance()->stats()->increaseStat( TINY_BREAKS_POSTPONED ); } resetAfterBreak(); } void RSITimer::updateConfig( bool doRestart ) { KConfigGroup popupConfig = KSharedConfig::openConfig()->group( "Popup Settings" ); m_usePopup = popupConfig.readEntry( "UsePopup", true ); bool oldUseIdleTimers = m_useIdleTimers; KConfigGroup generalConfig = KSharedConfig::openConfig()->group( "General Settings" ); m_useIdleTimers = !( generalConfig.readEntry( "UseNoIdleTimer", false ) ); doRestart = doRestart || ( oldUseIdleTimers != m_useIdleTimers ); const QVector oldIntervals = m_intervals; m_intervals = RSIGlobals::instance()->intervals(); doRestart = doRestart || ( m_intervals != oldIntervals ); if ( doRestart ) { qDebug() << "Timeout parameters have changed, counters were reset."; createTimers(); } } // ----------------------------- EVENTS -----------------------// void RSITimer::timeout() { // Don't change the tray icon when suspended, or evaluate a possible break. if ( m_state == TimerState::Suspended ) { return; } const int idleSeconds = idleTime(); // idleSeconds == 0 means activity RSIGlobals::instance()->stats()->increaseStat( TOTAL_TIME ); RSIGlobals::instance()->stats()->setStat( CURRENT_IDLE_TIME, idleSeconds ); if ( idleSeconds == 0 ) { RSIGlobals::instance()->stats()->increaseStat( ACTIVITY ); } else { RSIGlobals::instance()->stats()->setStat( MAX_IDLENESS, idleSeconds, true ); } switch ( m_state ) { case TimerState::Monitoring: { // This is a weird thing to track as now when user was away, they will get back to zero counters, // not to an arbitrary time elapsed since last "idleness-skip-break". bool bigWasReset = m_bigBreakCounter->isReset(); bool tinyWasReset = m_tinyBreakCounter->isReset(); int breakTime = std::max( m_bigBreakCounter->tick( idleSeconds ), m_tinyBreakCounter->tick( idleSeconds ) ); if ( breakTime > 0 ) { suggestBreak( breakTime ); } else { // Not a time for break yet, but if one of the counters got reset, that means we were idle enough to skip. if ( !bigWasReset && m_bigBreakCounter->isReset() ) { RSIGlobals::instance()->stats()->increaseStat( BIG_BREAKS ); RSIGlobals::instance()->stats()->increaseStat( IDLENESS_CAUSED_SKIP_BIG ); } if ( !tinyWasReset && m_tinyBreakCounter->isReset() ) { RSIGlobals::instance()->stats()->increaseStat( TINY_BREAKS ); RSIGlobals::instance()->stats()->increaseStat( IDLENESS_CAUSED_SKIP_TINY ); } } const double value = 100.0 - ( ( m_tinyBreakCounter->counterLeft() / ( double ) m_intervals[TINY_BREAK_INTERVAL] ) * 100.0 ); emit updateIdleAvg( value ); break; } case TimerState::Suggesting: { // Using popupCounter to count down our patience here. int breakTime = m_popupCounter->tick( idleSeconds ); if ( breakTime > 0 ) { // User kept working throw the suggestion timeout. Well, their loss. emit relax( -1, false ); breakTime = m_pauseCounter->counterLeft(); doBreakNow( breakTime, false ); break; } bool isInputLong = (m_shortInputCounter->tick(idleSeconds) > 0); int inverseTick = ( idleSeconds == 0 && isInputLong) ? 1 : 0; // inverting as we account idle seconds here. breakTime = m_pauseCounter->tick( inverseTick ); if ( breakTime > 0 ) { // User has waited out the pause, back to monitoring. resetAfterBreak(); break; } emit relax( m_pauseCounter->counterLeft(), false ); emit updateWidget( m_pauseCounter->counterLeft() ); break; } case TimerState::Resting: { bool isInputLong = (m_shortInputCounter->tick(idleSeconds) > 0); - int inverseTick = ( idleSeconds == 0 && isInputLong > 0 ) ? 1 : 0; // inverting as we account idle seconds here. + int inverseTick = ( idleSeconds == 0 && isInputLong ) ? 1 : 0; // inverting as we account idle seconds here. int breakTime = m_pauseCounter->tick( inverseTick ); if ( breakTime > 0 ) { resetAfterBreak(); } else { emit updateWidget( m_pauseCounter->counterLeft() ); } break; } default: qDebug() << "Reached unexpected state"; } defaultUpdateToolTip(); } void RSITimer::suggestBreak( const int breakTime ) { if ( m_bigBreakCounter->isReset() ) { RSIGlobals::instance()->stats()->increaseStat( BIG_BREAKS ); RSIGlobals::instance()->stats()->setStat( LAST_BIG_BREAK, QVariant( QDateTime::currentDateTime() ) ); } else { RSIGlobals::instance()->stats()->increaseStat( TINY_BREAKS ); RSIGlobals::instance()->stats()->setStat( LAST_TINY_BREAK, QVariant( QDateTime::currentDateTime() ) ); } bool nextOneIsBig = m_bigBreakCounter->counterLeft() <= m_tinyBreakCounter->getDelayTicks(); if ( !m_usePopup ) { doBreakNow( breakTime, nextOneIsBig ); return; } m_state = TimerState::Suggesting; // When pause is longer than patience, we need to reset patience timer so that we don't flip to break now in // mid-pause. Patience / 2 is a good alternative to it by extending patience if user was idle long enough. m_popupCounter = std::unique_ptr { new RSITimerCounter( m_intervals[PATIENCE_INTERVAL], breakTime, m_intervals[PATIENCE_INTERVAL] / 2 ) }; // Threshold of one means the timer is reset on every non-zero tick. m_pauseCounter = std::unique_ptr { new RSITimerCounter( breakTime, breakTime, 1 ) }; // For measuring input duration in order to limit influence of short inputs on resetting pause counter. // Example of short input is: mouse sent input due to accidental touch or desk vibration. - m_shortInputCounter = std::unique_ptr { new RSITimerCounter( 2, 2, 1 ) }; + m_shortInputCounter = std::unique_ptr { new RSITimerCounter( m_intervals[SHORT_INPUT_INTERVAL], 1, 1 ) }; emit relax( breakTime, nextOneIsBig ); } void RSITimer::defaultUpdateToolTip() { emit updateToolTip( m_tinyBreakCounter->counterLeft(), m_bigBreakCounter->counterLeft() ); } diff --git a/test/rsitimer_test.cpp b/test/rsitimer_test.cpp index 709716a..ffeb976 100644 --- a/test/rsitimer_test.cpp +++ b/test/rsitimer_test.cpp @@ -1,388 +1,390 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "rsitimer_test.h" #include "rsiglobals.h" #include "rsitimer.h" static constexpr int RELAX_ENDED_MAGIC_VALUE = -1; RSITimerTest::RSITimerTest( void ) { m_intervals.resize( INTERVAL_COUNT ); m_intervals[TINY_BREAK_INTERVAL] = 15 * 60; m_intervals[TINY_BREAK_DURATION] = 20; m_intervals[TINY_BREAK_THRESHOLD] = 60; m_intervals[BIG_BREAK_INTERVAL] = 60 * 60; m_intervals[BIG_BREAK_DURATION] = 60; m_intervals[BIG_BREAK_THRESHOLD] = 5 * 60; m_intervals[POSTPONE_BREAK_INTERVAL] = 3 * 60; m_intervals[PATIENCE_INTERVAL] = 30; + m_intervals[SHORT_INPUT_INTERVAL] = 2; } void RSITimerTest::triggerSimpleTinyBreak() { std::unique_ptr idle_time( new RSIIdleTimeFake() ); RSIIdleTimeFake* idle_time_ptr = idle_time.get(); RSITimer timer( std::move( idle_time ), m_intervals, true, true ); QSignalSpy spyEndShortBreak( &timer, SIGNAL(endShortBreak()) ); // Part one, no idleness till small break. QSignalSpy spy1Relax( &timer, SIGNAL(relax(int,bool)) ); QSignalSpy spy1UpdateIdleAvg( &timer, SIGNAL(updateIdleAvg(double)) ); idle_time_ptr->setIdleTime( 0 ); for ( int i = 0; i < m_intervals[TINY_BREAK_INTERVAL]; i++ ) { QCOMPARE( timer.m_state, RSITimer::TimerState::Monitoring ); timer.timeout(); } QCOMPARE( timer.m_state, RSITimer::TimerState::Suggesting ); QCOMPARE( spy1Relax.count(), 1 ); QList spy1RelaxSignals = spy1Relax.takeFirst(); QCOMPARE( spy1RelaxSignals.at( 0 ).toInt(), m_intervals[TINY_BREAK_DURATION] ); QCOMPARE( spy1RelaxSignals.at( 1 ).toBool(), false ); QCOMPARE( spy1UpdateIdleAvg.count(), m_intervals[TINY_BREAK_INTERVAL] ); double lastAvg = 0; for ( int i = 0; i < m_intervals[TINY_BREAK_INTERVAL]; i++ ) { QList spy1UpdateIdleAvgSignals = spy1UpdateIdleAvg.takeFirst(); bool ok; double newAvg = spy1UpdateIdleAvgSignals.at( 0 ).toDouble( &ok ); QVERIFY2( ok && ( newAvg >= lastAvg ) && ( newAvg <= 100.0 ), QString( "Unexpected newAvg value: %1, lastAvg: %2" ).arg( newAvg ).arg( lastAvg ).toLatin1() ); } // Part two, obeying and idle as suggested. QSignalSpy spy2Relax( &timer, SIGNAL(relax(int,bool)) ); QSignalSpy spy2UpdateIdleAvg( &timer, SIGNAL(updateIdleAvg(double)) ); QSignalSpy spy2Minimize( &timer, SIGNAL(minimize()) ); for ( int i = 0; i < m_intervals[TINY_BREAK_DURATION]; i++ ) { QCOMPARE( timer.m_state, RSITimer::TimerState::Suggesting ); idle_time_ptr->setIdleTime( ( i + 1 ) * 1000 ); timer.timeout(); } QCOMPARE( timer.m_state, RSITimer::TimerState::Monitoring ); QCOMPARE( spy2Minimize.count(), 1 ); QCOMPARE( spy2Relax.count(), m_intervals[TINY_BREAK_DURATION] ); for ( int i = 1; i < m_intervals[TINY_BREAK_DURATION]; i++ ) { QList spy2RelaxSignals = spy2Relax.takeFirst(); QCOMPARE( spy2RelaxSignals.at( 0 ).toInt(), m_intervals[TINY_BREAK_DURATION] - i ); } QList spy2RelaxSignals = spy2Relax.takeFirst(); // The last one is special. QCOMPARE( spy2RelaxSignals.at( 0 ).toInt(), RELAX_ENDED_MAGIC_VALUE ); QCOMPARE( spyEndShortBreak.count(), 1 ); } void RSITimerTest::triggerComplexTinyBreak() { std::unique_ptr idle_time( new RSIIdleTimeFake() ); RSIIdleTimeFake* idle_time_ptr = idle_time.get(); RSITimer timer( std::move( idle_time ), m_intervals, true, true ); int part1 = 10; // Non-idle int part2 = 40; // Idle int part3 = m_intervals[TINY_BREAK_INTERVAL] - part1 - part2; // The rest non-idle. // Part 1, no idleness. QSignalSpy spy1Relax( &timer, SIGNAL(relax(int,bool)) ); QSignalSpy spy1UpdateIdleAvg( &timer, SIGNAL(updateIdleAvg(double)) ); idle_time_ptr->setIdleTime( 0 ); for ( int i = 0; i < part1; i++ ) { timer.timeout(); QCOMPARE( timer.m_state, RSITimer::TimerState::Monitoring ); } QCOMPARE( spy1Relax.count(), 0 ); QCOMPARE( spy1UpdateIdleAvg.count(), part1 ); // Part 2, idle for a while. QSignalSpy spy2Relax( &timer, SIGNAL(relax(int,bool)) ); QSignalSpy spy2UpdateIdleAvg( &timer, SIGNAL(updateIdleAvg(double)) ); for ( int i = 0; i < part2; i++ ) { idle_time_ptr->setIdleTime( ( i + 1 ) * 1000 ); timer.timeout(); QCOMPARE( timer.m_state, RSITimer::TimerState::Monitoring ); } QCOMPARE( spy2Relax.count(), 0 ); QCOMPARE( spy2UpdateIdleAvg.count(), part2 ); // Part 3, non-idle till break. QSignalSpy spy3Relax( &timer, SIGNAL(relax(int,bool)) ); QSignalSpy spy3UpdateIdleAvg( &timer, SIGNAL(updateIdleAvg(double)) ); for ( int i = 0; i < part3; i++ ) { QCOMPARE( timer.m_state, RSITimer::TimerState::Monitoring ); idle_time_ptr->setIdleTime( 0 ); timer.timeout(); } QCOMPARE( timer.m_state, RSITimer::TimerState::Suggesting ); QCOMPARE( spy3Relax.count(), 1 ); } void RSITimerTest::testSuspended() { std::unique_ptr idle_time( new RSIIdleTimeFake() ); RSIIdleTimeFake* idle_time_ptr = idle_time.get(); RSITimer timer( std::move( idle_time ), m_intervals, true, true ); timer.slotStop(); QCOMPARE( timer.m_state, RSITimer::TimerState::Suspended ); QSignalSpy spy1Relax( &timer, SIGNAL(relax(int,bool)) ); QSignalSpy spy1UpdateIdleAvg( &timer, SIGNAL(updateIdleAvg(double)) ); // Not idle for long enough to have a break. idle_time_ptr->setIdleTime( 0 ); for ( int i = 0; i < m_intervals[TINY_BREAK_INTERVAL]; i++ ) { timer.timeout(); QCOMPARE( timer.m_state, RSITimer::TimerState::Suspended ); } QCOMPARE( spy1Relax.count(), 0 ); QCOMPARE( spy1UpdateIdleAvg.count(), 0 ); timer.slotStart(); QCOMPARE( timer.m_state, RSITimer::TimerState::Monitoring ); } void RSITimerTest::triggerSimpleBigBreak() { std::unique_ptr idle_time( new RSIIdleTimeFake() ); RSIIdleTimeFake* idle_time_ptr = idle_time.get(); RSITimer timer( std::move( idle_time ), m_intervals, true, true ); int tinyBreaks = m_intervals[BIG_BREAK_INTERVAL] / ( m_intervals[TINY_BREAK_INTERVAL] + m_intervals[PATIENCE_INTERVAL] + m_intervals[TINY_BREAK_DURATION] ); // We don't tick big pause timer during tiny breaks and patience, so it will actually happen later. - int ticks = m_intervals[BIG_BREAK_INTERVAL] + tinyBreaks * ( m_intervals[PATIENCE_INTERVAL] + m_intervals[TINY_BREAK_DURATION] ); + // In time the patience wears out, the tiny break could already accumulate some seconds due to SHORT_INPUT_INTERVAL filter, so substract them. + int ticks = m_intervals[BIG_BREAK_INTERVAL] + tinyBreaks * ( m_intervals[PATIENCE_INTERVAL] + m_intervals[TINY_BREAK_DURATION] - (m_intervals[PATIENCE_INTERVAL] - 1) % m_intervals[SHORT_INPUT_INTERVAL] ); QSignalSpy spyEndLongBreak( &timer, SIGNAL(endLongBreak()) ); // Part one, no idleness till big break. QSignalSpy spy1Relax( &timer, SIGNAL(relax(int,bool)) ); QSignalSpy spy1UpdateIdleAvg( &timer, SIGNAL(updateIdleAvg(double)) ); idle_time_ptr->setIdleTime( 0 ); for ( int i = 0; i < ticks; i++ ) { timer.timeout(); } // Number of relax updates during N tiny breaks, plus one for the actual big break. int relaxCountExp = tinyBreaks * ( 2 + m_intervals[PATIENCE_INTERVAL] ) + 1; QCOMPARE( spy1Relax.count(), relaxCountExp ); QVERIFY2( spy1UpdateIdleAvg.count() >= m_intervals[BIG_BREAK_INTERVAL], "Failed to update the indicator regularly." ); // Part two, making the big break. QSignalSpy spy2Relax( &timer, SIGNAL(relax(int,bool)) ); for ( int i = 0; i < m_intervals[BIG_BREAK_DURATION]; i++ ) { QCOMPARE( timer.m_state, RSITimer::TimerState::Suggesting ); idle_time_ptr->setIdleTime( ( i + 1 ) * 1000 ); timer.timeout(); } QCOMPARE( timer.m_state, RSITimer::TimerState::Monitoring ); QCOMPARE( spy2Relax.count(), m_intervals[BIG_BREAK_DURATION] ); QCOMPARE( spyEndLongBreak.count(), 1 ); } void RSITimerTest::postponeBreak() { std::unique_ptr idle_time( new RSIIdleTimeFake() ); RSIIdleTimeFake* idle_time_ptr = idle_time.get(); RSITimer timer( std::move( idle_time ), m_intervals, true, true ); // Not idle for long enough to have a break. idle_time_ptr->setIdleTime( 0 ); for ( int i = 0; i < m_intervals[TINY_BREAK_INTERVAL]; i++ ) { timer.timeout(); } QCOMPARE( timer.m_state, RSITimer::TimerState::Suggesting ); QSignalSpy spyRelax( &timer, SIGNAL(relax(int,bool)) ); QSignalSpy spyMinimize( &timer, SIGNAL(minimize()) ); timer.postponeBreak(); QCOMPARE( timer.m_state, RSITimer::TimerState::Monitoring ); QList spyRelaxSignals = spyRelax.takeFirst(); QCOMPARE( spyRelaxSignals.at( 0 ).toInt(), RELAX_ENDED_MAGIC_VALUE ); QCOMPARE( spyMinimize.count(), 1 ); // Waiting out postpone interval to confirm the break happens. for ( int i = 0; i < m_intervals[POSTPONE_BREAK_INTERVAL]; i++ ) { timer.timeout(); } QCOMPARE( timer.m_state, RSITimer::TimerState::Suggesting ); } void RSITimerTest::screenLock() { std::unique_ptr idle_time( new RSIIdleTimeFake() ); RSIIdleTimeFake* idle_time_ptr = idle_time.get(); RSITimer timer( std::move( idle_time ), m_intervals, true, true ); // Not idle for long enough to have a break. idle_time_ptr->setIdleTime( 0 ); for ( int i = 0; i < m_intervals[TINY_BREAK_INTERVAL]; i++ ) { timer.timeout(); } QCOMPARE( timer.m_state, RSITimer::TimerState::Suggesting ); QSignalSpy spyRelax( &timer, SIGNAL(relax(int,bool)) ); QSignalSpy spyMinimize( &timer, SIGNAL(minimize()) ); timer.slotLock(); QCOMPARE( timer.m_state, RSITimer::TimerState::Monitoring ); QList spyRelaxSignals = spyRelax.takeFirst(); QCOMPARE( spyRelaxSignals.at( 0 ).toInt(), RELAX_ENDED_MAGIC_VALUE ); QCOMPARE( spyMinimize.count(), 1 ); QVERIFY2( timer.m_bigBreakCounter->counterLeft() < m_intervals[BIG_BREAK_INTERVAL], "Big break counter was reset on screen lock when it should have not." ); } void RSITimerTest::skipBreak() { std::unique_ptr idle_time( new RSIIdleTimeFake() ); RSIIdleTimeFake* idle_time_ptr = idle_time.get(); RSITimer timer( std::move( idle_time ), m_intervals, true, true ); // Not idle for long enough to have a break. idle_time_ptr->setIdleTime( 0 ); for ( int i = 0; i < m_intervals[TINY_BREAK_INTERVAL]; i++ ) { timer.timeout(); } QCOMPARE( timer.m_state, RSITimer::TimerState::Suggesting ); QSignalSpy spyRelax( &timer, SIGNAL(relax(int,bool)) ); QSignalSpy spyMinimize( &timer, SIGNAL(minimize()) ); timer.skipBreak(); QCOMPARE( timer.m_state, RSITimer::TimerState::Monitoring ); QList spyRelaxSignals = spyRelax.takeFirst(); QCOMPARE( spyRelaxSignals.at( 0 ).toInt(), RELAX_ENDED_MAGIC_VALUE ); QCOMPARE( spyMinimize.count(), 1 ); QVERIFY2( timer.m_bigBreakCounter->counterLeft() < m_intervals[BIG_BREAK_INTERVAL], "Big break counter was reset on skip break when it should have not." ); } void RSITimerTest::noPopupBreak() { std::unique_ptr idle_time( new RSIIdleTimeFake() ); RSIIdleTimeFake* idle_time_ptr = idle_time.get(); RSITimer timer( std::move( idle_time ), m_intervals, false, true ); QSignalSpy spyStartShortBreak( &timer, SIGNAL(startShortBreak()) ); QSignalSpy spyEndShortBreak( &timer, SIGNAL(endShortBreak()) ); // Part one, no idleness till small break. QSignalSpy spy1BreakNow( &timer, SIGNAL(breakNow()) ); QSignalSpy spy1UpdateWidget( &timer, SIGNAL(updateWidget(int)) ); idle_time_ptr->setIdleTime( 0 ); for ( int i = 0; i < m_intervals[TINY_BREAK_INTERVAL]; i++ ) { QCOMPARE( timer.m_state, RSITimer::TimerState::Monitoring ); timer.timeout(); } // Popup is disabled so straight to breaking. QCOMPARE( spyStartShortBreak.count(), 1 ); QCOMPARE( timer.m_state, RSITimer::TimerState::Resting ); QCOMPARE( spy1BreakNow.count(), 1 ); QList spy1UpdateWidgetSignals = spy1UpdateWidget.takeFirst(); QCOMPARE( spy1UpdateWidgetSignals.at( 0 ).toInt(), m_intervals[TINY_BREAK_DURATION] ); // Part two, waiting out break. QSignalSpy spy2UpdateWidget( &timer, SIGNAL(updateWidget(int)) ); QSignalSpy spy2Minimize( &timer, SIGNAL(minimize()) ); for ( int i = 0; i < m_intervals[TINY_BREAK_DURATION]; i++ ) { QCOMPARE( timer.m_state, RSITimer::TimerState::Resting ); idle_time_ptr->setIdleTime( ( i + 1 ) * 1000 ); timer.timeout(); } QCOMPARE( timer.m_state, RSITimer::TimerState::Monitoring ); QCOMPARE( spy2Minimize.count(), 1 ); QCOMPARE( spy2UpdateWidget.count(), m_intervals[TINY_BREAK_DURATION] - 1 ); for ( int i = 1; i < m_intervals[TINY_BREAK_DURATION]; i++ ) { QList spy2UpdateWidgetSignals = spy2UpdateWidget.takeFirst(); QCOMPARE( spy2UpdateWidgetSignals.at( 0 ).toInt(), m_intervals[TINY_BREAK_DURATION] - i ); } QCOMPARE( spyEndShortBreak.count(), 1 ); } void RSITimerTest::regularBreaks() { std::unique_ptr idle_time( new RSIIdleTimeFake() ); RSIIdleTimeFake* idle_time_ptr = idle_time.get(); RSITimer timer( std::move( idle_time ), m_intervals, true, false ); QSignalSpy spyEndShortBreak( &timer, SIGNAL(endShortBreak()) ); QSignalSpy spyEndLongBreak( &timer, SIGNAL(endLongBreak()) ); int tinyBreaks = m_intervals[BIG_BREAK_INTERVAL] / ( m_intervals[TINY_BREAK_INTERVAL] + m_intervals[PATIENCE_INTERVAL] + m_intervals[TINY_BREAK_DURATION] ); int tick = 0; for ( int j = 0; j < tinyBreaks; j++ ) { // Tiny break, mix of activity and idleness till small break. QSignalSpy spyRelax( &timer, SIGNAL(relax(int,bool)) ); QSignalSpy spyUpdateIdleAvg( &timer, SIGNAL(updateIdleAvg(double)) ); for ( int i = 0; i < m_intervals[TINY_BREAK_INTERVAL]; ++i, ++tick ) { QCOMPARE( timer.m_state, RSITimer::TimerState::Monitoring ); if ( i % 2 == 0 ) { idle_time_ptr->setIdleTime( 0 ); } else { idle_time_ptr->setIdleTime( 1000 ); } timer.timeout(); } for ( int i = 0; i < m_intervals[TINY_BREAK_DURATION]; ++i, ++tick ) { // No activity during break -- obeying. QCOMPARE( timer.m_state, RSITimer::TimerState::Suggesting ); idle_time_ptr->setIdleTime( ( i + 1 ) * 1000 ); timer.timeout(); } QCOMPARE( timer.m_state, RSITimer::TimerState::Monitoring ); } // Expected ticks till big break, accounting for pauses. int ticks = m_intervals[BIG_BREAK_INTERVAL] + tinyBreaks * m_intervals[TINY_BREAK_DURATION]; for ( int j = tick; j < ticks; j++ ) { QCOMPARE( timer.m_state, RSITimer::TimerState::Monitoring ); if ( j % 2 == 0 ) { idle_time_ptr->setIdleTime( 0 ); } else { idle_time_ptr->setIdleTime( 1000 ); } timer.timeout(); } for ( int i = 0; i < m_intervals[BIG_BREAK_DURATION]; i++ ) { // No activity during break -- obeying. QCOMPARE( timer.m_state, RSITimer::TimerState::Suggesting ); idle_time_ptr->setIdleTime( ( i + 1 ) * 1000 ); timer.timeout(); } QCOMPARE( timer.m_state, RSITimer::TimerState::Monitoring ); QCOMPARE( spyEndShortBreak.count(), tinyBreaks ); QCOMPARE( spyEndLongBreak.count(), 1 ); } #include "rsitimer_test.moc"