diff --git a/CMakeLists.txt b/CMakeLists.txt index e840b4ca2..555791368 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,7 @@ set(plasma_LIB_SRCS svg.cpp svgpanel.cpp theme.cpp + timer.cpp desktoptoolbox.cpp uiloader.cpp view.cpp @@ -132,6 +133,7 @@ set(plasma_LIB_INCLUDES svg.h svgpanel.h theme.h + timer.h uiloader.h view.h delegate.h) diff --git a/sharedtimer_p.h b/sharedtimer_p.h new file mode 100644 index 000000000..5dc232953 --- /dev/null +++ b/sharedtimer_p.h @@ -0,0 +1,54 @@ +/* + * Copyright 2008 Aaron Seigo + * + * 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. + */ + + +#ifndef PLASMA_SHAREDTIMER_P_H +#define PLASMA_SHAREDTIMER_P_H + +#include + +namespace Plasma +{ + +class Timer; + +class TimerDrive : public QObject +{ + Q_OBJECT + +public: + static TimerDrive *self(); + void registerTimer(const Timer *t, int msec); + void unregisterTimer(const Timer *t, int msec); + +protected: + void timerEvent(QTimerEvent* event); + +private: + friend class TimerDriveSingleton; + explicit TimerDrive(QObject *parent = 0); + ~TimerDrive(); + class Private; + Private * const d; +}; + +} // namespace Plasma + +#endif + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bac9b28c6..33a6f17f4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -17,3 +17,7 @@ set(appletbrowser_SRCS appletbrowser.cpp) kde4_add_executable(plasmaappletbrowser ${appletbrowser_SRCS}) target_link_libraries(plasmaappletbrowser ${KDE4_KDEUI_LIBS} plasma) +set(sharedtimertest_SRCS sharedtimertest.cpp) +kde4_add_executable(sharedtimertest ${sharedtimertest_SRCS}) +target_link_libraries(sharedtimertest ${KDE4_KDEUI_LIBS} plasma) + diff --git a/tests/sharedtimertest.cpp b/tests/sharedtimertest.cpp new file mode 100644 index 000000000..323698139 --- /dev/null +++ b/tests/sharedtimertest.cpp @@ -0,0 +1,131 @@ +/* + * Copyright 2008 Aaron J. Seigo + * + * + * 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 "sharedtimertest.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "plasma/timer.h" + +static const char description[] = I18N_NOOP("Applet browser dialog"); +static const char version[] = "1.0"; + +Tester::Tester(int rounds) + : QObject(0), + m_count(0), + m_round(0), + m_targetRounds(rounds) +{ + Plasma::Timer *t50 = new Plasma::Timer(this); + connect(t50, SIGNAL(timeout()), this, SLOT(timeout())); + t50->start(50); + + Plasma::Timer *t70 = new Plasma::Timer(this); + connect(t70, SIGNAL(timeout()), this, SLOT(timeout())); + t70->start(70); + + Plasma::Timer *t210 = new Plasma::Timer(this); + connect(t210, SIGNAL(timeout()), this, SLOT(timeout())); + t210->start(210); + + Plasma::Timer *t500 = new Plasma::Timer(this); + connect(t500, SIGNAL(timeout()), this, SLOT(timeout())); + t500->start(500); +// QObject::connect(t, SIGNAL(timeout()), &app, SLOT(quit())); + + Plasma::Timer *t1000 = new Plasma::Timer(this); + t1000->start(1000); + delete t1000; + + Plasma::Timer *t95 = new Plasma::Timer(this); + connect(t95, SIGNAL(timeout()), this, SLOT(timeout())); + t95->start(95); + + Plasma::Timer *t200 = new Plasma::Timer(this); + connect(t200, SIGNAL(timeout()), this, SLOT(timeout())); + t200->start(200); + + + m_order << t50 << t70 << t95 + << t50 << t70 << t95 + << t200 << t210 + << t50 << t70 << t95 + << t50 << t70 << t95 + << t200 << t210 + << t50 << t70 + << t500; + +} + +void Tester::timeout() +{ + Plasma::Timer *t = qobject_cast(sender()); + if (m_order[m_count] != t) { + kFatal() << "round" << m_round << "call" << m_count << "expected" + << m_order[m_count]->interval() << "but got" << t->interval(); + } + + //kDebug() << "round" << m_round << "call" << m_count << "got" << t->interval(); + ++m_count; + + if (m_count == m_order.count()) { + m_count = 0; + ++m_round; + //kDebug() << "starting round" << m_round; + } + + if (m_round == m_targetRounds) { + qApp->quit(); + } +} + +int main(int argc, char *argv[]) +{ + KAboutData aboutData("sharedtimertest", 0, ki18n("Plasma Shared Timer Test"), + version, ki18n( description ), KAboutData::License_GPL, + ki18n("(C) 2008, Aaron Seigo")); + aboutData.addAuthor(ki18n("Aaron Seigo"), ki18n("Original author"), "aseigo@kde.org"); + KCmdLineArgs::init(argc, argv, &aboutData); + KApplication app; + + Tester t(2); + //QTimer::singleShot(2000, &app, SLOT(quit())); + + QTime elapsed; + elapsed.start(); + + int rv = app.exec(); + + int totalTime = elapsed.elapsed(); + kDebug() << "successful run, took" << totalTime; + return rv; +} + +#include "sharedtimertest.moc" + diff --git a/tests/sharedtimertest.h b/tests/sharedtimertest.h new file mode 100644 index 000000000..62b1ee5c4 --- /dev/null +++ b/tests/sharedtimertest.h @@ -0,0 +1,31 @@ + +#ifndef SHAREDTIMERTEST_H +#define SHAREDTIMERTEST_H + +#include +#include + +namespace Plasma +{ + class Timer; +} // namespace Plasma + +class Tester : public QObject +{ + Q_OBJECT + +public: + Tester(int rounds); + +private Q_SLOTS: + void timeout(); + +private: + int m_count; + int m_round; + int m_targetRounds; + QList m_order; +}; + +#endif + diff --git a/timer.cpp b/timer.cpp new file mode 100644 index 000000000..e8d362f1d --- /dev/null +++ b/timer.cpp @@ -0,0 +1,427 @@ +/* + * Copyright 2008 Aaron Seigo + * + * 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 +#include +#include + +#include +#include + +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 timers; +}; + +class TimerDrive::Private +{ +public: + Private() + : interval(0), + timerId(-1) + { + } + + // msec -> TimerBundle + QMap 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::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::iterator prev = it - 1; + prev.value().setMultiple(prev.key(), msec); + } + + QMap::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::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::iterator next = it + 1; + if (next != d->bundles.end()) { + nextMsec = next.key(); + } + } + + QMap::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::iterator it = d->bundles.begin(); + QMap::iterator begin = d->bundles.begin(); + QMap::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" + diff --git a/timer.h b/timer.h new file mode 100644 index 000000000..bc78dc100 --- /dev/null +++ b/timer.h @@ -0,0 +1,60 @@ +/* + * Copyright 2008 Aaron Seigo + * + * 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. + */ + +#ifndef PLASMA_TIMER_H +#define PLASMA_TIMER_H + +#include + +#include + +namespace Plasma +{ + +class PLASMA_EXPORT Timer : public QObject +{ + Q_OBJECT + +public: + explicit Timer(QObject *parent = 0); + ~Timer(); + int interval() const; + bool isActive() const; + bool isSingleShot() const; + void setInterval(int msec); + void setSingleShot(bool singleShot); + void activate() const; + +public Q_SLOTS: + void start(int msec); + void start(); + void stop(); + +Q_SIGNALS: + void timeout() const; + +private: + class Private; + Private * const d; +}; + +} // namespace Plasma + +#endif +