Make a branch to make krunner Good Enough For Aaron™.
[kdebase/uwolfer.git] / workspace / libs / plasma / phase.cpp
blob106b873565c7312226691785209702e86b59d89d
1 /*
2 * Copyright 2007 Aaron Seigo <aseigo@kde.org>
3 * 2007 Alexis Ménard <darktears31@gmail.com>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU Library General Public License as
7 * published by the Free Software Foundation; either version 2, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details
15 * You should have received a copy of the GNU Library General Public
16 * License along with this program; if not, write to the
17 * Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "phase.h"
23 #include <QGraphicsItem>
25 #include <KConfig>
26 #include <KConfigGroup>
27 #include <KService>
28 #include <KServiceTypeTrader>
29 #include <KGlobalSettings>
31 #include "animator.h"
32 #include "widgets/widget.h"
34 namespace Plasma
37 static const qreal MIN_TICK_RATE = 40;
39 struct AnimationState
41 QGraphicsItem *item;
42 QObject *qobj;
43 Phase::Animation animation;
44 Phase::CurveShape curve;
45 int interval;
46 int currentInterval;
47 int frames;
48 int currentFrame;
51 struct ElementAnimationState
53 QGraphicsItem *item;
54 QObject *qobj;
55 Phase::CurveShape curve;
56 Phase::ElementAnimation animation;
57 int interval;
58 int currentInterval;
59 int frames;
60 int currentFrame;
61 int id;
62 QPixmap pixmap;
65 struct MovementState
67 QGraphicsItem *item;
68 QObject *qobj;
69 Phase::CurveShape curve;
70 Phase::Movement movement;
71 int interval;
72 int currentInterval;
73 int frames;
74 int currentFrame;
75 QPoint start;
76 QPoint destination;
79 struct CustomAnimationState
81 Phase::CurveShape curve;
82 int frames;
83 int currentFrame;
84 int interval;
85 int currentInterval;
86 Phase::AnimId id;
87 QObject* receiver;
88 char* slot;
91 class Phase::Private
93 public:
95 Private()
96 : animator(0),
97 animId(0),
98 timerId(0)
102 ~Private()
104 qDeleteAll(animatedItems);
105 qDeleteAll(animatedElements);
106 qDeleteAll(movingItems);
108 QMutableMapIterator<AnimId, CustomAnimationState*> it(customAnims);
109 while (it.hasNext()) {
110 delete it.value()->slot;
111 delete it.value();
112 it.remove();
115 // Animator is a QObject
116 // and we don't own the items
119 qreal calculateProgress(int frames, int currentFrame)
121 if (!(KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects)) {
122 return qreal(1.0);
125 qreal progress = frames;
126 progress = currentFrame / progress;
127 progress = qMin(qreal(1.0), qMax(qreal(0.0), progress));
128 return progress;
131 void performAnimation(qreal amount, const AnimationState* state)
133 switch (state->animation) {
134 case Phase::Appear:
135 animator->appear(amount, state->item);
136 break;
137 case Phase::Disappear:
138 animator->disappear(amount, state->item);
139 if (amount >= 1) {
140 state->item->hide();
142 break;
143 case Phase::Activate:
144 animator->activate(amount, state->item);
145 break;
149 void performMovement(qreal amount, const MovementState* state)
151 switch (state->movement) {
152 case Phase::SlideIn:
153 case Phase::FastSlideIn:
154 //kDebug() << "performMovement, SlideIn";
155 animator->slideIn(amount, state->item, state->start, state->destination);
156 break;
157 case Phase::SlideOut:
158 case Phase::FastSlideOut:
159 //kDebug() << "performMovement, SlideOut";
160 animator->slideOut(amount, state->item, state->start, state->destination);
161 break;
165 Animator* animator;
166 int animId;
167 int timerId;
168 QTime time;
170 //TODO: eventually perhaps we should allow multiple animations simulataneously
171 // which would imply changing this to a QMap<QGraphicsItem*, QList<QTimeLine*> >
172 // and really making the code fun ;)
173 QMap<QGraphicsItem*, AnimationState*> animatedItems;
174 QMap<QGraphicsItem*, MovementState*> movingItems;
175 QMap<AnimId, ElementAnimationState*> animatedElements;
176 QMap<AnimId, CustomAnimationState*> customAnims;
179 class PhaseSingleton
181 public:
182 Phase self;
185 K_GLOBAL_STATIC( PhaseSingleton, privateSelf )
187 Phase* Phase::self()
189 return &privateSelf->self;
193 Phase::Phase(QObject * parent)
194 : QObject(parent),
195 d(new Private)
197 init();
200 Phase::~Phase()
202 delete d;
205 void Phase::animatedItemDestroyed(QObject* o)
207 //kDebug() << "testing for" << (void*)o;
208 QMutableMapIterator<QGraphicsItem*, AnimationState*> it(d->animatedItems);
209 while (it.hasNext()) {
210 it.next();
211 //kDebug() << "comparing against" << it.value()->qobj;
212 if (it.value()->qobj == o) {
213 kDebug() << "found deleted animated item";
214 delete it.value();
215 it.remove();
220 void Phase::movingItemDestroyed(QObject* o)
222 QMutableMapIterator<QGraphicsItem*, MovementState*> it(d->movingItems);
223 while (it.hasNext()) {
224 it.next();
225 if (it.value()->qobj == o) {
226 delete it.value();
227 it.remove();
232 void Phase::animatedElementDestroyed(QObject* o)
234 QMutableMapIterator<AnimId, ElementAnimationState*> it(d->animatedElements);
235 while (it.hasNext()) {
236 it.next();
237 if (it.value()->qobj == o) {
238 delete it.value();
239 it.remove();
244 void Phase::customAnimReceiverDestroyed(QObject* o)
246 QMutableMapIterator<AnimId, CustomAnimationState*> it(d->customAnims);
247 while (it.hasNext()) {
248 if (it.next().value()->receiver == o) {
249 delete it.value()->slot;
250 delete it.value();
251 it.remove();
256 void Phase::animateItem(QGraphicsItem* item, Animation animation)
258 //kDebug();
259 // get rid of any existing animations on this item.
260 //TODO: shoudl we allow multiple anims per item?
261 QMap<QGraphicsItem*, AnimationState*>::iterator it = d->animatedItems.find(item);
262 if (it != d->animatedItems.end()) {
263 delete it.value();
264 d->animatedItems.erase(it);
267 int frames = d->animator->framesPerSecond(animation);
269 if (frames < 1) {
270 // evidently this animator doesn't have an implementation
271 // for this Animation
272 return;
275 AnimationState* state = new AnimationState;
276 state->item = item;
277 state->animation = animation;
278 state->curve = d->animator->curve(animation);
279 //TODO: variance in times based on the value of animation
280 state->frames = frames / 3;
281 state->currentFrame = 0;
282 state->interval = d->animator->duration(animation) / state->frames;
283 state->interval = (state->interval / MIN_TICK_RATE) * MIN_TICK_RATE;
284 state->currentInterval = state->interval;
285 state->qobj = dynamic_cast<QObject*>(item);
287 if (state->qobj) {
288 //kDebug() << "!!!!!!!!!!!!!!!!!!!!!!!!! got us an object!";
289 disconnect(state->qobj, SIGNAL(destroyed(QObject*)), this, SLOT(animatedItemDestroyed(QObject*)));
290 connect(state->qobj, SIGNAL(destroyed(QObject*)), this, SLOT(animatedItemDestroyed(QObject*)));
293 d->animatedItems[item] = state;
294 d->performAnimation(0, state);
296 if (!d->timerId) {
297 d->timerId = startTimer(MIN_TICK_RATE);
298 d->time.restart();
302 void Phase::moveItem(QGraphicsItem* item, Movement movement, const QPoint &destination)
304 //kDebug();
305 QMap<QGraphicsItem*, MovementState*>::iterator it = d->movingItems.find(item);
306 if (it != d->movingItems.end()) {
307 delete it.value();
308 d->movingItems.erase(it);
311 int frames = d->animator->framesPerSecond(movement);
312 if (frames <= 1) {
313 // evidently this animator doesn't have an implementation
314 // for this Animation
315 return;
318 MovementState* state = new MovementState;
319 state->destination = destination;
320 state->start = item->pos().toPoint();
321 state->item = item;
322 state->movement = movement;
323 state->curve = d->animator->curve(movement);
324 //TODO: variance in times based on the value of animation
325 state->frames = frames / 2;
326 state->currentFrame = 0;
327 state->interval = d->animator->duration(movement) / state->frames;
328 state->interval = (state->interval / MIN_TICK_RATE) * MIN_TICK_RATE;
329 state->currentInterval = state->interval;
330 state->qobj = dynamic_cast<QObject*>(item);
332 if (state->qobj) {
333 disconnect(state->qobj, SIGNAL(destroyed(QObject*)), this, SLOT(movingItemDestroyed(QObject*)));
334 connect(state->qobj, SIGNAL(destroyed(QObject*)), this, SLOT(movingItemDestroyed(QObject*)));
337 d->movingItems[item] = state;
338 d->performMovement(0, state);
340 if (!d->timerId) {
341 d->timerId = startTimer(MIN_TICK_RATE);
342 d->time.restart();
346 Phase::AnimId Phase::customAnimation(int frames, int duration, Phase::CurveShape curve,
347 QObject* receiver, const char* slot)
349 if (frames < 1 || duration < 1 || !receiver || !slot) {
350 return -1;
353 CustomAnimationState *state = new CustomAnimationState;
354 state->id = ++d->animId;
355 state->frames = frames;
356 state->currentFrame = 0;
357 state->curve = curve;
358 state->interval = duration / qreal(state->frames);
359 state->interval = qMax( 1, state->interval );
360 state->interval = (state->interval / MIN_TICK_RATE) * MIN_TICK_RATE;
361 state->currentInterval = state->interval;
362 state->receiver = receiver;
363 state->slot = qstrdup(slot);
365 d->customAnims[state->id] = state;
367 disconnect(receiver, SIGNAL(destroyed(QObject*)),
368 this, SLOT(customAnimReceiverDestroyed(QObject*)));
369 connect(receiver, SIGNAL(destroyed(QObject*)),
370 this, SLOT(customAnimReceiverDestroyed(QObject*)));
372 QMetaObject::invokeMethod(receiver, slot, Q_ARG(qreal, 0));
374 if (!d->timerId) {
375 d->timerId = startTimer(MIN_TICK_RATE);
376 d->time.restart();
379 return state->id;
382 void Phase::stopCustomAnimation(AnimId id)
384 QMap<AnimId, CustomAnimationState*>::iterator it = d->customAnims.find(id);
385 if (it != d->customAnims.end()) {
386 delete [] it.value()->slot;
387 delete it.value();
388 d->customAnims.erase(it);
390 //kDebug() << "stopCustomAnimation(AnimId " << id << ") done";
393 Phase::AnimId Phase::animateElement(QGraphicsItem *item, ElementAnimation animation)
395 //kDebug() << "startElementAnimation(AnimId " << animation << ")";
396 ElementAnimationState *state = new ElementAnimationState;
397 state->item = item;
398 state->curve = d->animator->curve(animation);
399 state->animation = animation;
400 //TODO: variance in times based on the value of animation
401 state->frames = d->animator->framesPerSecond(animation) / 5;
402 state->currentFrame = 0;
403 state->interval = d->animator->duration(animation) / state->frames;
404 state->interval = (state->interval / MIN_TICK_RATE) * MIN_TICK_RATE;
405 state->currentInterval = state->interval;
406 state->id = ++d->animId;
407 state->qobj = dynamic_cast<QObject*>(item);
409 if (state->qobj) {
410 disconnect(state->qobj, SIGNAL(destroyed(QObject*)), this, SLOT(animatedElementDestroyed(QObject*)));
411 connect(state->qobj, SIGNAL(destroyed(QObject*)), this, SLOT(animatedElementDestroyed(QObject*)));
414 //kDebug() << "animateElement " << animation << ", interval: " << state->interval << ", frames: " << state->frames;
415 bool needTimer = true;
416 if (state->frames < 1) {
417 state->frames = 1;
418 state->currentFrame = 1;
419 needTimer = false;
422 d->animatedElements[state->id] = state;
423 // nasty hack because QGraphicsItem::update isn't virtual!
424 // FIXME: remove in 4.1 as we will no longer need the caching in Plasma::Widget with Qt 4.4
425 Plasma::Widget *widget = dynamic_cast<Plasma::Widget*>(state->item);
426 if (widget) {
427 widget->update();
428 } else {
429 state->item->update();
432 //kDebug() << "startElementAnimation(AnimId " << animation << ") returning " << state->id;
433 if (needTimer && !d->timerId) {
434 // start a 20fps timer;
435 //TODO: should be started at the maximum frame rate needed only?
436 d->timerId = startTimer(MIN_TICK_RATE);
437 d->time.restart();
439 return state->id;
442 void Phase::stopElementAnimation(AnimId id)
444 QMap<AnimId, ElementAnimationState*>::iterator it = d->animatedElements.find(id);
445 if (it != d->animatedElements.end()) {
446 delete it.value();
447 d->animatedElements.erase(it);
449 //kDebug() << "stopElementAnimation(AnimId " << id << ") done";
452 void Phase::setAnimationPixmap(AnimId id, const QPixmap &pixmap)
454 QMap<AnimId, ElementAnimationState*>::iterator it = d->animatedElements.find(id);
456 if (it == d->animatedElements.end()) {
457 kDebug() << "Phase::setAnimationPixmap(" << id << ") found no entry for it!";
458 return;
461 it.value()->pixmap = pixmap;
464 QPixmap Phase::animationResult(AnimId id)
466 QMap<AnimId, ElementAnimationState*>::const_iterator it = d->animatedElements.find(id);
468 if (it == d->animatedElements.constEnd()) {
469 //kDebug() << "Phase::animationResult(" << id << ") found no entry for it!";
470 return QPixmap();
473 ElementAnimationState* state = it.value();
474 qreal progress = state->frames;
475 //kDebug() << "Phase::animationResult(" << id << " at " << progress;
476 progress = state->currentFrame / progress;
477 progress = qMin(qreal(1.0), qMax(qreal(0.0), progress));
478 //kDebug() << "Phase::animationResult(" << id << " at " << progress;
480 switch (state->animation) {
481 case ElementAppear:
482 return d->animator->elementAppear(progress, state->pixmap);
483 break;
484 case ElementDisappear:
485 return d->animator->elementDisappear(progress, state->pixmap);
486 break;
489 return state->pixmap;
492 void Phase::timerEvent(QTimerEvent *event)
494 Q_UNUSED(event)
495 bool animationsRemain = false;
496 int elapsed = MIN_TICK_RATE;
497 if (d->time.elapsed() > elapsed) {
498 elapsed = d->time.elapsed();
500 d->time.restart();
501 //kDebug() << "timeEvent, elapsed time: " << elapsed;
503 foreach (AnimationState* state, d->animatedItems) {
504 if (state->currentInterval <= elapsed) {
505 // we need to step forward!
506 state->currentFrame += (KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects) ?
507 qMax(1, elapsed / state->interval) : state->frames - state->currentFrame;
509 if (state->currentFrame < state->frames) {
510 qreal progress = d->calculateProgress(state->frames, state->currentFrame);
511 d->performAnimation(progress, state);
512 state->currentInterval = state->interval;
513 //TODO: calculate a proper interval based on the curve
514 state->interval *= 1 - progress;
515 animationsRemain = true;
516 } else {
517 d->performAnimation(1, state);
518 d->animatedItems.erase(d->animatedItems.find(state->item));
519 emit animationComplete(state->item, state->animation);
520 delete state;
522 } else {
523 state->currentInterval -= elapsed;
524 animationsRemain = true;
528 foreach (MovementState* state, d->movingItems) {
529 if (state->currentInterval <= elapsed) {
530 // we need to step forward!
531 state->currentFrame += (KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects) ?
532 qMax(1, elapsed / state->interval) : state->frames - state->currentFrame;
534 if (state->currentFrame < state->frames) {
535 d->performMovement(d->calculateProgress(state->frames, state->currentFrame), state);
536 //TODO: calculate a proper interval based on the curve
537 state->currentInterval = state->interval;
538 animationsRemain = true;
539 } else {
540 d->performMovement(1, state);
541 d->movingItems.erase(d->movingItems.find(state->item));
542 emit movementComplete(state->item);
543 delete state;
545 } else {
546 state->currentInterval -= elapsed;
547 animationsRemain = true;
551 foreach (ElementAnimationState* state, d->animatedElements) {
552 if (state->currentFrame == state->frames) {
553 //kDebug() << "skipping" << state->id << "as its already at frame" << state->currentFrame << "of" << state->frames;
554 // since we keep element animations around until they are
555 // removed, we will end up with finished animations in the queue;
556 // just skip them
557 //TODO: should we move them to a separate QMap?
558 continue;
561 if (state->currentInterval <= elapsed) {
562 // we need to step forward!
563 /*kDebug() << "stepping forwards element anim " << state->id << " from " << state->currentFrame
564 << " by " << qMax(1, elapsed / state->interval) << " to "
565 << state->currentFrame + qMax(1, elapsed / state->interval) << endl;*/
566 state->currentFrame += (KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects) ?
567 qMax(1, elapsed / state->interval) : state->frames - state->currentFrame;
568 // nasty hack because QGraphicsItem::update isn't virtual!
569 // FIXME: remove in 4.1 as we will no longer need the caching in Plasma::Widget with Qt 4.4
570 Plasma::Widget *widget = dynamic_cast<Plasma::Widget*>(state->item);
571 if (widget) {
572 widget->update();
573 } else {
574 state->item->update();
577 if (state->currentFrame < state->frames) {
578 state->currentInterval = state->interval;
579 //TODO: calculate a proper interval based on the curve
580 state->interval *= 1 - d->calculateProgress(state->frames, state->currentFrame);
581 animationsRemain = true;
582 } else {
583 d->animatedElements.remove(state->id);
584 emit elementAnimationComplete(state->id);
585 delete state;
587 } else {
588 state->currentInterval -= elapsed;
589 animationsRemain = true;
593 foreach (CustomAnimationState *state, d->customAnims) {
594 if (state->currentInterval <= elapsed) {
595 // advance the frame
596 state->currentFrame += (KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects) ?
597 qMax(1, elapsed / state->interval) : state->frames - state->currentFrame;
598 /*kDebug() << "custom anim for" << state->receiver << "to slot" << state->slot
599 << "with interval of" << state->interval << "at frame" << state->currentFrame;*/
601 if (state->currentFrame < state->frames) {
602 //kDebug () << "not the final frame";
603 //TODO: calculate a proper interval based on the curve
604 state->currentInterval = state->interval;
605 animationsRemain = true;
606 // signal the object
607 QMetaObject::invokeMethod(state->receiver, state->slot,
608 Q_ARG(qreal,
609 d->calculateProgress(state->frames, state->currentFrame)));
610 } else {
611 QMetaObject::invokeMethod(state->receiver, state->slot, Q_ARG(qreal, 1));
612 d->customAnims.erase(d->customAnims.find(state->id));
613 emit customAnimationComplete(state->id);
614 delete [] state->slot;
615 delete state;
617 } else {
618 state->currentInterval -= elapsed;
619 animationsRemain = true;
623 if (!animationsRemain && d->timerId) {
624 killTimer(d->timerId);
625 d->timerId = 0;
629 void Phase::init()
631 KConfig c("plasmarc");
632 KConfigGroup cg(&c, "Phase");
633 QString pluginName = cg.readEntry("animator", "default");
635 if (!pluginName.isEmpty()) {
636 QString constraint = QString("[X-KDE-PluginInfo-Name] == '%1'").arg(pluginName);
637 KService::List offers = KServiceTypeTrader::self()->query("Plasma/Animator", constraint);
639 if (!offers.isEmpty()) {
640 QString error;
641 d->animator = offers.first()->createInstance<Plasma::Animator>(0, QVariantList(), &error);
642 if (!d->animator) {
643 kDebug() << "Could not load requested animator " << offers.first() << ". Error given: " << error;
648 if (!d->animator) {
649 d->animator = new Animator(this);
653 } // namespace Plasma
655 #include <phase.moc>