| /************************************************************** |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| *************************************************************/ |
| |
| |
| |
| // MARKER(update_precomp.py): autogen include statement, do not remove |
| #include "precompiled_slideshow.hxx" |
| |
| // must be first |
| #include <canvas/debug.hxx> |
| #include <canvas/verbosetrace.hxx> |
| #include <cppuhelper/exc_hlp.hxx> |
| #include <comphelper/anytostring.hxx> |
| #include <com/sun/star/presentation/ParagraphTarget.hpp> |
| #include <com/sun/star/animations/Timing.hpp> |
| #include <com/sun/star/animations/AnimationAdditiveMode.hpp> |
| #include <com/sun/star/presentation/ShapeAnimationSubType.hpp> |
| |
| #include "nodetools.hxx" |
| #include "doctreenode.hxx" |
| #include "animationbasenode.hxx" |
| #include "delayevent.hxx" |
| #include "framerate.hxx" |
| |
| #include <boost/bind.hpp> |
| #include <boost/optional.hpp> |
| #include <algorithm> |
| |
| using namespace com::sun::star; |
| |
| namespace slideshow { |
| namespace internal { |
| |
| AnimationBaseNode::AnimationBaseNode( |
| const uno::Reference< animations::XAnimationNode >& xNode, |
| const BaseContainerNodeSharedPtr& rParent, |
| const NodeContext& rContext ) |
| : BaseNode( xNode, rParent, rContext ), |
| mxAnimateNode( xNode, uno::UNO_QUERY_THROW ), |
| maAttributeLayerHolder(), |
| maSlideSize( rContext.maSlideSize ), |
| mpActivity(), |
| mpShape(), |
| mpShapeSubset(), |
| mpSubsetManager(rContext.maContext.mpSubsettableShapeManager), |
| mbIsIndependentSubset( rContext.mbIsIndependentSubset ) |
| { |
| // extract native node targets |
| // =========================== |
| |
| // plain shape target |
| uno::Reference< drawing::XShape > xShape( mxAnimateNode->getTarget(), |
| uno::UNO_QUERY ); |
| |
| // distinguish 5 cases: |
| // |
| // - plain shape target |
| // (NodeContext.mpMasterShapeSubset full set) |
| // |
| // - parent-generated subset (generate an |
| // independent subset) |
| // |
| // - parent-generated subset from iteration |
| // (generate a dependent subset) |
| // |
| // - XShape target at the XAnimatioNode (generate |
| // a plain shape target) |
| // |
| // - ParagraphTarget target at the XAnimationNode |
| // (generate an independent shape subset) |
| if( rContext.mpMasterShapeSubset ) |
| { |
| if( rContext.mpMasterShapeSubset->isFullSet() ) |
| { |
| // case 1: plain shape target from parent |
| mpShape = rContext.mpMasterShapeSubset->getSubsetShape(); |
| } |
| else |
| { |
| // cases 2 & 3: subset shape |
| mpShapeSubset = rContext.mpMasterShapeSubset; |
| } |
| } |
| else |
| { |
| // no parent-provided shape, try to extract |
| // from XAnimationNode - cases 4 and 5 |
| |
| if( xShape.is() ) |
| { |
| mpShape = lookupAttributableShape( getContext().mpSubsettableShapeManager, |
| xShape ); |
| } |
| else |
| { |
| // no shape provided. Maybe a ParagraphTarget? |
| presentation::ParagraphTarget aTarget; |
| |
| if( !(mxAnimateNode->getTarget() >>= aTarget) ) |
| ENSURE_OR_THROW( |
| false, "could not extract any target information" ); |
| |
| xShape = aTarget.Shape; |
| |
| ENSURE_OR_THROW( xShape.is(), "invalid shape in ParagraphTarget" ); |
| |
| mpShape = lookupAttributableShape( getContext().mpSubsettableShapeManager, |
| xShape ); |
| |
| // NOTE: For shapes with ParagraphTarget, we ignore |
| // the SubItem property. We implicitly assume that it |
| // is set to ONLY_TEXT. |
| OSL_ENSURE( |
| mxAnimateNode->getSubItem() == |
| presentation::ShapeAnimationSubType::ONLY_TEXT || |
| mxAnimateNode->getSubItem() == |
| presentation::ShapeAnimationSubType::AS_WHOLE, |
| "ParagraphTarget given, but subitem not AS_TEXT or AS_WHOLE? " |
| "Make up your mind, I'll ignore the subitem." ); |
| |
| // okay, found a ParagraphTarget with a valid XShape. Does the shape |
| // provide the given paragraph? |
| const DocTreeNode& rTreeNode( |
| mpShape->getTreeNodeSupplier().getTreeNode( |
| aTarget.Paragraph, |
| DocTreeNode::NODETYPE_LOGICAL_PARAGRAPH ) ); |
| |
| // CAUTION: the creation of the subset shape |
| // _must_ stay in the node constructor, since |
| // Slide::prefetchShow() initializes shape |
| // attributes right after animation import (or |
| // the Slide class must be changed). |
| mpShapeSubset.reset( |
| new ShapeSubset( mpShape, |
| rTreeNode, |
| mpSubsetManager )); |
| |
| // Override NodeContext, and flag this node as |
| // a special independent subset one. This is |
| // important when applying initial attributes: |
| // independent shape subsets must be setup |
| // when the slide starts, since they, as their |
| // name suggest, can have state independent to |
| // the master shape. The following example |
| // might illustrate that: a master shape has |
| // no effect, one of the text paragraphs |
| // within it has an appear effect. Now, the |
| // respective paragraph must be invisible when |
| // the slide is initially shown, and become |
| // visible only when the effect starts. |
| mbIsIndependentSubset = true; |
| |
| // already enable subset right here, the |
| // setup of initial shape attributes of |
| // course needs the subset shape |
| // generated, to apply e.g. visibility |
| // changes. |
| mpShapeSubset->enableSubsetShape(); |
| } |
| } |
| } |
| |
| void AnimationBaseNode::dispose() |
| { |
| if (mpActivity) { |
| mpActivity->dispose(); |
| mpActivity.reset(); |
| } |
| |
| maAttributeLayerHolder.reset(); |
| mxAnimateNode.clear(); |
| mpShape.reset(); |
| mpShapeSubset.reset(); |
| |
| BaseNode::dispose(); |
| } |
| |
| bool AnimationBaseNode::init_st() |
| { |
| // if we've still got an old activity lying around, dispose it: |
| if (mpActivity) { |
| mpActivity->dispose(); |
| mpActivity.reset(); |
| } |
| |
| // note: actually disposing the activity too early might cause problems, |
| // because on dequeued() it calls endAnimation(pAnim->end()), thus ending |
| // animation _after_ last screen update. |
| // review that end() is properly called (which calls endAnimation(), too). |
| |
| try { |
| // TODO(F2): For restart functionality, we must regenerate activities, |
| // since they are not able to reset their state (or implement _that_) |
| mpActivity = createActivity(); |
| } |
| catch (uno::Exception const&) { |
| OSL_ENSURE( false, rtl::OUStringToOString( |
| comphelper::anyToString(cppu::getCaughtException()), |
| RTL_TEXTENCODING_UTF8 ) ); |
| // catch and ignore. We later handle empty activities, but for |
| // other nodes to function properly, the core functionality of |
| // this node must remain up and running. |
| } |
| return true; |
| } |
| |
| bool AnimationBaseNode::resolve_st() |
| { |
| // enable shape subset for automatically generated |
| // subsets. Independent subsets are already setup |
| // during construction time. Doing it only here |
| // saves us a lot of sprites and shapes lying |
| // around. This is especially important for |
| // character-wise iterations, since the shape |
| // content (e.g. thousands of characters) would |
| // otherwise be painted character-by-character. |
| if (isDependentSubsettedShape() && mpShapeSubset) { |
| mpShapeSubset->enableSubsetShape(); |
| } |
| return true; |
| } |
| |
| void AnimationBaseNode::activate_st() |
| { |
| // create new attribute layer |
| maAttributeLayerHolder.createAttributeLayer( getShape() ); |
| |
| ENSURE_OR_THROW( maAttributeLayerHolder.get(), |
| "Could not generate shape attribute layer" ); |
| |
| // TODO(Q2): This affects the way mpActivity |
| // works, but is performed here because of |
| // locality (we're fiddling with the additive mode |
| // here, anyway, and it's the only place where we |
| // do). OTOH, maybe the complete additive mode |
| // setup should be moved to the activities. |
| |
| // for simple by-animations, the SMIL spec |
| // requires us to emulate "0,by-value" value list |
| // behaviour, with additive mode forced to "sum", |
| // no matter what the input is |
| // (http://www.w3.org/TR/smil20/animation.html#adef-by). |
| if( mxAnimateNode->getBy().hasValue() && |
| !mxAnimateNode->getTo().hasValue() && |
| !mxAnimateNode->getFrom().hasValue() ) |
| { |
| // force attribute mode to REPLACE (note the |
| // subtle discrepancy to the paragraph above, |
| // where SMIL requires SUM. This is internally |
| // handled by the FromToByActivity, and is |
| // because otherwise DOM values would not be |
| // handled correctly: the activity cannot |
| // determine whether an |
| // Activity::getUnderlyingValue() yields the |
| // DOM value, or already a summed-up conglomerate) |
| // |
| // Note that this poses problems with our |
| // hybrid activity duration (time or min number of frames), |
| // since if activities |
| // exceed their duration, wrong 'by' start |
| // values might arise ('Laser effect') |
| maAttributeLayerHolder.get()->setAdditiveMode( |
| animations::AnimationAdditiveMode::REPLACE ); |
| } |
| else |
| { |
| // apply additive mode to newly created Attribute layer |
| maAttributeLayerHolder.get()->setAdditiveMode( |
| mxAnimateNode->getAdditive() ); |
| } |
| |
| // fake normal animation behaviour, even if we |
| // show nothing. This is the appropriate way to |
| // handle errors on Activity generation, because |
| // maybe all other effects on the slide are |
| // correctly initialized (but won't run, if we |
| // signal an error here) |
| if (mpActivity) { |
| // supply Activity (and the underlying Animation) with |
| // it's AttributeLayer, to perform the animation on |
| mpActivity->setTargets( getShape(), maAttributeLayerHolder.get() ); |
| |
| // add to activities queue |
| getContext().mrActivitiesQueue.addActivity( mpActivity ); |
| } |
| else { |
| // Actually, DO generate the event for empty activity, |
| // to keep the chain of animations running |
| BaseNode::scheduleDeactivationEvent(); |
| } |
| } |
| |
| void AnimationBaseNode::deactivate_st( NodeState eDestState ) |
| { |
| if (eDestState == FROZEN) { |
| if (mpActivity) |
| mpActivity->end(); |
| } |
| |
| if (isDependentSubsettedShape()) { |
| // for dependent subsets, remove subset shape |
| // from layer, re-integrate subsetted part |
| // back into original shape. For independent |
| // subsets, we cannot make any assumptions |
| // about subset attribute state relative to |
| // master shape, thus, have to keep it. This |
| // will effectively re-integrate the subsetted |
| // part into the original shape (whose |
| // animation will hopefully have ended, too) |
| |
| // this statement will save a whole lot of |
| // sprites for iterated text effects, since |
| // those sprites will only exist during the |
| // actual lifetime of the effects |
| if (mpShapeSubset) { |
| mpShapeSubset->disableSubsetShape(); |
| } |
| } |
| |
| if (eDestState == ENDED) { |
| |
| // no shape anymore, no layer needed: |
| maAttributeLayerHolder.reset(); |
| |
| if (! isDependentSubsettedShape()) { |
| |
| // for all other shapes, removing the |
| // attribute layer quite possibly changes |
| // shape display. Thus, force update |
| AttributableShapeSharedPtr const pShape( getShape() ); |
| |
| // don't anybody dare to check against |
| // pShape->isVisible() here, removing the |
| // attribute layer might actually make the |
| // shape invisible! |
| getContext().mpSubsettableShapeManager->notifyShapeUpdate( pShape ); |
| } |
| |
| if (mpActivity) { |
| // kill activity, if still running |
| mpActivity->dispose(); |
| mpActivity.reset(); |
| } |
| } |
| } |
| |
| bool AnimationBaseNode::hasPendingAnimation() const |
| { |
| // TODO(F1): This might not always be true. Are there 'inactive' |
| // animation nodes? |
| return true; |
| } |
| |
| #if defined(VERBOSE) && defined(DBG_UTIL) |
| void AnimationBaseNode::showState() const |
| { |
| BaseNode::showState(); |
| |
| VERBOSE_TRACE( "AnimationBaseNode info: independent subset=%s", |
| mbIsIndependentSubset ? "y" : "n" ); |
| } |
| #endif |
| |
| ActivitiesFactory::CommonParameters |
| AnimationBaseNode::fillCommonParameters() const |
| { |
| double nDuration = 0.0; |
| |
| // TODO(F3): Duration/End handling is barely there |
| if( !(mxAnimateNode->getDuration() >>= nDuration) ) { |
| mxAnimateNode->getEnd() >>= nDuration; // Wah. |
| } |
| |
| // minimal duration we fallback to (avoid 0 here!) |
| nDuration = ::std::max( 0.001, nDuration ); |
| |
| const bool bAutoReverse( mxAnimateNode->getAutoReverse() ); |
| |
| boost::optional<double> aRepeats; |
| double nRepeats = 0; |
| bool bRepeatIndefinite = false; |
| animations::Timing eTiming; |
| |
| // Search parent nodes for an explicitly stated repeat count. |
| BaseNodeSharedPtr const pSelf( getSelf() ); |
| for ( boost::shared_ptr<BaseNode> pNode( pSelf ); |
| pNode; |
| pNode = pNode->getParentNode() ) |
| { |
| uno::Reference<animations::XAnimationNode> const xAnimationNode( |
| pNode->getXAnimationNode() ); |
| if( (xAnimationNode->getRepeatCount() >>= nRepeats) ) |
| { |
| // Found an explicit repeat count. |
| break; |
| } |
| if( (xAnimationNode->getRepeatCount() >>= eTiming) && |
| (eTiming == animations::Timing_INDEFINITE )) |
| { |
| // Found an explicit repeat count of Timing::INDEFINITE. |
| bRepeatIndefinite = true; |
| break; |
| } |
| } |
| |
| if( nRepeats || bRepeatIndefinite ) { |
| if (nRepeats) |
| { |
| aRepeats.reset( nRepeats ); |
| } |
| } |
| else { |
| if( (mxAnimateNode->getRepeatDuration() >>= nRepeats) ) { |
| // when repeatDuration is given, |
| // autoreverse does _not_ modify the |
| // active duration. Thus, calc repeat |
| // count with already adapted simple |
| // duration (twice the specified duration) |
| |
| // convert duration back to repeat counts |
| if( bAutoReverse ) |
| aRepeats.reset( nRepeats / (2.0 * nDuration) ); |
| else |
| aRepeats.reset( nRepeats / nDuration ); |
| } |
| else { |
| // no double value for both values - Timing::INDEFINITE? |
| animations::Timing eTiming; |
| |
| if( !(mxAnimateNode->getRepeatDuration() >>= eTiming) || |
| eTiming != animations::Timing_INDEFINITE ) |
| { |
| if( !(mxAnimateNode->getRepeatCount() >>= eTiming) || |
| eTiming != animations::Timing_INDEFINITE ) |
| { |
| // no indefinite timing, no other values given - |
| // use simple run, i.e. repeat of 1.0 |
| aRepeats.reset( 1.0 ); |
| } |
| } |
| } |
| } |
| |
| // calc accel/decel: |
| double nAcceleration = 0.0; |
| double nDeceleration = 0.0; |
| for ( boost::shared_ptr<BaseNode> pNode( pSelf ); |
| pNode; pNode = pNode->getParentNode() ) |
| { |
| uno::Reference<animations::XAnimationNode> const xAnimationNode( |
| pNode->getXAnimationNode() ); |
| nAcceleration = std::max( nAcceleration, |
| xAnimationNode->getAcceleration() ); |
| nDeceleration = std::max( nDeceleration, |
| xAnimationNode->getDecelerate() ); |
| } |
| |
| EventSharedPtr pEndEvent; |
| if (pSelf) { |
| pEndEvent = makeEvent( |
| boost::bind( &AnimationNode::deactivate, pSelf ), |
| "AnimationBaseNode::deactivate"); |
| } |
| |
| // Calculate the minimum frame count that depends on the duration and |
| // the minimum frame count. |
| const sal_Int32 nMinFrameCount (basegfx::clamp<sal_Int32>( |
| basegfx::fround(nDuration * FrameRate::MinimumFramesPerSecond), 1, 10)); |
| |
| return ActivitiesFactory::CommonParameters( |
| pEndEvent, |
| getContext().mrEventQueue, |
| getContext().mrActivitiesQueue, |
| nDuration, |
| nMinFrameCount, |
| bAutoReverse, |
| aRepeats, |
| nAcceleration, |
| nDeceleration, |
| getShape(), |
| getSlideSize()); |
| } |
| |
| AttributableShapeSharedPtr AnimationBaseNode::getShape() const |
| { |
| // any subsetting at all? |
| if (mpShapeSubset) |
| return mpShapeSubset->getSubsetShape(); |
| else |
| return mpShape; // nope, plain shape always |
| } |
| |
| } // namespace internal |
| } // namespace slideshow |
| |