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"
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
88 MovieClip::TextFields
* textfieldVar(MovieClip::TextFieldIndex
* t
,
89 const ObjectURI
& name
);
95 /// ConstructEvent, used for queuing construction
97 /// Its execution will call constructAsScriptObject()
98 /// on the target movieclip
100 class ConstructEvent
: public ExecutableCode
104 explicit ConstructEvent(MovieClip
* nTarget
)
106 ExecutableCode(nTarget
)
109 virtual void execute() {
110 static_cast<MovieClip
*>(target())->constructAsScriptObject();
115 /// Find a DisplayObject hit by the given coordinates.
117 /// This class takes care about taking masks layers into
118 /// account, but nested masks aren't properly tested yet.
120 class MouseEntityFinder
125 /// Query point in world coordinate space
128 /// Query point in parent coordinate space
130 MouseEntityFinder(point wp
, point pp
)
132 _highestHiddenDepth(std::numeric_limits
<int>::min()),
140 void operator() (DisplayObject
* ch
) {
142 if (ch
->get_depth() <= _highestHiddenDepth
) {
143 if (ch
->isMaskLayer()) {
144 log_debug(_("CHECKME: nested mask in MouseEntityFinder. "
145 "This mask is %s at depth %d outer mask masked "
147 ch
->getTarget(), ch
->get_depth(),
148 _highestHiddenDepth
);
149 // Hiding mask still in effect...
154 if (ch
->isMaskLayer()) {
155 if (!ch
->pointInShape(_wp
.x
, _wp
.y
)) {
156 #ifdef DEBUG_MOUSE_ENTITY_FINDING
157 log_debug(_("Character %s at depth %d is a mask not hitting "
158 "the query point %g,%g and masking up to "
159 "depth %d"), ch
->getTarget(), ch
->get_depth(),
160 _wp
.x
, _wp
.y
, ch
->get_clip_depth());
162 _highestHiddenDepth
= ch
->get_clip_depth();
165 #ifdef DEBUG_MOUSE_ENTITY_FINDING
166 log_debug(_("Character %s at depth %d is a mask hitting the "
167 "query point %g,%g"),
168 ch
->getTarget(), ch
->get_depth(), _wp
.x
, _wp
.y
);
173 if (!ch
->visible()) return;
175 _candidates
.push_back(ch
);
178 void checkCandidates() {
179 if (_checked
) return;
180 for (Candidates::reverse_iterator i
=_candidates
.rbegin(),
181 e
=_candidates
.rend(); i
!=e
; ++i
) {
182 DisplayObject
* ch
= *i
;
183 InteractiveObject
* te
= ch
->topmostMouseEntity(_pp
.x
, _pp
.y
);
192 InteractiveObject
* getEntity() {
194 #ifdef DEBUG_MOUSE_ENTITY_FINDING
196 log_debug(_("MouseEntityFinder found DisplayObject %s (depth %d) "
197 "hitting point %g,%g"),
198 _m
->getTarget(), _m
->get_depth(), _wp
.x
, _wp
.y
);
200 #endif // DEBUG_MOUSE_ENTITY_FINDING
206 /// Highest depth hidden by a mask
208 /// This will be -1 initially, and set
209 /// the the depth of a mask when the mask
210 /// doesn't contain the query point, while
211 /// scanning a DisplayList bottom-up
213 int _highestHiddenDepth
;
215 InteractiveObject
* _m
;
217 typedef std::vector
<DisplayObject
*> Candidates
;
218 Candidates _candidates
;
220 /// Query point in world coordinate space
223 /// Query point in parent coordinate space
230 /// Find the first DisplayObject whose shape contain the point
232 /// Point coordinates in world TWIPS
234 class ShapeContainerFinder
238 ShapeContainerFinder(boost::int32_t x
, boost::int32_t y
)
245 bool operator()(const DisplayObject
* ch
) {
246 if (ch
->pointInShape(_x
, _y
)) {
253 bool hitFound() const { return _found
; }
257 const boost::int32_t _x
;
258 const boost::int32_t _y
;
261 /// Find the first visible DisplayObject whose shape contain the point
263 /// Point coordinates in world TWIPS
265 class VisibleShapeContainerFinder
269 VisibleShapeContainerFinder(boost::int32_t x
, boost::int32_t y
)
276 bool operator()(const DisplayObject
* ch
) {
278 if (ch
->pointInVisibleShape(_x
, _y
)) {
285 bool hitFound() const { return _found
; }
289 const boost::int32_t _x
;
290 const boost::int32_t _y
;
293 /// Find the first hitable DisplayObject whose shape contain the point
295 /// Point coordinates in world TWIPS
297 class HitableShapeContainerFinder
300 HitableShapeContainerFinder(boost::int32_t x
, boost::int32_t y
)
307 bool operator()(const DisplayObject
* ch
) {
308 if (ch
->isDynamicMask()) return true;
309 if (ch
->pointInShape(_x
, _y
)) {
316 bool hitFound() const { return _found
; }
322 // x position in twips.
323 const boost::int32_t _x
;
325 // y position in twips.
326 const boost::int32_t _y
;
329 /// A DisplayList visitor used to compute its overall bounds.
334 explicit BoundsFinder(SWFRect
& b
) : _bounds(b
) {}
336 void operator()(DisplayObject
* ch
) {
337 // don't include bounds of unloaded DisplayObjects
338 if (ch
->unloaded()) return;
339 SWFRect chb
= ch
->getBounds();
340 SWFMatrix m
= getMatrix(*ch
);
341 _bounds
.expand_to_transformed_rect(m
, chb
);
348 struct ReachableMarker
350 void operator()(DisplayObject
*ch
) const {
355 /// Find the first visible DisplayObject whose shape contain the point
356 /// and is not the DisplayObject being dragged or any of its childs
358 /// Point coordinates in world TWIPS
360 class DropTargetFinder
363 DropTargetFinder(boost::int32_t x
, boost::int32_t y
, DisplayObject
* dragging
)
365 _highestHiddenDepth(std::numeric_limits
<int>::min()),
374 void operator()(const DisplayObject
* ch
) {
376 if (ch
->get_depth() <= _highestHiddenDepth
) {
377 if (ch
->isMaskLayer()) {
378 log_debug(_("CHECKME: nested mask in DropTargetFinder. "
379 "This mask is %s at depth %d outer mask masked "
381 ch
->getTarget(), ch
->get_depth(), _highestHiddenDepth
);
382 // Hiding mask still in effect...
387 if (ch
->isMaskLayer()) {
388 if (!ch
->visible()) {
389 log_debug(_("FIXME: invisible mask in MouseEntityFinder."));
391 if (!ch
->pointInShape(_x
, _y
)) {
392 #ifdef DEBUG_MOUSE_ENTITY_FINDING
393 log_debug(_("Character %s at depth %d is a mask not hitting "
394 "the query point %g,%g and masking up to depth %d"),
395 ch
->getTarget(), ch
->get_depth(), _x
, _y
,
396 ch
->get_clip_depth());
398 _highestHiddenDepth
= ch
->get_clip_depth();
401 #ifdef DEBUG_MOUSE_ENTITY_FINDING
402 log_debug(_("Character %s at depth %d is a mask "
403 "hitting the query point %g,%g"),
404 ch
->getTarget(), ch
->get_depth(), _x
, _y
);
409 _candidates
.push_back(ch
);
412 void checkCandidates() const {
413 if (_checked
) return;
414 for (Candidates::const_reverse_iterator i
=_candidates
.rbegin(),
415 e
=_candidates
.rend(); i
!=e
; ++i
) {
416 const DisplayObject
* ch
= *i
;
417 const DisplayObject
* dropChar
=
418 ch
->findDropTarget(_x
, _y
, _dragging
);
427 const DisplayObject
* getDropChar() const {
432 /// Highest depth hidden by a mask
434 /// This will be -1 initially, and set
435 /// the the depth of a mask when the mask
436 /// doesn't contain the query point, while
437 /// scanning a DisplayList bottom-up
439 int _highestHiddenDepth
;
443 DisplayObject
* _dragging
;
444 mutable const DisplayObject
* _dropch
;
446 typedef std::vector
<const DisplayObject
*> Candidates
;
447 Candidates _candidates
;
449 mutable bool _checked
;
452 class DisplayListVisitor
455 DisplayListVisitor(KeyVisitor
& v
) : _v(v
) {}
457 void operator()(DisplayObject
* ch
) const {
458 if (!isReferenceable(*ch
)) return;
459 // Don't enumerate unloaded DisplayObjects
460 if (ch
->unloaded()) return;
462 const ObjectURI
& name
= ch
->get_name();
463 // Don't enumerate unnamed DisplayObjects
464 if (name
.empty()) return;
466 // Referenceable DisplayObject always have an object.
467 assert(getObject(ch
));
474 } // anonymous namespace
477 MovieClip::MovieClip(as_object
* object
, const movie_definition
* def
,
478 Movie
* r
, DisplayObject
* parent
)
480 DisplayObjectContainer(object
, parent
),
483 _playState(PLAYSTATE_PLAY
),
484 _environment(getVM(*object
)),
486 m_sound_stream_id(-1),
488 _callingFrameActions(false),
494 _environment
.set_target(this);
497 MovieClip::~MovieClip()
503 MovieClip::getDefinitionVersion() const
505 return _swf
->version();
508 // Execute the actions in the action list, in the given
509 // environment. The list of action will be consumed
510 // starting from the first element. When the function returns
511 // the list should be empty.
513 MovieClip::execute_actions(MovieClip::ActionList
& action_list
)
515 // action_list may be changed due to actions (appended-to)
516 // This loop is probably quicker than using an iterator
517 // and a final call to .clear(), as repeated calls to
518 // .size() or .end() are no quicker (and probably slower)
519 // than pop_front(), which is constant time.
520 while (!action_list
.empty()) {
521 const action_buffer
* ab
= action_list
.front();
522 action_list
.pop_front();
529 MovieClip::getDisplayObjectAtDepth(int depth
)
531 return _displayList
.getDisplayObjectAtDepth(depth
);
534 /// This handles special properties of MovieClip.
536 /// The only genuine special properties are DisplayList members. These
537 /// are accessible as properties and are enumerated, but not ownProperties
540 /// The TextField variables should probably be handled in a more generic
543 MovieClip::getTextFieldVariables(const ObjectURI
& uri
, as_value
& val
)
545 // Try textfield variables
546 TextFields
* etc
= textfieldVar(_text_variables
.get(), uri
);
548 for (TextFields::const_iterator i
=etc
->begin(), e
=etc
->end();
552 if (tf
->getTextDefined()) {
553 val
= tf
->get_text_value();
562 MovieClip::get_frame_number(const as_value
& frame_spec
, size_t& frameno
) const
564 // If there is no definition, this is a dynamically-created MovieClip
565 // and has no frames.
566 if (!_def
) return false;
568 std::string fspecStr
= frame_spec
.to_string();
570 as_value
str(fspecStr
);
572 const double num
= toNumber(str
, getVM(*getObject(this)));
574 if (!isFinite(num
) || int(num
) != num
|| num
== 0) {
575 bool ret
= _def
->get_labeled_frame(fspecStr
, frameno
);
579 if (num
< 0) return false;
581 // all frame numbers > 0 are valid, but a valid frame number may still
582 // reference a non-exist frame(eg. frameno > total_frames).
583 frameno
= size_t(num
) - 1;
588 /// Execute the actions for the specified frame.
590 /// The frame_spec could be an integer or a string.
593 MovieClip::call_frame_actions(const as_value
& frame_spec
)
595 // If there is no definition, this is a dynamically-created MovieClip
596 // and has no frames.
600 if (!get_frame_number(frame_spec
, frame_number
)) {
602 IF_VERBOSE_ASCODING_ERRORS(
603 log_aserror(_("call_frame('%s') -- invalid frame"),
609 // Execute the ControlTag actions
610 // We set _callingFrameActions to true so that add_action_buffer
611 // will execute immediately instead of queuing them.
612 // NOTE: in case gotoFrame is executed by code in the called frame
613 // we'll temporarly clear the _callingFrameActions flag
614 // to properly queue actions back on the global queue.
616 _callingFrameActions
= true;
617 const PlayList
* playlist
= _def
->getPlaylist(frame_number
);
619 PlayList::const_iterator it
= playlist
->begin();
620 const PlayList::const_iterator e
= playlist
->end();
621 for (; it
!= e
; it
++) {
622 (*it
)->executeActions(this, _displayList
);
625 _callingFrameActions
= false;
630 MovieClip::addDisplayListObject(DisplayObject
* obj
, int depth
)
632 // TODO: only call set_invalidated if this DisplayObject actually overrides
635 _displayList
.placeDisplayObject(obj
, depth
);
642 MovieClip::duplicateMovieClip(const std::string
& newname
, int depth
,
643 as_object
* initObject
)
645 DisplayObject
* parent_ch
= parent();
647 IF_VERBOSE_ASCODING_ERRORS(
648 log_aserror(_("Can't clone root of the movie"));
653 MovieClip
* parent
= parent_ch
->to_movie();
655 IF_VERBOSE_ASCODING_ERRORS(
656 log_error(_("%s parent is not a movieclip, can't clone"),
662 as_object
* o
= getObjectWithPrototype(getGlobal(*getObject(this)),
663 NSV::CLASS_MOVIE_CLIP
);
665 MovieClip
* newmovieclip
= new MovieClip(o
, _def
.get(), _swf
, parent
);
667 const ObjectURI
& nn
= getURI(getVM(*getObject(this)), newname
);
668 newmovieclip
->set_name(nn
);
670 newmovieclip
->setDynamic();
672 // Copy event handlers from movieclip
673 // We should not copy 'm_action_buffer' since the
674 // 'm_method' already contains it
675 newmovieclip
->set_event_handlers(get_event_handlers());
678 newmovieclip
->_drawable
= _drawable
;
680 newmovieclip
->setCxForm(getCxForm(*this));
681 newmovieclip
->setMatrix(getMatrix(*this), true);
682 newmovieclip
->set_ratio(get_ratio());
683 newmovieclip
->set_clip_depth(get_clip_depth());
685 parent
->_displayList
.placeDisplayObject(newmovieclip
, depth
);
686 newmovieclip
->construct(initObject
);
692 MovieClip::queueAction(const action_buffer
& action
)
694 stage().pushAction(action
, this);
698 MovieClip::notifyEvent(const event_id
& id
)
701 log_debug(_("Event %s invoked for movieclip %s"), id
, getTarget());
704 // We do not execute ENTER_FRAME if unloaded
705 if (id
.id() == event_id::ENTER_FRAME
&& unloaded()) {
707 log_debug(_("Sprite %s ignored ENTER_FRAME event (is unloaded)"), getTarget());
712 if (isButtonEvent(id
) && !isEnabled()) {
714 log_debug(_("Sprite %s ignored button-like event %s as not 'enabled'"),
720 std::auto_ptr
<ExecutableCode
> code (get_event_handler(id
));
726 // user-defined onInitialize is never called
727 if (id
.id() == event_id::INITIALIZE
) return;
729 // NOTE: user-defined onLoad is not invoked for static
730 // clips on which no clip-events are defined.
731 // see testsuite/misc-ming.all/action_execution_order_extend_test.swf
733 // Note that this can't be true for movieclips
734 // not placed by PlaceObject, see
735 // testsuite/misc-ming.all/registerClassTest.swf
737 // Note that this is also not true for movieclips which have
738 // a registered class on them, see
739 // testsuite/misc-ming.all/registerClassTest2.swf
741 // TODO: test the case in which it's MovieClip.prototype.onLoad
743 if (id
.id() == event_id::LOAD
) {
745 // TODO: we're likely making too much noise for nothing here,
746 // there must be some action-execution-order related problem instead....
747 // See testsuite/misc-ming.all/registerClassTest2.swf for an onLoad
748 // execution order related problem ...
750 // we don't skip calling user-defined onLoad for top-level movies
751 if (!parent()) break;
752 // nor if there are clip-defined handler
753 if (!get_event_handlers().empty()) break;
754 // nor if it's dynamic
755 if (isDynamic()) break;
757 const sprite_definition
* def
=
758 dynamic_cast<const sprite_definition
*>(_def
.get());
760 // must be a loaded movie (loadMovie doesn't mark it as
761 // "dynamic" - should it? no, or getBytesLoaded will always
765 // if it has a registered class it can have an onLoad
767 if (def
->getRegisteredClass()) break;
770 log_debug(_("Sprite %s (depth %d) won't check for user-defined "
771 "LOAD event (is not dynamic, has a parent, "
772 "no registered class and no clip events defined)"),
773 getTarget(), get_depth());
780 // Call the appropriate member function.
781 if (!isKeyEvent(id
)) {
782 sendEvent(*getObject(this), get_environment(), id
.functionURI());
788 MovieClip::pathElement(const ObjectURI
& uri
)
790 as_object
* obj
= DisplayObject::pathElement(uri
);
793 // See if we have a match on the display list.
794 obj
= getObject(getDisplayListObject(uri
));
797 obj
= getObject(this);
800 // See if it's a member
802 if (!obj
->as_object::get_member(uri
, &tmp
)) return 0;
803 if (!tmp
.is_object()) return 0;
805 if (tmp
.is_sprite()) {
806 return getObject(tmp
.toDisplayObject(true));
809 return toObject(tmp
, getVM(*getObject(this)));
813 MovieClip::setTextFieldVariables(const ObjectURI
& uri
, const as_value
& val
)
815 // Try textfield variables
816 TextFields
* etc
= textfieldVar(_text_variables
.get(), uri
);
818 if (!etc
) return false;
820 for (TextFields::iterator i
=etc
->begin(), e
=etc
->end(); i
!=e
; ++i
) {
821 (*i
)->updateText(val
.to_string(getSWFVersion(*getObject(this))));
826 /// Remove the 'contents' of the MovieClip, but leave properties and
827 /// event handlers intact.
829 MovieClip::unloadMovie()
831 LOG_ONCE(log_unimpl("MovieClip.unloadMovie()"));
834 // child movieclip advance
839 log_debug(_("Advance movieclip '%s' at frame %u/%u"),
840 getTargetPath(), _currentFrame
,
846 // call_frame should never trigger advance_movieclip
847 assert(!_callingFrameActions
);
849 // We might have loaded NO frames !
850 if (get_loaded_frames() == 0) {
851 IF_VERBOSE_MALFORMED_SWF(
852 LOG_ONCE( log_swferror(_("advance_movieclip: no frames loaded "
853 "for movieclip/movie %s"), getTarget()) );
858 // Process any pending loadVariables request
859 processCompletedLoadVariableRequests();
862 size_t frame_count
= _def
->get_frame_count();
864 log_debug(_("Advance_movieclip for movieclip '%s' - frame %u/%u "),
865 getTarget(), _currentFrame
,
869 // I'm not sure ENTERFRAME goes in a different queue then DOACTION...
870 queueEvent(event_id(event_id::ENTER_FRAME
), movie_root::PRIORITY_DOACTION
);
872 // Update current and next frames.
873 if (_playState
== PLAYSTATE_PLAY
) {
875 log_debug(_("MovieClip::advance_movieclip we're in PLAYSTATE_PLAY mode"));
878 const size_t prev_frame
= _currentFrame
;
881 log_debug(_("on_event_load called, incrementing"));
883 increment_frame_and_check_for_loop();
885 log_debug(_("after increment we are at frame %u/%u"), _currentFrame
, frame_count
);
888 // Execute the current frame's tags.
889 // First time executeFrameTags(0) executed in dlist.cpp(child) or
890 // SWFMovieDefinition(root)
891 if (_currentFrame
!= prev_frame
) {
893 if (_currentFrame
== 0 && _hasLooped
) {
895 log_debug(_("Jumping back to frame 0 of movieclip %s"),
898 restoreDisplayList(0); // seems OK to me.
902 log_debug(_("Executing frame%d (0-based) tags of movieclip "
903 "%s"), _currentFrame
, getTarget());
905 // Make sure _currentFrame is 0-based during execution of
907 executeFrameTags(_currentFrame
, _displayList
,
908 SWF::ControlTag::TAG_DLIST
|
909 SWF::ControlTag::TAG_ACTION
);
916 log_debug(_("MovieClip::advance_movieclip we're in STOP mode"));
922 MovieClip::execute_init_action_buffer(const action_buffer
& a
, int cid
)
926 if (_swf
->initializeCharacter(cid
)) {
928 log_debug(_("Queuing init actions in frame %d of movieclip %s"),
929 _currentFrame
, getTarget());
931 std::auto_ptr
<ExecutableCode
> code(new GlobalCode(a
, this));
933 stage().pushAction(code
, movie_root::PRIORITY_INIT
);
937 log_debug(_("Init actions for DisplayObject %d already executed"), cid
);
943 MovieClip::execute_action(const action_buffer
& ab
)
945 ActionExec
exec(ab
, _environment
);
950 MovieClip::restoreDisplayList(size_t tgtFrame
)
952 // This is not tested as usable for jump-forwards (yet)...
953 // TODO: I guess just moving here the code currently in goto_frame
954 // for jump-forwards would do
955 assert(tgtFrame
<= _currentFrame
);
957 // Just invalidate this DisplayObject before jumping back.
958 // Should be optimized, but the invalidating model is not clear enough,
959 // and there are some old questions spreading the source files.
963 for (size_t f
= 0; f
< tgtFrame
; ++f
) {
965 executeFrameTags(f
, tmplist
, SWF::ControlTag::TAG_DLIST
);
968 // Execute both action tags and DLIST tags of the target frame
969 _currentFrame
= tgtFrame
;
970 executeFrameTags(tgtFrame
, tmplist
, SWF::ControlTag::TAG_DLIST
|
971 SWF::ControlTag::TAG_ACTION
);
973 _displayList
.mergeDisplayList(tmplist
);
976 // 0-based frame number !
978 MovieClip::executeFrameTags(size_t frame
, DisplayList
& dlist
, int typeflags
)
980 // If there is no definition, this is a dynamically-created MovieClip
981 // and has no frames.
986 const PlayList
* playlist
= _def
->getPlaylist(frame
);
990 // Use 1-based frame numbers
991 log_action(_("Executing %d tags in frame %d/%d of movieclip %s"),
992 playlist
->size(), frame
+ 1, get_frame_count(),
996 // Generally tags should be executed in the order they are found in.
997 for (PlayList::const_iterator it
= playlist
->begin(),
998 e
= playlist
->end(); it
!= e
; ++it
) {
1000 if (typeflags
& SWF::ControlTag::TAG_DLIST
) {
1001 (*it
)->executeState(this, dlist
);
1004 if (typeflags
& SWF::ControlTag::TAG_ACTION
) {
1005 (*it
)->executeActions(this, _displayList
);
1012 MovieClip::goto_frame(size_t target_frame_number
)
1014 #if defined(DEBUG_GOTOFRAME) || defined(GNASH_DEBUG_TIMELINE)
1015 log_debug(_("movieclip %s ::goto_frame(%d) - current frame is %d"),
1016 getTargetPath(), target_frame_number
, _currentFrame
);
1019 // goto_frame stops by default.
1020 // ActionGotoFrame tells the movieClip to go to the target frame
1021 // and stop at that frame.
1022 setPlayState(PLAYSTATE_STOP
);
1024 if (target_frame_number
> _def
->get_frame_count() - 1) {
1026 target_frame_number
= _def
->get_frame_count() - 1;
1028 if (!_def
->ensure_frame_loaded(target_frame_number
+ 1)) {
1029 log_error(_("Target frame of a gotoFrame(%d) was never loaded,"
1030 "although frame count in header (%d) said we "
1031 "should have found it"),
1032 target_frame_number
+1, _def
->get_frame_count());
1036 // Just set _currentframe and return.
1037 _currentFrame
= target_frame_number
;
1039 // don't push actions, already tested.
1043 if (target_frame_number
== _currentFrame
) {
1044 // don't push actions
1048 // Unless the target frame is the next one, stop playback of soundstream
1049 if (target_frame_number
!= _currentFrame
+ 1) {
1053 const size_t loaded_frames
= get_loaded_frames();
1055 // target_frame_number is 0-based, get_loaded_frames() is 1-based
1056 // so in order to goto_frame(3) loaded_frames must be at least 4
1057 // if goto_frame(4) is called, and loaded_frames is 4 we're jumping
1059 if (target_frame_number
>= loaded_frames
) {
1060 IF_VERBOSE_ASCODING_ERRORS(
1061 log_aserror(_("GotoFrame(%d) targets a yet "
1062 "to be loaded frame (%d) loaded). "
1063 "We'll wait for it but a more correct form "
1064 "is explicitly using WaitForFrame instead"),
1065 target_frame_number
+1,
1069 if (!_def
->ensure_frame_loaded(target_frame_number
+ 1)) {
1070 log_error(_("Target frame of a gotoFrame(%d) was never loaded, "
1071 "although frame count in header (%d) said we should"
1073 target_frame_number
+ 1, _def
->get_frame_count());
1078 // Construct the DisplayList of the target frame
1079 if (target_frame_number
< _currentFrame
) {
1081 // Go backward to a previous frame
1082 // NOTE: just in case we're being called by code in a called frame
1083 // we'll backup and resume the _callingFrameActions flag
1084 bool callingFrameActionsBackup
= _callingFrameActions
;
1085 _callingFrameActions
= false;
1087 // restoreDisplayList takes care of properly setting the
1088 // _currentFrame variable
1089 restoreDisplayList(target_frame_number
);
1090 assert(_currentFrame
== target_frame_number
);
1091 _callingFrameActions
= callingFrameActionsBackup
;
1094 // Go forward to a later frame
1095 // We'd immediately return if target_frame_number == _currentFrame
1096 assert(target_frame_number
> _currentFrame
);
1097 while (++_currentFrame
< target_frame_number
) {
1098 //for (size_t f = _currentFrame+1; f<target_frame_number; ++f)
1099 // Second argument requests that only "DisplayList" tags
1100 // are executed. This means NO actions will be
1101 // pushed on m_action_list.
1102 executeFrameTags(_currentFrame
, _displayList
,
1103 SWF::ControlTag::TAG_DLIST
);
1105 assert(_currentFrame
== target_frame_number
);
1107 // Now execute target frame tags (queuing actions)
1108 // NOTE: just in case we're being called by code in a called frame
1109 // we'll backup and resume the _callingFrameActions flag
1110 bool callingFrameActionsBackup
= _callingFrameActions
;
1111 _callingFrameActions
= false;
1112 executeFrameTags(target_frame_number
, _displayList
,
1113 SWF::ControlTag::TAG_DLIST
| SWF::ControlTag::TAG_ACTION
);
1114 _callingFrameActions
= callingFrameActionsBackup
;
1117 assert(_currentFrame
== target_frame_number
);
1121 MovieClip::goto_labeled_frame(const std::string
& label
)
1123 // If there is no definition, this is a dynamically-created MovieClip
1124 // and has no frames. (We are also probably not called in this case).
1125 if (!_def
) return false;
1127 size_t target_frame
;
1128 if (_def
->get_labeled_frame(label
, target_frame
)) {
1129 goto_frame(target_frame
);
1133 IF_VERBOSE_MALFORMED_SWF(
1134 log_swferror(_("MovieClip::goto_labeled_frame('%s') "
1135 "unknown label"), label
);
1141 MovieClip::draw(Renderer
& renderer
, const Transform
& xform
)
1143 const DisplayObject::MaskRenderer
mr(renderer
, *this);
1145 _drawable
.finalize();
1146 _drawable
.display(renderer
, xform
);
1147 _displayList
.display(renderer
, xform
);
1151 MovieClip::display(Renderer
& renderer
, const Transform
& base
)
1153 // Note: DisplayList::display() will take care of the visibility checking.
1155 // Whether a DisplayObject should be rendered or not is dependent
1156 // on its parent: i.e. if its parent is a mask, this DisplayObject
1157 // should be rendered to the mask buffer even it is invisible.
1159 // Draw everything with our own transform.
1160 const Transform xform
= base
* transform();
1161 draw(renderer
, xform
);
1162 clear_invalidated();
1165 void MovieClip::omit_display()
1167 if (childInvalidated()) _displayList
.omit_display();
1168 clear_invalidated();
1172 MovieClip::attachCharacter(DisplayObject
& newch
, int depth
, as_object
* initObj
)
1174 _displayList
.placeDisplayObject(&newch
, depth
);
1175 newch
.construct(initObj
);
1179 MovieClip::add_display_object(const SWF::PlaceObject2Tag
* tag
,
1182 // If this MovieClip has no definition, it should also have no ControlTags,
1183 // and this shouldn't be called.
1187 SWF::DefinitionTag
* cdef
= _def
->getDefinitionTag(tag
->getID());
1189 IF_VERBOSE_MALFORMED_SWF(
1190 log_swferror(_("MovieClip::add_display_object(): "
1191 "unknown cid = %d"), tag
->getID());
1196 DisplayObject
* existing_char
= dlist
.getDisplayObjectAtDepth(tag
->getDepth());
1198 if (existing_char
) return NULL
;
1200 Global_as
& gl
= getGlobal(*getObject(this));
1201 VM
& vm
= getVM(*getObject(this));
1202 DisplayObject
* ch
= cdef
->createDisplayObject(gl
, this);
1204 if (tag
->hasName()) ch
->set_name(getURI(vm
, tag
->getName()));
1205 else if (isReferenceable(*ch
)) {
1206 const ObjectURI
& instance_name
= getNextUnnamedInstanceName();
1207 ch
->set_name(instance_name
);
1210 if (tag
->hasBlendMode()) {
1211 boost::uint8_t bm
= tag
->getBlendMode();
1212 ch
->setBlendMode(static_cast<DisplayObject::BlendMode
>(bm
));
1215 // Attach event handlers (if any).
1216 const SWF::PlaceObject2Tag::EventHandlers
& event_handlers
=
1217 tag
->getEventHandlers();
1219 for (size_t i
= 0, n
= event_handlers
.size(); i
< n
; ++i
) {
1220 const swf_event
& ev
= event_handlers
[i
];
1221 ch
->add_event_handler(ev
.event(), ev
.action());
1224 // TODO: check if we should check those has_xxx flags first.
1225 ch
->setCxForm(tag
->getCxform());
1226 ch
->setMatrix(tag
->getMatrix(), true); // update caches
1227 ch
->set_ratio(tag
->getRatio());
1228 ch
->set_clip_depth(tag
->getClipDepth());
1230 dlist
.placeDisplayObject(ch
, tag
->getDepth());
1236 MovieClip::move_display_object(const SWF::PlaceObject2Tag
* tag
, DisplayList
& dlist
)
1238 boost::uint16_t ratio
= tag
->getRatio();
1239 // clip_depth is not used in MOVE tag(at least no related tests).
1240 dlist
.moveDisplayObject(
1242 tag
->hasCxform() ? &tag
->getCxform() : NULL
,
1243 tag
->hasMatrix() ? &tag
->getMatrix() : NULL
,
1244 tag
->hasRatio() ? &ratio
: NULL
);
1248 MovieClip::replace_display_object(const SWF::PlaceObject2Tag
* tag
,
1251 // A MovieClip without a definition cannot have any ControlTags, so this
1252 // should not be called.
1254 assert(tag
!= NULL
);
1256 const boost::uint16_t id
= tag
->getID();
1258 SWF::DefinitionTag
* cdef
= _def
->getDefinitionTag(id
);
1260 log_error(_("movieclip::replace_display_object(): "
1261 "unknown cid = %d"), id
);
1266 DisplayObject
* existing_char
= dlist
.getDisplayObjectAtDepth(tag
->getDepth());
1268 if (!existing_char
) {
1269 log_error(_("MovieClip::replace_display_object: could not "
1270 "find any DisplayObject at depth %d"), tag
->getDepth());
1274 // if the existing DisplayObject is not a shape, move it instead
1276 if (isReferenceable(*existing_char
)) {
1277 move_display_object(tag
, dlist
);
1281 Global_as
& gl
= getGlobal(*getObject(this));
1282 DisplayObject
* ch
= cdef
->createDisplayObject(gl
, this);
1285 // TODO: check if we can drop this for REPLACE!
1286 // should we rename the DisplayObject when it's REPLACE tag?
1287 if (tag
->hasName()) {
1288 VM
& vm
= getVM(*getObject(this));
1289 ch
->set_name(getURI(vm
, tag
->getName()));
1291 else if (isReferenceable(*ch
)) {
1292 ch
->set_name(getNextUnnamedInstanceName());
1294 if (tag
->hasRatio()) {
1295 ch
->set_ratio(tag
->getRatio());
1297 if (tag
->hasCxform()) {
1298 ch
->setCxForm(tag
->getCxform());
1300 if (tag
->hasMatrix()) {
1301 ch
->setMatrix(tag
->getMatrix(), true);
1304 // use SWFMatrix from the old DisplayObject if tag doesn't provide one.
1305 dlist
.replaceDisplayObject(ch
, tag
->getDepth(),
1306 !tag
->hasCxform(), !tag
->hasMatrix());
1311 MovieClip::remove_display_object(const SWF::PlaceObject2Tag
* tag
,
1315 dlist
.removeDisplayObject(tag
->getDepth());
1319 MovieClip::remove_display_object(int depth
, int)
1322 _displayList
.removeDisplayObject(depth
);
1326 MovieClip::increment_frame_and_check_for_loop()
1328 const size_t frame_count
= get_loaded_frames();
1329 if (++_currentFrame
>= frame_count
) {
1337 MovieClip::handleFocus()
1339 as_object
* obj
= getObject(this);
1342 // For SWF6 and above: the MovieClip can always receive focus if
1343 // focusEnabled evaluates to true.
1344 if (getSWFVersion(*obj
) > 5) {
1345 as_value focusEnabled
;
1346 if (obj
->get_member(NSV::PROP_FOCUS_ENABLED
, &focusEnabled
)) {
1347 if (toBool(focusEnabled
, getVM(*obj
))) return true;
1351 // If focusEnabled doesn't evaluate to true or for SWF5, return true
1352 // only if at least one mouse event handler is defined.
1353 return mouseEnabled();
1357 MovieClip::pointInShape(boost::int32_t x
, boost::int32_t y
) const
1359 ShapeContainerFinder
finder(x
, y
);
1360 _displayList
.visitBackward(finder
);
1361 if ( finder
.hitFound() ) return true;
1362 return hitTestDrawable(x
, y
);
1366 MovieClip::pointInVisibleShape(boost::int32_t x
, boost::int32_t y
) const
1368 if (! visible()) return false;
1369 if (isDynamicMask() && ! mouseEnabled()) {
1370 // see testsuite/misc-ming.all/masks_test.swf
1371 #ifdef GNASH_DEBUG_HITTEST
1372 log_debug(_("%s is a dynamic mask and can't handle mouse "
1373 "events, no point will hit it"), getTarget());
1377 const DisplayObject
* mask
= getMask(); // dynamic one
1378 if (mask
&& mask
->visible() && !mask
->pointInShape(x
, y
)) {
1379 #ifdef GNASH_DEBUG_HITTEST
1380 log_debug(_("%s is dynamically masked by %s, which "
1381 "doesn't hit point %g,%g"), getTarget(),
1382 mask
->getTarget(), x
, y
);
1386 VisibleShapeContainerFinder
finder(x
, y
);
1387 _displayList
.visitBackward(finder
);
1388 if (finder
.hitFound()) return true;
1389 return hitTestDrawable(x
, y
);
1393 MovieClip::hitTestDrawable(boost::int32_t x
, boost::int32_t y
) const
1395 const SWFMatrix wm
= getWorldMatrix(*this).invert();
1398 if (!_drawable
.getBounds().point_test(lp
.x
, lp
.y
)) return false;
1399 return _drawable
.pointTestLocal(lp
.x
, lp
.y
, wm
);
1403 MovieClip::pointInHitableShape(boost::int32_t x
, boost::int32_t y
) const
1405 if (isDynamicMask() && !mouseEnabled()) return false;
1407 const DisplayObject
* mask
= getMask();
1408 if (mask
&& !mask
->pointInShape(x
, y
)) return false;
1410 HitableShapeContainerFinder
finder(x
, y
);
1411 _displayList
.visitBackward(finder
);
1412 if (finder
.hitFound()) return true;
1414 return hitTestDrawable(x
, y
);
1418 MovieClip::topmostMouseEntity(boost::int32_t x
, boost::int32_t y
)
1420 if (!visible()) return 0;
1422 // point is in parent's space, we need to convert it in world space
1424 DisplayObject
* p
= parent();
1426 // WARNING: if we have NO parent, our parent is the Stage (movie_root)
1427 // so, in case we'll add a "stage" matrix, we'll need to take
1428 // it into account here.
1429 // TODO: actually, why are we insisting in using parent's
1430 // coordinates for this method at all ?
1431 getWorldMatrix(*p
).transform(wp
);
1434 if (mouseEnabled()) {
1435 if (pointInVisibleShape(wp
.x
, wp
.y
)) return this;
1439 SWFMatrix m
= getMatrix(*this);
1444 MouseEntityFinder
finder(wp
, pp
);
1445 _displayList
.visitAll(finder
);
1446 InteractiveObject
* ch
= finder
.getEntity();
1448 // It doesn't make any sense to query _drawable, as it's
1449 // not an InteractiveObject.
1453 const DisplayObject
*
1454 MovieClip::findDropTarget(boost::int32_t x
, boost::int32_t y
,
1455 DisplayObject
* dragging
) const
1457 if (this == dragging
) return 0; // not here...
1459 if (!visible()) return 0; // isn't me !
1461 DropTargetFinder
finder(x
, y
, dragging
);
1462 _displayList
.visitAll(finder
);
1464 // does it hit any child ?
1465 const DisplayObject
* ch
= finder
.getDropChar();
1467 // TODO: find closest actionscript referenceable container
1468 // (possibly itself)
1473 if (hitTestDrawable(x
, y
)) return this;
1479 MovieClip::trackAsMenu()
1481 as_object
* obj
= getObject(this);
1485 VM
& vm
= getVM(*obj
);
1486 // TODO: use namedStrings here
1487 return (obj
->get_member(getURI(vm
, "trackAsMenu"), &track
) &&
1492 MovieClip::mouseEnabled() const
1494 if (!isEnabled()) return false;
1496 // Event handlers that qualify as mouse event handlers.
1497 static const event_id EH
[] = {
1498 event_id(event_id::PRESS
),
1499 event_id(event_id::RELEASE
),
1500 event_id(event_id::RELEASE_OUTSIDE
),
1501 event_id(event_id::ROLL_OVER
),
1502 event_id(event_id::ROLL_OUT
),
1503 event_id(event_id::DRAG_OVER
),
1504 event_id(event_id::DRAG_OUT
),
1507 const size_t size
= arraySize(EH
);
1509 for (size_t i
= 0; i
< size
; ++i
) {
1510 const event_id
&event
= EH
[i
];
1512 // Check event handlers
1513 if (hasEventHandler(event_id(event
.id()))) {
1521 MovieClip::stop_drag()
1523 stage().stop_drag();
1527 MovieClip::set_background_color(const rgba
& color
)
1529 stage().set_background_color(color
);
1533 MovieClip::cleanup_textfield_variables()
1536 if (!_text_variables
.get()) return;
1538 TextFieldIndex
& m
= *_text_variables
;
1540 for (TextFieldIndex::iterator i
=m
.begin(), ie
=m
.end(); i
!=ie
; ++i
)
1542 TextFields
& v
=i
->second
;
1543 TextFields::iterator lastValid
= std::remove_if(v
.begin(), v
.end(),
1544 boost::mem_fn(&DisplayObject::unloaded
));
1545 v
.erase(lastValid
, v
.end());
1551 MovieClip::set_textfield_variable(const ObjectURI
& name
, TextField
* ch
)
1556 if (!_text_variables
.get()) {
1557 _text_variables
.reset(new TextFieldIndex
);
1560 (*_text_variables
)[name
].push_back(ch
);
1564 MovieClip::getDisplayListObject(const ObjectURI
& uri
)
1566 as_object
* obj
= getObject(this);
1569 string_table
& st
= getStringTable(*obj
);
1571 // Try items on our display list.
1572 DisplayObject
* ch
= _displayList
.getDisplayObjectByName(st
, uri
,
1579 // If the object is an ActionScript referenciable one we
1580 // return it, otherwise we return ourselves
1581 if (isReferenceable(*ch
)) {
1588 MovieClip::add_invalidated_bounds(InvalidatedRanges
& ranges
, bool force
)
1590 // nothing to do if this movieclip is not visible
1591 if (!visible() || invisible(getCxForm(*this))) {
1592 ranges
.add(m_old_invalidated_ranges
);
1596 if (!invalidated() && !childInvalidated() && !force
) return;
1598 // m_child_invalidated does not require our own bounds
1599 if (invalidated() || force
) {
1600 // Add old invalidated bounds
1601 ranges
.add(m_old_invalidated_ranges
);
1604 _displayList
.add_invalidated_bounds(ranges
, force
|| invalidated());
1608 bounds
.expand_to_transformed_rect(getWorldMatrix(*this),
1609 _drawable
.getBounds());
1611 ranges
.add(bounds
.getRange());
1616 MovieClip::constructAsScriptObject()
1618 as_object
* mc
= getObject(this);
1620 // A MovieClip should always have an associated object.
1624 mc
->init_member("$version", getVM(*mc
).getPlayerVersion(), 0);
1627 const sprite_definition
* def
=
1628 dynamic_cast<const sprite_definition
*>(_def
.get());
1630 // We won't "construct" top-level movies
1631 as_function
* ctor
= def
? def
->getRegisteredClass() : 0;
1634 log_debug(_("Attached movieclips %s registered class is %p"),
1635 getTarget(), (void*)ctor
);
1638 // Set this MovieClip object to be an instance of the class.
1640 Property
* proto
= ctor
->getOwnProperty(NSV::PROP_PROTOTYPE
);
1641 if (proto
) mc
->set_prototype(proto
->getValue(*ctor
));
1644 // Send the construct event. This must be done after the __proto__
1645 // member is set. It is always done.
1646 notifyEvent(event_id(event_id::CONSTRUCT
));
1649 const int swfversion
= getSWFVersion(*mc
);
1650 if (swfversion
> 5) {
1652 ctor
->construct(*mc
, get_environment(), args
);
1658 MovieClip::construct(as_object
* initObj
)
1660 assert(!unloaded());
1662 saveOriginalTarget();
1665 log_debug(_("Sprite '%s' placed on stage"), getTarget());
1668 // Register this movieclip as a live one
1669 stage().addLiveChar(this);
1671 // It seems it's legal to place 0-framed movieclips on stage.
1672 // See testsuite/misc-swfmill.all/zeroframe_definemovieclip.swf
1674 // Now execute frame tags and take care of queuing the LOAD event.
1676 // DLIST tags are executed immediately while ACTION tags are queued.
1678 // For _root movie, LOAD event is invoked *after* actions in first frame
1679 // See misc-ming.all/action_execution_order_test4.{c,swf}
1681 assert(!_callingFrameActions
); // or will not be queuing actions
1684 executeFrameTags(0, _displayList
, SWF::ControlTag::TAG_DLIST
|
1685 SWF::ControlTag::TAG_ACTION
);
1687 if (getSWFVersion(*getObject(this)) > 5) {
1688 queueEvent(event_id(event_id::LOAD
),
1689 movie_root::PRIORITY_DOACTION
);
1694 queueEvent(event_id(event_id::LOAD
), movie_root::PRIORITY_DOACTION
);
1695 executeFrameTags(0, _displayList
, SWF::ControlTag::TAG_DLIST
|
1696 SWF::ControlTag::TAG_ACTION
);
1699 as_object
* mc
= getObject(this);
1701 // A MovieClip should always have an associated object.
1704 // We execute events immediately when the stage-placed DisplayObject
1705 // is dynamic, This is becase we assume that this means that
1706 // the DisplayObject is placed during processing of actions (opposed
1707 // that during advancement iteration).
1709 // A more general implementation might ask movie_root about its state
1710 // (iterating or processing actions?)
1711 // Another possibility to inspect could be letting movie_root decide
1712 // when to really queue and when rather to execute immediately the
1713 // events with priority INITIALIZE or CONSTRUCT ...
1717 log_debug(_("Queuing INITIALIZE and CONSTRUCT events for movieclip %s"),
1721 std::auto_ptr
<ExecutableCode
> code(new ConstructEvent(this));
1722 stage().pushAction(code
, movie_root::PRIORITY_CONSTRUCT
);
1727 // Properties from an initObj must be copied before construction, but
1728 // after the display list has been populated, so that _height and
1729 // _width (which depend on bounds) are correct.
1731 mc
->copyProperties(*initObj
);
1733 constructAsScriptObject();
1736 // Tested in testsuite/swfdec/duplicateMovieclip-events.c and
1737 // testsuite/swfdec/clone-sprite-events.c not to call notifyEvent
1739 queueEvent(event_id(event_id::INITIALIZE
), movie_root::PRIORITY_INIT
);
1743 MovieClip::unloadChildren()
1746 log_debug(_("Unloading movieclip '%s'"), getTargetPath());
1749 // stop any pending streaming sounds
1752 // We won't be displayed again, so worth releasing
1753 // some memory. The drawable might take a lot of memory
1757 return _displayList
.unload();
1761 MovieClip::getLoadedMovie(Movie
* extern_movie
)
1763 DisplayObject
* p
= parent();
1765 extern_movie
->set_parent(p
);
1767 // Copy own lockroot value
1768 extern_movie
->setLockRoot(getLockRoot());
1770 // Copy own event handlers
1771 // see testsuite/misc-ming.all/loadMovieTest.swf
1772 const Events
& clipEvs
= get_event_handlers();
1773 // top-level movies can't have clip events, right ?
1774 assert (extern_movie
->get_event_handlers().empty());
1775 extern_movie
->set_event_handlers(clipEvs
);
1778 // TODO: check empty != none...
1779 const ObjectURI
& name
= get_name();
1780 if (!name
.empty()) extern_movie
->set_name(name
);
1782 // Copy own clip depth (TODO: check this)
1783 extern_movie
->set_clip_depth(get_clip_depth());
1785 // Replace ourselves in parent
1786 // TODO: don't pretend our parent is a MovieClip,
1787 // could as well be a button I guess...
1788 // At most we should require it to be a
1789 // DisplayObjectContainer and log an error if it's not.
1790 MovieClip
* parent_sp
= p
->to_movie();
1792 parent_sp
->_displayList
.replaceDisplayObject(extern_movie
, get_depth(),
1794 extern_movie
->construct();
1797 // replaceLevel will set depth for us
1798 stage().replaceLevel(get_depth() - DisplayObject::staticDepthOffset
,
1804 MovieClip::loadVariables(const std::string
& urlstr
,
1805 VariablesMethod sendVarsMethod
)
1807 // Host security check will be will be done by LoadVariablesThread
1808 // (down by getStream, that is)
1810 const movie_root
& mr
= stage();
1811 URL
url(urlstr
, mr
.runResources().streamProvider().baseURL());
1813 std::string postdata
;
1815 // Encode our vars for sending.
1816 if (sendVarsMethod
!= METHOD_NONE
) {
1817 postdata
= getURLEncodedVars(*getObject(this));
1821 const StreamProvider
& sp
=
1822 getRunResources(*getObject(this)).streamProvider();
1824 if (sendVarsMethod
== METHOD_POST
) {
1826 _loadVariableRequests
.push_back(
1827 new LoadVariablesThread(sp
, url
, postdata
));
1831 if (sendVarsMethod
== METHOD_GET
) {
1833 std::string qs
= url
.querystring();
1834 if (qs
.empty()) url
.set_querystring(postdata
);
1835 else url
.set_querystring(qs
+ "&" + postdata
);
1837 _loadVariableRequests
.push_back(new LoadVariablesThread(sp
, url
));
1839 _loadVariableRequests
.back().process();
1841 catch (const NetworkException
& ex
) {
1842 log_error(_("Could not load variables from %s"), url
.str());
1847 MovieClip::processCompletedLoadVariableRequest(LoadVariablesThread
& request
)
1849 assert(request
.completed());
1851 MovieVariables
& vals
= request
.getValues();
1854 // We want to call a clip-event too if available, see bug #22116
1855 notifyEvent(event_id(event_id::DATA
));
1859 MovieClip::processCompletedLoadVariableRequests()
1861 // Nothing to do (just for clarity)
1862 if (_loadVariableRequests
.empty()) return;
1864 for (LoadVariablesThreads::iterator it
=_loadVariableRequests
.begin();
1865 it
!= _loadVariableRequests
.end();) {
1867 LoadVariablesThread
& request
= *it
;
1868 if (request
.completed()) {
1869 processCompletedLoadVariableRequest(request
);
1870 it
= _loadVariableRequests
.erase(it
);
1877 MovieClip::setVariables(const MovieVariables
& vars
)
1879 VM
& vm
= getVM(*getObject(this));
1880 for (MovieVariables::const_iterator it
=vars
.begin(), itEnd
=vars
.end();
1881 it
!= itEnd
; ++it
) {
1883 const std::string
& name
= it
->first
;
1884 const std::string
& val
= it
->second
;
1885 getObject(this)->set_member(getURI(vm
, name
), val
);
1890 MovieClip::removeMovieClip()
1892 const int depth
= get_depth();
1893 if (depth
< 0 || depth
> 1048575) {
1894 IF_VERBOSE_ASCODING_ERRORS(
1895 log_aserror(_("removeMovieClip(%s): movieclip depth (%d) out of "
1896 "the 'dynamic' zone [0..1048575], won't remove"),
1897 getTarget(), depth
);
1902 MovieClip
* p
= dynamic_cast<MovieClip
*>(parent());
1904 // second argument is arbitrary, see comments above
1905 // the function declaration in MovieClip.h
1906 p
->remove_display_object(depth
, 0);
1910 stage().dropLevel(depth
);
1911 // I guess this can only happen if someone uses
1912 // _swf.swapDepth([0..1048575])
1918 MovieClip::getBounds() const
1921 BoundsFinder
f(bounds
);
1922 _displayList
.visitAll(f
);
1923 SWFRect drawableBounds
= _drawable
.getBounds();
1924 bounds
.expand_to_rect(drawableBounds
);
1930 MovieClip::isEnabled() const
1932 as_object
* obj
= getObject(this);
1936 if (!obj
->get_member(NSV::PROP_ENABLED
, &enabled
)) {
1937 // We're enabled if there's no 'enabled' member...
1940 return toBool(enabled
, getVM(*obj
));
1945 MovieClip::visitNonProperties(KeyVisitor
& v
) const
1947 DisplayListVisitor
dv(v
);
1948 _displayList
.visitAll(dv
);
1952 MovieClip::cleanupDisplayList()
1954 _displayList
.removeUnloaded();
1955 cleanup_textfield_variables();
1959 MovieClip::markOwnResources() const
1961 ReachableMarker marker
;
1963 _displayList
.visitAll(marker
);
1965 _environment
.markReachableResources();
1967 // Mark textfields in the TextFieldIndex
1968 if (_text_variables
.get()) {
1969 for (TextFieldIndex::const_iterator i
=_text_variables
->begin(),
1970 e
=_text_variables
->end();
1973 const TextFields
& tfs
=i
->second
;
1974 std::for_each(tfs
.begin(), tfs
.end(),
1975 boost::mem_fn(&DisplayObject::setReachable
));
1979 // Mark our relative root
1980 _swf
->setReachable();
1984 MovieClip::destroy()
1987 _displayList
.destroy();
1988 DisplayObject::destroy();
1992 MovieClip::get_root() const
1998 MovieClip::getAsRoot()
2001 // TODO1: as an optimization, if swf version < 7
2002 // we might as well just return _swf,
2003 // the whole chain from this movieclip to it's
2004 // _swf should have the same version...
2006 // TODO2: implement this with iteration rather
2010 DisplayObject
* p
= parent();
2011 if (!p
) return this; // no parent, we're the root
2013 // If we have a parent, we descend to it unless
2014 // our _lockroot is true AND our or the VM's
2015 // SWF version is > 6
2016 int topSWFVersion
= stage().getRootMovie().version();
2018 if (getDefinitionVersion() > 6 || topSWFVersion
> 6) {
2019 if (getLockRoot()) return this;
2022 return p
->getAsRoot();
2027 MovieClip::setStreamSoundId(int id
)
2029 if (id
!= m_sound_stream_id
) {
2030 log_debug(_("Stream sound id from %d to %d, stopping old"),
2031 m_sound_stream_id
, id
);
2034 m_sound_stream_id
= id
;
2038 MovieClip::stopStreamSound()
2040 if (m_sound_stream_id
== -1) return; // nothing to do
2042 sound::sound_handler
* handler
= getRunResources(*getObject(this)).soundHandler();
2044 handler
->stop_sound(m_sound_stream_id
);
2047 m_sound_stream_id
= -1;
2051 MovieClip::setPlayState(PlayState s
)
2053 if (s
== _playState
) return; // nothing to do
2054 if (s
== PLAYSTATE_STOP
) stopStreamSound();
2060 MovieClip::TextFields
*
2061 textfieldVar(MovieClip::TextFieldIndex
* t
, const ObjectURI
& name
)
2063 // nothing allocated yet...
2066 // TODO: should variable name be considered case-insensitive ?
2067 MovieClip::TextFieldIndex::iterator it
= t
->find(name
);
2068 if (it
== t
->end()) return 0;
2069 return &(it
->second
);
2072 } // unnamed namespace
2073 } // namespace gnash