1 // MovieClip.cpp: Stateful live Sprite instance, for Gnash.
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010,
4 // 2011 Free Software Foundation, Inc
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();
509 MovieClip::getDisplayObjectAtDepth(int depth
)
511 return _displayList
.getDisplayObjectAtDepth(depth
);
514 /// This handles special properties of MovieClip.
516 /// The only genuine special properties are DisplayList members. These
517 /// are accessible as properties and are enumerated, but not ownProperties
520 /// The TextField variables should probably be handled in a more generic
523 MovieClip::getTextFieldVariables(const ObjectURI
& uri
, as_value
& val
)
525 // Try textfield variables
526 TextFields
* etc
= textfieldVar(_text_variables
.get(), uri
);
528 for (TextFields::const_iterator i
=etc
->begin(), e
=etc
->end();
532 if (tf
->getTextDefined()) {
533 val
= tf
->get_text_value();
542 MovieClip::get_frame_number(const as_value
& frame_spec
, size_t& frameno
) const
544 // If there is no definition, this is a dynamically-created MovieClip
545 // and has no frames.
546 if (!_def
) return false;
548 std::string fspecStr
= frame_spec
.to_string();
550 as_value
str(fspecStr
);
552 const double num
= toNumber(str
, getVM(*getObject(this)));
554 if (!isFinite(num
) || int(num
) != num
|| num
== 0) {
555 bool ret
= _def
->get_labeled_frame(fspecStr
, frameno
);
559 if (num
< 0) return false;
561 // all frame numbers > 0 are valid, but a valid frame number may still
562 // reference a non-exist frame(eg. frameno > total_frames).
563 frameno
= size_t(num
) - 1;
568 /// Execute the actions for the specified frame.
570 /// The frame_spec could be an integer or a string.
573 MovieClip::call_frame_actions(const as_value
& frame_spec
)
575 // If there is no definition, this is a dynamically-created MovieClip
576 // and has no frames.
579 // TODO: check to see how this can be prevented.
580 if (isDestroyed()) return;
583 if (!get_frame_number(frame_spec
, frame_number
)) {
585 IF_VERBOSE_ASCODING_ERRORS(
586 log_aserror(_("call_frame('%s') -- invalid frame"),
592 // Execute the ControlTag actions
593 // We set _callingFrameActions to true so that add_action_buffer
594 // will execute immediately instead of queuing them.
595 // NOTE: in case gotoFrame is executed by code in the called frame
596 // we'll temporarly clear the _callingFrameActions flag
597 // to properly queue actions back on the global queue.
599 _callingFrameActions
= true;
600 const PlayList
* playlist
= _def
->getPlaylist(frame_number
);
602 PlayList::const_iterator it
= playlist
->begin();
603 const PlayList::const_iterator e
= playlist
->end();
604 for (; it
!= e
; it
++) {
605 (*it
)->executeActions(this, _displayList
);
608 _callingFrameActions
= false;
613 MovieClip::addDisplayListObject(DisplayObject
* obj
, int depth
)
615 // TODO: only call set_invalidated if this DisplayObject actually overrides
618 _displayList
.placeDisplayObject(obj
, depth
);
625 MovieClip::duplicateMovieClip(const std::string
& newname
, int depth
,
626 as_object
* initObject
)
628 DisplayObject
* parent_ch
= parent();
630 IF_VERBOSE_ASCODING_ERRORS(
631 log_aserror(_("Can't clone root of the movie"));
636 MovieClip
* parent
= parent_ch
->to_movie();
638 IF_VERBOSE_ASCODING_ERRORS(
639 log_error(_("%s parent is not a movieclip, can't clone"),
645 as_object
* o
= getObjectWithPrototype(getGlobal(*getObject(this)),
646 NSV::CLASS_MOVIE_CLIP
);
648 MovieClip
* newmovieclip
= new MovieClip(o
, _def
.get(), _swf
, parent
);
650 const ObjectURI
& nn
= getURI(getVM(*getObject(this)), newname
);
651 newmovieclip
->set_name(nn
);
653 newmovieclip
->setDynamic();
655 // Copy event handlers from movieclip
656 // We should not copy 'm_action_buffer' since the
657 // 'm_method' already contains it
658 newmovieclip
->set_event_handlers(get_event_handlers());
661 newmovieclip
->_drawable
= _drawable
;
663 newmovieclip
->setCxForm(getCxForm(*this));
664 newmovieclip
->setMatrix(getMatrix(*this), true);
665 newmovieclip
->set_ratio(get_ratio());
666 newmovieclip
->set_clip_depth(get_clip_depth());
668 parent
->_displayList
.placeDisplayObject(newmovieclip
, depth
);
669 newmovieclip
->construct(initObject
);
675 MovieClip::queueAction(const action_buffer
& action
)
677 stage().pushAction(action
, this);
681 MovieClip::notifyEvent(const event_id
& id
)
684 log_debug(_("Event %s invoked for movieclip %s"), id
, getTarget());
687 // We do not execute ENTER_FRAME if unloaded
688 if (id
.id() == event_id::ENTER_FRAME
&& unloaded()) {
690 log_debug(_("Sprite %s ignored ENTER_FRAME event (is unloaded)"), getTarget());
695 if (isButtonEvent(id
) && !isEnabled()) {
697 log_debug(_("Sprite %s ignored button-like event %s as not 'enabled'"),
703 std::auto_ptr
<ExecutableCode
> code (get_event_handler(id
));
709 // user-defined onInitialize is never called
710 if (id
.id() == event_id::INITIALIZE
) return;
712 // NOTE: user-defined onLoad is not invoked for static
713 // clips on which no clip-events are defined.
714 // see testsuite/misc-ming.all/action_execution_order_extend_test.swf
716 // Note that this can't be true for movieclips
717 // not placed by PlaceObject, see
718 // testsuite/misc-ming.all/registerClassTest.swf
720 // Note that this is also not true for movieclips which have
721 // a registered class on them, see
722 // testsuite/misc-ming.all/registerClassTest2.swf
724 // TODO: test the case in which it's MovieClip.prototype.onLoad
726 if (id
.id() == event_id::LOAD
) {
728 // TODO: we're likely making too much noise for nothing here,
729 // there must be some action-execution-order related problem instead....
730 // See testsuite/misc-ming.all/registerClassTest2.swf for an onLoad
731 // execution order related problem ...
733 // we don't skip calling user-defined onLoad for top-level movies
734 if (!parent()) break;
735 // nor if there are clip-defined handler
736 if (!get_event_handlers().empty()) break;
737 // nor if it's dynamic
738 if (isDynamic()) break;
740 const sprite_definition
* def
=
741 dynamic_cast<const sprite_definition
*>(_def
.get());
743 // must be a loaded movie (loadMovie doesn't mark it as
744 // "dynamic" - should it? no, or getBytesLoaded will always
748 // if it has a registered class it can have an onLoad
750 if (def
->getRegisteredClass()) break;
753 log_debug(_("Sprite %s (depth %d) won't check for user-defined "
754 "LOAD event (is not dynamic, has a parent, "
755 "no registered class and no clip events defined)"),
756 getTarget(), get_depth());
763 // Call the appropriate member function.
764 if (!isKeyEvent(id
)) {
765 sendEvent(*getObject(this), get_environment(), id
.functionURI());
771 MovieClip::pathElement(const ObjectURI
& uri
)
773 as_object
* obj
= DisplayObject::pathElement(uri
);
776 // See if we have a match on the display list.
777 obj
= getObject(getDisplayListObject(uri
));
780 obj
= getObject(this);
783 // See if it's a member
785 if (!obj
->as_object::get_member(uri
, &tmp
)) return 0;
786 if (!tmp
.is_object()) return 0;
788 if (tmp
.is_sprite()) {
789 return getObject(tmp
.toDisplayObject(true));
792 return toObject(tmp
, getVM(*getObject(this)));
796 MovieClip::setTextFieldVariables(const ObjectURI
& uri
, const as_value
& val
)
798 // Try textfield variables
799 TextFields
* etc
= textfieldVar(_text_variables
.get(), uri
);
801 if (!etc
) return false;
803 for (TextFields::iterator i
=etc
->begin(), e
=etc
->end(); i
!=e
; ++i
) {
804 (*i
)->updateText(val
.to_string(getSWFVersion(*getObject(this))));
809 /// Remove the 'contents' of the MovieClip, but leave properties and
810 /// event handlers intact.
812 MovieClip::unloadMovie()
814 LOG_ONCE(log_unimpl("MovieClip.unloadMovie()"));
817 // child movieclip advance
822 log_debug(_("Advance movieclip '%s' at frame %u/%u"),
823 getTargetPath(), _currentFrame
,
829 // call_frame should never trigger advance_movieclip
830 assert(!_callingFrameActions
);
832 // We might have loaded NO frames !
833 if (get_loaded_frames() == 0) {
834 IF_VERBOSE_MALFORMED_SWF(
835 LOG_ONCE( log_swferror(_("advance_movieclip: no frames loaded "
836 "for movieclip/movie %s"), getTarget()) );
841 // Process any pending loadVariables request
842 processCompletedLoadVariableRequests();
845 size_t frame_count
= _def
->get_frame_count();
847 log_debug(_("Advance_movieclip for movieclip '%s' - frame %u/%u "),
848 getTarget(), _currentFrame
,
852 // I'm not sure ENTERFRAME goes in a different queue then DOACTION...
853 queueEvent(event_id(event_id::ENTER_FRAME
), movie_root::PRIORITY_DOACTION
);
855 // Update current and next frames.
856 if (_playState
== PLAYSTATE_PLAY
) {
858 log_debug(_("MovieClip::advance_movieclip we're in PLAYSTATE_PLAY mode"));
861 const size_t prev_frame
= _currentFrame
;
864 log_debug(_("on_event_load called, incrementing"));
866 increment_frame_and_check_for_loop();
868 log_debug(_("after increment we are at frame %u/%u"), _currentFrame
, frame_count
);
871 // Execute the current frame's tags.
872 // First time executeFrameTags(0) executed in dlist.cpp(child) or
873 // SWFMovieDefinition(root)
874 if (_currentFrame
!= prev_frame
) {
876 if (_currentFrame
== 0 && _hasLooped
) {
878 log_debug(_("Jumping back to frame 0 of movieclip %s"),
881 restoreDisplayList(0); // seems OK to me.
885 log_debug(_("Executing frame%d (0-based) tags of movieclip "
886 "%s"), _currentFrame
, getTarget());
888 // Make sure _currentFrame is 0-based during execution of
890 executeFrameTags(_currentFrame
, _displayList
,
891 SWF::ControlTag::TAG_DLIST
|
892 SWF::ControlTag::TAG_ACTION
);
899 log_debug(_("MovieClip::advance_movieclip we're in STOP mode"));
905 MovieClip::execute_init_action_buffer(const action_buffer
& a
, int cid
)
909 if (_swf
->initializeCharacter(cid
)) {
911 log_debug(_("Queuing init actions in frame %d of movieclip %s"),
912 _currentFrame
, getTarget());
914 std::auto_ptr
<ExecutableCode
> code(new GlobalCode(a
, this));
916 stage().pushAction(code
, movie_root::PRIORITY_INIT
);
920 log_debug(_("Init actions for DisplayObject %d already executed"), cid
);
926 MovieClip::execute_action(const action_buffer
& ab
)
928 ActionExec
exec(ab
, _environment
);
933 MovieClip::restoreDisplayList(size_t tgtFrame
)
935 // This is not tested as usable for jump-forwards (yet)...
936 // TODO: I guess just moving here the code currently in goto_frame
937 // for jump-forwards would do
938 assert(tgtFrame
<= _currentFrame
);
940 // Just invalidate this DisplayObject before jumping back.
941 // Should be optimized, but the invalidating model is not clear enough,
942 // and there are some old questions spreading the source files.
946 for (size_t f
= 0; f
< tgtFrame
; ++f
) {
948 executeFrameTags(f
, tmplist
, SWF::ControlTag::TAG_DLIST
);
951 // Execute both action tags and DLIST tags of the target frame
952 _currentFrame
= tgtFrame
;
953 executeFrameTags(tgtFrame
, tmplist
, SWF::ControlTag::TAG_DLIST
|
954 SWF::ControlTag::TAG_ACTION
);
956 _displayList
.mergeDisplayList(tmplist
);
959 // 0-based frame number !
961 MovieClip::executeFrameTags(size_t frame
, DisplayList
& dlist
, int typeflags
)
963 // If there is no definition, this is a dynamically-created MovieClip
964 // and has no frames.
966 if (isDestroyed()) return;
970 const PlayList
* playlist
= _def
->getPlaylist(frame
);
974 // Use 1-based frame numbers
975 log_action(_("Executing %d tags in frame %d/%d of movieclip %s"),
976 playlist
->size(), frame
+ 1, get_frame_count(),
980 // Generally tags should be executed in the order they are found in.
981 for (PlayList::const_iterator it
= playlist
->begin(),
982 e
= playlist
->end(); it
!= e
; ++it
) {
984 if (typeflags
& SWF::ControlTag::TAG_DLIST
) {
985 (*it
)->executeState(this, dlist
);
988 if (typeflags
& SWF::ControlTag::TAG_ACTION
) {
989 (*it
)->executeActions(this, _displayList
);
996 MovieClip::goto_frame(size_t target_frame_number
)
998 #if defined(DEBUG_GOTOFRAME) || defined(GNASH_DEBUG_TIMELINE)
999 log_debug(_("movieclip %s ::goto_frame(%d) - current frame is %d"),
1000 getTargetPath(), target_frame_number
, _currentFrame
);
1003 // goto_frame stops by default.
1004 // ActionGotoFrame tells the movieClip to go to the target frame
1005 // and stop at that frame.
1006 setPlayState(PLAYSTATE_STOP
);
1008 if (target_frame_number
> _def
->get_frame_count() - 1) {
1010 target_frame_number
= _def
->get_frame_count() - 1;
1012 if (!_def
->ensure_frame_loaded(target_frame_number
+ 1)) {
1013 log_error(_("Target frame of a gotoFrame(%d) was never loaded,"
1014 "although frame count in header (%d) said we "
1015 "should have found it"),
1016 target_frame_number
+1, _def
->get_frame_count());
1020 // Just set _currentframe and return.
1021 _currentFrame
= target_frame_number
;
1023 // don't push actions, already tested.
1027 if (target_frame_number
== _currentFrame
) {
1028 // don't push actions
1032 // Unless the target frame is the next one, stop playback of soundstream
1033 if (target_frame_number
!= _currentFrame
+ 1) {
1037 const size_t loaded_frames
= get_loaded_frames();
1039 // target_frame_number is 0-based, get_loaded_frames() is 1-based
1040 // so in order to goto_frame(3) loaded_frames must be at least 4
1041 // if goto_frame(4) is called, and loaded_frames is 4 we're jumping
1043 if (target_frame_number
>= loaded_frames
) {
1044 IF_VERBOSE_ASCODING_ERRORS(
1045 log_aserror(_("GotoFrame(%d) targets a yet "
1046 "to be loaded frame (%d) loaded). "
1047 "We'll wait for it but a more correct form "
1048 "is explicitly using WaitForFrame instead"),
1049 target_frame_number
+1,
1053 if (!_def
->ensure_frame_loaded(target_frame_number
+ 1)) {
1054 log_error(_("Target frame of a gotoFrame(%d) was never loaded, "
1055 "although frame count in header (%d) said we should"
1057 target_frame_number
+ 1, _def
->get_frame_count());
1062 // Construct the DisplayList of the target frame
1063 if (target_frame_number
< _currentFrame
) {
1065 // Go backward to a previous frame
1066 // NOTE: just in case we're being called by code in a called frame
1067 // we'll backup and resume the _callingFrameActions flag
1068 bool callingFrameActionsBackup
= _callingFrameActions
;
1069 _callingFrameActions
= false;
1071 // restoreDisplayList takes care of properly setting the
1072 // _currentFrame variable
1073 restoreDisplayList(target_frame_number
);
1074 assert(_currentFrame
== target_frame_number
);
1075 _callingFrameActions
= callingFrameActionsBackup
;
1078 // Go forward to a later frame
1079 // We'd immediately return if target_frame_number == _currentFrame
1080 assert(target_frame_number
> _currentFrame
);
1081 while (++_currentFrame
< target_frame_number
) {
1082 //for (size_t f = _currentFrame+1; f<target_frame_number; ++f)
1083 // Second argument requests that only "DisplayList" tags
1084 // are executed. This means NO actions will be
1085 // pushed on m_action_list.
1086 executeFrameTags(_currentFrame
, _displayList
,
1087 SWF::ControlTag::TAG_DLIST
);
1089 assert(_currentFrame
== target_frame_number
);
1091 // Now execute target frame tags (queuing actions)
1092 // NOTE: just in case we're being called by code in a called frame
1093 // we'll backup and resume the _callingFrameActions flag
1094 bool callingFrameActionsBackup
= _callingFrameActions
;
1095 _callingFrameActions
= false;
1096 executeFrameTags(target_frame_number
, _displayList
,
1097 SWF::ControlTag::TAG_DLIST
| SWF::ControlTag::TAG_ACTION
);
1098 _callingFrameActions
= callingFrameActionsBackup
;
1101 assert(_currentFrame
== target_frame_number
);
1105 MovieClip::goto_labeled_frame(const std::string
& label
)
1107 // If there is no definition, this is a dynamically-created MovieClip
1108 // and has no frames. (We are also probably not called in this case).
1109 if (!_def
) return false;
1111 size_t target_frame
;
1112 if (_def
->get_labeled_frame(label
, target_frame
)) {
1113 goto_frame(target_frame
);
1117 IF_VERBOSE_MALFORMED_SWF(
1118 log_swferror(_("MovieClip::goto_labeled_frame('%s') "
1119 "unknown label"), label
);
1125 MovieClip::draw(Renderer
& renderer
, const Transform
& xform
)
1127 const DisplayObject::MaskRenderer
mr(renderer
, *this);
1129 _drawable
.finalize();
1130 _drawable
.display(renderer
, xform
);
1131 _displayList
.display(renderer
, xform
);
1135 MovieClip::display(Renderer
& renderer
, const Transform
& base
)
1137 // Note: DisplayList::display() will take care of the visibility checking.
1139 // Whether a DisplayObject should be rendered or not is dependent
1140 // on its parent: i.e. if its parent is a mask, this DisplayObject
1141 // should be rendered to the mask buffer even it is invisible.
1143 // Draw everything with our own transform.
1144 const Transform xform
= base
* transform();
1145 draw(renderer
, xform
);
1146 clear_invalidated();
1149 void MovieClip::omit_display()
1151 if (childInvalidated()) _displayList
.omit_display();
1152 clear_invalidated();
1156 MovieClip::attachCharacter(DisplayObject
& newch
, int depth
, as_object
* initObj
)
1158 _displayList
.placeDisplayObject(&newch
, depth
);
1159 newch
.construct(initObj
);
1163 MovieClip::add_display_object(const SWF::PlaceObject2Tag
* tag
,
1166 // If this MovieClip has no definition, it should also have no ControlTags,
1167 // and this shouldn't be called.
1171 // No tags should ever be executed on destroyed MovieClips.
1172 assert(!isDestroyed());
1174 SWF::DefinitionTag
* cdef
= _def
->getDefinitionTag(tag
->getID());
1176 IF_VERBOSE_MALFORMED_SWF(
1177 log_swferror(_("MovieClip::add_display_object(): "
1178 "unknown cid = %d"), tag
->getID());
1183 DisplayObject
* existing_char
= dlist
.getDisplayObjectAtDepth(tag
->getDepth());
1185 if (existing_char
) return NULL
;
1187 Global_as
& gl
= getGlobal(*getObject(this));
1188 VM
& vm
= getVM(*getObject(this));
1189 DisplayObject
* ch
= cdef
->createDisplayObject(gl
, this);
1191 if (tag
->hasName()) ch
->set_name(getURI(vm
, tag
->getName()));
1192 else if (isReferenceable(*ch
)) {
1193 const ObjectURI
& instance_name
= getNextUnnamedInstanceName();
1194 ch
->set_name(instance_name
);
1197 if (tag
->hasBlendMode()) {
1198 boost::uint8_t bm
= tag
->getBlendMode();
1199 ch
->setBlendMode(static_cast<DisplayObject::BlendMode
>(bm
));
1202 // Attach event handlers (if any).
1203 const SWF::PlaceObject2Tag::EventHandlers
& event_handlers
=
1204 tag
->getEventHandlers();
1206 for (size_t i
= 0, n
= event_handlers
.size(); i
< n
; ++i
) {
1207 const swf_event
& ev
= event_handlers
[i
];
1208 ch
->add_event_handler(ev
.event(), ev
.action());
1211 // TODO: check if we should check those has_xxx flags first.
1212 ch
->setCxForm(tag
->getCxform());
1213 ch
->setMatrix(tag
->getMatrix(), true); // update caches
1214 ch
->set_ratio(tag
->getRatio());
1215 ch
->set_clip_depth(tag
->getClipDepth());
1217 dlist
.placeDisplayObject(ch
, tag
->getDepth());
1223 MovieClip::move_display_object(const SWF::PlaceObject2Tag
* tag
, DisplayList
& dlist
)
1225 boost::uint16_t ratio
= tag
->getRatio();
1226 // clip_depth is not used in MOVE tag(at least no related tests).
1227 dlist
.moveDisplayObject(
1229 tag
->hasCxform() ? &tag
->getCxform() : NULL
,
1230 tag
->hasMatrix() ? &tag
->getMatrix() : NULL
,
1231 tag
->hasRatio() ? &ratio
: NULL
);
1235 MovieClip::replace_display_object(const SWF::PlaceObject2Tag
* tag
,
1238 // A MovieClip without a definition cannot have any ControlTags, so this
1239 // should not be called.
1241 assert(tag
!= NULL
);
1243 const boost::uint16_t id
= tag
->getID();
1245 SWF::DefinitionTag
* cdef
= _def
->getDefinitionTag(id
);
1247 log_error(_("movieclip::replace_display_object(): "
1248 "unknown cid = %d"), id
);
1253 DisplayObject
* existing_char
= dlist
.getDisplayObjectAtDepth(tag
->getDepth());
1255 if (!existing_char
) {
1256 log_error(_("MovieClip::replace_display_object: could not "
1257 "find any DisplayObject at depth %d"), tag
->getDepth());
1261 // if the existing DisplayObject is not a shape, move it instead
1263 if (isReferenceable(*existing_char
)) {
1264 move_display_object(tag
, dlist
);
1268 Global_as
& gl
= getGlobal(*getObject(this));
1269 DisplayObject
* ch
= cdef
->createDisplayObject(gl
, this);
1272 // TODO: check if we can drop this for REPLACE!
1273 // should we rename the DisplayObject when it's REPLACE tag?
1274 if (tag
->hasName()) {
1275 VM
& vm
= getVM(*getObject(this));
1276 ch
->set_name(getURI(vm
, tag
->getName()));
1278 else if (isReferenceable(*ch
)) {
1279 ch
->set_name(getNextUnnamedInstanceName());
1281 if (tag
->hasRatio()) {
1282 ch
->set_ratio(tag
->getRatio());
1284 if (tag
->hasCxform()) {
1285 ch
->setCxForm(tag
->getCxform());
1287 if (tag
->hasMatrix()) {
1288 ch
->setMatrix(tag
->getMatrix(), true);
1291 // use SWFMatrix from the old DisplayObject if tag doesn't provide one.
1292 dlist
.replaceDisplayObject(ch
, tag
->getDepth(),
1293 !tag
->hasCxform(), !tag
->hasMatrix());
1298 MovieClip::remove_display_object(const SWF::PlaceObject2Tag
* tag
,
1302 dlist
.removeDisplayObject(tag
->getDepth());
1306 MovieClip::remove_display_object(int depth
, int)
1309 _displayList
.removeDisplayObject(depth
);
1313 MovieClip::increment_frame_and_check_for_loop()
1315 const size_t frame_count
= get_loaded_frames();
1316 if (++_currentFrame
>= frame_count
) {
1324 MovieClip::handleFocus()
1326 as_object
* obj
= getObject(this);
1329 // For SWF6 and above: the MovieClip can always receive focus if
1330 // focusEnabled evaluates to true.
1331 if (getSWFVersion(*obj
) > 5) {
1332 as_value focusEnabled
;
1333 if (obj
->get_member(NSV::PROP_FOCUS_ENABLED
, &focusEnabled
)) {
1334 if (toBool(focusEnabled
, getVM(*obj
))) return true;
1338 // If focusEnabled doesn't evaluate to true or for SWF5, return true
1339 // only if at least one mouse event handler is defined.
1340 return mouseEnabled();
1344 MovieClip::pointInShape(boost::int32_t x
, boost::int32_t y
) const
1346 ShapeContainerFinder
finder(x
, y
);
1347 _displayList
.visitBackward(finder
);
1348 if ( finder
.hitFound() ) return true;
1349 return hitTestDrawable(x
, y
);
1353 MovieClip::pointInVisibleShape(boost::int32_t x
, boost::int32_t y
) const
1355 if (! visible()) return false;
1356 if (isDynamicMask() && ! mouseEnabled()) {
1357 // see testsuite/misc-ming.all/masks_test.swf
1358 #ifdef GNASH_DEBUG_HITTEST
1359 log_debug(_("%s is a dynamic mask and can't handle mouse "
1360 "events, no point will hit it"), getTarget());
1364 const DisplayObject
* mask
= getMask(); // dynamic one
1365 if (mask
&& mask
->visible() && !mask
->pointInShape(x
, y
)) {
1366 #ifdef GNASH_DEBUG_HITTEST
1367 log_debug(_("%s is dynamically masked by %s, which "
1368 "doesn't hit point %g,%g"), getTarget(),
1369 mask
->getTarget(), x
, y
);
1373 VisibleShapeContainerFinder
finder(x
, y
);
1374 _displayList
.visitBackward(finder
);
1375 if (finder
.hitFound()) return true;
1376 return hitTestDrawable(x
, y
);
1380 MovieClip::hitTestDrawable(boost::int32_t x
, boost::int32_t y
) const
1382 const SWFMatrix wm
= getWorldMatrix(*this).invert();
1385 if (!_drawable
.getBounds().point_test(lp
.x
, lp
.y
)) return false;
1386 return _drawable
.pointTestLocal(lp
.x
, lp
.y
, wm
);
1390 MovieClip::pointInHitableShape(boost::int32_t x
, boost::int32_t y
) const
1392 if (isDynamicMask() && !mouseEnabled()) return false;
1394 const DisplayObject
* mask
= getMask();
1395 if (mask
&& !mask
->pointInShape(x
, y
)) return false;
1397 HitableShapeContainerFinder
finder(x
, y
);
1398 _displayList
.visitBackward(finder
);
1399 if (finder
.hitFound()) return true;
1401 return hitTestDrawable(x
, y
);
1405 MovieClip::topmostMouseEntity(boost::int32_t x
, boost::int32_t y
)
1407 if (!visible()) return 0;
1409 // point is in parent's space, we need to convert it in world space
1411 DisplayObject
* p
= parent();
1413 // WARNING: if we have NO parent, our parent is the Stage (movie_root)
1414 // so, in case we'll add a "stage" matrix, we'll need to take
1415 // it into account here.
1416 // TODO: actually, why are we insisting in using parent's
1417 // coordinates for this method at all ?
1418 getWorldMatrix(*p
).transform(wp
);
1421 if (mouseEnabled()) {
1422 if (pointInVisibleShape(wp
.x
, wp
.y
)) return this;
1426 SWFMatrix m
= getMatrix(*this);
1431 MouseEntityFinder
finder(wp
, pp
);
1432 _displayList
.visitAll(finder
);
1433 InteractiveObject
* ch
= finder
.getEntity();
1435 // It doesn't make any sense to query _drawable, as it's
1436 // not an InteractiveObject.
1440 const DisplayObject
*
1441 MovieClip::findDropTarget(boost::int32_t x
, boost::int32_t y
,
1442 DisplayObject
* dragging
) const
1444 if (this == dragging
) return 0; // not here...
1446 if (!visible()) return 0; // isn't me !
1448 DropTargetFinder
finder(x
, y
, dragging
);
1449 _displayList
.visitAll(finder
);
1451 // does it hit any child ?
1452 const DisplayObject
* ch
= finder
.getDropChar();
1454 // TODO: find closest actionscript referenceable container
1455 // (possibly itself)
1460 if (hitTestDrawable(x
, y
)) return this;
1466 MovieClip::trackAsMenu()
1468 as_object
* obj
= getObject(this);
1472 VM
& vm
= getVM(*obj
);
1473 // TODO: use namedStrings here
1474 return (obj
->get_member(getURI(vm
, "trackAsMenu"), &track
) &&
1479 MovieClip::mouseEnabled() const
1481 if (!isEnabled()) return false;
1483 // Event handlers that qualify as mouse event handlers.
1484 static const event_id EH
[] = {
1485 event_id(event_id::PRESS
),
1486 event_id(event_id::RELEASE
),
1487 event_id(event_id::RELEASE_OUTSIDE
),
1488 event_id(event_id::ROLL_OVER
),
1489 event_id(event_id::ROLL_OUT
),
1490 event_id(event_id::DRAG_OVER
),
1491 event_id(event_id::DRAG_OUT
),
1494 const size_t size
= arraySize(EH
);
1496 for (size_t i
= 0; i
< size
; ++i
) {
1497 const event_id
&event
= EH
[i
];
1499 // Check event handlers
1500 if (hasEventHandler(event_id(event
.id()))) {
1508 MovieClip::stop_drag()
1510 stage().stop_drag();
1514 MovieClip::set_background_color(const rgba
& color
)
1516 stage().set_background_color(color
);
1520 MovieClip::cleanup_textfield_variables()
1523 if (!_text_variables
.get()) return;
1525 TextFieldIndex
& m
= *_text_variables
;
1527 for (TextFieldIndex::iterator i
=m
.begin(), ie
=m
.end(); i
!=ie
; ++i
)
1529 TextFields
& v
=i
->second
;
1530 TextFields::iterator lastValid
= std::remove_if(v
.begin(), v
.end(),
1531 boost::mem_fn(&DisplayObject::unloaded
));
1532 v
.erase(lastValid
, v
.end());
1538 MovieClip::set_textfield_variable(const ObjectURI
& name
, TextField
* ch
)
1543 if (!_text_variables
.get()) {
1544 _text_variables
.reset(new TextFieldIndex
);
1547 (*_text_variables
)[name
].push_back(ch
);
1551 MovieClip::getDisplayListObject(const ObjectURI
& uri
)
1553 as_object
* obj
= getObject(this);
1556 string_table
& st
= getStringTable(*obj
);
1558 // Try items on our display list.
1559 DisplayObject
* ch
= _displayList
.getDisplayObjectByName(st
, uri
,
1566 // If the object is an ActionScript referenciable one we
1567 // return it, otherwise we return ourselves
1568 if (isReferenceable(*ch
)) {
1575 MovieClip::add_invalidated_bounds(InvalidatedRanges
& ranges
, bool force
)
1577 // nothing to do if this movieclip is not visible
1578 if (!visible() || invisible(getCxForm(*this))) {
1579 ranges
.add(m_old_invalidated_ranges
);
1583 if (!invalidated() && !childInvalidated() && !force
) return;
1585 // m_child_invalidated does not require our own bounds
1586 if (invalidated() || force
) {
1587 // Add old invalidated bounds
1588 ranges
.add(m_old_invalidated_ranges
);
1591 _displayList
.add_invalidated_bounds(ranges
, force
|| invalidated());
1595 bounds
.expand_to_transformed_rect(getWorldMatrix(*this),
1596 _drawable
.getBounds());
1598 ranges
.add(bounds
.getRange());
1603 MovieClip::constructAsScriptObject()
1605 as_object
* mc
= getObject(this);
1607 // A MovieClip should always have an associated object.
1611 mc
->init_member("$version", getVM(*mc
).getPlayerVersion(), 0);
1614 const sprite_definition
* def
=
1615 dynamic_cast<const sprite_definition
*>(_def
.get());
1617 // We won't "construct" top-level movies
1618 as_function
* ctor
= def
? def
->getRegisteredClass() : 0;
1621 log_debug(_("Attached movieclips %s registered class is %p"),
1622 getTarget(), (void*)ctor
);
1625 // Set this MovieClip object to be an instance of the class.
1627 Property
* proto
= ctor
->getOwnProperty(NSV::PROP_PROTOTYPE
);
1628 if (proto
) mc
->set_prototype(proto
->getValue(*ctor
));
1631 // Send the construct event. This must be done after the __proto__
1632 // member is set. It is always done.
1633 notifyEvent(event_id(event_id::CONSTRUCT
));
1636 const int swfversion
= getSWFVersion(*mc
);
1637 if (swfversion
> 5) {
1639 ctor
->construct(*mc
, get_environment(), args
);
1645 MovieClip::construct(as_object
* initObj
)
1647 assert(!unloaded());
1649 saveOriginalTarget();
1652 log_debug(_("Sprite '%s' placed on stage"), getTarget());
1655 // Register this movieclip as a live one
1656 stage().addLiveChar(this);
1658 // It seems it's legal to place 0-framed movieclips on stage.
1659 // See testsuite/misc-swfmill.all/zeroframe_definemovieclip.swf
1661 // Now execute frame tags and take care of queuing the LOAD event.
1663 // DLIST tags are executed immediately while ACTION tags are queued.
1665 // For _root movie, LOAD event is invoked *after* actions in first frame
1666 // See misc-ming.all/action_execution_order_test4.{c,swf}
1668 assert(!_callingFrameActions
); // or will not be queuing actions
1671 executeFrameTags(0, _displayList
, SWF::ControlTag::TAG_DLIST
|
1672 SWF::ControlTag::TAG_ACTION
);
1674 if (getSWFVersion(*getObject(this)) > 5) {
1675 queueEvent(event_id(event_id::LOAD
),
1676 movie_root::PRIORITY_DOACTION
);
1681 queueEvent(event_id(event_id::LOAD
), movie_root::PRIORITY_DOACTION
);
1682 executeFrameTags(0, _displayList
, SWF::ControlTag::TAG_DLIST
|
1683 SWF::ControlTag::TAG_ACTION
);
1686 as_object
* mc
= getObject(this);
1688 // A MovieClip should always have an associated object.
1691 // We execute events immediately when the stage-placed DisplayObject
1692 // is dynamic, This is becase we assume that this means that
1693 // the DisplayObject is placed during processing of actions (opposed
1694 // that during advancement iteration).
1696 // A more general implementation might ask movie_root about its state
1697 // (iterating or processing actions?)
1698 // Another possibility to inspect could be letting movie_root decide
1699 // when to really queue and when rather to execute immediately the
1700 // events with priority INITIALIZE or CONSTRUCT ...
1704 log_debug(_("Queuing INITIALIZE and CONSTRUCT events for movieclip %s"),
1708 std::auto_ptr
<ExecutableCode
> code(new ConstructEvent(this));
1709 stage().pushAction(code
, movie_root::PRIORITY_CONSTRUCT
);
1714 // Properties from an initObj must be copied before construction, but
1715 // after the display list has been populated, so that _height and
1716 // _width (which depend on bounds) are correct.
1718 mc
->copyProperties(*initObj
);
1720 constructAsScriptObject();
1723 // Tested in testsuite/swfdec/duplicateMovieclip-events.c and
1724 // testsuite/swfdec/clone-sprite-events.c not to call notifyEvent
1726 queueEvent(event_id(event_id::INITIALIZE
), movie_root::PRIORITY_INIT
);
1730 MovieClip::unloadChildren()
1733 log_debug(_("Unloading movieclip '%s'"), getTargetPath());
1736 // stop any pending streaming sounds
1739 // We won't be displayed again, so worth releasing
1740 // some memory. The drawable might take a lot of memory
1744 return _displayList
.unload();
1748 MovieClip::getLoadedMovie(Movie
* extern_movie
)
1750 DisplayObject
* p
= parent();
1752 extern_movie
->set_parent(p
);
1754 // Copy own lockroot value
1755 extern_movie
->setLockRoot(getLockRoot());
1757 // Copy own event handlers
1758 // see testsuite/misc-ming.all/loadMovieTest.swf
1759 const Events
& clipEvs
= get_event_handlers();
1760 // top-level movies can't have clip events, right ?
1761 assert (extern_movie
->get_event_handlers().empty());
1762 extern_movie
->set_event_handlers(clipEvs
);
1765 // TODO: check empty != none...
1766 const ObjectURI
& name
= get_name();
1767 if (!name
.empty()) extern_movie
->set_name(name
);
1769 // Copy own clip depth (TODO: check this)
1770 extern_movie
->set_clip_depth(get_clip_depth());
1772 // Replace ourselves in parent
1773 // TODO: don't pretend our parent is a MovieClip,
1774 // could as well be a button I guess...
1775 // At most we should require it to be a
1776 // DisplayObjectContainer and log an error if it's not.
1777 MovieClip
* parent_sp
= p
->to_movie();
1779 parent_sp
->_displayList
.replaceDisplayObject(extern_movie
, get_depth(),
1781 extern_movie
->construct();
1784 // replaceLevel will set depth for us
1785 stage().replaceLevel(get_depth() - DisplayObject::staticDepthOffset
,
1791 MovieClip::loadVariables(const std::string
& urlstr
,
1792 VariablesMethod sendVarsMethod
)
1794 // Host security check will be will be done by LoadVariablesThread
1795 // (down by getStream, that is)
1797 const movie_root
& mr
= stage();
1798 URL
url(urlstr
, mr
.runResources().streamProvider().baseURL());
1800 std::string postdata
;
1802 // Encode our vars for sending.
1803 if (sendVarsMethod
!= METHOD_NONE
) {
1804 postdata
= getURLEncodedVars(*getObject(this));
1808 const StreamProvider
& sp
=
1809 getRunResources(*getObject(this)).streamProvider();
1811 if (sendVarsMethod
== METHOD_POST
) {
1813 _loadVariableRequests
.push_back(
1814 new LoadVariablesThread(sp
, url
, postdata
));
1818 if (sendVarsMethod
== METHOD_GET
) {
1820 std::string qs
= url
.querystring();
1821 if (qs
.empty()) url
.set_querystring(postdata
);
1822 else url
.set_querystring(qs
+ "&" + postdata
);
1824 _loadVariableRequests
.push_back(new LoadVariablesThread(sp
, url
));
1826 _loadVariableRequests
.back().process();
1828 catch (const NetworkException
& ex
) {
1829 log_error(_("Could not load variables from %s"), url
.str());
1834 MovieClip::processCompletedLoadVariableRequest(LoadVariablesThread
& request
)
1836 assert(request
.completed());
1838 MovieVariables
& vals
= request
.getValues();
1841 // We want to call a clip-event too if available, see bug #22116
1842 notifyEvent(event_id(event_id::DATA
));
1846 MovieClip::processCompletedLoadVariableRequests()
1848 // Nothing to do (just for clarity)
1849 if (_loadVariableRequests
.empty()) return;
1851 for (LoadVariablesThreads::iterator it
=_loadVariableRequests
.begin();
1852 it
!= _loadVariableRequests
.end();) {
1854 LoadVariablesThread
& request
= *it
;
1855 if (request
.completed()) {
1856 processCompletedLoadVariableRequest(request
);
1857 it
= _loadVariableRequests
.erase(it
);
1864 MovieClip::setVariables(const MovieVariables
& vars
)
1866 VM
& vm
= getVM(*getObject(this));
1867 for (MovieVariables::const_iterator it
=vars
.begin(), itEnd
=vars
.end();
1868 it
!= itEnd
; ++it
) {
1870 const std::string
& name
= it
->first
;
1871 const std::string
& val
= it
->second
;
1872 getObject(this)->set_member(getURI(vm
, name
), val
);
1877 MovieClip::removeMovieClip()
1879 const int depth
= get_depth();
1880 if (depth
< 0 || depth
> 1048575) {
1881 IF_VERBOSE_ASCODING_ERRORS(
1882 log_aserror(_("removeMovieClip(%s): movieclip depth (%d) out of "
1883 "the 'dynamic' zone [0..1048575], won't remove"),
1884 getTarget(), depth
);
1889 MovieClip
* p
= dynamic_cast<MovieClip
*>(parent());
1891 // second argument is arbitrary, see comments above
1892 // the function declaration in MovieClip.h
1893 p
->remove_display_object(depth
, 0);
1897 stage().dropLevel(depth
);
1898 // I guess this can only happen if someone uses
1899 // _swf.swapDepth([0..1048575])
1905 MovieClip::getBounds() const
1908 BoundsFinder
f(bounds
);
1909 _displayList
.visitAll(f
);
1910 SWFRect drawableBounds
= _drawable
.getBounds();
1911 bounds
.expand_to_rect(drawableBounds
);
1917 MovieClip::isEnabled() const
1919 as_object
* obj
= getObject(this);
1923 if (!obj
->get_member(NSV::PROP_ENABLED
, &enabled
)) {
1924 // We're enabled if there's no 'enabled' member...
1927 return toBool(enabled
, getVM(*obj
));
1932 MovieClip::visitNonProperties(KeyVisitor
& v
) const
1934 DisplayListVisitor
dv(v
);
1935 _displayList
.visitAll(dv
);
1939 MovieClip::cleanupDisplayList()
1941 _displayList
.removeUnloaded();
1942 cleanup_textfield_variables();
1946 MovieClip::markOwnResources() const
1948 ReachableMarker marker
;
1950 _displayList
.visitAll(marker
);
1952 _environment
.markReachableResources();
1954 // Mark textfields in the TextFieldIndex
1955 if (_text_variables
.get()) {
1956 for (TextFieldIndex::const_iterator i
=_text_variables
->begin(),
1957 e
=_text_variables
->end();
1960 const TextFields
& tfs
=i
->second
;
1961 std::for_each(tfs
.begin(), tfs
.end(),
1962 boost::mem_fn(&DisplayObject::setReachable
));
1966 // Mark our relative root
1967 _swf
->setReachable();
1971 MovieClip::destroy()
1974 _displayList
.destroy();
1975 DisplayObject::destroy();
1979 MovieClip::get_root() const
1985 MovieClip::getAsRoot()
1988 // TODO1: as an optimization, if swf version < 7
1989 // we might as well just return _swf,
1990 // the whole chain from this movieclip to it's
1991 // _swf should have the same version...
1993 // TODO2: implement this with iteration rather
1997 DisplayObject
* p
= parent();
1998 if (!p
) return this; // no parent, we're the root
2000 // If we have a parent, we descend to it unless
2001 // our _lockroot is true AND our or the VM's
2002 // SWF version is > 6
2003 int topSWFVersion
= stage().getRootMovie().version();
2005 if (getDefinitionVersion() > 6 || topSWFVersion
> 6) {
2006 if (getLockRoot()) return this;
2009 return p
->getAsRoot();
2014 MovieClip::setStreamSoundId(int id
)
2016 if (id
!= m_sound_stream_id
) {
2017 log_debug(_("Stream sound id from %d to %d, stopping old"),
2018 m_sound_stream_id
, id
);
2021 m_sound_stream_id
= id
;
2025 MovieClip::stopStreamSound()
2027 if (m_sound_stream_id
== -1) return; // nothing to do
2029 sound::sound_handler
* handler
= getRunResources(*getObject(this)).soundHandler();
2031 handler
->stop_sound(m_sound_stream_id
);
2034 m_sound_stream_id
= -1;
2038 MovieClip::setPlayState(PlayState s
)
2040 if (s
== _playState
) return; // nothing to do
2041 if (s
== PLAYSTATE_STOP
) stopStreamSound();
2047 MovieClip::TextFields
*
2048 textfieldVar(MovieClip::TextFieldIndex
* t
, const ObjectURI
& name
)
2050 // nothing allocated yet...
2053 // TODO: should variable name be considered case-insensitive ?
2054 MovieClip::TextFieldIndex::iterator it
= t
->find(name
);
2055 if (it
== t
->end()) return 0;
2056 return &(it
->second
);
2059 } // unnamed namespace
2060 } // namespace gnash