|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2008-2010 Nokia Corporation and/or its subsidiary(-ies). |
|
4 ** All rights reserved. |
|
5 ** Contact: Nokia Corporation (developer.feedback@nokia.com) |
|
6 ** |
|
7 ** This file is part of the HbCore module of the UI Extensions for Mobile. |
|
8 ** |
|
9 ** GNU Lesser General Public License Usage |
|
10 ** This file may be used under the terms of the GNU Lesser General Public |
|
11 ** License version 2.1 as published by the Free Software Foundation and |
|
12 ** appearing in the file LICENSE.LGPL included in the packaging of this file. |
|
13 ** Please review the following information to ensure the GNU Lesser General |
|
14 ** Public License version 2.1 requirements will be met: |
|
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
|
16 ** |
|
17 ** In addition, as a special exception, Nokia gives you certain additional |
|
18 ** rights. These rights are described in the Nokia Qt LGPL Exception |
|
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
|
20 ** |
|
21 ** If you have questions regarding the use of this file, please contact |
|
22 ** Nokia at developer.feedback@nokia.com. |
|
23 ** |
|
24 ****************************************************************************/ |
|
25 |
|
26 #include "hbeffectgroup_p.h" |
|
27 #include "hbeffectabstract_p.h" |
|
28 #include "hbeffect.h" |
|
29 #include "hbtimer_p.h" |
|
30 #include "hbeffectdef_p.h" |
|
31 #include "hbeffectinternal_p.h" |
|
32 #include "hbmainwindow.h" |
|
33 #include "hbinstance.h" |
|
34 |
|
35 #include <qglobal.h> |
|
36 #include <QMetaObject> |
|
37 #include <QTransform> |
|
38 #include <QGraphicsItem> |
|
39 #include <QGraphicsWidget> |
|
40 #include <QGraphicsView> |
|
41 #include <QTimer> |
|
42 |
|
43 #ifdef HB_FILTER_EFFECTS |
|
44 #include "hbvgeffect_p.h" |
|
45 #include "hbvgchainedeffect_p.h" |
|
46 #endif |
|
47 |
|
48 |
|
49 HbEffectGroup::HbEffectGroup( |
|
50 const QString &effectEventType, |
|
51 QGraphicsItem *registrationItem, |
|
52 QGraphicsItem *targetItem, |
|
53 const QString &itemType) |
|
54 : mRegistrationItem(registrationItem), |
|
55 mTargetItem(targetItem), |
|
56 mEffectEventType(effectEventType), |
|
57 mItemType(itemType), |
|
58 mDirty(false), |
|
59 mVgEffect(0), |
|
60 mVgEffectActivated(false), |
|
61 mFinishedCount(0), |
|
62 mObserver(0), |
|
63 mRunningState(NotRunning), |
|
64 mLooping(false), |
|
65 mView(0), |
|
66 mHideWhenFinished(false) |
|
67 { |
|
68 } |
|
69 |
|
70 HbEffectGroup::~HbEffectGroup() |
|
71 { |
|
72 qDeleteAll(mEffects); |
|
73 |
|
74 #ifdef HB_FILTER_EFFECTS |
|
75 // Delete the vg effect if its ownership has not been transferred to the graphics item |
|
76 if (!mVgEffectActivated && !mVgEffectGuard.isNull()) { |
|
77 delete mVgEffect; |
|
78 } |
|
79 #endif |
|
80 } |
|
81 |
|
82 QString HbEffectGroup::effectEventType() const |
|
83 { |
|
84 return mEffectEventType; |
|
85 } |
|
86 |
|
87 QString HbEffectGroup::itemType() const |
|
88 { |
|
89 return mItemType; |
|
90 } |
|
91 |
|
92 QGraphicsItem *HbEffectGroup::registrationItem() const |
|
93 { |
|
94 return mRegistrationItem; |
|
95 } |
|
96 |
|
97 QGraphicsItem *HbEffectGroup::targetItem() const |
|
98 { |
|
99 return mTargetItem; |
|
100 } |
|
101 |
|
102 void HbEffectGroup::addEffect(HbEffectAbstract *effect) |
|
103 { |
|
104 mEffects.append(effect); |
|
105 } |
|
106 |
|
107 void HbEffectGroup::removeEffect(HbEffectAbstract *effect) |
|
108 { |
|
109 mEffects.removeAll(effect); |
|
110 } |
|
111 |
|
112 /* |
|
113 * Fixes the order of the effects to be optimal for item transformations. |
|
114 * E.g. translate needs to be after scale because translating a scaled matrix would not lead to the wanted result. |
|
115 * Also there are some problems with rotation effect if it's done after scale effect |
|
116 */ |
|
117 void HbEffectGroup::fixEffectOrder() |
|
118 { |
|
119 // If there is only one effect, no need to change order |
|
120 if (mEffects.count() > 1) { |
|
121 int last = mEffects.count() - 1; |
|
122 |
|
123 for (int i = last; i >= 0; --i) { |
|
124 HbEffectAbstract *effect = mEffects[i]; |
|
125 if (effect->name() == HB_EFFECT_NAME_TRANSLATE) { |
|
126 // Move translate effect last in the effect list |
|
127 mEffects.takeAt(i); |
|
128 mEffects.append(effect); |
|
129 } |
|
130 } |
|
131 |
|
132 for (int i = last; i >= 0; --i) { |
|
133 HbEffectAbstract *effect = mEffects[i]; |
|
134 if (effect->name() == HB_EFFECT_NAME_SCALE) { |
|
135 // Move scale effect second last in the effect list |
|
136 mEffects.takeAt(i); |
|
137 mEffects.insert(mEffects.size()-1, effect); |
|
138 } |
|
139 } |
|
140 |
|
141 } |
|
142 } |
|
143 |
|
144 void HbEffectGroup::setObserver(QObject *observer, const QString &effectFinishedSlotName) |
|
145 { |
|
146 mObserver = observer; |
|
147 mEffectFinishedSlotName = effectFinishedSlotName; |
|
148 } |
|
149 |
|
150 void HbEffectGroup::updateItemTransform() |
|
151 { |
|
152 QGraphicsView *gv(0); |
|
153 // support for graphics view transforms |
|
154 if (mTargetItem->type() == HbGVWrapperItemType) { |
|
155 HbGVWrapperItem *gvw = static_cast<HbGVWrapperItem*>(mTargetItem); |
|
156 if (gvw) |
|
157 gv = gvw->mainWindow(); |
|
158 } |
|
159 QTransform transform; |
|
160 |
|
161 Q_FOREACH(HbEffectAbstract *effect, mEffects) { |
|
162 if (effect) |
|
163 effect->updateItemTransform(transform); |
|
164 } |
|
165 if (!gv) |
|
166 mTargetItem->setTransform(transform); |
|
167 else |
|
168 gv->setTransform(transform); |
|
169 } |
|
170 |
|
171 bool HbEffectGroup::dirty() const |
|
172 { |
|
173 return mDirty; |
|
174 } |
|
175 |
|
176 void HbEffectGroup::setDirty(bool dirty) |
|
177 { |
|
178 mDirty = dirty; |
|
179 } |
|
180 |
|
181 int HbEffectGroup::effectCount() const |
|
182 { |
|
183 return mEffects.count(); |
|
184 } |
|
185 |
|
186 bool HbEffectGroup::isRunning() const |
|
187 { |
|
188 return mRunningState != NotRunning; |
|
189 } |
|
190 |
|
191 void HbEffectGroup::setLooping(bool looping) |
|
192 { |
|
193 mLooping = looping; |
|
194 } |
|
195 |
|
196 bool HbEffectGroup::isLooping() const |
|
197 { |
|
198 return mLooping; |
|
199 } |
|
200 |
|
201 void HbEffectGroup::pause() |
|
202 { |
|
203 Q_FOREACH(HbEffectAbstract *effect, mEffects) { |
|
204 effect->pause(); |
|
205 } |
|
206 } |
|
207 |
|
208 void HbEffectGroup::resume() |
|
209 { |
|
210 Q_FOREACH(HbEffectAbstract *effect, mEffects) { |
|
211 effect->resume(); |
|
212 } |
|
213 } |
|
214 |
|
215 const QVariant &HbEffectGroup::userData() const |
|
216 { |
|
217 return mUserData; |
|
218 } |
|
219 |
|
220 void HbEffectGroup::setUserData(const QVariant &userData) |
|
221 { |
|
222 mUserData = userData; |
|
223 } |
|
224 |
|
225 QRectF HbEffectGroup::extRect() const |
|
226 { |
|
227 return mExtRect; |
|
228 } |
|
229 |
|
230 void HbEffectGroup::setExtRect(const QRectF &extRect) |
|
231 { |
|
232 mExtRect = extRect; |
|
233 |
|
234 // This is needed to make scaling work from an extrect that has an equal size with the item's rect |
|
235 if (mTargetItem) { |
|
236 QRectF itemRect = mTargetItem->boundingRect(); |
|
237 qreal width = itemRect.width(); |
|
238 if (qFuzzyCompare(width, mExtRect.width())) { |
|
239 mExtRect.setWidth(width + 0.00001); |
|
240 } |
|
241 qreal height = itemRect.height(); |
|
242 if (qFuzzyCompare(height, mExtRect.height())) { |
|
243 mExtRect.setHeight(height + 0.00001); |
|
244 } |
|
245 } |
|
246 } |
|
247 |
|
248 HbView *HbEffectGroup::view() const |
|
249 { |
|
250 return mView; |
|
251 } |
|
252 |
|
253 void HbEffectGroup::setView(HbView *view) |
|
254 { |
|
255 mView = view; |
|
256 } |
|
257 |
|
258 #ifdef HB_FILTER_EFFECTS |
|
259 |
|
260 HbVgChainedEffect *HbEffectGroup::vgEffect() |
|
261 { |
|
262 if (!mVgEffect) { |
|
263 mVgEffect = new HbVgChainedEffect; |
|
264 } |
|
265 return mVgEffect; |
|
266 } |
|
267 |
|
268 void HbEffectGroup::activateVgEffect() |
|
269 { |
|
270 if (!mVgEffectActivated) { |
|
271 mVgEffectGuard = QPointer<QGraphicsEffect>(); |
|
272 vgEffect()->install(mTargetItem); |
|
273 mVgEffectActivated = true; |
|
274 } |
|
275 } |
|
276 |
|
277 /** |
|
278 * Removes the VG effect from the graphics item without deleting it. |
|
279 * The ownership is moved back to the effect group. |
|
280 */ |
|
281 void HbEffectGroup::deactivateVgEffect() |
|
282 { |
|
283 if (mVgEffectActivated) { |
|
284 // This does not delete the effect so ownership is transferred back to |
|
285 // the effect group. However this is believed to be a bug in Qt 4.6.0 |
|
286 // so use a QPointer to make sure that we do not do double deletion in |
|
287 // case Qt starts deleting the effect correctly in the future. |
|
288 mVgEffectGuard = QPointer<QGraphicsEffect>(mTargetItem->graphicsEffect()); |
|
289 mTargetItem->setGraphicsEffect(0); |
|
290 mVgEffectActivated = false; |
|
291 } |
|
292 } |
|
293 |
|
294 #endif // HB_FILTER_EFFECTS |
|
295 |
|
296 void HbEffectGroup::startAll() |
|
297 { |
|
298 if (!mEffects.empty()) { |
|
299 mRunningState = Running; |
|
300 mFinishedCount = 0; |
|
301 } |
|
302 |
|
303 // First resolve parameters and set the start states for all the effects. |
|
304 // This is done before starting the effect animations to avoid screen flickering. |
|
305 |
|
306 QTransform transform; |
|
307 |
|
308 Q_FOREACH(HbEffectAbstract *effect, mEffects) { |
|
309 // Resolve parameters etc. |
|
310 effect->init(); |
|
311 if (effect->interval() == 0) { |
|
312 // Set start state if effect starts immediately |
|
313 effect->setStartState(transform); |
|
314 } |
|
315 } |
|
316 |
|
317 mTargetItem->setTransform(transform); |
|
318 |
|
319 if (mEffects.empty()) { |
|
320 // No effect exists but user wants notification when effect finishes. |
|
321 // Let the user do whatever he wanted to do when effect finishes. |
|
322 invokeObserver(Hb::EffectNotStarted); |
|
323 } |
|
324 else { |
|
325 // Start state has been set for all the effects, |
|
326 // next step is to start the effect animations. |
|
327 // Before that, resolve the view where the effect belongs if the effect is looping. |
|
328 // This is needed for being able to pause looping effects when their view is inactive. |
|
329 if (isLooping()) { |
|
330 resolveView(); |
|
331 } |
|
332 |
|
333 Q_FOREACH(HbEffectAbstract *effect, mEffects) { |
|
334 // If the starttime is zero, start effect immediately |
|
335 if (effect->interval() == 0) { |
|
336 effect->start(); // This may call group's effectFinished if the effect was empty. |
|
337 } else { |
|
338 //Else register the effect to timeline to wait its turn. |
|
339 HbTimer::instance()->registerEntry(effect); |
|
340 } |
|
341 } |
|
342 } |
|
343 } |
|
344 |
|
345 void HbEffectGroup::resolveView() { |
|
346 if (!mView) { |
|
347 if (mTargetItem) { |
|
348 QGraphicsScene *scene = mTargetItem->scene(); |
|
349 if (scene) { |
|
350 // Resolve the main window having the same scene that the item belongs to |
|
351 QList<HbMainWindow *> windowList = hbInstance->allMainWindows(); |
|
352 Q_FOREACH(const HbMainWindow *window, windowList) { |
|
353 if (window->scene() == scene) { |
|
354 mView = window->currentView(); |
|
355 break; |
|
356 } |
|
357 } |
|
358 } |
|
359 } |
|
360 } |
|
361 } |
|
362 |
|
363 bool HbEffectGroup::hasTranslateEffect() const |
|
364 { |
|
365 foreach(HbEffectAbstract *effect, mEffects) { |
|
366 if (effect->name() == HB_EFFECT_NAME_TRANSLATE) { |
|
367 return true; |
|
368 } |
|
369 } |
|
370 |
|
371 return false; |
|
372 } |
|
373 |
|
374 bool HbEffectGroup::hasRotateEffect() const |
|
375 { |
|
376 foreach(HbEffectAbstract *effect, mEffects) { |
|
377 if (effect->name() == HB_EFFECT_NAME_ROTATE) { |
|
378 return true; |
|
379 } |
|
380 } |
|
381 |
|
382 return false; |
|
383 } |
|
384 |
|
385 bool HbEffectGroup::hasScaleEffect() const |
|
386 { |
|
387 foreach(HbEffectAbstract *effect, mEffects) { |
|
388 if (effect->name() == HB_EFFECT_NAME_SCALE) { |
|
389 return true; |
|
390 } |
|
391 } |
|
392 |
|
393 return false; |
|
394 } |
|
395 |
|
396 bool HbEffectGroup::hasOpacityEffect() const |
|
397 { |
|
398 foreach(HbEffectAbstract *effect, mEffects) { |
|
399 if (effect->name() == HB_EFFECT_NAME_OPACITY) { |
|
400 return true; |
|
401 } |
|
402 } |
|
403 |
|
404 return false; |
|
405 } |
|
406 |
|
407 void HbEffectGroup::doHideEffect(const QTransform *transform, bool opacityEffectUsed) |
|
408 { |
|
409 mTargetItem->setTransform(transform ? *transform : QTransform()); |
|
410 if (opacityEffectUsed) { |
|
411 // Hide opacity effect by setting item fully opaque regardless of what |
|
412 // its opacity value was before the effect. |
|
413 mTargetItem->setOpacity(1.0f); |
|
414 } |
|
415 #ifdef HB_FILTER_EFFECTS |
|
416 deactivateVgEffect(); |
|
417 #endif |
|
418 } |
|
419 |
|
420 void HbEffectGroup::cancelAll(bool sendCallback, bool itemIsValid, bool hideEffect, const QTransform &initialItemTransform) |
|
421 { |
|
422 QTransform transform; |
|
423 bool opacityEffectUsed = false; |
|
424 |
|
425 Q_FOREACH(HbEffectAbstract *effect, mEffects) { |
|
426 if (effect) { |
|
427 HbTimer::instance()->unregisterEntry(effect); |
|
428 effect->cancel(transform, itemIsValid); |
|
429 if (effect->name() == HB_EFFECT_NAME_OPACITY) { |
|
430 opacityEffectUsed = true; |
|
431 } |
|
432 } |
|
433 } |
|
434 |
|
435 if (itemIsValid) { |
|
436 // If effect needs to be removed, reset transform matrix and deactivate VG effect |
|
437 if (hideEffect || mHideWhenFinished) { |
|
438 doHideEffect(&initialItemTransform, opacityEffectUsed); |
|
439 } else { // Otherwise set transform corresponding to the end state of the effect |
|
440 mTargetItem->setTransform(initialItemTransform * transform); |
|
441 } |
|
442 } |
|
443 |
|
444 // Do not set this to NotRunning before effect->cancel has been called because filter |
|
445 // effects cancel does stuff that requires group->isRunning() return true. |
|
446 mRunningState = NotRunning; |
|
447 |
|
448 // Invoke observer with cancel signal |
|
449 if (sendCallback) |
|
450 invokeObserver(Hb::EffectCancelled); |
|
451 } |
|
452 |
|
453 void HbEffectGroup::effectFinished(Hb::EffectEvent reason) |
|
454 { |
|
455 // Inform the animated item when the whole effect group has finished. |
|
456 if (++mFinishedCount == mEffects.count()) { |
|
457 mFinishedCount = 0; |
|
458 |
|
459 // The animation framework funnily enough sends the finished signal before updating the animation with the final |
|
460 // value, so here we set running state to NotRunning asynchronously so the effect's final value gets still updated. |
|
461 mRunningState = FinishInProgress; |
|
462 QTimer::singleShot(0, this, SLOT(clearEffectRunning())); |
|
463 |
|
464 // Send callback if observer has been provided |
|
465 invokeObserver(reason); |
|
466 } |
|
467 |
|
468 // The effect group object is not deleted here, |
|
469 // because it would need to be removed from the list of the effect groups in |
|
470 // the effect engine as well. |
|
471 // It will be deleted and a new effect group created when the same effect is started again. |
|
472 } |
|
473 |
|
474 void HbEffectGroup::clearEffectRunning() |
|
475 { |
|
476 // Comes here when effect has finished and running state is set to NotRunning asynchronously. |
|
477 // Only set running state to 'NotRunning' if the finish is still in progress, i.e. the effect |
|
478 // has not been restarted meanwhile. |
|
479 if (mRunningState == FinishInProgress) { |
|
480 mRunningState = NotRunning; |
|
481 // We are finished either normally or with EffectNotStarted. It is now the time to |
|
482 // get rid of all the "effects" caused by the effects in this group if the |
|
483 // hide-when-finished flag is set. |
|
484 if (mHideWhenFinished) { |
|
485 doHideEffect(0, hasOpacityEffect()); |
|
486 } |
|
487 } |
|
488 } |
|
489 |
|
490 void HbEffectGroup::invokeObserver(Hb::EffectEvent reason) |
|
491 { |
|
492 // Send callback if observer has been provided |
|
493 if (mRegistrationItem && mTargetItem && mObserver && !mEffectFinishedSlotName.isEmpty()) { |
|
494 HbEffect::EffectStatus status; |
|
495 status.item = mRegistrationItem; |
|
496 status.effectEvent = mEffectEventType; |
|
497 status.userData = mUserData; |
|
498 status.reason = reason; |
|
499 |
|
500 QObject *observer = mObserver; |
|
501 |
|
502 // Clear the observer to make sure it is not sent more than once. |
|
503 // This is done before invokeMethod to avoid crash if the callback |
|
504 // deletes this object. |
|
505 mObserver = 0; |
|
506 |
|
507 // Send callback if observer has been provided. Use queued connection if |
|
508 // the effect finished normally, because otherwise deleting the effect during the callback |
|
509 // would cause crash because this function finally returns back to animation framework code |
|
510 // which assumes the effect objects are alive. |
|
511 QMetaObject::invokeMethod( |
|
512 observer, |
|
513 mEffectFinishedSlotName.toAscii().data(), |
|
514 reason == Hb::EffectFinished ? Qt::QueuedConnection : Qt::AutoConnection, |
|
515 QGenericReturnArgument(), |
|
516 Q_ARG(HbEffect::EffectStatus, status)); |
|
517 |
|
518 // Do not access member variables after invoking the callback since it might |
|
519 // have deleted this object. |
|
520 } |
|
521 } |
|
522 |
|
523 void HbEffectGroup::setHideWhenFinished(bool hideWhenFinished) |
|
524 { |
|
525 mHideWhenFinished = hideWhenFinished; |
|
526 } |
|
527 |
|
528 // End of File |