root/libcore/MovieClip.h

/* [<][>][^][v][top][bottom][index][help] */

INCLUDED FROM


// MovieClip.h:  Stateful live Sprite instance, for Gnash.
// 
//   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010,
//   2011 Free Software Foundation, Inc
// 
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

// Stateful live Sprite instance

#ifndef GNASH_MOVIECLIP_H
#define GNASH_MOVIECLIP_H

#ifdef HAVE_CONFIG_H
#include "gnashconfig.h" 
#endif

#include <vector>
#include <list>
#include <map>
#include <string>
#include <boost/ptr_container/ptr_list.hpp>
#include <boost/intrusive_ptr.hpp>

#include "ControlTag.h"
#include "movie_definition.h" // for inlines
#include "DisplayList.h" // DisplayList 
#include "DisplayObjectContainer.h"
#include "as_environment.h" // for composition
#include "DynamicShape.h" // for composition
#include "snappingrange.h"
#include "dsodefs.h" // for DSOEXPORT

// Forward declarations
namespace gnash {
    class Movie;
    class swf_event;
    class drag_state;
    class LoadVariablesThread;
    class GradientRecord;
    class TextField;
    class BitmapData_as;
    class CachedBitmap;
    namespace SWF {
        class PlaceObject2Tag;
    }
}

namespace gnash {

/// A MovieClip is a container for DisplayObjects.
//
/// TODO: This class should inherit from Sprite
//
/// In AS3 is it distinguished from a Sprite by having a timeline, i.e.
/// more than one frame. In AS2, there is no Sprite class.
//
/// There are basically two types of MovieClip: dynamic and non-dynamic.
/// Dynamic clips are created using createEmptyMovieClip() or 
/// duplicateMovieClip(). Non-dynamic MovieClips are parsed from a SWF file.
/// The isDynamic() member function is the only way to tell the difference
/// (see following paragraph).
//
/// The presence of a definition (the _def member) reveals whether the
/// MovieClip was constructed with an immutable definition or not. MovieClips
/// created using createEmptyMovieClip() have no definition. MovieClips
/// constructed using duplicateMovieClip() have the same definition as the
/// duplicated clip. They are "dynamic", but may have a definition!
//
/// A MovieClip always has an _swf member. This is the top-level SWF 
/// (Movie) containing either the definition or the code from
/// which the MovieClip was created. The _url member and SWF version are
/// dependent on the _swf. Exports are also sought in this Movie.
class MovieClip : public DisplayObjectContainer 
{
public:

    typedef std::vector<TextField*> TextFields;

    /// A container for textfields, indexed by their variable name
    typedef std::map<ObjectURI, TextFields, ObjectURI::LessThan>
        TextFieldIndex;

    typedef std::map<std::string, std::string> MovieVariables;

    typedef std::list<const action_buffer*> ActionList;

    typedef movie_definition::PlayList PlayList;

    enum PlayState
    {
        PLAYSTATE_PLAY,
        PLAYSTATE_STOP
    };

    /// Construct a MovieClip instance
    //
    /// @param def
    ///     Pointer to the movie_definition this object is an
    ///     instance of (may be a top-level movie or a sprite).
    ///     This may be 0 if there is no immutable definition.
    ///
    /// @param root
    /// The "relative" _swf of this sprite, which is the 
    /// instance of top-level sprite defined by the same
    /// SWF that also contained *this* sprite definition.
    /// Note that this can be *different* from the top-level
    /// movie accessible through the VM, in case this sprite
    /// was defined in an externally loaded movie.
    ///
    /// @param parent
    ///     Parent of the created instance in the display list.
    ///     May be 0 for top-level movies (_level#).
    MovieClip(as_object* object, const movie_definition* def,
            Movie* root, DisplayObject* parent);

    virtual ~MovieClip();

    // Return the originating SWF
    virtual Movie* get_root() const;

    virtual bool trackAsMenu();

    /// Return the _root ActionScript property of this sprite.
    //
    /// Relative or absolute is determined by the _lockroot property,
    /// see getLockRoot and setLockRoot. May return this.
    virtual MovieClip* getAsRoot();

    /// Get the composite bounds of all component drawing elements
    virtual SWFRect getBounds() const;

    // See dox in DisplayObject.h
    virtual bool pointInShape(boost::int32_t x, boost::int32_t y) const;

    // See dox in DisplayObject.h
    virtual bool pointInVisibleShape(boost::int32_t x, boost::int32_t y) const;

    /// return true if the given point is located in a(this) hitable sprite.
    ///
    /// all sprites except mouse-insensitive dynamic masks are hitable.
    /// _visible property is ignored for hitable DisplayObjects.
    virtual bool pointInHitableShape(boost::int32_t x, boost::int32_t y) const;

    /// Return 0-based index to current frame
    size_t get_current_frame() const
    {
        return _currentFrame;
    }

    size_t get_frame_count() const
    {
        return _def ? _def->get_frame_count() : 1;
    }

    /// Return number of completely loaded frames of this sprite/movie
    //
    /// Note: the number is also the last frame accessible (frames
    /// numberes are 1-based)
    ///
    size_t get_loaded_frames() const
    {
        return _def ? _def->get_loading_frame() : 1;
    }

    /// Return total number of bytes in the movie
    /// (not sprite!)
    size_t get_bytes_total() const
    {
        return isDynamic() ? 0 : _def->get_bytes_total();
    }

    /// Return number of loaded bytes in the movie
    /// (not sprite!)
    size_t get_bytes_loaded() const
    {
        return isDynamic() ? 0 : _def->get_bytes_loaded();
    }

    const SWFRect& get_frame_size() const
    {
        static const SWFRect r;
        return _def ? _def->get_frame_size() : r;
    }

    /// Stop or play the sprite.
    //
    /// If stopped, any stream sound associated with this sprite
    /// will also be stopped.
    ///
    DSOEXPORT void setPlayState(PlayState s);

    PlayState getPlayState() const { return _playState; }

    // delegates to movie_root (possibly wrong)
    void set_background_color(const rgba& color);

    /// Return true if we have any mouse event handlers.
    //
    /// NOTE: this function currently does not consider
    ///       general mouse event handlers MOUSE_MOVE, MOUSE
    virtual bool mouseEnabled() const;

    /// \brief
    /// Return the topmost entity that the given point
    /// covers that can receive mouse events.  NULL if
    /// none.  Coords are in parent's frame.
    virtual InteractiveObject* topmostMouseEntity(boost::int32_t x,
            boost::int32_t y);

    // see dox in DisplayObject.h
    const DisplayObject* findDropTarget(boost::int32_t x, boost::int32_t y,
            DisplayObject* dragging) const;

    void setDropTarget(const std::string& tgt) {
        _droptarget = tgt;
    }

    const std::string& getDropTarget() const {
        return _droptarget;
    }
    
    /// Advance to the next frame of the MovieClip.
    //
    /// Actions will be executed or pushed to the queue as necessary.
    virtual void advance();

    /// Set the sprite state at the specified frame number.
    //
    /// 0-based frame numbers!! 
    ///(in contrast to ActionScript and Flash MX)
    ///
    DSOEXPORT void goto_frame(size_t target_frame_number);

    /// Parse frame spec and return a 0-based frame number.
    //
    /// If frame spec cannot be converted to !NAN and !Infinity number
    /// it will be converted to a string and considered a
    /// frame label (returns false if referring to an
    /// unknwown label).
    ///
    /// @param frame_spec
    /// The frame specification.
    ///
    /// @param frameno
    /// The evaluated frame number (0-based)
    ///
    /// @return
    /// True if the frame_spec could be resolved to a frame number.
    /// False if the frame_spec was invalid.
    bool get_frame_number(const as_value& frame_spec, size_t& frameno) const;

    /// Look up the labeled frame, and jump to it.
    bool goto_labeled_frame(const std::string& label);
        
    /// Render this MovieClip.
    virtual void display(Renderer& renderer, const Transform& xform);
    
    /// Draw this MovieClip
    //
    /// This is effectively the same as display(), but uses only the passed
    /// transform.
    void draw(Renderer& renderer, const Transform& xform);

    void omit_display();

    /// Swap depth of the given DisplayObjects in the DisplayList
    //
    /// See DisplayList::swapDepths for more info
    void swapDepths(DisplayObject* ch1, int newdepth)
    {
        _displayList.swapDepths(ch1, newdepth);
    }

    /// Return the DisplayObject at given depth in our DisplayList.
    //
    /// @return NULL if the specified depth is available (no chars there)
    DisplayObject* getDisplayObjectAtDepth(int depth);

    /// Attach a DisplayObject at the specified depth.
    DisplayObject* addDisplayListObject(DisplayObject* obj, int depth);

    /// Place a DisplayObject or mask to the DisplayList.
    //
    /// This method instantiates the given DisplayObject definition
    /// and places it on the stage at the given depth.
    ///
    /// If the specified depth is already occupied, it results a no-ops.
    /// Otherwise, a new DisplayObject will be created and onload handler
    /// will be triggerred.
    ///
    /// @param tag
    ///     A swf defined placement tag (PlaceObject, or PlaceObject2,
    ///     or PlaceObject3).
    ///     No ownership transfer, the tag is still owned by the
    ///     movie_definition class.
    ///
    /// @param dlist
    ///     The display list to add the DisplayObject to.
    ///
    /// @return
    ///     A pointer to the DisplayObject being added or NULL
    DisplayObject* add_display_object(const SWF::PlaceObject2Tag* tag,
            DisplayList& dlist);

    /// Proxy of DisplayList::moveDisplayObject()
    void move_display_object(const SWF::PlaceObject2Tag* tag,
            DisplayList& dlist);

    /// Proxy of DisplayList::replaceDisplayObject()
    void replace_display_object(const SWF::PlaceObject2Tag* tag,
            DisplayList& dlist);

    /// Proxy of DisplayList::removeDisplayObject()
    void remove_display_object(const SWF::PlaceObject2Tag* tag,
            DisplayList& dlist);

    /// \brief
    /// Remove the object at the specified depth.
    //
    /// NOTE: 
    /// (1)the id parameter is currently unused, but 
    /// required to avoid breaking of inheritance from movie.h.
    /// (2)the id might be used for specifying a DisplayObject
    /// in the depth(think about multiple DisplayObjects within the same
    /// depth, not tested and a rare case)
    void remove_display_object(int depth, int /*id*/);

    void unloadMovie();

    /// Attach the given DisplayObject instance to current display list
    //
    /// @param newch    The DisplayObject instance to attach.
    /// @param depth    The depth to assign to the instance.
    void attachCharacter(DisplayObject& newch, int depth, as_object* initObject);

    /// Handle placement event
    //
    /// This callback will (not known to be a problem):
    ///
    /// (1) Register ourselves with the global instance list
    /// (2) Take note of our original target path
    /// (3) Register as listener of core broadcasters
    /// (4) Execute tags of frame 0
    ///
    /// The callback will also (known to be bogus):
    //
    /// (1) Construct this instance as an ActionScript object.
    ///     See constructAsScriptObject() method, including constructing
    ///     registered class and adding properties.
    virtual void construct(as_object* initObj = 0);

    /// Mark this sprite as destroyed
    //
    /// This is an override of DisplayObject::destroy()
    ///
    /// A sprite should be destroyed when is removed from the display
    /// list and is not more needed for names (target) resolutions.
    /// Sprites are needed for names resolution whenever themselves
    /// or a contained object has an onUnload event handler defined, 
    /// in which case we want the event handler to find the 'this'
    /// variable w/out attempting to rebind it.
    ///
    /// When a sprite is destroyed, all its children are also destroyed.
    /// 
    /// Note: this function will release most memory associated with
    /// the sprite as no members or drawable should be needed anymore.
    void destroy();
        
    /// Add the given action buffer to the list of action
    /// buffers to be processed at the end of the next
    /// frame advance.
    void add_action_buffer(const action_buffer* a)
    {
        if (!_callingFrameActions) queueAction(*a);
        else execute_action(*a);
    }

    
    /// \brief
    /// Execute the given init action buffer, if not done yet
    /// for the target DisplayObject id.
    //
    /// The action will normally be pushed on queue, but will
    /// be executed immediately if we are executing actions
    /// resulting from a callFame instead.
    ///
    /// @param a
    /// The action buffer to execute
    ///
    /// @param cid
    /// The referenced DisplayObject id
    void execute_init_action_buffer(const action_buffer& a, int cid);

    /// Execute a single action buffer (DOACTION block)
    void execute_action(const action_buffer& ab);

    MovieClip* to_movie () { return this; }

    /// The various methods for sending data in requests.
    //
    /// Used in loadMovie, getURL, loadVariables etc.
    enum VariablesMethod
    {
        METHOD_NONE = 0,
        METHOD_GET,
        METHOD_POST
    };

    // See dox in DisplayObject.h
    virtual void getLoadedMovie(Movie* newMovie);

    /// \brief
    /// Load url-encoded variables from the given url, optionally
    /// sending variables from this timeline too.
    //
    /// A LoadVariablesThread will be started to load and parse variables
    /// and added to the _loadVariableRequests. Then, at every ::advance_sprite
    /// any completed threads will be processed
    /// (see processCompletedLoadVariableRequests)
    ///
    /// NOTE: the given url will be security-checked
    ///
    /// @param urlstr: The url to load variables from.
    ///
    /// @param sendVarsMethod: The VariablesMethod to use. If METHOD_NONE,
    ///                        no data will be sent.
    void loadVariables(const std::string& urlstr,
            VariablesMethod sendVarsMethod);

    /// Get TextField variables
    //
    /// TODO: this is unlikely to be the best way of doing it, and it would
    /// simplify things if this function could be dropped.
    bool getTextFieldVariables(const ObjectURI& uri, as_value& val);

    // Set TextField variables
    //
    /// TODO: this is also unlikely to be the best way to do it.
    bool setTextFieldVariables(const ObjectURI& uri, const as_value& val);

    /// Search for a named object on the DisplayList
    //
    /// These are properties, but not attached as genuine members to the
    /// MovieClip object. They take priority over DisplayObject magic
    /// properties and inherited properties, but not over own properties.
    //
    /// @param name     Object identifier. This function handles
    ///                 case-sensitivity.
    /// @return         The object if found, otherwise 0.
    DisplayObject* getDisplayListObject(const ObjectURI& uri);

    /// Overridden to look in DisplayList for a match
    as_object* pathElement(const ObjectURI& uri);

    /// Execute the actions for the specified frame. 
    //
    /// The frame_spec could be an integer or a string.
    virtual void call_frame_actions(const as_value& frame_spec);

    // delegates to movie_root 
    virtual void stop_drag();

    /// Duplicate this sprite in its timeline
    //
    /// Add the new DisplayObject at a the given depth to this sprite
    /// parent displaylist.
    ///
    /// NOTE: the call will fail for the root movie (no parent).
    /// NOTE2: any DisplayObject at the given target depth will be
    ///        replaced by the new DisplayObject
    /// NOTE3: event handlers will also be copied
    ///
    /// @param newname
    ///     Name for the copy
    ///
    /// @param newdepth
    ///     Depth for the copy
    ///
    /// @param init_object
    ///     If not null, will be used to copy properties over.
    MovieClip* duplicateMovieClip(const std::string& newname,
        int newdepth, as_object* init_object=NULL);
        
    /// Dispatch event handler(s), if any.
    virtual void notifyEvent(const event_id& id);

    // inherited from DisplayObject class, see dox in DisplayObject.h
    virtual as_environment& get_environment() {
        return _environment;
    }

    /// \brief
    /// Set a TextField variable to this timeline
    //
    /// A TextField variable is a variable that acts
    /// as a setter/getter for a TextField 'text' member.
    void set_textfield_variable(const ObjectURI& name, TextField* ch);

    void add_invalidated_bounds(InvalidatedRanges& ranges, bool force);
    
    const DisplayList& getDisplayList() const {
            return _displayList;
    }

    /// Return the next highest available depth
    //
    /// Placing an object at the depth returned by
    /// this function should result in a DisplayObject
    /// that is displayd above all others
    int getNextHighestDepth() const {
        return _displayList.getNextHighestDepth();
    }

    /// Set the currently playing m_sound_stream_id
    // 
    // TODO: rename to setStreamingSoundId
    void setStreamSoundId(int id);

    /// Remove this sprite from the stage.
    //
    /// This function is intended to be called by 
    /// effect of a removeMovieClip() ActionScript call
    /// and implements the checks required for this specific
    /// case.
    ///
    /// Callers are:
    /// - The ActionRemoveClip tag handler.
    /// - The global removeMovieClip(target) function.
    /// - The MovieClip.removeMovieClip() method.
    ///
    /// The removal will not occur if the depth of this
    /// DisplayObjects is not in the "dynamic" range [0..1048575]
    /// as described at the following URL:
    /// 
    /// http://www.senocular.com/flash/tutorials/depths/?page=2
    ///
    /// A testcases for this behaviour can be found in 
    ///
    /// testsuite/misc-ming.all/displaylist_depths_test.swf
    void removeMovieClip();

    /// Direct access to the Graphics object for drawing.
    DynamicShape& graphics() {
        set_invalidated();
        return _drawable;
    }

    /// Set focus to this MovieClip
    //
    /// @return true if this MovieClip can receive focus.
    virtual bool handleFocus();

    /// @} Drawing API

    /// Set all variables in the given map with their corresponding values
    DSOEXPORT void setVariables(const MovieVariables& vars);

    /// Enumerate child DisplayObjects
    //
    /// See DisplayObject::enumerateNonProperties for more info.
    virtual void visitNonProperties(KeyVisitor& v) const;

    /// Delete DisplayObjects removed from the stage
    /// from the display lists
    void cleanupDisplayList();

    /// Queue the given action buffer
    //
    /// The action will be pushed on the current
    /// global list (see movie_root).
    ///
    void queueAction(const action_buffer& buf);

    /// Construct this instance as an ActionScript object
    //
    /// This method invokes the constructor associated with our
    /// definition, either MovieClip or any user-speficied one
    /// (see sprite_definition::registerClass). 
    /// It will also invoke the onClipConstruct and onConstruct handlers.
    void constructAsScriptObject();

    /// Return true if getAsRoot() should return the *relative* root,
    /// false otherwise.
    bool getLockRoot() const { return _lockroot; }

    /// Set whether getAsRoot() should return the *relative* root,
    /// false otherwise. True for relative root.
    void setLockRoot(bool lr) { _lockroot=lr; }

    /// Return the version of the SWF this MovieClip was parsed from.
    virtual int getDefinitionVersion() const;

protected:

    /// Unload all contents in the displaylist and this instance
    //
    /// Return true if there was an unloadHandler.
    virtual bool unloadChildren();

    /// Mark sprite-specific reachable resources.
    //
    /// sprite-specific reachable resources are:
    ///     - DisplayList items (current, backup and frame0 ones)
    /// - Canvas for dynamic drawing (_drawable)
    /// - sprite environment
    /// - definition the sprite has been instantiated from
    /// - Textfields having an associated variable registered in this instance.
    /// - Relative root of this instance (_swf)
    ///
    virtual void markOwnResources() const;
    
    // Used by BitmapMovie.
    void placeDisplayObject(DisplayObject* ch, int depth) {       
        _displayList.placeDisplayObject(ch, depth);  
    }

private:

    /// Process any completed loadVariables request
    void processCompletedLoadVariableRequests();

    /// Process a completed loadVariables request
    void processCompletedLoadVariableRequest(LoadVariablesThread& request);

    
    /// Execute the tags associated with the specified frame.
    //
    /// @param frame
    ///     Frame number. 0-based
    ///
    /// @param dlist
    ///     The display list to have control tags act upon.
    ///
    /// @param typeflags
    ///     Which kind of control tags we want to execute. 
    void executeFrameTags(size_t frame, DisplayList& dlist,
            int typeflags = SWF::ControlTag::TAG_DLIST |
                            SWF::ControlTag::TAG_ACTION);

    void stopStreamSound();

    /// Return value of the 'enabled' property cast to a boolean value.
    //
    /// This is true if not found (undefined to bool evaluates to false).
    //
    /// When a MovieClip is "disabled", its handlers of button-like events 
    /// are disabled, and automatic tab ordering won't include it.
    bool isEnabled() const;

    /// Check whether a point hits our drawable shape.
    //
    /// This is possible because the drawable does not have its own
    /// transform, so we can use our own. The points are expressed in
    /// world space.
    bool hitTestDrawable(boost::int32_t x, boost::int32_t y) const;

    /// Advance to a previous frame.
    //
    /// This function will basically restore the DisplayList as it supposedly
    /// was *before* executing tags in target frame and then execute target
    /// frame tags (both DLIST and ACTION ones).
    ///
    /// In practice, it will:
    ///
    /// - Remove from current DisplayList:
    /// - Timeline instances constructed after target frame 
    /// - Timeline instances constructed before or at the target frame but no
    ///   more at the original depth
    /// - Dynamic instances found in the static depth zone
    /// - Execute all displaylist tags from first to one-before target frame,
    ///   appropriately setting _currentFrame as it goes, finally execute
    ///   both displaylist and action
    ///   tags for target frame.
    ///
    /// Callers of this methods are:
    /// - goto_frame (for jump-backs)
    /// - advance_sprite (for loop-back)
    ///
    /// See:
    //  http://www.gnashdev.org/wiki/index.php/TimelineControl
    ///              #Timeline_instances
    ///
    /// @param targetFrame
    /// The target frame for which we're willing to restore the static
    /// DisplayList.
    /// 0-based.
    //
    /// POSTCONDITIONS:
    ///
    /// - _currentFrame == targetFrame
    ///
    /// TODO: consider using this same function for jump-forward too,
    ///       with some modifications...
    ///
    void restoreDisplayList(size_t targetFrame);

    /// Increment _currentFrame, and take care of looping.
    void increment_frame_and_check_for_loop();

    /// Unregister textfield variables bound to unloaded TextFields
    void cleanup_textfield_variables();

    /// This is either sprite_definition (for sprites defined by
    /// DefineSprite tag) or movie_def_impl (for the top-level movie).
    const boost::intrusive_ptr<const movie_definition> _def;

    /// List of loadVariables requests
    typedef boost::ptr_list<LoadVariablesThread> LoadVariablesThreads;
    
    /// List of active loadVariable requests 
    //
    /// At ::advance_sprite time, all completed requests will
    /// be processed (variables imported in this timeline scope)
    /// and removed from the list.
    LoadVariablesThreads _loadVariableRequests;

    /// The SWF that this MovieClip belongs to.
    Movie* _swf;

    /// The canvas for dynamic drawing
    DynamicShape _drawable;

    PlayState _playState;

    /// This timeline's variable scope
    as_environment _environment;

    /// We'll only allocate Textfield variables map if
    /// we need them (ie: anyone calls set_textfield_variable)
    ///
    std::auto_ptr<TextFieldIndex> _text_variables;

    std::string _droptarget;

    // 0-based index to current frame
    size_t _currentFrame;
    
    /// soundid for current playing stream. If no stream set to -1
    int m_sound_stream_id;

    // true if this sprite reached the last frame and restarted
    bool _hasLooped;

    // true is we're calling frame actions
    bool _callingFrameActions;

    bool _lockroot;
};

} // end of namespace gnash

#endif // GNASH_SPRITE_INSTANCE_H

/* [<][>][^][v][top][bottom][index][help] */