1 // MovieClip.cpp: Stateful live Sprite instance, for Gnash.
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22 #include "gnashconfig.h" // USE_SWFTREE
25 #include "MovieClip.h"
29 #include <algorithm> // for std::swap
30 #include <boost/algorithm/string/case_conv.hpp>
31 #include <boost/bind.hpp>
34 #include "movie_definition.h"
36 #include "as_function.h"
37 #include "TextField.h"
38 #include "ControlTag.h"
40 #include "movie_root.h"
42 #include "swf_event.h"
43 #include "sprite_definition.h"
44 #include "ActionExec.h"
45 #include "smart_ptr.h" // GNASH_USE_GC
47 #include "Range2d.h" // for getBounds
48 #include "GnashException.h"
49 #include "GnashNumeric.h"
50 #include "GnashAlgorithm.h"
52 #include "sound_handler.h"
53 #include "StreamProvider.h"
54 #include "LoadVariablesThread.h"
55 #include "ExecutableCode.h" // for inheritance of ConstructEvent
56 #include "DynamicShape.h" // for composition
57 #include "namedStrings.h"
58 #include "LineStyle.h"
59 #include "PlaceObject2Tag.h"
60 #include "flash/geom/Matrix_as.h"
61 #include "GnashNumeric.h"
62 #include "InteractiveObject.h"
63 #include "DisplayObjectContainer.h"
64 #include "Global_as.h"
65 #include "RunResources.h"
66 #include "Transform.h"
70 //#define GNASH_DEBUG 1
71 //#define GNASH_DEBUG_TIMELINE 1
72 //#define GNASH_DEBUG_REPLACE 1
73 //#define DEBUG_DYNTEXT_VARIABLES 1
74 //#define GNASH_DEBUG_HITTEST 1
75 //#define DEBUG_LOAD_VARIABLES 1
77 // Defining the following macro you'll get a DEBUG lien
78 // for each call to the drawing API, in a format which is
79 // easily re-compilable to obtain a smaller testcase
80 //#define DEBUG_DRAWING_API 1
82 // Define this to make mouse entity finding verbose
83 // This includes topmostMouseEntity and findDropTarget
85 //#define DEBUG_MOUSE_ENTITY_FINDING 1
87 // Anonymous namespace for module-private definitions
90 /// ConstructEvent, used for queuing construction
92 /// Its execution will call constructAsScriptObject()
93 /// on the target movieclip
95 class ConstructEvent
: public ExecutableCode
{
99 explicit ConstructEvent(MovieClip
* nTarget
)
101 ExecutableCode(nTarget
)
104 virtual void execute()
106 static_cast<MovieClip
*>(target())->constructAsScriptObject();
111 /// Find a DisplayObject hit by the given coordinates.
113 /// This class takes care about taking masks layers into
114 /// account, but nested masks aren't properly tested yet.
116 class MouseEntityFinder
121 /// Query point in world coordinate space
124 /// Query point in parent coordinate space
126 MouseEntityFinder(point wp
, point pp
)
128 _highestHiddenDepth(std::numeric_limits
<int>::min()),
136 void operator() (DisplayObject
* ch
)
139 if ( ch
->get_depth() <= _highestHiddenDepth
)
141 if ( ch
->isMaskLayer() )
143 log_debug(_("CHECKME: nested mask in MouseEntityFinder. "
144 "This mask is %s at depth %d outer mask masked "
146 ch
->getTarget(), ch
->get_depth(),
147 _highestHiddenDepth
);
148 // Hiding mask still in effect...
153 if ( ch
->isMaskLayer() )
155 if ( ! ch
->pointInShape(_wp
.x
, _wp
.y
) )
157 #ifdef DEBUG_MOUSE_ENTITY_FINDING
158 log_debug(_("Character %s at depth %d is a mask not hitting "
159 "the query point %g,%g and masking up to "
160 "depth %d"), ch
->getTarget(), ch
->get_depth(),
161 _wp
.x
, _wp
.y
, ch
->get_clip_depth());
163 _highestHiddenDepth
= ch
->get_clip_depth();
167 #ifdef DEBUG_MOUSE_ENTITY_FINDING
168 log_debug(_("Character %s at depth %d is a mask hitting the "
169 "query point %g,%g"),
170 ch
->getTarget(), ch
->get_depth(), _wp
.x
, _wp
.y
);
175 if (! ch
->visible()) return;
177 _candidates
.push_back(ch
);
180 void checkCandidates()
182 if (_checked
) return;
183 for (Candidates::reverse_iterator i
=_candidates
.rbegin(),
184 e
=_candidates
.rend(); i
!=e
; ++i
) {
185 DisplayObject
* ch
= *i
;
186 InteractiveObject
* te
= ch
->topmostMouseEntity(_pp
.x
, _pp
.y
);
195 InteractiveObject
* getEntity()
198 #ifdef DEBUG_MOUSE_ENTITY_FINDING
201 log_debug(_("MouseEntityFinder found DisplayObject %s (depth %d) "
202 "hitting point %g,%g"),
203 _m
->getTarget(), _m
->get_depth(), _wp
.x
, _wp
.y
);
205 #endif // DEBUG_MOUSE_ENTITY_FINDING
211 /// Highest depth hidden by a mask
213 /// This will be -1 initially, and set
214 /// the the depth of a mask when the mask
215 /// doesn't contain the query point, while
216 /// scanning a DisplayList bottom-up
218 int _highestHiddenDepth
;
220 InteractiveObject
* _m
;
222 typedef std::vector
<DisplayObject
*> Candidates
;
223 Candidates _candidates
;
225 /// Query point in world coordinate space
228 /// Query point in parent coordinate space
235 /// Find the first DisplayObject whose shape contain the point
237 /// Point coordinates in world TWIPS
239 class ShapeContainerFinder
243 ShapeContainerFinder(boost::int32_t x
, boost::int32_t y
)
250 bool operator()(const DisplayObject
* ch
) {
251 if (ch
->pointInShape(_x
, _y
)) {
258 bool hitFound() const { return _found
; }
262 const boost::int32_t _x
;
263 const boost::int32_t _y
;
266 /// Find the first visible DisplayObject whose shape contain the point
268 /// Point coordinates in world TWIPS
270 class VisibleShapeContainerFinder
274 VisibleShapeContainerFinder(boost::int32_t x
, boost::int32_t y
)
281 bool operator()(const DisplayObject
* ch
)
283 if (ch
->pointInVisibleShape(_x
, _y
)) {
290 bool hitFound() const { return _found
; }
294 const boost::int32_t _x
;
295 const boost::int32_t _y
;
298 /// Find the first hitable DisplayObject whose shape contain the point
300 /// Point coordinates in world TWIPS
302 class HitableShapeContainerFinder
305 HitableShapeContainerFinder(boost::int32_t x
, boost::int32_t y
)
312 bool operator()(const DisplayObject
* ch
)
314 if (ch
->isDynamicMask()) return true;
315 if (ch
->pointInShape(_x
, _y
)) {
322 bool hitFound() const { return _found
; }
328 // x position in twips.
329 const boost::int32_t _x
;
331 // y position in twips.
332 const boost::int32_t _y
;
335 /// A DisplayList visitor used to compute its overall bounds.
340 explicit BoundsFinder(SWFRect
& b
) : _bounds(b
) {}
342 void operator()(DisplayObject
* ch
) {
343 // don't include bounds of unloaded DisplayObjects
344 if (ch
->unloaded()) return;
345 SWFRect chb
= ch
->getBounds();
346 SWFMatrix m
= getMatrix(*ch
);
347 _bounds
.expand_to_transformed_rect(m
, chb
);
354 struct ReachableMarker
356 void operator()(DisplayObject
*ch
) const {
361 /// Find the first visible DisplayObject whose shape contain the point
362 /// and is not the DisplayObject being dragged or any of its childs
364 /// Point coordinates in world TWIPS
366 class DropTargetFinder
{
368 /// Highest depth hidden by a mask
370 /// This will be -1 initially, and set
371 /// the the depth of a mask when the mask
372 /// doesn't contain the query point, while
373 /// scanning a DisplayList bottom-up
375 int _highestHiddenDepth
;
379 DisplayObject
* _dragging
;
380 mutable const DisplayObject
* _dropch
;
382 typedef std::vector
<const DisplayObject
*> Candidates
;
383 Candidates _candidates
;
385 mutable bool _checked
;
389 DropTargetFinder(boost::int32_t x
, boost::int32_t y
, DisplayObject
* dragging
)
391 _highestHiddenDepth(std::numeric_limits
<int>::min()),
400 void operator() (const DisplayObject
* ch
)
403 if ( ch
->get_depth() <= _highestHiddenDepth
)
405 if ( ch
->isMaskLayer() )
407 log_debug(_("CHECKME: nested mask in DropTargetFinder. "
408 "This mask is %s at depth %d outer mask masked "
410 ch
->getTarget(), ch
->get_depth(), _highestHiddenDepth
);
411 // Hiding mask still in effect...
416 if ( ch
->isMaskLayer() )
418 if ( ! ch
->visible() )
420 log_debug(_("FIXME: invisible mask in MouseEntityFinder."));
422 if ( ! ch
->pointInShape(_x
, _y
) )
424 #ifdef DEBUG_MOUSE_ENTITY_FINDING
425 log_debug(_("Character %s at depth %d is a mask not hitting "
426 "the query point %g,%g and masking up to depth %d"),
427 ch
->getTarget(), ch
->get_depth(), _x
, _y
,
428 ch
->get_clip_depth());
430 _highestHiddenDepth
= ch
->get_clip_depth();
434 #ifdef DEBUG_MOUSE_ENTITY_FINDING
435 log_debug(_("Character %s at depth %d is a mask "
436 "hitting the query point %g,%g"),
437 ch
->getTarget(), ch
->get_depth(), _x
, _y
);
444 _candidates
.push_back(ch
);
448 void checkCandidates() const
450 if ( _checked
) return;
451 for (Candidates::const_reverse_iterator i
=_candidates
.rbegin(),
452 e
=_candidates
.rend(); i
!=e
; ++i
)
454 const DisplayObject
* ch
= *i
;
455 const DisplayObject
* dropChar
= ch
->findDropTarget(_x
, _y
, _dragging
);
465 const DisplayObject
* getDropChar() const
472 class DisplayListVisitor
475 DisplayListVisitor(KeyVisitor
& v
) : _v(v
) {}
477 void operator()(DisplayObject
* ch
) const {
478 if (!isReferenceable(*ch
)) return;
479 // Don't enumerate unloaded DisplayObjects
480 if (ch
->unloaded()) return;
482 const ObjectURI
& name
= ch
->get_name();
483 // Don't enumerate unnamed DisplayObjects
484 if (name
.empty()) return;
486 // Referenceable DisplayObject always have an object.
487 assert(getObject(ch
));
494 } // anonymous namespace
497 MovieClip::MovieClip(as_object
* object
, const movie_definition
* def
,
498 Movie
* r
, DisplayObject
* parent
)
500 DisplayObjectContainer(object
, parent
),
503 _playState(PLAYSTATE_PLAY
),
504 _environment(getVM(*object
)),
506 m_sound_stream_id(-1),
508 _callingFrameActions(false),
514 _environment
.set_target(this);
518 MovieClip::~MovieClip()
521 deleteChecked(_loadVariableRequests
.begin(), _loadVariableRequests
.end());
525 MovieClip::getDefinitionVersion() const
527 return _swf
->version();
530 // Execute the actions in the action list, in the given
531 // environment. The list of action will be consumed
532 // starting from the first element. When the function returns
533 // the list should be empty.
535 MovieClip::execute_actions(MovieClip::ActionList
& action_list
)
537 // action_list may be changed due to actions (appended-to)
538 // This loop is probably quicker than using an iterator
539 // and a final call to .clear(), as repeated calls to
540 // .size() or .end() are no quicker (and probably slower)
541 // than pop_front(), which is constant time.
542 while (!action_list
.empty()) {
543 const action_buffer
* ab
= action_list
.front();
544 action_list
.pop_front();
551 MovieClip::getDisplayObjectAtDepth(int depth
)
553 return _displayList
.getDisplayObjectAtDepth(depth
);
556 /// This handles special properties of MovieClip.
558 /// The only genuine special properties are DisplayList members. These
559 /// are accessible as properties and are enumerated, but not ownProperties
562 /// The TextField variables should probably be handled in a more generic
565 MovieClip::getTextFieldVariables(const ObjectURI
& uri
, as_value
& val
)
567 const string_table::key name_key
= getName(uri
);
569 const std::string
& name
= getStringTable(*getObject(this)).value(name_key
);
571 // Try textfield variables
572 TextFields
* etc
= get_textfield_variable(name
);
575 for (TextFields::const_iterator i
=etc
->begin(), e
=etc
->end();
579 if ( tf
->getTextDefined() )
581 val
= tf
->get_text_value();
592 MovieClip::get_frame_number(const as_value
& frame_spec
, size_t& frameno
) const
595 // If there is no definition, this is a dynamically-created MovieClip
596 // and has no frames.
597 if (!_def
) return false;
599 std::string fspecStr
= frame_spec
.to_string();
601 as_value
str(fspecStr
);
603 const double num
= toNumber(str
, getVM(*getObject(this)));
605 if (!isFinite(num
) || int(num
) != num
|| num
== 0)
607 bool ret
= _def
->get_labeled_frame(fspecStr
, frameno
);
611 if (num
< 0) return false;
613 // all frame numbers > 0 are valid, but a valid frame number may still
614 // reference a non-exist frame(eg. frameno > total_frames).
615 frameno
= size_t(num
) - 1;
620 /// Execute the actions for the specified frame.
622 /// The frame_spec could be an integer or a string.
625 MovieClip::call_frame_actions(const as_value
& frame_spec
)
627 // If there is no definition, this is a dynamically-created MovieClip
628 // and has no frames.
632 if (!get_frame_number(frame_spec
, frame_number
)) {
634 IF_VERBOSE_ASCODING_ERRORS(
635 log_aserror(_("call_frame('%s') -- invalid frame"),
641 // Execute the ControlTag actions
642 // We set _callingFrameActions to true so that add_action_buffer
643 // will execute immediately instead of queuing them.
644 // NOTE: in case gotoFrame is executed by code in the called frame
645 // we'll temporarly clear the _callingFrameActions flag
646 // to properly queue actions back on the global queue.
648 _callingFrameActions
= true;
649 const PlayList
* playlist
= _def
->getPlaylist(frame_number
);
651 PlayList::const_iterator it
= playlist
->begin();
652 const PlayList::const_iterator e
= playlist
->end();
653 for (; it
!= e
; it
++) {
654 (*it
)->executeActions(this, _displayList
);
657 _callingFrameActions
= false;
662 MovieClip::addDisplayListObject(DisplayObject
* obj
, int depth
)
664 // TODO: only call set_invalidated if this DisplayObject actually overrides
667 _displayList
.placeDisplayObject(obj
, depth
);
674 MovieClip::duplicateMovieClip(const std::string
& newname
, int depth
,
675 as_object
* initObject
)
677 DisplayObject
* parent_ch
= parent();
679 log_error(_("Can't clone root of the movie"));
682 MovieClip
* parent
= parent_ch
->to_movie();
685 log_error(_("%s parent is not a movieclip, can't clone"), getTarget());
689 as_object
* o
= getObjectWithPrototype(getGlobal(*getObject(this)),
690 NSV::CLASS_MOVIE_CLIP
);
692 MovieClip
* newmovieclip
= new MovieClip(o
, _def
.get(), _swf
, parent
);
694 const string_table::key nn
= getStringTable(*getObject(this)).find(newname
);
695 newmovieclip
->set_name(nn
);
697 newmovieclip
->setDynamic();
699 // Copy event handlers from movieclip
700 // We should not copy 'm_action_buffer' since the
701 // 'm_method' already contains it
702 newmovieclip
->set_event_handlers(get_event_handlers());
705 newmovieclip
->_drawable
= _drawable
;
707 newmovieclip
->setCxForm(getCxForm(*this));
708 newmovieclip
->setMatrix(getMatrix(*this), true);
709 newmovieclip
->set_ratio(get_ratio());
710 newmovieclip
->set_clip_depth(get_clip_depth());
712 parent
->_displayList
.placeDisplayObject(newmovieclip
, depth
);
713 newmovieclip
->construct(initObject
);
719 MovieClip::queueAction(const action_buffer
& action
)
721 stage().pushAction(action
, this);
725 MovieClip::queueActions(ActionList
& actions
)
727 for(ActionList::const_iterator it
=actions
.begin(), itEnd
=actions
.end();
730 const action_buffer
* buf
= *it
;
737 MovieClip::notifyEvent(const event_id
& id
)
741 log_debug(_("Event %s invoked for movieclip %s"), id
, getTarget());
744 // We do not execute ENTER_FRAME if unloaded
745 if (id
.id() == event_id::ENTER_FRAME
&& unloaded()) {
747 log_debug(_("Sprite %s ignored ENTER_FRAME event (is unloaded)"), getTarget());
752 if (isButtonEvent(id
) && !isEnabled()) {
754 log_debug(_("Sprite %s ignored button-like event %s as not 'enabled'"),
760 std::auto_ptr
<ExecutableCode
> code (get_event_handler(id
));
766 // user-defined onInitialize is never called
767 if (id
.id() == event_id::INITIALIZE
) return;
769 // NOTE: user-defined onLoad is not invoked for static
770 // clips on which no clip-events are defined.
771 // see testsuite/misc-ming.all/action_execution_order_extend_test.swf
773 // Note that this can't be true for movieclips
774 // not placed by PlaceObject, see
775 // testsuite/misc-ming.all/registerClassTest.swf
777 // Note that this is also not true for movieclips which have
778 // a registered class on them, see
779 // testsuite/misc-ming.all/registerClassTest2.swf
781 // TODO: test the case in which it's MovieClip.prototype.onLoad
783 if (id
.id() == event_id::LOAD
) {
785 // TODO: we're likely making too much noise for nothing here,
786 // there must be some action-execution-order related problem instead....
787 // See testsuite/misc-ming.all/registerClassTest2.swf for an onLoad
788 // execution order related problem ...
791 // we don't skip calling user-defined onLoad for top-level movies
792 if ( ! parent() ) break;
793 // nor if there are clip-defined handler
794 if ( ! get_event_handlers().empty() ) break;
795 // nor if it's dynamic
796 if ( isDynamic() ) break;
798 const sprite_definition
* def
=
799 dynamic_cast<const sprite_definition
*>(_def
.get());
801 // must be a loaded movie (loadMovie doesn't mark it as
802 // "dynamic" - should it? no, or getBytesLoaded will always
806 // if it has a registered class it can have an onLoad
808 if (def
->getRegisteredClass()) break;
811 log_debug(_("Sprite %s (depth %d) won't check for user-defined "
812 "LOAD event (is not dynamic, has a parent, "
813 "no registered class and no clip events defined)"),
814 getTarget(), get_depth());
821 // Call the appropriate member function.
822 if (!isKeyEvent(id
)) {
823 sendEvent(*getObject(this), get_environment(), id
.functionURI());
829 MovieClip::pathElement(const ObjectURI
& uri
)
831 as_object
* obj
= DisplayObject::pathElement(uri
);
834 // See if we have a match on the display list.
835 obj
= getObject(getDisplayListObject(uri
));
838 obj
= getObject(this);
841 // See if it's a member
843 if (!obj
->as_object::get_member(uri
, &tmp
)) {
846 if (!tmp
.is_object()) {
851 return getObject(tmp
.toDisplayObject(true));
854 return toObject(tmp
, getVM(*getObject(this)));
858 MovieClip::setTextFieldVariables(const ObjectURI
& uri
, const as_value
& val
)
861 const string_table::key name
= getName(uri
);
863 // Try textfield variables
865 // FIXME: Turn textfield variables into Getter/Setters (Properties)
866 // so that as_object::set_member will do this automatically.
867 // The problem is that setting a TextVariable named after
868 // a builtin property will prevent *any* setting for the
869 // property (ie: have a textfield use _x as variable name and
871 TextFields
* etc
= get_textfield_variable(
872 getStringTable(*getObject(this)).value(name
));
874 if (!etc
) return false;
876 for (TextFields::iterator i
=etc
->begin(), e
=etc
->end(); i
!=e
; ++i
) {
877 (*i
)->updateText(val
.to_string(getSWFVersion(*getObject(this))));
882 /// Remove the 'contents' of the MovieClip, but leave properties and
883 /// event handlers intact.
885 MovieClip::unloadMovie()
887 LOG_ONCE(log_unimpl("MovieClip.unloadMovie()"));
890 // child movieclip advance
896 log_debug(_("Advance movieclip '%s' at frame %u/%u"),
897 getTargetPath(), _currentFrame
,
903 // call_frame should never trigger advance_movieclip
904 assert(!_callingFrameActions
);
906 // We might have loaded NO frames !
907 if (get_loaded_frames() == 0) {
908 IF_VERBOSE_MALFORMED_SWF(
909 LOG_ONCE( log_swferror(_("advance_movieclip: no frames loaded "
910 "for movieclip/movie %s"), getTarget()) );
915 // Process any pending loadVariables request
916 processCompletedLoadVariableRequests();
919 size_t frame_count
= _def
->get_frame_count();
921 log_debug(_("Advance_movieclip for movieclip '%s' - frame %u/%u "),
922 getTarget(), _currentFrame
,
926 // I'm not sure ENTERFRAME goes in a different queue then DOACTION...
927 queueEvent(event_id::ENTER_FRAME
, movie_root::PRIORITY_DOACTION
);
929 // Update current and next frames.
930 if (_playState
== PLAYSTATE_PLAY
)
933 log_debug(_("MovieClip::advance_movieclip we're in PLAYSTATE_PLAY mode"));
936 int prev_frame
= _currentFrame
;
939 log_debug(_("on_event_load called, incrementing"));
941 increment_frame_and_check_for_loop();
943 log_debug(_("after increment we are at frame %u/%u"), _currentFrame
, frame_count
);
946 // Execute the current frame's tags.
947 // First time executeFrameTags(0) executed in dlist.cpp(child) or
948 // SWFMovieDefinition(root)
949 if (_currentFrame
!= (size_t)prev_frame
)
951 if (_currentFrame
== 0 && _hasLooped
)
954 log_debug(_("Jumping back to frame 0 of movieclip %s"),
957 restoreDisplayList(0); // seems OK to me.
962 log_debug(_("Executing frame%d (0-based) tags of movieclip "
963 "%s"), _currentFrame
, getTarget());
965 // Make sure _currentFrame is 0-based during execution of
967 executeFrameTags(_currentFrame
, _displayList
,
968 SWF::ControlTag::TAG_DLIST
|
969 SWF::ControlTag::TAG_ACTION
);
977 log_debug(_("MovieClip::advance_movieclip we're in STOP mode"));
984 MovieClip::execute_init_action_buffer(const action_buffer
& a
, int cid
)
988 if ( _swf
->initializeCharacter(cid
) )
991 log_debug(_("Queuing init actions in frame %d of movieclip %s"),
992 _currentFrame
, getTarget());
994 std::auto_ptr
<ExecutableCode
> code(new GlobalCode(a
, this));
996 stage().pushAction(code
, movie_root::PRIORITY_INIT
);
1001 log_debug(_("Init actions for DisplayObject %d already executed"), cid
);
1007 MovieClip::execute_action(const action_buffer
& ab
)
1009 ActionExec
exec(ab
, _environment
);
1014 MovieClip::restoreDisplayList(size_t tgtFrame
)
1017 // This is not tested as usable for jump-forwards (yet)...
1018 // TODO: I guess just moving here the code currently in goto_frame
1019 // for jump-forwards would do
1020 assert(tgtFrame
<= _currentFrame
);
1022 // Just invalidate this DisplayObject before jumping back.
1023 // Should be optimized, but the invalidating model is not clear enough,
1024 // and there are some old questions spreading the source files.
1027 DisplayList tmplist
;
1028 for (size_t f
= 0; f
< tgtFrame
; ++f
)
1031 executeFrameTags(f
, tmplist
, SWF::ControlTag::TAG_DLIST
);
1034 // Execute both action tags and DLIST tags of the target frame
1035 _currentFrame
= tgtFrame
;
1036 executeFrameTags(tgtFrame
, tmplist
, SWF::ControlTag::TAG_DLIST
|
1037 SWF::ControlTag::TAG_ACTION
);
1039 _displayList
.mergeDisplayList(tmplist
);
1042 // 0-based frame number !
1044 MovieClip::executeFrameTags(size_t frame
, DisplayList
& dlist
, int typeflags
)
1046 // If there is no definition, this is a dynamically-created MovieClip
1047 // and has no frames.
1052 const PlayList
* playlist
= _def
->getPlaylist(frame
);
1056 // Use 1-based frame numbers
1057 log_action(_("Executing %d tags in frame %d/%d of movieclip %s"),
1058 playlist
->size(), frame
+ 1, get_frame_count(),
1062 // Generally tags should be executed in the order they are found in.
1063 for (PlayList::const_iterator it
= playlist
->begin(),
1064 e
= playlist
->end(); it
!= e
; ++it
) {
1066 if (typeflags
& SWF::ControlTag::TAG_DLIST
) {
1067 (*it
)->executeState(this, dlist
);
1070 if (typeflags
& SWF::ControlTag::TAG_ACTION
) {
1071 (*it
)->executeActions(this, _displayList
);
1081 MovieClip::goto_frame(size_t target_frame_number
)
1083 #if defined(DEBUG_GOTOFRAME) || defined(GNASH_DEBUG_TIMELINE)
1084 log_debug(_("movieclip %s ::goto_frame(%d) - current frame is %d"),
1085 getTargetPath(), target_frame_number
, _currentFrame
);
1088 // goto_frame stops by default.
1089 // ActionGotoFrame tells the movieClip to go to the target frame
1090 // and stop at that frame.
1091 setPlayState(PLAYSTATE_STOP
);
1093 if (target_frame_number
> _def
->get_frame_count() - 1) {
1095 target_frame_number
= _def
->get_frame_count() - 1;
1097 if (!_def
->ensure_frame_loaded(target_frame_number
+ 1)) {
1098 log_error(_("Target frame of a gotoFrame(%d) was never loaded,"
1099 "although frame count in header (%d) said we "
1100 "should have found it"),
1101 target_frame_number
+1, _def
->get_frame_count());
1105 // Just set _currentframe and return.
1106 _currentFrame
= target_frame_number
;
1108 // don't push actions, already tested.
1112 if (target_frame_number
== _currentFrame
) {
1113 // don't push actions
1117 // Unless the target frame is the next one, stop playback of soundstream
1118 if (target_frame_number
!= _currentFrame
+ 1) {
1122 const size_t loaded_frames
= get_loaded_frames();
1124 // target_frame_number is 0-based, get_loaded_frames() is 1-based
1125 // so in order to goto_frame(3) loaded_frames must be at least 4
1126 // if goto_frame(4) is called, and loaded_frames is 4 we're jumping
1128 if (target_frame_number
>= loaded_frames
) {
1129 IF_VERBOSE_ASCODING_ERRORS(
1130 log_aserror(_("GotoFrame(%d) targets a yet "
1131 "to be loaded frame (%d) loaded). "
1132 "We'll wait for it but a more correct form "
1133 "is explicitly using WaitForFrame instead"),
1134 target_frame_number
+1,
1138 if (!_def
->ensure_frame_loaded(target_frame_number
+ 1)) {
1139 log_error(_("Target frame of a gotoFrame(%d) was never loaded, "
1140 "although frame count in header (%d) said we should"
1142 target_frame_number
+ 1, _def
->get_frame_count());
1149 // Construct the DisplayList of the target frame
1152 if (target_frame_number
< _currentFrame
) {
1154 // Go backward to a previous frame
1155 // NOTE: just in case we're being called by code in a called frame
1156 // we'll backup and resume the _callingFrameActions flag
1157 bool callingFrameActionsBackup
= _callingFrameActions
;
1158 _callingFrameActions
= false;
1160 // restoreDisplayList takes care of properly setting the
1161 // _currentFrame variable
1162 restoreDisplayList(target_frame_number
);
1163 assert(_currentFrame
== target_frame_number
);
1164 _callingFrameActions
= callingFrameActionsBackup
;
1167 // Go forward to a later frame
1168 // We'd immediately return if target_frame_number == _currentFrame
1169 assert(target_frame_number
> _currentFrame
);
1170 while (++_currentFrame
< target_frame_number
) {
1171 //for (size_t f = _currentFrame+1; f<target_frame_number; ++f)
1172 // Second argument requests that only "DisplayList" tags
1173 // are executed. This means NO actions will be
1174 // pushed on m_action_list.
1175 executeFrameTags(_currentFrame
, _displayList
,
1176 SWF::ControlTag::TAG_DLIST
);
1178 assert(_currentFrame
== target_frame_number
);
1180 // Now execute target frame tags (queuing actions)
1181 // NOTE: just in case we're being called by code in a called frame
1182 // we'll backup and resume the _callingFrameActions flag
1183 bool callingFrameActionsBackup
= _callingFrameActions
;
1184 _callingFrameActions
= false;
1185 executeFrameTags(target_frame_number
, _displayList
,
1186 SWF::ControlTag::TAG_DLIST
| SWF::ControlTag::TAG_ACTION
);
1187 _callingFrameActions
= callingFrameActionsBackup
;
1190 assert(_currentFrame
== target_frame_number
);
1194 MovieClip::goto_labeled_frame(const std::string
& label
)
1197 // If there is no definition, this is a dynamically-created MovieClip
1198 // and has no frames. (We are also probably not called in this case).
1199 if (!_def
) return false;
1201 size_t target_frame
;
1202 if (_def
->get_labeled_frame(label
, target_frame
)) {
1203 goto_frame(target_frame
);
1207 IF_VERBOSE_MALFORMED_SWF(
1208 log_swferror(_("MovieClip::goto_labeled_frame('%s') "
1209 "unknown label"), label
);
1215 MovieClip::draw(Renderer
& renderer
, const Transform
& xform
)
1217 const DisplayObject::MaskRenderer
mr(renderer
, *this);
1219 _drawable
.finalize();
1220 _drawable
.display(renderer
, xform
);
1221 _displayList
.display(renderer
, xform
);
1225 MovieClip::display(Renderer
& renderer
, const Transform
& base
)
1227 // Note: DisplayList::display() will take care of the visibility checking.
1229 // Whether a DisplayObject should be rendered or not is dependent
1230 // on its parent: i.e. if its parent is a mask, this DisplayObject
1231 // should be rendered to the mask buffer even it is invisible.
1233 // Draw everything with our own transform.
1234 const Transform xform
= base
* transform();
1235 draw(renderer
, xform
);
1236 clear_invalidated();
1239 void MovieClip::omit_display()
1241 if (childInvalidated()) _displayList
.omit_display();
1243 clear_invalidated();
1247 MovieClip::attachCharacter(DisplayObject
& newch
, int depth
, as_object
* initObj
)
1249 _displayList
.placeDisplayObject(&newch
, depth
);
1250 newch
.construct(initObj
);
1252 // FIXME: check return from placeDisplayObject above ?
1257 MovieClip::add_display_object(const SWF::PlaceObject2Tag
* tag
,
1261 // If this MovieClip has no definition, it should also have no ControlTags,
1262 // and this shouldn't be called.
1266 SWF::DefinitionTag
* cdef
= _def
->getDefinitionTag(tag
->getID());
1269 IF_VERBOSE_MALFORMED_SWF(
1270 log_swferror(_("MovieClip::add_display_object(): "
1271 "unknown cid = %d"), tag
->getID());
1276 DisplayObject
* existing_char
= dlist
.getDisplayObjectAtDepth(tag
->getDepth());
1278 if (existing_char
) return NULL
;
1280 Global_as
& gl
= getGlobal(*getObject(this));
1281 DisplayObject
* ch
= cdef
->createDisplayObject(gl
, this);
1283 string_table
& st
= getStringTable(*getObject(this));
1285 if (tag
->hasName()) ch
->set_name(st
.find(tag
->getName()));
1286 else if (isReferenceable(*ch
))
1288 const string_table::key instance_name
= getNextUnnamedInstanceName();
1289 ch
->set_name(instance_name
);
1292 if (tag
->hasBlendMode()) {
1293 boost::uint8_t bm
= tag
->getBlendMode();
1294 ch
->setBlendMode(static_cast<DisplayObject::BlendMode
>(bm
));
1297 // Attach event handlers (if any).
1298 const std::vector
<swf_event
*>& event_handlers
= tag
->getEventHandlers();
1299 for (size_t i
= 0, n
= event_handlers
.size(); i
< n
; i
++)
1301 swf_event
* ev
= event_handlers
[i
];
1302 ch
->add_event_handler(ev
->event(), ev
->action());
1305 // TODO: check if we should check those has_xxx flags first.
1306 ch
->setCxForm(tag
->getCxform());
1307 ch
->setMatrix(tag
->getMatrix(), true); // update caches
1308 ch
->set_ratio(tag
->getRatio());
1309 ch
->set_clip_depth(tag
->getClipDepth());
1311 dlist
.placeDisplayObject(ch
, tag
->getDepth());
1317 MovieClip::move_display_object(const SWF::PlaceObject2Tag
* tag
, DisplayList
& dlist
)
1319 int ratio
= tag
->getRatio();
1320 // clip_depth is not used in MOVE tag(at least no related tests).
1321 dlist
.moveDisplayObject(
1323 tag
->hasCxform() ? &tag
->getCxform() : NULL
,
1324 tag
->hasMatrix() ? &tag
->getMatrix() : NULL
,
1325 tag
->hasRatio() ? &ratio
: NULL
,
1330 MovieClip::replace_display_object(const SWF::PlaceObject2Tag
* tag
,
1333 // A MovieClip without a definition cannot have any ControlTags, so this
1334 // should not be called.
1336 assert(tag
!= NULL
);
1338 const boost::uint16_t id
= tag
->getID();
1340 SWF::DefinitionTag
* cdef
= _def
->getDefinitionTag(id
);
1343 log_error(_("movieclip::replace_display_object(): "
1344 "unknown cid = %d"), id
);
1349 DisplayObject
* existing_char
= dlist
.getDisplayObjectAtDepth(tag
->getDepth());
1351 if (!existing_char
) {
1352 log_error(_("MovieClip::replace_display_object: could not "
1353 "find any DisplayObject at depth %d"), tag
->getDepth());
1357 // if the existing DisplayObject is not a shape, move it instead
1359 if (isReferenceable(*existing_char
)) {
1360 move_display_object(tag
, dlist
);
1364 Global_as
& gl
= getGlobal(*getObject(this));
1365 DisplayObject
* ch
= cdef
->createDisplayObject(gl
, this);
1368 // TODO: check if we can drop this for REPLACE!
1369 // should we rename the DisplayObject when it's REPLACE tag?
1370 if (tag
->hasName()) {
1371 string_table
& st
= getStringTable(*getObject(this));
1372 ch
->set_name(st
.find(tag
->getName()));
1374 else if (isReferenceable(*ch
)) {
1375 const string_table::key instance_name
= getNextUnnamedInstanceName();
1376 ch
->set_name(instance_name
);
1378 if (tag
->hasRatio()) {
1379 ch
->set_ratio(tag
->getRatio());
1381 if (tag
->hasCxform()) {
1382 ch
->setCxForm(tag
->getCxform());
1384 if (tag
->hasMatrix()) {
1385 ch
->setMatrix(tag
->getMatrix(), true);
1388 // use SWFMatrix from the old DisplayObject if tag doesn't provide one.
1389 dlist
.replaceDisplayObject(ch
, tag
->getDepth(),
1390 !tag
->hasCxform(), !tag
->hasMatrix());
1395 MovieClip::remove_display_object(const SWF::PlaceObject2Tag
* tag
,
1399 dlist
.removeDisplayObject(tag
->getDepth());
1403 MovieClip::remove_display_object(int depth
, int)
1406 _displayList
.removeDisplayObject(depth
);
1410 MovieClip::increment_frame_and_check_for_loop()
1412 const size_t frame_count
= get_loaded_frames();
1413 if (++_currentFrame
>= frame_count
) {
1422 MovieClip::handleFocus()
1425 as_object
* obj
= getObject(this);
1428 // For SWF6 and above: the MovieClip can always receive focus if
1429 // focusEnabled evaluates to true.
1430 if (getSWFVersion(*obj
) > 5) {
1431 as_value focusEnabled
;
1432 if (obj
->get_member(NSV::PROP_FOCUS_ENABLED
, &focusEnabled
)) {
1433 if (toBool(focusEnabled
, getVM(*obj
))) return true;
1437 // If focusEnabled doesn't evaluate to true or for SWF5, return true
1438 // only if at least one mouse event handler is defined.
1439 return mouseEnabled();
1443 MovieClip::pointInShape(boost::int32_t x
, boost::int32_t y
) const
1445 ShapeContainerFinder
finder(x
, y
);
1446 _displayList
.visitBackward(finder
);
1447 if ( finder
.hitFound() ) return true;
1448 return hitTestDrawable(x
, y
);
1452 MovieClip::pointInVisibleShape(boost::int32_t x
, boost::int32_t y
) const
1454 if ( ! visible() ) return false;
1455 if ( isDynamicMask() && ! mouseEnabled() )
1457 // see testsuite/misc-ming.all/masks_test.swf
1458 #ifdef GNASH_DEBUG_HITTEST
1459 log_debug(_("%s is a dynamic mask and can't handle mouse "
1460 "events, no point will hit it"), getTarget());
1464 const DisplayObject
* mask
= getMask(); // dynamic one
1465 if ( mask
&& mask
->visible() && ! mask
->pointInShape(x
, y
) )
1467 #ifdef GNASH_DEBUG_HITTEST
1468 log_debug(_("%s is dynamically masked by %s, which "
1469 "doesn't hit point %g,%g"), getTarget(),
1470 mask
->getTarget(), x
, y
);
1474 VisibleShapeContainerFinder
finder(x
, y
);
1475 _displayList
.visitBackward(finder
);
1476 if (finder
.hitFound()) return true;
1477 return hitTestDrawable(x
, y
);
1481 MovieClip::hitTestDrawable(boost::int32_t x
, boost::int32_t y
) const
1483 const SWFMatrix wm
= getWorldMatrix(*this).invert();
1486 if (!_drawable
.getBounds().point_test(lp
.x
, lp
.y
)) return false;
1487 return _drawable
.pointTestLocal(lp
.x
, lp
.y
, wm
);
1491 MovieClip::pointInHitableShape(boost::int32_t x
, boost::int32_t y
) const
1493 if (isDynamicMask() && !mouseEnabled()) return false;
1495 const DisplayObject
* mask
= getMask();
1496 if (mask
&& !mask
->pointInShape(x
, y
)) return false;
1498 HitableShapeContainerFinder
finder(x
, y
);
1499 _displayList
.visitBackward(finder
);
1500 if (finder
.hitFound()) return true;
1502 return hitTestDrawable(x
, y
);
1506 MovieClip::topmostMouseEntity(boost::int32_t x
, boost::int32_t y
)
1508 //GNASH_REPORT_FUNCTION;
1510 if (!visible()) return 0;
1512 // point is in parent's space, we need to convert it in world space
1514 DisplayObject
* p
= parent();
1516 // WARNING: if we have NO parent, our parent is the Stage (movie_root)
1517 // so, in case we'll add a "stage" matrix, we'll need to take
1518 // it into account here.
1519 // TODO: actually, why are we insisting in using parent's
1520 // coordinates for this method at all ?
1521 getWorldMatrix(*p
).transform(wp
);
1526 if (pointInVisibleShape(wp
.x
, wp
.y
)) return this;
1530 SWFMatrix m
= getMatrix(*this);
1535 MouseEntityFinder
finder(wp
, pp
);
1536 _displayList
.visitAll(finder
);
1537 InteractiveObject
* ch
= finder
.getEntity();
1539 // It doesn't make any sense to query _drawable, as it's
1540 // not an InteractiveObject.
1544 const DisplayObject
*
1545 MovieClip::findDropTarget(boost::int32_t x
, boost::int32_t y
,
1546 DisplayObject
* dragging
) const
1548 if ( this == dragging
) return 0; // not here...
1550 if ( ! visible() ) return 0; // isn't me !
1552 DropTargetFinder
finder(x
, y
, dragging
);
1553 _displayList
.visitAll(finder
);
1555 // does it hit any child ?
1556 const DisplayObject
* ch
= finder
.getDropChar();
1559 // TODO: find closest actionscript referenceable container
1560 // (possibly itself)
1565 if (hitTestDrawable(x
, y
)) return this;
1571 MovieClip::trackAsMenu()
1573 as_object
* obj
= getObject(this);
1576 string_table
& st
= getStringTable(*obj
);
1579 return (obj
->get_member(st
.find("trackAsMenu"), &track
) &&
1580 toBool(track
, getVM(*obj
)));
1584 MovieClip::mouseEnabled() const
1586 if ( ! isEnabled() ) return false;
1588 // Event handlers that qualify as mouse event handlers.
1589 static const event_id EH
[] =
1591 event_id(event_id::PRESS
),
1592 event_id(event_id::RELEASE
),
1593 event_id(event_id::RELEASE_OUTSIDE
),
1594 event_id(event_id::ROLL_OVER
),
1595 event_id(event_id::ROLL_OUT
),
1596 event_id(event_id::DRAG_OVER
),
1597 event_id(event_id::DRAG_OUT
),
1600 const size_t size
= arraySize(EH
);
1602 for (size_t i
= 0; i
< size
; ++i
) {
1603 const event_id
&event
= EH
[i
];
1605 // Check event handlers
1606 if (hasEventHandler(event
.id())) {
1615 MovieClip::stop_drag()
1617 stage().stop_drag();
1621 MovieClip::set_background_color(const rgba
& color
)
1623 stage().set_background_color(color
);
1627 MovieClip::cleanup_textfield_variables()
1630 if (!_text_variables
.get()) return;
1632 TextFieldIndex
& m
= *_text_variables
;
1634 for (TextFieldIndex::iterator i
=m
.begin(), ie
=m
.end(); i
!=ie
; ++i
)
1636 TextFields
& v
=i
->second
;
1637 TextFields::iterator lastValid
= std::remove_if(v
.begin(), v
.end(),
1638 boost::mem_fn(&DisplayObject::unloaded
));
1639 v
.erase(lastValid
, v
.end());
1645 MovieClip::set_textfield_variable(const std::string
& name
, TextField
* ch
)
1650 if ( ! _text_variables
.get() )
1652 _text_variables
.reset(new TextFieldIndex
);
1655 (*_text_variables
)[name
].push_back(ch
);
1658 MovieClip::TextFields
*
1659 MovieClip::get_textfield_variable(const std::string
& name
)
1661 // nothing allocated yet...
1662 if ( ! _text_variables
.get() ) return NULL
;
1664 // TODO: should variable name be considered case-insensitive ?
1665 TextFieldIndex::iterator it
= _text_variables
->find(name
);
1666 if (it
== _text_variables
->end()) return 0;
1667 else return &(it
->second
);
1672 MovieClip::getDisplayListObject(const ObjectURI
& uri
)
1675 as_object
* obj
= getObject(this);
1678 string_table
& st
= getStringTable(*obj
);
1680 // Try items on our display list.
1681 DisplayObject
* ch
= _displayList
.getDisplayObjectByName(st
, uri
,
1688 // If the object is an ActionScript referenciable one we
1689 // return it, otherwise we return ourselves
1690 if (isReferenceable(*ch
)) {
1697 MovieClip::add_invalidated_bounds(InvalidatedRanges
& ranges
, bool force
)
1700 // nothing to do if this movieclip is not visible
1701 if (!visible() || invisible(getCxForm(*this))) {
1702 ranges
.add(m_old_invalidated_ranges
);
1706 if (!invalidated() && !childInvalidated() && !force
) return;
1709 // m_child_invalidated does not require our own bounds
1710 if (invalidated() || force
) {
1711 // Add old invalidated bounds
1712 ranges
.add(m_old_invalidated_ranges
);
1715 _displayList
.add_invalidated_bounds(ranges
, force
|| invalidated());
1719 bounds
.expand_to_transformed_rect(getWorldMatrix(*this),
1720 _drawable
.getBounds());
1722 ranges
.add(bounds
.getRange());
1728 MovieClip::constructAsScriptObject()
1730 as_object
* mc
= getObject(this);
1732 // A MovieClip should always have an associated object.
1736 mc
->init_member("$version", getVM(*mc
).getPlayerVersion(), 0);
1739 const sprite_definition
* def
=
1740 dynamic_cast<const sprite_definition
*>(_def
.get());
1742 // We won't "construct" top-level movies
1743 as_function
* ctor
= def
? def
->getRegisteredClass() : 0;
1746 log_debug(_("Attached movieclips %s registered class is %p"),
1747 getTarget(), (void*)ctor
);
1750 // Set this MovieClip object to be an instance of the class.
1752 Property
* proto
= ctor
->getOwnProperty(NSV::PROP_PROTOTYPE
);
1753 if (proto
) mc
->set_prototype(proto
->getValue(*ctor
));
1756 // Send the construct event. This must be done after the __proto__
1757 // member is set. It is always done.
1758 notifyEvent(event_id::CONSTRUCT
);
1761 const int swfversion
= getSWFVersion(*mc
);
1762 if (swfversion
> 5) {
1764 ctor
->construct(*mc
, get_environment(), args
);
1771 MovieClip::construct(as_object
* initObj
)
1774 assert(!unloaded());
1776 saveOriginalTarget();
1779 log_debug(_("Sprite '%s' placed on stage"), getTarget());
1782 // Register this movieclip as a live one
1783 stage().addLiveChar(this);
1785 // It seems it's legal to place 0-framed movieclips on stage.
1786 // See testsuite/misc-swfmill.all/zeroframe_definemovieclip.swf
1788 // Now execute frame tags and take care of queuing the LOAD event.
1790 // DLIST tags are executed immediately while ACTION tags are queued.
1792 // For _root movie, LOAD event is invoked *after* actions in first frame
1793 // See misc-ming.all/action_execution_order_test4.{c,swf}
1795 assert(!_callingFrameActions
); // or will not be queuing actions
1797 executeFrameTags(0, _displayList
, SWF::ControlTag::TAG_DLIST
|
1798 SWF::ControlTag::TAG_ACTION
);
1799 if (getSWFVersion(*getObject(this)) > 5) {
1800 queueEvent(event_id::LOAD
, movie_root::PRIORITY_DOACTION
);
1805 queueEvent(event_id::LOAD
, movie_root::PRIORITY_DOACTION
);
1806 executeFrameTags(0, _displayList
, SWF::ControlTag::TAG_DLIST
|
1807 SWF::ControlTag::TAG_ACTION
);
1810 as_object
* mc
= getObject(this);
1812 // A MovieClip should always have an associated object.
1815 // We execute events immediately when the stage-placed DisplayObject
1816 // is dynamic, This is becase we assume that this means that
1817 // the DisplayObject is placed during processing of actions (opposed
1818 // that during advancement iteration).
1820 // A more general implementation might ask movie_root about its state
1821 // (iterating or processing actions?)
1822 // Another possibility to inspect could be letting movie_root decide
1823 // when to really queue and when rather to execute immediately the
1824 // events with priority INITIALIZE or CONSTRUCT ...
1828 log_debug(_("Queuing INITIALIZE and CONSTRUCT events for movieclip %s"),
1832 std::auto_ptr
<ExecutableCode
> code(new ConstructEvent(this));
1833 stage().pushAction(code
, movie_root::PRIORITY_CONSTRUCT
);
1838 // Properties from an initObj must be copied before construction, but
1839 // after the display list has been populated, so that _height and
1840 // _width (which depend on bounds) are correct.
1842 mc
->copyProperties(*initObj
);
1845 constructAsScriptObject();
1849 // Tested in testsuite/swfdec/duplicateMovieclip-events.c and
1850 // testsuite/swfdec/clone-sprite-events.c not to call notifyEvent
1852 queueEvent(event_id::INITIALIZE
, movie_root::PRIORITY_INIT
);
1857 MovieClip::unloadChildren()
1860 log_debug(_("Unloading movieclip '%s'"), getTargetPath());
1863 // stop any pending streaming sounds
1866 // We won't be displayed again, so worth releasing
1867 // some memory. The drawable might take a lot of memory
1871 return _displayList
.unload();
1876 MovieClip::getLoadedMovie(Movie
* extern_movie
)
1878 DisplayObject
* p
= parent();
1880 extern_movie
->set_parent(p
);
1882 // Copy own lockroot value
1883 extern_movie
->setLockRoot(getLockRoot());
1885 // Copy own event handlers
1886 // see testsuite/misc-ming.all/loadMovieTest.swf
1887 const Events
& clipEvs
= get_event_handlers();
1888 // top-level movies can't have clip events, right ?
1889 assert (extern_movie
->get_event_handlers().empty());
1890 extern_movie
->set_event_handlers(clipEvs
);
1893 // TODO: check empty != none...
1894 const ObjectURI
& name
= get_name();
1895 if (!name
.empty()) extern_movie
->set_name(name
);
1897 // Copy own clip depth (TODO: check this)
1898 extern_movie
->set_clip_depth(get_clip_depth());
1900 // Replace ourselves in parent
1901 // TODO: don't pretend our parent is a MovieClip,
1902 // could as well be a button I guess...
1903 // At most we should require it to be a
1904 // DisplayObjectContainer and log an error if it's not.
1905 MovieClip
* parent_sp
= p
->to_movie();
1907 parent_sp
->_displayList
.replaceDisplayObject(extern_movie
, get_depth(),
1909 extern_movie
->construct();
1913 // replaceLevel will set depth for us
1914 stage().replaceLevel(get_depth() - DisplayObject::staticDepthOffset
,
1920 MovieClip::loadVariables(const std::string
& urlstr
,
1921 VariablesMethod sendVarsMethod
)
1923 // Host security check will be will be done by LoadVariablesThread
1924 // (down by getStream, that is)
1926 const movie_root
& mr
= stage();
1927 URL
url(urlstr
, mr
.runResources().streamProvider().originalURL());
1929 std::string postdata
;
1931 // Encode our vars for sending.
1932 if (sendVarsMethod
!= METHOD_NONE
) {
1933 postdata
= getURLEncodedVars(*getObject(this));
1937 const StreamProvider
& sp
=
1938 getRunResources(*getObject(this)).streamProvider();
1940 if (sendVarsMethod
== METHOD_POST
) {
1942 _loadVariableRequests
.push_back(
1943 new LoadVariablesThread(sp
, url
, postdata
));
1947 if (sendVarsMethod
== METHOD_GET
) {
1949 std::string qs
= url
.querystring();
1950 if (qs
.empty()) url
.set_querystring(postdata
);
1951 else url
.set_querystring(qs
+ "&" + postdata
);
1953 _loadVariableRequests
.push_back(new LoadVariablesThread(sp
, url
));
1955 _loadVariableRequests
.back()->process();
1957 catch (NetworkException
& ex
)
1959 log_error(_("Could not load variables from %s"), url
.str());
1965 MovieClip::processCompletedLoadVariableRequest(LoadVariablesThread
& request
)
1967 assert(request
.completed());
1969 MovieVariables
& vals
= request
.getValues();
1972 // We want to call a clip-event too if available, see bug #22116
1973 notifyEvent(event_id::DATA
);
1978 MovieClip::processCompletedLoadVariableRequests()
1980 // Nothing to do (just for clarity)
1981 if ( _loadVariableRequests
.empty() ) return;
1983 for (LoadVariablesThreads::iterator it
=_loadVariableRequests
.begin();
1984 it
!= _loadVariableRequests
.end(); )
1986 LoadVariablesThread
& request
= *(*it
);
1987 if (request
.completed())
1989 processCompletedLoadVariableRequest(request
);
1991 it
= _loadVariableRequests
.erase(it
);
1998 MovieClip::setVariables(const MovieVariables
& vars
)
2000 string_table
& st
= getStringTable(*getObject(this));
2001 for (MovieVariables::const_iterator it
=vars
.begin(), itEnd
=vars
.end();
2004 const std::string
& name
= it
->first
;
2005 const std::string
& val
= it
->second
;
2006 getObject(this)->set_member(st
.find(name
), val
);
2011 MovieClip::removeMovieClip()
2013 int depth
= get_depth();
2014 if ( depth
< 0 || depth
> 1048575 )
2016 IF_VERBOSE_ASCODING_ERRORS(
2017 log_aserror(_("removeMovieClip(%s): movieclip depth (%d) out of the "
2018 "'dynamic' zone [0..1048575], won't remove"),
2019 getTarget(), depth
);
2024 MovieClip
* p
= dynamic_cast<MovieClip
*>(parent());
2026 // second argument is arbitrary, see comments above
2027 // the function declaration in MovieClip.h
2028 p
->remove_display_object(depth
, 0);
2032 stage().dropLevel(depth
);
2033 // I guess this can only happen if someone uses
2034 // _swf.swapDepth([0..1048575])
2040 MovieClip::getBounds() const
2043 BoundsFinder
f(bounds
);
2044 _displayList
.visitAll(f
);
2045 SWFRect drawableBounds
= _drawable
.getBounds();
2046 bounds
.expand_to_rect(drawableBounds
);
2052 MovieClip::isEnabled() const
2054 as_object
* obj
= getObject(this);
2058 if (!obj
->get_member(NSV::PROP_ENABLED
, &enabled
)) {
2059 // We're enabled if there's no 'enabled' member...
2062 return toBool(enabled
, getVM(*obj
));
2067 MovieClip::visitNonProperties(KeyVisitor
& v
) const
2069 DisplayListVisitor
dv(v
);
2070 _displayList
.visitAll(dv
);
2074 MovieClip::cleanupDisplayList()
2076 _displayList
.removeUnloaded();
2077 cleanup_textfield_variables();
2081 MovieClip::markOwnResources() const
2083 ReachableMarker marker
;
2085 _displayList
.visitAll(marker
);
2087 _environment
.markReachableResources();
2089 // Mark textfields in the TextFieldIndex
2090 if ( _text_variables
.get() )
2092 for (TextFieldIndex::const_iterator i
=_text_variables
->begin(),
2093 e
=_text_variables
->end();
2096 const TextFields
& tfs
=i
->second
;
2097 std::for_each(tfs
.begin(), tfs
.end(),
2098 boost::mem_fn(&DisplayObject::setReachable
));
2102 // Mark our relative root
2103 _swf
->setReachable();
2108 MovieClip::destroy()
2111 _displayList
.destroy();
2112 DisplayObject::destroy();
2116 MovieClip::get_root() const
2122 MovieClip::getAsRoot()
2125 // TODO1: as an optimization, if swf version < 7
2126 // we might as well just return _swf,
2127 // the whole chain from this movieclip to it's
2128 // _swf should have the same version...
2130 // TODO2: implement this with iteration rather
2134 DisplayObject
* p
= parent();
2135 if (!p
) return this; // no parent, we're the root
2137 // If we have a parent, we descend to it unless
2138 // our _lockroot is true AND our or the VM's
2139 // SWF version is > 6
2140 int topSWFVersion
= stage().getRootMovie().version();
2142 if (getDefinitionVersion() > 6 || topSWFVersion
> 6) {
2143 if (getLockRoot()) return this;
2146 return p
->getAsRoot();
2151 MovieClip::setStreamSoundId(int id
)
2153 if ( id
!= m_sound_stream_id
)
2155 log_debug(_("Stream sound id from %d to %d, stopping old"),
2156 m_sound_stream_id
, id
);
2159 m_sound_stream_id
= id
;
2163 MovieClip::stopStreamSound()
2165 if ( m_sound_stream_id
== -1 ) return; // nothing to do
2167 sound::sound_handler
* handler
= getRunResources(*getObject(this)).soundHandler();
2170 handler
->stop_sound(m_sound_stream_id
);
2173 m_sound_stream_id
= -1;
2177 MovieClip::setPlayState(PlayState s
)
2179 if (s
== _playState
) return; // nothing to do
2180 if (s
== PLAYSTATE_STOP
) stopStreamSound();
2184 } // namespace gnash