<commit_before>/*************************************************************************
 *
 *  OpenOffice.org - a multi-platform office productivity suite
 *
 *  $RCSfile: animationbasenode.cxx,v $
 *
 *  $Revision: 1.13 $
 *
 *  last change: $Author: kz $ $Date: 2006-12-13 15:28:27 $
 *
 *  The Contents of this file are made available subject to
 *  the terms of GNU Lesser General Public License Version 2.1.
 *
 *
 *    GNU Lesser General Public License Version 2.1
 *    =============================================
 *    Copyright 2005 by Sun Microsystems, Inc.
 *    901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License version 2.1, as published by the Free Software Foundation.
 *
 *    This library 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
 *    Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public
 *    License along with this library; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *    MA  02111-1307  USA
 *
 ************************************************************************/

// 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 "boost/bind.hpp"
#include "boost/optional.hpp"
#include <vector>
#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(),
      mpActivity(),
      mpShape(),
      mpShapeSubset(),
      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().mpLayerManager,
                                               xShape );
        }
        else
        {
            // no shape provided. Maybe a ParagraphTarget?
            presentation::ParagraphTarget aTarget;

            if( !(mxAnimateNode->getTarget() >>= aTarget) )
                ENSURE_AND_THROW(
                    false, "could not extract any target information" );

            xShape = aTarget.Shape;

            ENSURE_AND_THROW( xShape.is(), "invalid shape in ParagraphTarget" );

            mpShape = lookupAttributableShape( getContext().mpLayerManager,
                                               xShape );

            // NOTE: For shapes with ParagraphTarget, we ignore
            // the SubItem property. We implicitely 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,
                                 getContext().mpLayerManager ));

            // 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_AND_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().mpLayerManager->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;
    if( (mxAnimateNode->getRepeatCount() >>= 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;
    BaseNodeSharedPtr const pSelf( getSelf() );
    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 ) );
    }

    return ActivitiesFactory::CommonParameters(
        pEndEvent,
        getContext().mrEventQueue,
        getContext().mrActivitiesQueue,
        nDuration,
        10, // always display at least 10 frames
        bAutoReverse,
        aRepeats,
        nAcceleration,
        nDeceleration,
        getShape(),
        getContext().mpLayerManager );
}

AttributableShapeSharedPtr AnimationBaseNode::getShape() const
{
    // any subsetting at all?
    if (mpShapeSubset)
        return mpShapeSubset->getSubsetShape();
    else
        return mpShape; // nope, plain shape always
}

} // namespace internal
} // namespace slideshow

<commit_msg>INTEGRATION: CWS presfixes12 (1.13.12); FILE MERGED 2007/02/06 17:18:04 thb 1.13.12.2: #i37778# Moved clear() method from View to ViewLayer (also sprites need to be cleared); fixed a few more cases of local code style violations; removed redundant inline keywords; finished Layer/LayerManager rework (Layer now represents ViewLayers, shapes and rendering are fully under LayerManager control); made shape comparator reusable 2007/01/29 14:02:00 thb 1.13.12.1: Issue number: #i37778#<commit_after>/*************************************************************************
 *
 *  OpenOffice.org - a multi-platform office productivity suite
 *
 *  $RCSfile: animationbasenode.cxx,v $
 *
 *  $Revision: 1.14 $
 *
 *  last change: $Author: obo $ $Date: 2007-07-17 14:46:40 $
 *
 *  The Contents of this file are made available subject to
 *  the terms of GNU Lesser General Public License Version 2.1.
 *
 *
 *    GNU Lesser General Public License Version 2.1
 *    =============================================
 *    Copyright 2005 by Sun Microsystems, Inc.
 *    901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License version 2.1, as published by the Free Software Foundation.
 *
 *    This library 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
 *    Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public
 *    License along with this library; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *    MA  02111-1307  USA
 *
 ************************************************************************/

// 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 <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_AND_THROW(
                    false, "could not extract any target information" );

            xShape = aTarget.Shape;

            ENSURE_AND_THROW( xShape.is(), "invalid shape in ParagraphTarget" );

            mpShape = lookupAttributableShape( getContext().mpSubsettableShapeManager,
                                               xShape );

            // NOTE: For shapes with ParagraphTarget, we ignore
            // the SubItem property. We implicitely 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_AND_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;
    if( (mxAnimateNode->getRepeatCount() >>= 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;
    BaseNodeSharedPtr const pSelf( getSelf() );
    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 ) );
    }

    return ActivitiesFactory::CommonParameters(
        pEndEvent,
        getContext().mrEventQueue,
        getContext().mrActivitiesQueue,
        nDuration,
        10, // always display at least 10 frames
        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

<|endoftext|>


<commit_before>/*************************************************************************
 *
 *  $RCSfile: animationbasenode.hxx,v $
 *
 *  $Revision: 1.2 $
 *
 *  last change: $Author: kz $ $Date: 2005-01-21 17:02:04 $
 *
 *  The Contents of this file are made available subject to the terms of
 *  either of the following licenses
 *
 *         - GNU Lesser General Public License Version 2.1
 *         - Sun Industry Standards Source License Version 1.1
 *
 *  Sun Microsystems Inc., October, 2000
 *
 *  GNU Lesser General Public License Version 2.1
 *  =============================================
 *  Copyright 2000 by Sun Microsystems, Inc.
 *  901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License version 2.1, as published by the Free Software Foundation.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *  MA  02111-1307  USA
 *
 *
 *  Sun Industry Standards Source License Version 1.1
 *  =================================================
 *  The contents of this file are subject to the Sun Industry Standards
 *  Source License Version 1.1 (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.openoffice.org/license.html.
 *
 *  Software provided under this License is provided on an "AS IS" basis,
 *  WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
 *  WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
 *  MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
 *  See the License for the specific provisions governing your rights and
 *  obligations concerning the Software.
 *
 *  The Initial Developer of the Original Code is: Sun Microsystems, Inc.
 *
 *  Copyright: 2000 by Sun Microsystems, Inc.
 *
 *  All Rights Reserved.
 *
 *  Contributor(s): _______________________________________
 *
 *
 ************************************************************************/

#ifndef _SLIDESHOW_ANIMATIONBASENODE_HXX
#define _SLIDESHOW_ANIMATIONBASENODE_HXX

#include <basecontainernode.hxx>
#include <activitiesfactory.hxx>
#include <shapeattributelayer.hxx>
#include <shapeattributelayerholder.hxx>
#include <attributableshape.hxx>
#include <shapesubset.hxx>

#ifndef _COM_SUN_STAR_ANIMATIONS_XANIMATE_HPP_
#include <com/sun/star/animations/XAnimate.hpp>
#endif

namespace presentation
{
    namespace internal
    {
        /** Common base class for all leaf animation nodes.

        This class basically holds the target shape
        */
        class AnimationBaseNode : public BaseNode
        {
        public:
            AnimationBaseNode( const ::com::sun::star::uno::Reference<
                                       ::com::sun::star::animations::XAnimationNode >&  xNode,
                               const BaseContainerNodeSharedPtr&                    rParent,
                               const NodeContext&                                   rContext );

            virtual bool init();
            virtual void dispose();
            virtual bool resolve();
            virtual bool activate();
            virtual void deactivate();
            virtual void end();
            virtual void notifyDeactivating( const AnimationNodeSharedPtr& rNotifier );
            virtual bool hasPendingAnimation() const;

#if defined(VERBOSE) && defined(DBG_UTIL)
            virtual void showState() const;
#endif
        protected:

            /** Animation-specific end().

                This is also called from deactivate, for subsetted
                shapes. Actually force-ends the underlying animation
                activity.
            */
            void endAnimation();

            /** Create parameter struct for ActivitiesFactory
             */
            ActivitiesFactory::CommonParameters fillCommonParameters() const;

            /** Create AnimationActivity from common XAnimate parameters
             */
            AnimationActivitySharedPtr createActivity() const;

            /// Returns true, if this node animates a subset of a shape
            bool isSubsettedShape() const;

            /** Returns true, if this is a subset animation, and
                the subset is autogenerated (e.g. from an
                iteration)
            */
            bool isDependentSubsettedShape() const;

            /** This method enables a subset shape
             */
            void enableShapeSubset();

            /** This method removes subset shapes

                Subset shapes may be only temporarily added to a
                slide. When the animation active duration ends,
                and they are generated from iterated effects, they
                are removed again for redraw
                performance. Furthermore, they are revoked from
                the original shape, such that it displays the full
                content again.
            */
            void disableShapeSubset();

            AttributableShapeSharedPtr getShape() const;

            // inline accessors
            // ----------------

            const ::com::sun::star::uno::Reference<
                ::com::sun::star::animations::XAnimate >& getXAnimateNode() const { return mxAnimateNode; }
            const ShapeAttributeLayerHolder&              getAttributeLayerHolder() const { return maAttributeLayerHolder; }
            const AnimationActivitySharedPtr&             getActivity() const { return mpActivity; }
            AnimationActivitySharedPtr&                   getActivity() { return mpActivity; }

        private:
            ::com::sun::star::uno::Reference<
                ::com::sun::star::animations::XAnimate >    mxAnimateNode;
            ShapeAttributeLayerHolder                       maAttributeLayerHolder;
            AnimationActivitySharedPtr                      mpActivity;

            /// When valid, this node has a plain target shape
            AttributableShapeSharedPtr                      mpShape;
            /// When valid, this is a subsetted target shape
            ShapeSubsetSharedPtr                            mpShapeSubset;
            bool                                            mbIsIndependentSubset;
        };
    }
}

#endif /* _SLIDESHOW_ANIMATIONBASENODE_HXX */
<commit_msg>INTEGRATION: CWS ooo19126 (1.2.36); FILE MERGED 2005/09/05 17:41:14 rt 1.2.36.1: #i54170# Change license header: remove SISSL<commit_after>/*************************************************************************
 *
 *  OpenOffice.org - a multi-platform office productivity suite
 *
 *  $RCSfile: animationbasenode.hxx,v $
 *
 *  $Revision: 1.3 $
 *
 *  last change: $Author: rt $ $Date: 2005-09-07 20:39:43 $
 *
 *  The Contents of this file are made available subject to
 *  the terms of GNU Lesser General Public License Version 2.1.
 *
 *
 *    GNU Lesser General Public License Version 2.1
 *    =============================================
 *    Copyright 2005 by Sun Microsystems, Inc.
 *    901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License version 2.1, as published by the Free Software Foundation.
 *
 *    This library 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
 *    Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public
 *    License along with this library; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *    MA  02111-1307  USA
 *
 ************************************************************************/

#ifndef _SLIDESHOW_ANIMATIONBASENODE_HXX
#define _SLIDESHOW_ANIMATIONBASENODE_HXX

#include <basecontainernode.hxx>
#include <activitiesfactory.hxx>
#include <shapeattributelayer.hxx>
#include <shapeattributelayerholder.hxx>
#include <attributableshape.hxx>
#include <shapesubset.hxx>

#ifndef _COM_SUN_STAR_ANIMATIONS_XANIMATE_HPP_
#include <com/sun/star/animations/XAnimate.hpp>
#endif

namespace presentation
{
    namespace internal
    {
        /** Common base class for all leaf animation nodes.

        This class basically holds the target shape
        */
        class AnimationBaseNode : public BaseNode
        {
        public:
            AnimationBaseNode( const ::com::sun::star::uno::Reference<
                                       ::com::sun::star::animations::XAnimationNode >&  xNode,
                               const BaseContainerNodeSharedPtr&                    rParent,
                               const NodeContext&                                   rContext );

            virtual bool init();
            virtual void dispose();
            virtual bool resolve();
            virtual bool activate();
            virtual void deactivate();
            virtual void end();
            virtual void notifyDeactivating( const AnimationNodeSharedPtr& rNotifier );
            virtual bool hasPendingAnimation() const;

#if defined(VERBOSE) && defined(DBG_UTIL)
            virtual void showState() const;
#endif
        protected:

            /** Animation-specific end().

                This is also called from deactivate, for subsetted
                shapes. Actually force-ends the underlying animation
                activity.
            */
            void endAnimation();

            /** Create parameter struct for ActivitiesFactory
             */
            ActivitiesFactory::CommonParameters fillCommonParameters() const;

            /** Create AnimationActivity from common XAnimate parameters
             */
            AnimationActivitySharedPtr createActivity() const;

            /// Returns true, if this node animates a subset of a shape
            bool isSubsettedShape() const;

            /** Returns true, if this is a subset animation, and
                the subset is autogenerated (e.g. from an
                iteration)
            */
            bool isDependentSubsettedShape() const;

            /** This method enables a subset shape
             */
            void enableShapeSubset();

            /** This method removes subset shapes

                Subset shapes may be only temporarily added to a
                slide. When the animation active duration ends,
                and they are generated from iterated effects, they
                are removed again for redraw
                performance. Furthermore, they are revoked from
                the original shape, such that it displays the full
                content again.
            */
            void disableShapeSubset();

            AttributableShapeSharedPtr getShape() const;

            // inline accessors
            // ----------------

            const ::com::sun::star::uno::Reference<
                ::com::sun::star::animations::XAnimate >& getXAnimateNode() const { return mxAnimateNode; }
            const ShapeAttributeLayerHolder&              getAttributeLayerHolder() const { return maAttributeLayerHolder; }
            const AnimationActivitySharedPtr&             getActivity() const { return mpActivity; }
            AnimationActivitySharedPtr&                   getActivity() { return mpActivity; }

        private:
            ::com::sun::star::uno::Reference<
                ::com::sun::star::animations::XAnimate >    mxAnimateNode;
            ShapeAttributeLayerHolder                       maAttributeLayerHolder;
            AnimationActivitySharedPtr                      mpActivity;

            /// When valid, this node has a plain target shape
            AttributableShapeSharedPtr                      mpShape;
            /// When valid, this is a subsetted target shape
            ShapeSubsetSharedPtr                            mpShapeSubset;
            bool                                            mbIsIndependentSubset;
        };
    }
}

#endif /* _SLIDESHOW_ANIMATIONBASENODE_HXX */
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2021 William Silvermsith
 * Licensed 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.
 */

/*
 * Connected Components for 2D images.

 * Author: William Silversmith
 * Affiliation: Seung Lab, Princeton University
 * Date: August 2018 - June 2019, June 2021
 *
 * ----
 * Notes on the license:
 *
 * This is a special reduced feature version of cc3d
 * that includes only the logic needed for CCL 4-connected
 * and 6-connected. It is also modified to treat black as
 * foreground and black as background.
 *
 * cc3d is ordinarily licensed as GPL v3.
 * Get the full version of cc3d here:
 *
 * https://github.com/seung-lab/connected-components-3d
 */

#ifndef CC3D_SPECIAL_4_HPP
#define CC3D_SPECIAL_4_HPP

#include <algorithm>
#include <cmath>
#include <cstdint>
#include <stdexcept>

namespace cc3d {

static size_t _dummy_N;

template <typename T>
class DisjointSet {
public:
  T *ids;
  size_t length;

  DisjointSet () {
    length = 65536; // 2^16, some "reasonable" starting size
    ids = new T[length]();
    if (!ids) {
      throw std::runtime_error("Failed to allocate memory for the Union-Find datastructure for connected components.");
    }
  }

  DisjointSet (size_t len) {
    length = len;
    ids = new T[length]();
    if (!ids) {
      throw std::runtime_error("Failed to allocate memory for the Union-Find datastructure for connected components.");
    }
  }

  DisjointSet (const DisjointSet &cpy) {
    length = cpy.length;
    ids = new T[length]();
    if (!ids) {
      throw std::runtime_error("Failed to allocate memory for the Union-Find datastructure for connected components.");
    }

    for (int i = 0; i < length; i++) {
      ids[i] = cpy.ids[i];
    }
  }

  ~DisjointSet () {
    delete []ids;
  }

  T root (T n) {
    T i = ids[n];
    while (i != ids[i]) {
      ids[i] = ids[ids[i]]; // path compression
      i = ids[i];
    }

    return i;
  }

  bool find (T p, T q) {
    return root(p) == root(q);
  }

  void add(T p) {
    if (p >= length) {
      printf("Connected Components Error: Label %d cannot be mapped to union-find array of length %lu.\n", p, length);
      throw "maximum length exception";
    }

    if (ids[p] == 0) {
      ids[p] = p;
    }
  }

  void unify (T p, T q) {
    if (p == q) {
      return;
    }

    T i = root(p);
    T j = root(q);

    if (i == 0) {
      add(p);
      i = p;
    }

    if (j == 0) {
      add(q);
      j = q;
    }

    ids[i] = j;
  }

  void print() {
    for (int i = 0; i < 15; i++) {
      printf("%d, ", ids[i]);
    }
    printf("\n");
  }

  // would be easy to write remove.
  // Will be O(n).
};

// This is the second raster pass of the two pass algorithm family.
// The input array (output_labels) has been assigned provisional
// labels and this resolves them into their final labels. We
// modify this pass to also ensure that the output labels are
// numbered from 1 sequentially.
template <typename OUT = uint32_t>
OUT* relabel(
    OUT* out_labels, const int64_t voxels,
    const int64_t num_labels, DisjointSet<uint32_t> &equivalences,
    size_t &N = _dummy_N, OUT start_label = 1
  ) {

  OUT label;
  OUT* renumber = new OUT[num_labels + 1]();
  OUT next_label = start_label;

  for (int64_t i = 1; i <= num_labels; i++) {
    label = equivalences.root(i);
    if (renumber[label] == 0) {
      renumber[label] = next_label;
      renumber[i] = next_label;
      next_label++;
    }
    else {
      renumber[i] = renumber[label];
    }
  }

  // Raster Scan 2: Write final labels based on equivalences
  N = next_label - start_label;
  if (N < static_cast<size_t>(num_labels) || start_label != 1) {
    for (int64_t loc = 0; loc < voxels; loc++) {
      out_labels[loc] = renumber[out_labels[loc]];
    }
  }

  delete[] renumber;

  return out_labels;
}

template <typename OUT = uint32_t>
OUT* connected_components2d_4(
    bool* in_labels,
    const int64_t sx, const int64_t sy, const int64_t sz,
    size_t max_labels, OUT *out_labels = NULL,
    size_t &N = _dummy_N, OUT start_label = 1
  ) {

  const int64_t sxy = sx * sy;
  const int64_t voxels = sx * sy * sz;

  max_labels++;
  max_labels = std::min(max_labels, static_cast<size_t>(voxels) + 1); // + 1L for an array with no zeros
  max_labels = std::min(max_labels, static_cast<size_t>(std::numeric_limits<OUT>::max()));

  DisjointSet<uint32_t> equivalences(max_labels);

  if (out_labels == NULL) {
    out_labels = new OUT[voxels]();
  }

  /*
    Layout of forward pass mask.
    A is the current location.
    D C
    B A
  */

  // const int64_t A = 0;
  const int64_t B = -1;
  const int64_t C = -sx;
  const int64_t D = -1-sx;

  int64_t loc = 0;
  OUT next_label = 0;

  // Raster Scan 1: Set temporary labels and
  // record equivalences in a disjoint set.

  bool cur = 0;
  for (int64_t z = 0; z < sz; z++) {
    for (int64_t y = 0; y < sy; y++) {
      for (int64_t x = 0; x < sx; x++) {
        loc = x + sx * y + sxy * z;
        cur = in_labels[loc];

        if (cur) {
          continue;
        }

        if (x > 0 && !in_labels[loc + B]) {
          out_labels[loc] = out_labels[loc + B];
          if (y > 0 && in_labels[loc + D] && !in_labels[loc + C]) {
            equivalences.unify(out_labels[loc], out_labels[loc + C]);
          }
        }
        else if (y > 0 && !in_labels[loc + C]) {
          out_labels[loc] = out_labels[loc + C];
        }
        else {
          next_label++;
          out_labels[loc] = next_label;
          equivalences.add(out_labels[loc]);
        }
      }
    }
  }

  return relabel<OUT>(out_labels, voxels, next_label, equivalences, N, start_label);
}

template <typename OUT = uint32_t>
OUT* connected_components3d_6(
    bool* in_labels,
    const int64_t sx, const int64_t sy, const int64_t sz,
    size_t max_labels,
    OUT *out_labels = NULL, size_t &N = _dummy_N
  ) {

  const int64_t sxy = sx * sy;
  const int64_t voxels = sxy * sz;

  if (out_labels == NULL) {
    out_labels = new OUT[voxels]();
  }

  if (max_labels == 0) {
    return out_labels;
  }

  max_labels++; // corrects Cython estimation
  max_labels = std::min(max_labels, static_cast<size_t>(voxels) + 1); // + 1L for an array with no zeros
  max_labels = std::min(max_labels, static_cast<size_t>(std::numeric_limits<OUT>::max()));

  DisjointSet<OUT> equivalences(max_labels);

  /*
    Layout of forward pass mask (which faces backwards).
    N is the current location.

    z = -1     z = 0
    A B C      J K L   y = -1
    D E F      M N     y =  0
    G H I              y = +1
   -1 0 +1    -1 0   <-- x axis
  */

  // Z - 1
  const int64_t B = -sx - sxy;
  const int64_t E = -sxy;
  const int64_t D = -1 - sxy;

  // Current Z
  const int64_t K = -sx;
  const int64_t M = -1;
  const int64_t J = -1 - sx;
  // N = 0;

  int64_t loc = 0;
  OUT next_label = 0;

  // Raster Scan 1: Set temporary labels and
  // record equivalences in a disjoint set.

  for (int64_t z = 0; z < sz; z++) {
    for (int64_t y = 0; y < sy; y++) {
      for (int64_t x = 0; x < sx; x++) {
        loc = x + sx * (y + sy * z);

        const bool cur = in_labels[loc];

        if (cur) {
          continue;
        }

        if (x > 0 && !in_labels[loc + M]) {
          out_labels[loc] = out_labels[loc + M];

          if (y > 0 && !in_labels[loc + K] && in_labels[loc + J]) {
            equivalences.unify(out_labels[loc], out_labels[loc + K]);
            if (z > 0 && !in_labels[loc + E]) {
              if (in_labels[loc + D] && in_labels[loc + B]) {
                equivalences.unify(out_labels[loc], out_labels[loc + E]);
              }
            }
          }
          else if (z > 0 && !in_labels[loc + E] && in_labels[loc + D]) {
            equivalences.unify(out_labels[loc], out_labels[loc + E]);
          }
        }
        else if (y > 0 && !in_labels[loc + K]) {
          out_labels[loc] = out_labels[loc + K];

          if (z > 0 && !in_labels[loc + E] && in_labels[loc + B]) {
            equivalences.unify(out_labels[loc], out_labels[loc + E]);
          }
        }
        else if (z > 0 && !in_labels[loc + E]) {
          out_labels[loc] = out_labels[loc + E];
        }
        else {
          next_label++;
          out_labels[loc] = next_label;
          equivalences.add(out_labels[loc]);
        }
      }
    }
  }

  if (next_label <= 1) {
    N = next_label;
    return out_labels;
  }

  return relabel<OUT>(out_labels, voxels, next_label, equivalences, N, 1);
}

template <typename OUT = uint64_t>
OUT* connected_components(
  bool* in_labels,
  const int64_t sx, const int64_t sy, const int64_t sz,
  const size_t connectivity = 4, size_t &N = _dummy_N
) {

  const int64_t sxy = sx * sy;
  const int64_t voxels = sxy * sz;

  size_t max_labels = voxels;
  OUT* out_labels = new OUT[voxels]();
  N = 0;

  if (connectivity == 4) {
    max_labels = static_cast<size_t>((sxy + 2) / 2);

    for (int64_t z = 0; z < sz; z++) {
      size_t tmp_N = 0;
      connected_components2d_4<OUT>(
        (in_labels + sxy * z), sx, sy, 1,
        max_labels, (out_labels + sxy * z),
        tmp_N, N + 1
      );
      N += tmp_N;
    }
  }
  else if (connectivity == 6) {
    max_labels =  static_cast<size_t>(((sx + 1) * (sy + 1) * (sz + 1)) / 2);
    connected_components3d_6<OUT>(
      in_labels, sx, sy, sz,
      max_labels, out_labels, N
    );
  }
  else {
    throw std::runtime_error("Only 4 and 6 connectivities are supported.");
  }

  return out_labels;
}

};

#endif
<commit_msg>refactor: remove unnecessary allocation checks<commit_after>/**
 * @license
 * Copyright 2021 William Silvermsith
 * Licensed 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.
 */

/*
 * Connected Components for 2D images.

 * Author: William Silversmith
 * Affiliation: Seung Lab, Princeton University
 * Date: August 2018 - June 2019, June 2021
 *
 * ----
 * Notes on the license:
 *
 * This is a special reduced feature version of cc3d
 * that includes only the logic needed for CCL 4-connected
 * and 6-connected. It is also modified to treat black as
 * foreground and black as background.
 *
 * cc3d is ordinarily licensed as GPL v3.
 * Get the full version of cc3d here:
 *
 * https://github.com/seung-lab/connected-components-3d
 */

#ifndef CC3D_SPECIAL_4_HPP
#define CC3D_SPECIAL_4_HPP

#include <algorithm>
#include <cmath>
#include <cstdint>
#include <stdexcept>

namespace cc3d {

static size_t _dummy_N;

template <typename T>
class DisjointSet {
public:
  T *ids;
  size_t length;

  DisjointSet () {
    length = 65536; // 2^16, some "reasonable" starting size
    ids = new T[length]();
  }

  DisjointSet (size_t len) {
    length = len;
    ids = new T[length]();
  }

  DisjointSet (const DisjointSet &cpy) {
    length = cpy.length;
    ids = new T[length]();

    for (int i = 0; i < length; i++) {
      ids[i] = cpy.ids[i];
    }
  }

  ~DisjointSet () {
    delete []ids;
  }

  T root (T n) {
    T i = ids[n];
    while (i != ids[i]) {
      ids[i] = ids[ids[i]]; // path compression
      i = ids[i];
    }

    return i;
  }

  bool find (T p, T q) {
    return root(p) == root(q);
  }

  void add(T p) {
    if (p >= length) {
      printf("Connected Components Error: Label %d cannot be mapped to union-find array of length %lu.\n", p, length);
      throw "maximum length exception";
    }

    if (ids[p] == 0) {
      ids[p] = p;
    }
  }

  void unify (T p, T q) {
    if (p == q) {
      return;
    }

    T i = root(p);
    T j = root(q);

    if (i == 0) {
      add(p);
      i = p;
    }

    if (j == 0) {
      add(q);
      j = q;
    }

    ids[i] = j;
  }

  void print() {
    for (int i = 0; i < 15; i++) {
      printf("%d, ", ids[i]);
    }
    printf("\n");
  }

  // would be easy to write remove.
  // Will be O(n).
};

// This is the second raster pass of the two pass algorithm family.
// The input array (output_labels) has been assigned provisional
// labels and this resolves them into their final labels. We
// modify this pass to also ensure that the output labels are
// numbered from 1 sequentially.
template <typename OUT = uint32_t>
OUT* relabel(
    OUT* out_labels, const int64_t voxels,
    const int64_t num_labels, DisjointSet<uint32_t> &equivalences,
    size_t &N = _dummy_N, OUT start_label = 1
  ) {

  OUT label;
  OUT* renumber = new OUT[num_labels + 1]();
  OUT next_label = start_label;

  for (int64_t i = 1; i <= num_labels; i++) {
    label = equivalences.root(i);
    if (renumber[label] == 0) {
      renumber[label] = next_label;
      renumber[i] = next_label;
      next_label++;
    }
    else {
      renumber[i] = renumber[label];
    }
  }

  // Raster Scan 2: Write final labels based on equivalences
  N = next_label - start_label;
  if (N < static_cast<size_t>(num_labels) || start_label != 1) {
    for (int64_t loc = 0; loc < voxels; loc++) {
      out_labels[loc] = renumber[out_labels[loc]];
    }
  }

  delete[] renumber;

  return out_labels;
}

template <typename OUT = uint32_t>
OUT* connected_components2d_4(
    bool* in_labels,
    const int64_t sx, const int64_t sy, const int64_t sz,
    size_t max_labels, OUT *out_labels = NULL,
    size_t &N = _dummy_N, OUT start_label = 1
  ) {

  const int64_t sxy = sx * sy;
  const int64_t voxels = sx * sy * sz;

  max_labels++;
  max_labels = std::min(max_labels, static_cast<size_t>(voxels) + 1); // + 1L for an array with no zeros
  max_labels = std::min(max_labels, static_cast<size_t>(std::numeric_limits<OUT>::max()));

  DisjointSet<uint32_t> equivalences(max_labels);

  if (out_labels == NULL) {
    out_labels = new OUT[voxels]();
  }

  /*
    Layout of forward pass mask.
    A is the current location.
    D C
    B A
  */

  // const int64_t A = 0;
  const int64_t B = -1;
  const int64_t C = -sx;
  const int64_t D = -1-sx;

  int64_t loc = 0;
  OUT next_label = 0;

  // Raster Scan 1: Set temporary labels and
  // record equivalences in a disjoint set.

  bool cur = 0;
  for (int64_t z = 0; z < sz; z++) {
    for (int64_t y = 0; y < sy; y++) {
      for (int64_t x = 0; x < sx; x++) {
        loc = x + sx * y + sxy * z;
        cur = in_labels[loc];

        if (cur) {
          continue;
        }

        if (x > 0 && !in_labels[loc + B]) {
          out_labels[loc] = out_labels[loc + B];
          if (y > 0 && in_labels[loc + D] && !in_labels[loc + C]) {
            equivalences.unify(out_labels[loc], out_labels[loc + C]);
          }
        }
        else if (y > 0 && !in_labels[loc + C]) {
          out_labels[loc] = out_labels[loc + C];
        }
        else {
          next_label++;
          out_labels[loc] = next_label;
          equivalences.add(out_labels[loc]);
        }
      }
    }
  }

  return relabel<OUT>(out_labels, voxels, next_label, equivalences, N, start_label);
}

template <typename OUT = uint32_t>
OUT* connected_components3d_6(
    bool* in_labels,
    const int64_t sx, const int64_t sy, const int64_t sz,
    size_t max_labels,
    OUT *out_labels = NULL, size_t &N = _dummy_N
  ) {

  const int64_t sxy = sx * sy;
  const int64_t voxels = sxy * sz;

  if (out_labels == NULL) {
    out_labels = new OUT[voxels]();
  }

  if (max_labels == 0) {
    return out_labels;
  }

  max_labels++; // corrects Cython estimation
  max_labels = std::min(max_labels, static_cast<size_t>(voxels) + 1); // + 1L for an array with no zeros
  max_labels = std::min(max_labels, static_cast<size_t>(std::numeric_limits<OUT>::max()));

  DisjointSet<OUT> equivalences(max_labels);

  /*
    Layout of forward pass mask (which faces backwards).
    N is the current location.

    z = -1     z = 0
    A B C      J K L   y = -1
    D E F      M N     y =  0
    G H I              y = +1
   -1 0 +1    -1 0   <-- x axis
  */

  // Z - 1
  const int64_t B = -sx - sxy;
  const int64_t E = -sxy;
  const int64_t D = -1 - sxy;

  // Current Z
  const int64_t K = -sx;
  const int64_t M = -1;
  const int64_t J = -1 - sx;
  // N = 0;

  int64_t loc = 0;
  OUT next_label = 0;

  // Raster Scan 1: Set temporary labels and
  // record equivalences in a disjoint set.

  for (int64_t z = 0; z < sz; z++) {
    for (int64_t y = 0; y < sy; y++) {
      for (int64_t x = 0; x < sx; x++) {
        loc = x + sx * (y + sy * z);

        const bool cur = in_labels[loc];

        if (cur) {
          continue;
        }

        if (x > 0 && !in_labels[loc + M]) {
          out_labels[loc] = out_labels[loc + M];

          if (y > 0 && !in_labels[loc + K] && in_labels[loc + J]) {
            equivalences.unify(out_labels[loc], out_labels[loc + K]);
            if (z > 0 && !in_labels[loc + E]) {
              if (in_labels[loc + D] && in_labels[loc + B]) {
                equivalences.unify(out_labels[loc], out_labels[loc + E]);
              }
            }
          }
          else if (z > 0 && !in_labels[loc + E] && in_labels[loc + D]) {
            equivalences.unify(out_labels[loc], out_labels[loc + E]);
          }
        }
        else if (y > 0 && !in_labels[loc + K]) {
          out_labels[loc] = out_labels[loc + K];

          if (z > 0 && !in_labels[loc + E] && in_labels[loc + B]) {
            equivalences.unify(out_labels[loc], out_labels[loc + E]);
          }
        }
        else if (z > 0 && !in_labels[loc + E]) {
          out_labels[loc] = out_labels[loc + E];
        }
        else {
          next_label++;
          out_labels[loc] = next_label;
          equivalences.add(out_labels[loc]);
        }
      }
    }
  }

  if (next_label <= 1) {
    N = next_label;
    return out_labels;
  }

  return relabel<OUT>(out_labels, voxels, next_label, equivalences, N, 1);
}

template <typename OUT = uint64_t>
OUT* connected_components(
  bool* in_labels,
  const int64_t sx, const int64_t sy, const int64_t sz,
  const size_t connectivity = 4, size_t &N = _dummy_N
) {

  const int64_t sxy = sx * sy;
  const int64_t voxels = sxy * sz;

  size_t max_labels = voxels;
  OUT* out_labels = new OUT[voxels]();
  N = 0;

  if (connectivity == 4) {
    max_labels = static_cast<size_t>((sxy + 2) / 2);

    for (int64_t z = 0; z < sz; z++) {
      size_t tmp_N = 0;
      connected_components2d_4<OUT>(
        (in_labels + sxy * z), sx, sy, 1,
        max_labels, (out_labels + sxy * z),
        tmp_N, N + 1
      );
      N += tmp_N;
    }
  }
  else if (connectivity == 6) {
    max_labels =  static_cast<size_t>(((sx + 1) * (sy + 1) * (sz + 1)) / 2);
    connected_components3d_6<OUT>(
      in_labels, sx, sy, sz,
      max_labels, out_labels, N
    );
  }
  else {
    throw std::runtime_error("Only 4 and 6 connectivities are supported.");
  }

  return out_labels;
}

};

#endif
<|endoftext|>


<commit_before><commit_msg>fix walking to ball and unnecessarily backwards before kicking<commit_after><|endoftext|>


<commit_before>/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/

#include "qmlcodecompletion.h"
#include "qmljseditor.h"
#include "qmlmodelmanagerinterface.h"
#include "qmlexpressionundercursor.h"
#include "qmllookupcontext.h"
#include "qmlresolveexpression.h"

#include <qmljs/qmljssymbol.h>
#include <texteditor/basetexteditor.h>

#include <coreplugin/icore.h>

#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <QtCore/QXmlStreamReader>
#include <QtGui/QPainter>

#include <QtDebug>

using namespace QmlJSEditor;
using namespace QmlJSEditor::Internal;

// Temporary workaround until we have proper icons for QML completion items
static QIcon iconForColor(const QColor &color)
{
    QPixmap pix(6, 6);

    int pixSize = 20;
    QBrush br(color);

    QPixmap pm(2 * pixSize, 2 * pixSize);
    QPainter pmp(&pm);
    pmp.fillRect(0, 0, pixSize, pixSize, Qt::lightGray);
    pmp.fillRect(pixSize, pixSize, pixSize, pixSize, Qt::lightGray);
    pmp.fillRect(0, pixSize, pixSize, pixSize, Qt::darkGray);
    pmp.fillRect(pixSize, 0, pixSize, pixSize, Qt::darkGray);
    pmp.fillRect(0, 0, 2 * pixSize, 2 * pixSize, color);
    br = QBrush(pm);

    QPainter p(&pix);
    int corr = 1;
    QRect r = pix.rect().adjusted(corr, corr, -corr, -corr);
    p.setBrushOrigin((r.width() % pixSize + pixSize) / 2 + corr, (r.height() % pixSize + pixSize) / 2 + corr);
    p.fillRect(r, br);

    p.fillRect(r.width() / 4 + corr, r.height() / 4 + corr,
               r.width() / 2, r.height() / 2,
               QColor(color.rgb()));
    p.drawRect(pix.rect().adjusted(0, 0, -1, -1));

    return pix;
}

QmlCodeCompletion::QmlCodeCompletion(QmlModelManagerInterface *modelManager, QmlJS::TypeSystem *typeSystem, QObject *parent)
    : TextEditor::ICompletionCollector(parent),
      m_modelManager(modelManager),
      m_editor(0),
      m_startPosition(0),
      m_caseSensitivity(Qt::CaseSensitive),
      m_typeSystem(typeSystem)
{
    Q_ASSERT(modelManager);
    Q_ASSERT(typeSystem);
}

QmlCodeCompletion::~QmlCodeCompletion()
{ }

Qt::CaseSensitivity QmlCodeCompletion::caseSensitivity() const
{ return m_caseSensitivity; }

void QmlCodeCompletion::setCaseSensitivity(Qt::CaseSensitivity caseSensitivity)
{ m_caseSensitivity = caseSensitivity; }

bool QmlCodeCompletion::supportsEditor(TextEditor::ITextEditable *editor)
{
    if (qobject_cast<QmlJSTextEditor *>(editor->widget()))
        return true;

    return false;
}

bool QmlCodeCompletion::triggersCompletion(TextEditor::ITextEditable *)
{ return false; }

int QmlCodeCompletion::startCompletion(TextEditor::ITextEditable *editor)
{
    m_editor = editor;

    QmlJSTextEditor *edit = qobject_cast<QmlJSTextEditor *>(m_editor->widget());
    if (! edit)
        return -1;

    int pos = editor->position();

    while (editor->characterAt(pos - 1).isLetterOrNumber() || editor->characterAt(pos - 1) == QLatin1Char('_'))
        --pos;

    m_startPosition = pos;
    m_completions.clear();

    QmlJS::Document::Ptr qmlDocument = edit->qmlDocument();
//    qDebug() << "*** document:" << qmlDocument;
    if (qmlDocument.isNull())
        return -1;

    const QmlJS::Snapshot &snapshot = m_modelManager->snapshot();

    if (! qmlDocument->qmlProgram()) {
        qmlDocument = snapshot.value(qmlDocument->fileName());

        if (! qmlDocument)
            return -1;
    }

    const QIcon typeIcon = iconForColor(Qt::yellow);

    foreach (QmlJS::Document::Ptr doc, snapshot) {
        const QFileInfo fileInfo(doc->fileName());
        if (fileInfo.suffix() != QLatin1String("qml"))
            continue;

        const QString typeName = fileInfo.baseName();
        if (typeName.isEmpty())
            continue;

        if (typeName.at(0).isUpper()) {
            TextEditor::CompletionItem item(this);
            item.text = typeName;
            item.icon = typeIcon;
            m_completions.append(item);
        }
    }

    const QIcon idIcon = iconForColor(Qt::darkGray);
    QStringList ids = qmlDocument->ids().keys();
    foreach (const QString &id, ids) {
        if (id.isEmpty())
            continue;

        TextEditor::CompletionItem item(this);
        item.text = id;
        item.icon = idIcon;
        m_completions.append(item);
    }

    const QIcon otherIcon = iconForColor(Qt::darkCyan);
    // FIXME: this completion strategy is not going to work when the document was never parsed correctly.
    if (qmlDocument->qmlProgram() != 0) {
        // qDebug() << "*** program:" << program;
        QmlExpressionUnderCursor expressionUnderCursor;
        QTextCursor cursor(edit->document());
        cursor.setPosition(pos);
        expressionUnderCursor(cursor, qmlDocument);

        QmlLookupContext context(expressionUnderCursor.expressionScopes(), qmlDocument, m_modelManager->snapshot(), m_typeSystem);
        QmlResolveExpression resolver(context);
        // qDebug()<<"*** expression under cursor:"<<expressionUnderCursor.expressionNode();
        const QList<QmlJS::Symbol*> symbols = resolver.visibleSymbols(expressionUnderCursor.expressionNode());
        // qDebug()<<"***"<<symbols.size()<<"visible symbols";

        foreach (QmlJS::Symbol *symbol, symbols) {
            if (symbol->isIdSymbol())
                continue; // nothing to do here.

            const QString word = symbol->name();
            if (! word.isEmpty()) {
                TextEditor::CompletionItem item(this);
                item.text = word;
                item.icon = otherIcon;
                m_completions.append(item);
            }
        }
    }

    QChar previousChar;
    if (m_startPosition > 0)
        previousChar = editor->characterAt(m_startPosition - 1);

    if (previousChar.isNull()
            || previousChar.isSpace()
            || previousChar == QLatin1Char('{')
            || previousChar == QLatin1Char('}')
            || previousChar == QLatin1Char(':')
            || previousChar == QLatin1Char(';')) {
        updateSnippets();
        m_completions.append(m_snippets);
    }

    return pos;
}

void QmlCodeCompletion::completions(QList<TextEditor::CompletionItem> *completions)
{
    // ### FIXME: this code needs to be generalized.

    const int length = m_editor->position() - m_startPosition;

    if (length == 0)
        *completions = m_completions;
    else if (length > 0) {
        const QString key = m_editor->textAt(m_startPosition, length);

        /*
         * This code builds a regular expression in order to more intelligently match
         * camel-case style. This means upper-case characters will be rewritten as follows:
         *
         *   A => [a-z0-9_]*A (for any but the first capital letter)
         *
         * Meaning it allows any sequence of lower-case characters to preceed an
         * upper-case character. So for example gAC matches getActionController.
         */
        QString keyRegExp;
        keyRegExp += QLatin1Char('^');
        bool first = true;
        foreach (const QChar &c, key) {
            if (c.isUpper() && !first) {
                keyRegExp += QLatin1String("[a-z0-9_]*");
                keyRegExp += c;
            } else if (m_caseSensitivity == Qt::CaseInsensitive && c.isLower()) {
                keyRegExp += QLatin1Char('[');
                keyRegExp += c;
                keyRegExp += c.toUpper();
                keyRegExp += QLatin1Char(']');
            } else {
                keyRegExp += QRegExp::escape(c);
            }
            first = false;
        }
        const QRegExp regExp(keyRegExp, Qt::CaseSensitive);

        foreach (TextEditor::CompletionItem item, m_completions) {
            if (regExp.indexIn(item.text) == 0) {
                item.relevance = (key.length() > 0 &&
                                    item.text.startsWith(key, Qt::CaseInsensitive)) ? 1 : 0;
                (*completions) << item;
            }
        }
    }
}

void QmlCodeCompletion::complete(const TextEditor::CompletionItem &item)
{
    QString toInsert = item.text;

    if (QmlJSTextEditor *edit = qobject_cast<QmlJSTextEditor *>(m_editor->widget())) {
        if (item.data.isValid()) {
            QTextCursor tc = edit->textCursor();
            tc.beginEditBlock();
            tc.setPosition(m_startPosition);
            tc.setPosition(m_editor->position(), QTextCursor::KeepAnchor);
            tc.removeSelectedText();

            toInsert = item.data.toString();
            edit->insertCodeSnippet(toInsert);
            tc.endEditBlock();
            return;
        }
    }

    const int length = m_editor->position() - m_startPosition;
    m_editor->setCurPos(m_startPosition);
    m_editor->replace(length, toInsert);
}

bool QmlCodeCompletion::partiallyComplete(const QList<TextEditor::CompletionItem> &completionItems)
{
    if (completionItems.count() == 1) {
        const TextEditor::CompletionItem item = completionItems.first();

        if (!item.data.canConvert<QString>()) {
            complete(item);
            return true;
        }
    }

    // Compute common prefix
    QString firstKey = completionItems.first().text;
    QString lastKey = completionItems.last().text;
    const int length = qMin(firstKey.length(), lastKey.length());
    firstKey.truncate(length);
    lastKey.truncate(length);

    while (firstKey != lastKey) {
        firstKey.chop(1);
        lastKey.chop(1);
    }

    int typedLength = m_editor->position() - m_startPosition;
    if (!firstKey.isEmpty() && firstKey.length() > typedLength) {
        m_editor->setCurPos(m_startPosition);
        m_editor->replace(typedLength, firstKey);
    }

    return false;
}

void QmlCodeCompletion::cleanup()
{
    m_editor = 0;
    m_startPosition = 0;
    m_completions.clear();
}

void QmlCodeCompletion::updateSnippets()
{
    QString qmlsnippets = Core::ICore::instance()->resourcePath() + QLatin1String("/snippets/qml.xml");
    if (!QFile::exists(qmlsnippets))
        return;

    QDateTime lastModified = QFileInfo(qmlsnippets).lastModified();
    if (!m_snippetFileLastModified.isNull() &&  lastModified == m_snippetFileLastModified)
        return;

    const QIcon icon = iconForColor(Qt::red);

    m_snippetFileLastModified = lastModified;
    QFile file(qmlsnippets);
    file.open(QIODevice::ReadOnly);
    QXmlStreamReader xml(&file);
    while (!xml.atEnd() && xml.readNextStartElement()) {
        if (xml.name() == QLatin1String("snippets")) {
            while (xml.readNextStartElement()) {
                if (xml.name() == QLatin1String("snippet")) {
                    TextEditor::CompletionItem item(this);
                    QString title, data;
                    QString description = xml.attributes().value("description").toString();

                    while (!xml.atEnd()) {
                        xml.readNext();
                        if (xml.isEndElement()) {
                            int i = 0;
                            while (i < data.size() && data.at(i).isLetterOrNumber())
                                ++i;
                            title = data.left(i);
                            item.text = title;
                            if (!description.isEmpty()) {
                                item.text +=  QLatin1Char(' ');
                                item.text += description;
                            }
                            item.data = QVariant::fromValue(data);
                            item.icon = icon;
                            m_snippets.append(item);
                            break;

                        }

                        if (xml.isCharacters())
                            data += xml.text();
                        else if (xml.isStartElement()) {
                            if (xml.name() != QLatin1String("tab"))
                                xml.raiseError(QLatin1String("invalid snippets file"));
                            else {
                                data += QChar::ObjectReplacementCharacter;
                                data += xml.readElementText();
                                data += QChar::ObjectReplacementCharacter;
                            }
                        }

                    }
                }
            }
        }
    }
    if (xml.hasError())
        qWarning() << qmlsnippets << xml.errorString();
    file.close();
}

QList<TextEditor::CompletionItem> QmlCodeCompletion::getCompletions()
{
    QList<TextEditor::CompletionItem> completionItems;

    completions(&completionItems);

    qStableSort(completionItems.begin(), completionItems.end(), completionItemLessThan);

    // Remove duplicates
    QString lastKey;
    QList<TextEditor::CompletionItem> uniquelist;

    foreach (const TextEditor::CompletionItem &item, completionItems) {
        if (item.text != lastKey) {
            uniquelist.append(item);
            lastKey = item.text;
        } else {
            if (item.data.canConvert<QString>())
                uniquelist.append(item);
        }
    }

    return uniquelist;
}
<commit_msg>Evaluate simple JS expressions.<commit_after>/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/

#include "qmlcodecompletion.h"
#include "qmljseditor.h"
#include "qmlmodelmanagerinterface.h"
#include "qmlexpressionundercursor.h"
#include "qmllookupcontext.h"
#include "qmlresolveexpression.h"

#include <qmljs/parser/qmljsast_p.h>
#include <qmljs/qmljsinterpreter.h>
#include <qmljs/qmljssymbol.h>
#include <texteditor/basetexteditor.h>

#include <coreplugin/icore.h>

#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <QtCore/QXmlStreamReader>
#include <QtGui/QPainter>

#include <QtDebug>

using namespace QmlJSEditor;
using namespace QmlJSEditor::Internal;

// Temporary workaround until we have proper icons for QML completion items
static QIcon iconForColor(const QColor &color)
{
    QPixmap pix(6, 6);

    int pixSize = 20;
    QBrush br(color);

    QPixmap pm(2 * pixSize, 2 * pixSize);
    QPainter pmp(&pm);
    pmp.fillRect(0, 0, pixSize, pixSize, Qt::lightGray);
    pmp.fillRect(pixSize, pixSize, pixSize, pixSize, Qt::lightGray);
    pmp.fillRect(0, pixSize, pixSize, pixSize, Qt::darkGray);
    pmp.fillRect(pixSize, 0, pixSize, pixSize, Qt::darkGray);
    pmp.fillRect(0, 0, 2 * pixSize, 2 * pixSize, color);
    br = QBrush(pm);

    QPainter p(&pix);
    int corr = 1;
    QRect r = pix.rect().adjusted(corr, corr, -corr, -corr);
    p.setBrushOrigin((r.width() % pixSize + pixSize) / 2 + corr, (r.height() % pixSize + pixSize) / 2 + corr);
    p.fillRect(r, br);

    p.fillRect(r.width() / 4 + corr, r.height() / 4 + corr,
               r.width() / 2, r.height() / 2,
               QColor(color.rgb()));
    p.drawRect(pix.rect().adjusted(0, 0, -1, -1));

    return pix;
}

class Evaluate: public QmlJS::AST::Visitor
{
    QmlJS::Interpreter::Engine *_interp;
    const QmlJS::Interpreter::Value *_value;

public:
    Evaluate(QmlJS::Interpreter::Engine *interp)
        : _interp(interp), _value(0)
    {}

    const QmlJS::Interpreter::Value *operator()(QmlJS::AST::Node *node)
    { return evaluate(node); }

    const QmlJS::Interpreter::Value *evaluate(QmlJS::AST::Node *node)
    {
        const QmlJS::Interpreter::Value *previousValue = switchValue(0);

        if (node)
            node->accept(this);

        return switchValue(previousValue);
    }

protected:
    using QmlJS::AST::Visitor::visit;

    const QmlJS::Interpreter::Value *switchValue(const QmlJS::Interpreter::Value *value)
    {
        const QmlJS::Interpreter::Value *previousValue = _value;
        _value = value;
        return previousValue;
    }

    virtual bool preVisit(QmlJS::AST::Node *ast)
    {
        using namespace QmlJS::AST;

        if (cast<NumericLiteral *>(ast))
            return true;

        if (cast<StringLiteral *>(ast))
            return true;

        if (cast<IdentifierExpression *>(ast))
            return true;

        else if (cast<FieldMemberExpression *>(ast))
            return true;

        else if (cast<CallExpression *>(ast))
            return true;

        return false;
    }

    virtual bool visit(QmlJS::AST::StringLiteral *)
    {
        _value = _interp->convertToObject(_interp->stringValue());
        return false;
    }

    virtual bool visit(QmlJS::AST::NumericLiteral *)
    {
        _value = _interp->convertToObject(_interp->numberValue());
        return false;
    }

    virtual bool visit(QmlJS::AST::IdentifierExpression *ast)
    {
        _value = _interp->globalObject()->property(ast->name->asString());
        return false;
    }

    virtual bool visit(QmlJS::AST::FieldMemberExpression *ast)
    {
        if (const QmlJS::Interpreter::Value *base = evaluate(ast->base)) {
            if (const QmlJS::Interpreter::ObjectValue *obj = base->asObjectValue()) {
                _value = obj->property(ast->name->asString());
            }
        }

        return false;
    }

    virtual bool visit(QmlJS::AST::CallExpression *ast)
    {
        if (const QmlJS::Interpreter::Value *base = evaluate(ast->base)) {
            if (const QmlJS::Interpreter::FunctionValue *obj = base->asFunctionValue()) {
                _value = obj->returnValue();
            }
        }

        return false;
    }

};

QmlCodeCompletion::QmlCodeCompletion(QmlModelManagerInterface *modelManager, QmlJS::TypeSystem *typeSystem, QObject *parent)
    : TextEditor::ICompletionCollector(parent),
      m_modelManager(modelManager),
      m_editor(0),
      m_startPosition(0),
      m_caseSensitivity(Qt::CaseSensitive),
      m_typeSystem(typeSystem)
{
    Q_ASSERT(modelManager);
    Q_ASSERT(typeSystem);
}

QmlCodeCompletion::~QmlCodeCompletion()
{ }

Qt::CaseSensitivity QmlCodeCompletion::caseSensitivity() const
{ return m_caseSensitivity; }

void QmlCodeCompletion::setCaseSensitivity(Qt::CaseSensitivity caseSensitivity)
{ m_caseSensitivity = caseSensitivity; }

bool QmlCodeCompletion::supportsEditor(TextEditor::ITextEditable *editor)
{
    if (qobject_cast<QmlJSTextEditor *>(editor->widget()))
        return true;

    return false;
}

bool QmlCodeCompletion::triggersCompletion(TextEditor::ITextEditable *)
{ return false; }

int QmlCodeCompletion::startCompletion(TextEditor::ITextEditable *editor)
{
    m_editor = editor;

    QmlJSTextEditor *edit = qobject_cast<QmlJSTextEditor *>(m_editor->widget());
    if (! edit)
        return -1;

    int pos = editor->position();

    while (editor->characterAt(pos - 1).isLetterOrNumber() || editor->characterAt(pos - 1) == QLatin1Char('_'))
        --pos;

    m_startPosition = pos;
    m_completions.clear();

    QmlJS::Document::Ptr qmlDocument = edit->qmlDocument();
//    qDebug() << "*** document:" << qmlDocument;
    if (qmlDocument.isNull())
        return -1;

    const QmlJS::Snapshot &snapshot = m_modelManager->snapshot();

    if (! qmlDocument->qmlProgram()) {
        qmlDocument = snapshot.value(qmlDocument->fileName());

        if (! qmlDocument)
            return -1;
    }

    const QIcon typeIcon = iconForColor(Qt::yellow);

    foreach (QmlJS::Document::Ptr doc, snapshot) {
        const QFileInfo fileInfo(doc->fileName());
        if (fileInfo.suffix() != QLatin1String("qml"))
            continue;

        const QString typeName = fileInfo.baseName();
        if (typeName.isEmpty())
            continue;

        if (typeName.at(0).isUpper()) {
            TextEditor::CompletionItem item(this);
            item.text = typeName;
            item.icon = typeIcon;
            m_completions.append(item);
        }
    }

    QChar previousChar;
    if (m_startPosition > 0)
        previousChar = editor->characterAt(m_startPosition - 1);

    if (previousChar.isSpace() || previousChar.isNull()) {
        // ### FIXME
        const QIcon idIcon = iconForColor(Qt::darkGray);
        QStringList ids = qmlDocument->ids().keys();
        foreach (const QString &id, ids) {
            if (id.isEmpty())
                continue;

            TextEditor::CompletionItem item(this);
            item.text = id;
            item.icon = idIcon;
            m_completions.append(item);
        }
    }

    // FIXME: this completion strategy is not going to work when the document was never parsed correctly.
    if (qmlDocument->qmlProgram() != 0) {
        const QIcon otherIcon = iconForColor(Qt::darkCyan);

        // qDebug() << "*** program:" << program;
        QmlExpressionUnderCursor expressionUnderCursor;
        QTextCursor cursor(edit->document());
        cursor.setPosition(pos);
        expressionUnderCursor(cursor, qmlDocument);

        QmlLookupContext context(expressionUnderCursor.expressionScopes(), qmlDocument, m_modelManager->snapshot(), m_typeSystem);
        QmlResolveExpression resolver(context);

        QmlJS::AST::Node *expr = expressionUnderCursor.expressionNode();

        QmlJS::Interpreter::Engine engine;
        Evaluate evaluate(&engine);
        if (const QmlJS::Interpreter::Value *value = evaluate(expr)) {
            if (const QmlJS::Interpreter::ObjectValue *object = value->asObjectValue()) {
                for (QmlJS::Interpreter::ObjectValue::MemberIterator it = object->firstMember(); it != object->lastMember(); ++it) {
                    TextEditor::CompletionItem item(this);
                    item.text = it.key();
                    item.icon = otherIcon;
                    m_completions.append(item);
                }
                return pos;
            }
        }

        // qDebug()<<"*** expression under cursor:"<<expressionUnderCursor.expressionNode();
        const QList<QmlJS::Symbol*> symbols = resolver.visibleSymbols(expressionUnderCursor.expressionNode());
        // qDebug()<<"***"<<symbols.size()<<"visible symbols";

        foreach (QmlJS::Symbol *symbol, symbols) {
            if (symbol->isIdSymbol())
                continue; // nothing to do here.

            const QString word = symbol->name();
            if (! word.isEmpty()) {
                TextEditor::CompletionItem item(this);
                item.text = word;
                item.icon = otherIcon;
                m_completions.append(item);
            }
        }
    }

    if (previousChar.isNull()
            || previousChar.isSpace()
            || previousChar == QLatin1Char('{')
            || previousChar == QLatin1Char('}')
            || previousChar == QLatin1Char(':')
            || previousChar == QLatin1Char(';')) {
        updateSnippets();
        m_completions.append(m_snippets);
    }

    return pos;
}

void QmlCodeCompletion::completions(QList<TextEditor::CompletionItem> *completions)
{
    // ### FIXME: this code needs to be generalized.

    const int length = m_editor->position() - m_startPosition;

    if (length == 0)
        *completions = m_completions;
    else if (length > 0) {
        const QString key = m_editor->textAt(m_startPosition, length);

        /*
         * This code builds a regular expression in order to more intelligently match
         * camel-case style. This means upper-case characters will be rewritten as follows:
         *
         *   A => [a-z0-9_]*A (for any but the first capital letter)
         *
         * Meaning it allows any sequence of lower-case characters to preceed an
         * upper-case character. So for example gAC matches getActionController.
         */
        QString keyRegExp;
        keyRegExp += QLatin1Char('^');
        bool first = true;
        foreach (const QChar &c, key) {
            if (c.isUpper() && !first) {
                keyRegExp += QLatin1String("[a-z0-9_]*");
                keyRegExp += c;
            } else if (m_caseSensitivity == Qt::CaseInsensitive && c.isLower()) {
                keyRegExp += QLatin1Char('[');
                keyRegExp += c;
                keyRegExp += c.toUpper();
                keyRegExp += QLatin1Char(']');
            } else {
                keyRegExp += QRegExp::escape(c);
            }
            first = false;
        }
        const QRegExp regExp(keyRegExp, Qt::CaseSensitive);

        foreach (TextEditor::CompletionItem item, m_completions) {
            if (regExp.indexIn(item.text) == 0) {
                item.relevance = (key.length() > 0 &&
                                    item.text.startsWith(key, Qt::CaseInsensitive)) ? 1 : 0;
                (*completions) << item;
            }
        }
    }
}

void QmlCodeCompletion::complete(const TextEditor::CompletionItem &item)
{
    QString toInsert = item.text;

    if (QmlJSTextEditor *edit = qobject_cast<QmlJSTextEditor *>(m_editor->widget())) {
        if (item.data.isValid()) {
            QTextCursor tc = edit->textCursor();
            tc.beginEditBlock();
            tc.setPosition(m_startPosition);
            tc.setPosition(m_editor->position(), QTextCursor::KeepAnchor);
            tc.removeSelectedText();

            toInsert = item.data.toString();
            edit->insertCodeSnippet(toInsert);
            tc.endEditBlock();
            return;
        }
    }

    const int length = m_editor->position() - m_startPosition;
    m_editor->setCurPos(m_startPosition);
    m_editor->replace(length, toInsert);
}

bool QmlCodeCompletion::partiallyComplete(const QList<TextEditor::CompletionItem> &completionItems)
{
    if (completionItems.count() == 1) {
        const TextEditor::CompletionItem item = completionItems.first();

        if (!item.data.canConvert<QString>()) {
            complete(item);
            return true;
        }
    }

    // Compute common prefix
    QString firstKey = completionItems.first().text;
    QString lastKey = completionItems.last().text;
    const int length = qMin(firstKey.length(), lastKey.length());
    firstKey.truncate(length);
    lastKey.truncate(length);

    while (firstKey != lastKey) {
        firstKey.chop(1);
        lastKey.chop(1);
    }

    int typedLength = m_editor->position() - m_startPosition;
    if (!firstKey.isEmpty() && firstKey.length() > typedLength) {
        m_editor->setCurPos(m_startPosition);
        m_editor->replace(typedLength, firstKey);
    }

    return false;
}

void QmlCodeCompletion::cleanup()
{
    m_editor = 0;
    m_startPosition = 0;
    m_completions.clear();
}

void QmlCodeCompletion::updateSnippets()
{
    QString qmlsnippets = Core::ICore::instance()->resourcePath() + QLatin1String("/snippets/qml.xml");
    if (!QFile::exists(qmlsnippets))
        return;

    QDateTime lastModified = QFileInfo(qmlsnippets).lastModified();
    if (!m_snippetFileLastModified.isNull() &&  lastModified == m_snippetFileLastModified)
        return;

    const QIcon icon = iconForColor(Qt::red);

    m_snippetFileLastModified = lastModified;
    QFile file(qmlsnippets);
    file.open(QIODevice::ReadOnly);
    QXmlStreamReader xml(&file);
    while (!xml.atEnd() && xml.readNextStartElement()) {
        if (xml.name() == QLatin1String("snippets")) {
            while (xml.readNextStartElement()) {
                if (xml.name() == QLatin1String("snippet")) {
                    TextEditor::CompletionItem item(this);
                    QString title, data;
                    QString description = xml.attributes().value("description").toString();

                    while (!xml.atEnd()) {
                        xml.readNext();
                        if (xml.isEndElement()) {
                            int i = 0;
                            while (i < data.size() && data.at(i).isLetterOrNumber())
                                ++i;
                            title = data.left(i);
                            item.text = title;
                            if (!description.isEmpty()) {
                                item.text +=  QLatin1Char(' ');
                                item.text += description;
                            }
                            item.data = QVariant::fromValue(data);
                            item.icon = icon;
                            m_snippets.append(item);
                            break;

                        }

                        if (xml.isCharacters())
                            data += xml.text();
                        else if (xml.isStartElement()) {
                            if (xml.name() != QLatin1String("tab"))
                                xml.raiseError(QLatin1String("invalid snippets file"));
                            else {
                                data += QChar::ObjectReplacementCharacter;
                                data += xml.readElementText();
                                data += QChar::ObjectReplacementCharacter;
                            }
                        }

                    }
                }
            }
        }
    }
    if (xml.hasError())
        qWarning() << qmlsnippets << xml.errorString();
    file.close();
}

QList<TextEditor::CompletionItem> QmlCodeCompletion::getCompletions()
{
    QList<TextEditor::CompletionItem> completionItems;

    completions(&completionItems);

    qStableSort(completionItems.begin(), completionItems.end(), completionItemLessThan);

    // Remove duplicates
    QString lastKey;
    QList<TextEditor::CompletionItem> uniquelist;

    foreach (const TextEditor::CompletionItem &item, completionItems) {
        if (item.text != lastKey) {
            uniquelist.append(item);
            lastKey = item.text;
        } else {
            if (item.data.canConvert<QString>())
                uniquelist.append(item);
        }
    }

    return uniquelist;
}
<|endoftext|>