// MovieClip.h: Stateful live Sprite instance, for Gnash. // // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 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" // GNASH_USE_GC, USE_SWFTREE #endif #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 #include #include #include #include #include // Forward declarations namespace gnash { class Movie; class swf_event; class drag_state; class LoadVariablesThread; class gradient_record; class TextField; class BitmapData_as; class BitmapInfo; class GnashImage; 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::map MovieVariables; typedef std::list 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); bool has_looped() const { return _hasLooped; } /// 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; } 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; /// MovieClip instances need to handle cxform specially // /// This is to suppor the Color asobject class cxform get_world_cxform() const; /// Update user-defined color transform // /// This should only be used by the Color AS class void set_user_cxform(const cxform& cx) { set_invalidated(); _userCxform = cx; } /// Return the user-defined color transform // /// This should only be used by the Color AS class cxform get_user_cxform() const { return _userCxform; } /// Look up the labeled frame, and jump to it. bool goto_labeled_frame(const std::string& label); /// Display (render?) this Sprite/MovieClip, unless invisible void display(Renderer& renderer); 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. /// /// @return true on success, false on failure /// FIXME: currently never returns false ! bool 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. virtual void stagePlacementCallback(); /// Construct registered class and add properties. virtual void construct(as_object* initObj = 0); /// Unload all contents in the displaylist and this instance /// See DisplayObject::unload for more info bool unload(); /// 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 The name of the object. This function handles /// case-sensitivity. /// @return The object if found, otherwise 0. DisplayObject* getDisplayListObject(string_table::key name); /// Overridden to look in DisplayList for a match as_object* pathElement(string_table::key key); /// 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 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 std::string& 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(); /// Render this MovieClip to a GnashImage using the passed transform // /// @return The GnashImage with the MovieClip drawn onto it. virtual std::auto_ptr drawToBitmap( const SWFMatrix& mat = SWFMatrix(), const cxform& cx = cxform(), DisplayObject::BlendMode bm = DisplayObject::BLENDMODE_NORMAL, const SWFRect& clipRect = SWFRect(), bool smooth = false); /// @name Drawing API /// @{ void lineStyle(boost::uint16_t thickness, const rgba& color, bool vScale=true, bool hScale=true, bool pixelHinting=false, bool noClose=false, CapStyle startCapStyle=CAP_ROUND, CapStyle endCapStyle=CAP_ROUND, JoinStyle joinStyle=JOIN_ROUND, float miterLimitFactor=1.0f) { _drawable.lineStyle(thickness, color, vScale, hScale, pixelHinting, noClose, startCapStyle, endCapStyle, joinStyle, miterLimitFactor); } void resetLineStyle() { _drawable.resetLineStyle(); } void beginFill(const rgba& color) { _drawable.beginFill(color); } void beginLinearGradientFill(const std::vector& grad, const SWFMatrix& mat) { _drawable.beginLinearGradientFill(grad, mat); } void beginRadialGradientFill(const std::vector& grad, const SWFMatrix& mat) { _drawable.beginRadialGradientFill(grad, mat); } void endFill() { _drawable.endFill(); } void moveTo(boost::int32_t x, boost::int32_t y) { _drawable.moveTo(x, y); } void lineTo(boost::int32_t x, boost::int32_t y) { set_invalidated(); _drawable.lineTo(x, y, getDefinitionVersion()); } void curveTo(boost::int32_t cx, boost::int32_t cy, boost::int32_t ax, boost::int32_t ay) { set_invalidated(); _drawable.curveTo(cx, cy, ax, ay, getDefinitionVersion()); } void clear() { set_invalidated(); _drawable.clear(); } /// 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 as_object::enumerateNonProperties(as_environment&) for more info. /// virtual void enumerateNonProperties(as_environment&) 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: /// 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: typedef std::vector TextFields; /// A container for textfields, indexed by their variable name typedef std::map TextFieldIndex; /// 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(); /// Register this sprite as a listener of core broadcasters // /// This is currently only used for key events broadcaster /// and it's supposed to be called only once (or should we /// also call it whenever onKey* user-defined function is defined ? /// void registerAsListener(); /// 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); /// Queue actions in the action list // /// The list of action will be pushed on the current /// global list (see movie_root). /// void queueActions(ActionList& action_list); /// Execute the actions in the action list // /// The list of action will be consumed starting from the first /// element. When the function returns the list should be empty. /// void execute_actions(ActionList& action_list); /// Increment _currentFrame, and take care of looping. void increment_frame_and_check_for_loop(); /// \brief /// Returns a vector of TextField associated with the given variable name, /// or NULL if no such variable name is known. // /// A TextField variable is a variable that acts /// as a setter/getter for a TextField 'text' member. /// /// Search is case-sensitive. /// /// @todo find out wheter we should be case sensitive or not /// /// @return a pointer inside a vector, will be invalidated by modifications /// of the vector (set_textfield_variable) /// TextFields* get_textfield_variable(const std::string& name); /// 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 _def; /// List of loadVariables requests typedef std::list 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; // 0-based index to current frame size_t _currentFrame; // true if this sprite reached the last frame and restarted bool _hasLooped; // true is we're calling frame actions bool _callingFrameActions; /// 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 _text_variables; /// soundid for current playing stream. If no stream set to -1 int m_sound_stream_id; cxform _userCxform; std::string _droptarget; bool _lockroot; }; } // end of namespace gnash #endif // GNASH_SPRITE_INSTANCE_H