plasma-framework/timer.cpp

428 lines
11 KiB
C++
Raw Normal View History

/*
* Copyright 2008 Aaron Seigo <aseigo@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2, 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 Library 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 "timer.h"
#include "sharedtimer_p.h"
#include <QtCore/QList>
#include <QtCore/QMap>
#include <QtCore/QTimerEvent>
#include <KDE/KDebug>
#include <KDE/KGlobal>
namespace Plasma
{
class Timer::Private
{
public:
Private()
: interval(0),
active(false),
singleShot(false)
{
}
int interval;
bool active;
bool singleShot;
};
Timer::Timer(QObject *parent)
: QObject(parent),
d(new Private)
{
}
Timer::~Timer()
{
TimerDrive::self()->unregisterTimer(this, d->interval);
delete d;
}
void Timer::setInterval(int msec)
{
if (d->active) {
// unregister with the new time
TimerDrive::self()->unregisterTimer(this, d->interval);
}
d->interval = msec;
if (d->active) {
// register with the new time
TimerDrive::self()->registerTimer(this, d->interval);
}
}
int Timer::interval() const
{
return d->interval;
}
void Timer::setSingleShot(bool singleShot)
{
d->singleShot = singleShot;
}
bool Timer::isSingleShot() const
{
return d->singleShot;
}
bool Timer::isActive() const
{
return d->active;
}
void Timer::start(int msec)
{
if (d->interval != msec) {
setInterval(msec);
}
start();
}
void Timer::start()
{
if (d->active) {
return;
}
d->active = true;
TimerDrive::self()->registerTimer(this, d->interval);
}
void Timer::stop()
{
TimerDrive::self()->unregisterTimer(this, d->interval);
d->active = false;
}
void Timer::activate() const
{
emit timeout();
if (d->singleShot) {
TimerDrive::self()->unregisterTimer(this, d->interval);
d->active = false;
}
}
class TimerBundle
{
public:
TimerBundle()
: multiple(-1),
currentCount(-1)
{
}
void setMultiple(int msec, int nextMsec)
{
if (nextMsec > 0) {
currentCount = multiple = nextMsec / msec;
} else {
currentCount = multiple = -1;
}
//kDebug() << "setting msec of" << msec << "with" << nextMsec << multiple;
}
int multiple;
int currentCount;
QList<const Timer*> timers;
};
class TimerDrive::Private
{
public:
Private()
: interval(0),
timerId(-1)
{
}
// msec -> TimerBundle
QMap<int, TimerBundle> bundles;
int interval;
int timerId;
};
class TimerDriveSingleton
{
public:
TimerDrive self;
};
K_GLOBAL_STATIC(TimerDriveSingleton, privateTimerDriveSelf)
TimerDrive* TimerDrive::self()
{
return &privateTimerDriveSelf->self;
}
TimerDrive::TimerDrive(QObject *parent)
: QObject(parent),
d(new Private)
{
}
TimerDrive::~TimerDrive()
{
delete d;
}
void TimerDrive::registerTimer(const Timer *t, int msec)
{
kDebug() << "registering" << t << "for" << msec;
QMap<int, TimerBundle>::iterator it = d->bundles.find(msec);
if (it == d->bundles.end()) {
kDebug( ) << "creating a new bundle";
it = d->bundles.insert(msec, TimerBundle());
if (it != d->bundles.begin()) {
QMap<int, TimerBundle>::iterator prev = it - 1;
prev.value().setMultiple(prev.key(), msec);
}
QMap<int, TimerBundle>::iterator next = it + 1;
if (next != d->bundles.end()) {
it.value().setMultiple(msec, next.key());
}
if (d->bundles.count() == 1) {
kDebug() << "kickstarting the timer";
d->interval = msec;
if (d->timerId != -1) {
// this should never happen, but better safe than sorry?
killTimer(d->timerId);
}
d->timerId = startTimer(msec);
} else if (d->timerId != -1) {
//TODO: figure out if we're injecting an item between the two current timers
}
} else {
// prevent multiple inclusions
foreach (const Timer *existingTimer, it.value().timers) {
if (t == existingTimer) {
return;
}
}
}
kDebug() << "appending our timer";
it.value().timers.append(t);
}
void TimerDrive::unregisterTimer(const Timer *t, int msec)
{
kDebug() << "unregistering" << t << "for" << msec;
// hash of intervals to timer ids
QMap<int, TimerBundle>::iterator it = d->bundles.find(msec);
if (it != d->bundles.end()) {
TimerBundle &bundle = it.value();
bundle.timers.removeAll(t);
if (bundle.timers.count() == 0) {
kDebug() << "no more timers, removing this bundle";
if (it != d->bundles.begin()) {
// update the previous entries multiple
kDebug() << "reseting previous interval";
int nextMsec = 0;
if (it != d->bundles.end()) {
QMap<int, TimerBundle>::iterator next = it + 1;
if (next != d->bundles.end()) {
nextMsec = next.key();
}
}
QMap<int, TimerBundle>::iterator prev = it - 1;
if (prev != d->bundles.end()) {
prev.value().setMultiple(prev.key(), nextMsec);
}
}
d->bundles.erase(it);
}
}
if (d->bundles.count() == 0) {
if (d->timerId != -1) {
killTimer(d->timerId);
d->timerId = -1;
}
} else if (d->timerId != -1) {
//TODO: figure out if we were going to call this guy next, and if so,
// repair the timings
}
}
void TimerDrive::timerEvent(QTimerEvent *event)
{
int id = event->timerId();
if (d->timerId != -1) {
killTimer(d->timerId);
}
QMap<int, TimerBundle>::iterator it = d->bundles.begin();
QMap<int, TimerBundle>::iterator begin = d->bundles.begin();
QMap<int, TimerBundle>::iterator end = d->bundles.end();
if (it == end) {
return;
}
kDebug() << id << d->timerId << "got interval of" << d->interval;
int effectiveInterval = d->interval;
while (it != end) {
int msec = it.key();
TimerBundle &bundle = it.value();
if (msec <= effectiveInterval && bundle.currentCount != 0) {
kDebug() << " +++ activating" << msec << bundle.currentCount << effectiveInterval;
foreach (const Timer *t, bundle.timers) {
t->activate();
}
if (bundle.currentCount >= 0) {
--bundle.currentCount;
}
if (bundle.currentCount >= 0) {
// we haven't reached our multiple, so don't go further.
break;
}
} else {
//kDebug() << " --- skipping" << msec << bundle.currentCount << effectiveInterval;
bundle.currentCount = bundle.multiple;
}
effectiveInterval = msec * bundle.multiple + d->interval;
++it;
}
d->interval = 0;
int intervalAdjustment = 0;
if (it == end) {
d->interval = begin.key();
} else {
int currentCount = it.value().currentCount;
int prevMsec = it.key() * it.value().multiple;
if (++it == end) {
//kDebug() << " end of the line";
d->interval = begin.key();
// take into consideration time spent in the last moments prior to reset.
if (--it != begin) {
int prevInterval = it.key();
while (--it != begin) {
int gap = prevInterval - it.key();
if (gap + intervalAdjustment > d->interval) {
break;
}
intervalAdjustment += gap;
prevInterval = it.key();
}
}
// ensure all our current counts are reset
while (begin != end) {
begin.value().currentCount = begin.value().multiple;
++begin;
}
} else {
// calculate hop to the next value
int interval = it.key() - prevMsec;
if (currentCount != 0) {
// start at the beginning, because we have to take another lap
// before continuing on to the next item
//kDebug() << " reseting";
d->interval = begin.key();
// take into consideration time spent in the last moments prior to reset.
if (--it != begin) {
int prevInterval = it.key();
while (--it != begin) {
int gap = prevInterval - it.key();
if (gap + intervalAdjustment > d->interval) {
break;
}
intervalAdjustment += gap;
prevInterval = it.key();
}
}
} else if (interval > begin.key()) {
// start at the beginning, because our next interval is longer
// than some of our earlier timers
//kDebug() << " looping back";
d->interval = begin.key();
while (begin != it) {
if (begin.key() < interval) {
begin.value().currentCount = begin.value().multiple;
} else {
begin.value().currentCount = 0;
}
++begin;
}
} else if (it.value().currentCount == 0) {
// in this branch, we've looped back once due to the next step
// interval being larger than our beginning interval
//kDebug() << " loop back detected";
while (begin != it) {
begin.value().currentCount = 0;
++begin;
}
while (it.value().currentCount == 0) {
interval = it.key() * it.value().multiple;
++it;
}
d->interval = it.key() - interval;
} else {
// short hop to the next entry
//kDebug() << " short hop to the next stop";
d->interval = interval;
}
}
}
if (d->interval) {
d->timerId = startTimer(d->interval - intervalAdjustment);
kDebug() << " starting interval of" << d->interval
<< " (really" << d->interval - intervalAdjustment << ")" << d->timerId;
} else {
d->timerId = -1;
}
}
} // namespace Plasma
#include "timer.moc"
#include "sharedtimer_p.moc"