Include program counter on action limit notification log
[gnash.git] / libcore / MovieClip.cpp
blobeec8b4c3e45116c6c801d28852c73b63df19efc3
1 // MovieClip.cpp: Stateful live Sprite instance, for Gnash.
2 //
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
4 // Foundation, Inc
5 //
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.
10 //
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.
15 //
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
21 #ifdef HAVE_CONFIG_H
22 #include "gnashconfig.h" // USE_SWFTREE
23 #endif
25 #include "MovieClip.h"
27 #include <vector>
28 #include <string>
29 #include <algorithm> // for std::swap
30 #include <boost/algorithm/string/case_conv.hpp>
31 #include <boost/bind.hpp>
33 #include "log.h"
34 #include "movie_definition.h"
35 #include "as_value.h"
36 #include "as_function.h"
37 #include "TextField.h"
38 #include "ControlTag.h"
39 #include "fn_call.h"
40 #include "movie_root.h"
41 #include "Movie.h"
42 #include "swf_event.h"
43 #include "sprite_definition.h"
44 #include "ActionExec.h"
45 #include "smart_ptr.h"
46 #include "VM.h"
47 #include "Range2d.h" // for getBounds
48 #include "GnashException.h"
49 #include "GnashNumeric.h"
50 #include "GnashAlgorithm.h"
51 #include "URL.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"
68 namespace gnash {
70 //#define GNASH_DEBUG 1
71 //#define GNASH_DEBUG_TIMELINE 1
72 //#define GNASH_DEBUG_REPLACE 1
73 //#define DEBUG_DYNTEXT_VARIABLES 1
74 //#define GNASH_DEBUG_HITTEST 1
75 //#define DEBUG_LOAD_VARIABLES 1
77 // Defining the following macro you'll get a DEBUG lien
78 // for each call to the drawing API, in a format which is
79 // easily re-compilable to obtain a smaller testcase
80 //#define DEBUG_DRAWING_API 1
82 // Define this to make mouse entity finding verbose
83 // This includes topmostMouseEntity and findDropTarget
85 //#define DEBUG_MOUSE_ENTITY_FINDING 1
87 namespace {
88 MovieClip::TextFields* textfieldVar(MovieClip::TextFieldIndex* t,
89 const ObjectURI& name);
92 // Utility functors.
93 namespace {
95 /// ConstructEvent, used for queuing construction
97 /// Its execution will call constructAsScriptObject()
98 /// on the target movieclip
99 ///
100 class ConstructEvent: public ExecutableCode
102 public:
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
122 public:
124 /// @param wp
125 /// Query point in world coordinate space
127 /// @param pp
128 /// Query point in parent coordinate space
130 MouseEntityFinder(point wp, point pp)
132 _highestHiddenDepth(std::numeric_limits<int>::min()),
133 _m(NULL),
134 _candidates(),
135 _wp(wp),
136 _pp(pp),
137 _checked(false)
140 void operator() (DisplayObject* ch) {
141 assert(!_checked);
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 "
146 "up to depth %d."),
147 ch->getTarget(), ch->get_depth(),
148 _highestHiddenDepth);
149 // Hiding mask still in effect...
151 return;
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());
161 #endif
162 _highestHiddenDepth = ch->get_clip_depth();
164 else {
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);
169 #endif
171 return;
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);
184 if (te) {
185 _m = te;
186 break;
189 _checked = true;
192 InteractiveObject* getEntity() {
193 checkCandidates();
194 #ifdef DEBUG_MOUSE_ENTITY_FINDING
195 if (_m) {
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
201 return _m;
204 private:
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
221 point _wp;
223 /// Query point in parent coordinate space
224 point _pp;
226 bool _checked;
230 /// Find the first DisplayObject whose shape contain the point
232 /// Point coordinates in world TWIPS
234 class ShapeContainerFinder
236 public:
238 ShapeContainerFinder(boost::int32_t x, boost::int32_t y)
240 _found(false),
241 _x(x),
242 _y(y)
245 bool operator()(const DisplayObject* ch) {
246 if (ch->pointInShape(_x, _y)) {
247 _found = true;
248 return false;
250 return true;
253 bool hitFound() const { return _found; }
255 private:
256 bool _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
267 public:
269 VisibleShapeContainerFinder(boost::int32_t x, boost::int32_t y)
271 _found(false),
272 _x(x),
273 _y(y)
276 bool operator()(const DisplayObject* ch) {
278 if (ch->pointInVisibleShape(_x, _y)) {
279 _found = true;
280 return false;
282 return true;
285 bool hitFound() const { return _found; }
287 private:
288 bool _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
296 ///
297 class HitableShapeContainerFinder
299 public:
300 HitableShapeContainerFinder(boost::int32_t x, boost::int32_t y)
302 _found(false),
303 _x(x),
304 _y(y)
307 bool operator()(const DisplayObject* ch) {
308 if (ch->isDynamicMask()) return true;
309 if (ch->pointInShape(_x, _y)) {
310 _found = true;
311 return false;
313 return true;
316 bool hitFound() const { return _found; }
318 private:
320 bool _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.
331 class BoundsFinder
333 public:
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);
344 private:
345 SWFRect& _bounds;
348 struct ReachableMarker
350 void operator()(DisplayObject *ch) const {
351 ch->setReachable();
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
362 public:
363 DropTargetFinder(boost::int32_t x, boost::int32_t y, DisplayObject* dragging)
365 _highestHiddenDepth(std::numeric_limits<int>::min()),
366 _x(x),
367 _y(y),
368 _dragging(dragging),
369 _dropch(0),
370 _candidates(),
371 _checked(false)
374 void operator()(const DisplayObject* ch) {
375 assert(!_checked);
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 "
380 "up to depth %d."),
381 ch->getTarget(), ch->get_depth(), _highestHiddenDepth);
382 // Hiding mask still in effect...
384 return;
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());
397 #endif
398 _highestHiddenDepth = ch->get_clip_depth();
400 else {
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);
405 #endif
407 return;
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);
419 if (dropChar) {
420 _dropch = dropChar;
421 break;
424 _checked = true;
427 const DisplayObject* getDropChar() const {
428 checkCandidates();
429 return _dropch;
431 private:
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;
441 boost::int32_t _x;
442 boost::int32_t _y;
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
454 public:
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));
468 _v(name);
470 private:
471 KeyVisitor& _v;
474 } // anonymous namespace
477 MovieClip::MovieClip(as_object* object, const movie_definition* def,
478 Movie* r, DisplayObject* parent)
480 DisplayObjectContainer(object, parent),
481 _def(def),
482 _swf(r),
483 _playState(PLAYSTATE_PLAY),
484 _environment(getVM(*object)),
485 _currentFrame(0),
486 m_sound_stream_id(-1),
487 _hasLooped(false),
488 _callingFrameActions(false),
489 _lockroot(false)
491 assert(_swf);
492 assert(object);
494 _environment.set_target(this);
497 MovieClip::~MovieClip()
499 stopStreamSound();
503 MovieClip::getDefinitionVersion() const
505 return _swf->version();
508 DisplayObject*
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
518 /// of a MovieClip.
520 /// The TextField variables should probably be handled in a more generic
521 /// way.
522 bool
523 MovieClip::getTextFieldVariables(const ObjectURI& uri, as_value& val)
525 // Try textfield variables
526 TextFields* etc = textfieldVar(_text_variables.get(), uri);
527 if (etc) {
528 for (TextFields::const_iterator i=etc->begin(), e=etc->end();
529 i!=e; ++i) {
531 TextField* tf = *i;
532 if (tf->getTextDefined()) {
533 val = tf->get_text_value();
534 return true;
538 return false;
541 bool
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);
556 return ret;
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;
565 return true;
568 /// Execute the actions for the specified frame.
570 /// The frame_spec could be an integer or a string.
572 void
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.
577 if (!_def) return;
579 // TODO: check to see how this can be prevented.
580 if (isDestroyed()) return;
582 size_t frame_number;
583 if (!get_frame_number(frame_spec, frame_number)) {
584 // No dice.
585 IF_VERBOSE_ASCODING_ERRORS(
586 log_aserror(_("call_frame('%s') -- invalid frame"),
587 frame_spec);
589 return;
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);
601 if (playlist) {
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;
612 DisplayObject*
613 MovieClip::addDisplayListObject(DisplayObject* obj, int depth)
615 // TODO: only call set_invalidated if this DisplayObject actually overrides
616 // an existing one !
617 set_invalidated();
618 _displayList.placeDisplayObject(obj, depth);
619 obj->construct();
620 return obj;
624 MovieClip*
625 MovieClip::duplicateMovieClip(const std::string& newname, int depth,
626 as_object* initObject)
628 DisplayObject* parent_ch = parent();
629 if (!parent_ch) {
630 IF_VERBOSE_ASCODING_ERRORS(
631 log_aserror(_("Can't clone root of the movie"));
633 return 0;
636 MovieClip* parent = parent_ch->to_movie();
637 if (!parent) {
638 IF_VERBOSE_ASCODING_ERRORS(
639 log_error(_("%s parent is not a movieclip, can't clone"),
640 getTarget());
642 return 0;
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());
660 // Copy drawable
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);
671 return newmovieclip;
674 void
675 MovieClip::queueAction(const action_buffer& action)
677 stage().pushAction(action, this);
680 void
681 MovieClip::notifyEvent(const event_id& id)
683 #ifdef GNASH_DEBUG
684 log_debug(_("Event %s invoked for movieclip %s"), id, getTarget());
685 #endif
687 // We do not execute ENTER_FRAME if unloaded
688 if (id.id() == event_id::ENTER_FRAME && unloaded()) {
689 #ifdef GNASH_DEBUG
690 log_debug(_("Sprite %s ignored ENTER_FRAME event (is unloaded)"), getTarget());
691 #endif
692 return;
695 if (isButtonEvent(id) && !isEnabled()) {
696 #ifdef GNASH_DEBUG
697 log_debug(_("Sprite %s ignored button-like event %s as not 'enabled'"),
698 getTarget(), id);
699 #endif
700 return;
703 std::auto_ptr<ExecutableCode> code (get_event_handler(id));
704 if (code.get()) {
705 // Dispatch.
706 code->execute();
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
725 // defined !
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 ...
732 do {
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
745 // return 0)
746 if (!def) break;
748 // if it has a registered class it can have an onLoad
749 // in prototype...
750 if (def->getRegisteredClass()) break;
752 #ifdef GNASH_DEBUG
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());
757 #endif
758 return;
759 } while (0);
763 // Call the appropriate member function.
764 if (!isKeyEvent(id)) {
765 sendEvent(*getObject(this), get_environment(), id.functionURI());
770 as_object*
771 MovieClip::pathElement(const ObjectURI& uri)
773 as_object* obj = DisplayObject::pathElement(uri);
774 if (obj) return obj;
776 // See if we have a match on the display list.
777 obj = getObject(getDisplayListObject(uri));
778 if (obj) return obj;
780 obj = getObject(this);
781 assert(obj);
783 // See if it's a member
784 as_value tmp;
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)));
795 bool
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))));
806 return true;
809 /// Remove the 'contents' of the MovieClip, but leave properties and
810 /// event handlers intact.
811 void
812 MovieClip::unloadMovie()
814 LOG_ONCE(log_unimpl("MovieClip.unloadMovie()"));
817 // child movieclip advance
818 void
819 MovieClip::advance()
821 #ifdef GNASH_DEBUG
822 log_debug(_("Advance movieclip '%s' at frame %u/%u"),
823 getTargetPath(), _currentFrame,
824 get_frame_count());
825 #endif
827 assert(!unloaded());
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()) );
838 return;
841 // Process any pending loadVariables request
842 processCompletedLoadVariableRequests();
844 #ifdef GNASH_DEBUG
845 size_t frame_count = _def->get_frame_count();
847 log_debug(_("Advance_movieclip for movieclip '%s' - frame %u/%u "),
848 getTarget(), _currentFrame,
849 frame_count);
850 #endif
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) {
857 #ifdef GNASH_DEBUG
858 log_debug(_("MovieClip::advance_movieclip we're in PLAYSTATE_PLAY mode"));
859 #endif
861 const size_t prev_frame = _currentFrame;
863 #ifdef GNASH_DEBUG
864 log_debug(_("on_event_load called, incrementing"));
865 #endif
866 increment_frame_and_check_for_loop();
867 #ifdef GNASH_DEBUG
868 log_debug(_("after increment we are at frame %u/%u"), _currentFrame, frame_count);
869 #endif
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) {
877 #ifdef GNASH_DEBUG
878 log_debug(_("Jumping back to frame 0 of movieclip %s"),
879 getTarget());
880 #endif
881 restoreDisplayList(0); // seems OK to me.
883 else {
884 #ifdef GNASH_DEBUG
885 log_debug(_("Executing frame%d (0-based) tags of movieclip "
886 "%s"), _currentFrame, getTarget());
887 #endif
888 // Make sure _currentFrame is 0-based during execution of
889 // DLIST tags
890 executeFrameTags(_currentFrame, _displayList,
891 SWF::ControlTag::TAG_DLIST |
892 SWF::ControlTag::TAG_ACTION);
897 #ifdef GNASH_DEBUG
898 else {
899 log_debug(_("MovieClip::advance_movieclip we're in STOP mode"));
901 #endif
904 void
905 MovieClip::execute_init_action_buffer(const action_buffer& a, int cid)
907 assert(cid >= 0);
909 if (_swf->initializeCharacter(cid)) {
910 #ifdef GNASH_DEBUG
911 log_debug(_("Queuing init actions in frame %d of movieclip %s"),
912 _currentFrame, getTarget());
913 #endif
914 std::auto_ptr<ExecutableCode> code(new GlobalCode(a, this));
916 stage().pushAction(code, movie_root::PRIORITY_INIT);
918 else {
919 #ifdef GNASH_DEBUG
920 log_debug(_("Init actions for DisplayObject %d already executed"), cid);
921 #endif
925 void
926 MovieClip::execute_action(const action_buffer& ab)
928 ActionExec exec(ab, _environment);
929 exec();
932 void
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.
943 set_invalidated();
945 DisplayList tmplist;
946 for (size_t f = 0; f < tgtFrame; ++f) {
947 _currentFrame = 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 !
960 void
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.
965 if (!_def) return;
966 if (isDestroyed()) return;
968 assert(typeflags);
970 const PlayList* playlist = _def->getPlaylist(frame);
971 if (playlist) {
973 IF_VERBOSE_ACTION(
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(),
977 getTargetPath());
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);
995 void
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);
1001 #endif
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());
1017 return;
1020 // Just set _currentframe and return.
1021 _currentFrame = target_frame_number;
1023 // don't push actions, already tested.
1024 return;
1027 if (target_frame_number == _currentFrame) {
1028 // don't push actions
1029 return;
1032 // Unless the target frame is the next one, stop playback of soundstream
1033 if (target_frame_number != _currentFrame + 1) {
1034 stopStreamSound();
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
1042 // forward
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,
1050 loaded_frames);
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"
1056 " have found it"),
1057 target_frame_number + 1, _def->get_frame_count());
1058 return;
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;
1077 else {
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);
1104 bool
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);
1114 return true;
1117 IF_VERBOSE_MALFORMED_SWF(
1118 log_swferror(_("MovieClip::goto_labeled_frame('%s') "
1119 "unknown label"), label);
1121 return false;
1124 void
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);
1134 void
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();
1155 void
1156 MovieClip::attachCharacter(DisplayObject& newch, int depth, as_object* initObj)
1158 _displayList.placeDisplayObject(&newch, depth);
1159 newch.construct(initObj);
1162 DisplayObject*
1163 MovieClip::add_display_object(const SWF::PlaceObject2Tag* tag,
1164 DisplayList& dlist)
1166 // If this MovieClip has no definition, it should also have no ControlTags,
1167 // and this shouldn't be called.
1168 assert(_def);
1169 assert(tag);
1171 // No tags should ever be executed on destroyed MovieClips.
1172 assert(!isDestroyed());
1174 SWF::DefinitionTag* cdef = _def->getDefinitionTag(tag->getID());
1175 if (!cdef) {
1176 IF_VERBOSE_MALFORMED_SWF(
1177 log_swferror(_("MovieClip::add_display_object(): "
1178 "unknown cid = %d"), tag->getID());
1180 return NULL;
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());
1218 ch->construct();
1219 return ch;
1222 void
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(
1228 tag->getDepth(),
1229 tag->hasCxform() ? &tag->getCxform() : NULL,
1230 tag->hasMatrix() ? &tag->getMatrix() : NULL,
1231 tag->hasRatio() ? &ratio : NULL);
1234 void
1235 MovieClip::replace_display_object(const SWF::PlaceObject2Tag* tag,
1236 DisplayList& dlist)
1238 // A MovieClip without a definition cannot have any ControlTags, so this
1239 // should not be called.
1240 assert(_def);
1241 assert(tag != NULL);
1243 const boost::uint16_t id = tag->getID();
1245 SWF::DefinitionTag* cdef = _def->getDefinitionTag(id);
1246 if (!cdef) {
1247 log_error(_("movieclip::replace_display_object(): "
1248 "unknown cid = %d"), id);
1249 return;
1251 assert(cdef);
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());
1258 return;
1261 // if the existing DisplayObject is not a shape, move it instead
1262 // of replacing.
1263 if (isReferenceable(*existing_char)) {
1264 move_display_object(tag, dlist);
1265 return;
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());
1294 ch->construct();
1297 void
1298 MovieClip::remove_display_object(const SWF::PlaceObject2Tag* tag,
1299 DisplayList& dlist)
1301 set_invalidated();
1302 dlist.removeDisplayObject(tag->getDepth());
1305 void
1306 MovieClip::remove_display_object(int depth, int)
1308 set_invalidated();
1309 _displayList.removeDisplayObject(depth);
1312 void
1313 MovieClip::increment_frame_and_check_for_loop()
1315 const size_t frame_count = get_loaded_frames();
1316 if (++_currentFrame >= frame_count) {
1317 // Loop.
1318 _currentFrame = 0;
1319 _hasLooped = true;
1323 bool
1324 MovieClip::handleFocus()
1326 as_object* obj = getObject(this);
1327 assert(obj);
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();
1343 bool
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);
1352 bool
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());
1361 #endif
1362 return false;
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);
1370 #endif
1371 return false;
1373 VisibleShapeContainerFinder finder(x, y);
1374 _displayList.visitBackward(finder);
1375 if (finder.hitFound()) return true;
1376 return hitTestDrawable(x, y);
1379 inline bool
1380 MovieClip::hitTestDrawable(boost::int32_t x, boost::int32_t y) const
1382 const SWFMatrix wm = getWorldMatrix(*this).invert();
1383 point lp(x, y);
1384 wm.transform(lp);
1385 if (!_drawable.getBounds().point_test(lp.x, lp.y)) return false;
1386 return _drawable.pointTestLocal(lp.x, lp.y, wm);
1389 bool
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);
1404 InteractiveObject*
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
1410 point wp(x, y);
1411 DisplayObject* p = parent();
1412 if (p) {
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;
1423 return 0;
1426 SWFMatrix m = getMatrix(*this);
1427 m.invert();
1428 point pp(x, y);
1429 m.transform(pp);
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.
1437 return ch;
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();
1453 if (ch) {
1454 // TODO: find closest actionscript referenceable container
1455 // (possibly itself)
1456 return ch;
1459 // does it hit us ?
1460 if (hitTestDrawable(x, y)) return this;
1462 return 0;
1465 bool
1466 MovieClip::trackAsMenu()
1468 as_object* obj = getObject(this);
1469 assert(obj);
1471 as_value track;
1472 VM& vm = getVM(*obj);
1473 // TODO: use namedStrings here
1474 return (obj->get_member(getURI(vm, "trackAsMenu"), &track) &&
1475 toBool(track, vm));
1478 bool
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()))) {
1501 return true;
1504 return false;
1507 void
1508 MovieClip::stop_drag()
1510 stage().stop_drag();
1513 void
1514 MovieClip::set_background_color(const rgba& color)
1516 stage().set_background_color(color);
1519 void
1520 MovieClip::cleanup_textfield_variables()
1522 // nothing to do
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());
1537 void
1538 MovieClip::set_textfield_variable(const ObjectURI& name, TextField* ch)
1540 assert(ch);
1542 // lazy allocation
1543 if (!_text_variables.get()) {
1544 _text_variables.reset(new TextFieldIndex);
1547 (*_text_variables)[name].push_back(ch);
1550 DisplayObject*
1551 MovieClip::getDisplayListObject(const ObjectURI& uri)
1553 as_object* obj = getObject(this);
1554 assert(obj);
1556 string_table& st = getStringTable(*obj);
1558 // Try items on our display list.
1559 DisplayObject* ch = _displayList.getDisplayObjectByName(st, uri,
1560 caseless(*obj));
1562 if (!ch) return 0;
1564 // Found object.
1566 // If the object is an ActionScript referenciable one we
1567 // return it, otherwise we return ourselves
1568 if (isReferenceable(*ch)) {
1569 return ch;
1571 return this;
1574 void
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);
1580 return;
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());
1593 /// Add drawable.
1594 SWFRect bounds;
1595 bounds.expand_to_transformed_rect(getWorldMatrix(*this),
1596 _drawable.getBounds());
1598 ranges.add(bounds.getRange());
1602 void
1603 MovieClip::constructAsScriptObject()
1605 as_object* mc = getObject(this);
1607 // A MovieClip should always have an associated object.
1608 assert(mc);
1610 if (!parent()) {
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;
1620 #ifdef GNASH_DEBUG
1621 log_debug(_("Attached movieclips %s registered class is %p"),
1622 getTarget(), (void*)ctor);
1623 #endif
1625 // Set this MovieClip object to be an instance of the class.
1626 if (ctor) {
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));
1635 if (ctor) {
1636 const int swfversion = getSWFVersion(*mc);
1637 if (swfversion > 5) {
1638 fn_call::Args args;
1639 ctor->construct(*mc, get_environment(), args);
1644 void
1645 MovieClip::construct(as_object* initObj)
1647 assert(!unloaded());
1649 saveOriginalTarget();
1651 #ifdef GNASH_DEBUG
1652 log_debug(_("Sprite '%s' placed on stage"), getTarget());
1653 #endif
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
1669 if (!parent()) {
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);
1680 else {
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.
1689 assert(mc);
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 ...
1701 if (!isDynamic()) {
1703 #ifdef GNASH_DEBUG
1704 log_debug(_("Queuing INITIALIZE and CONSTRUCT events for movieclip %s"),
1705 getTarget());
1706 #endif
1708 std::auto_ptr<ExecutableCode> code(new ConstructEvent(this));
1709 stage().pushAction(code, movie_root::PRIORITY_CONSTRUCT);
1712 else {
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.
1717 if (initObj) {
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
1725 // immediately.
1726 queueEvent(event_id(event_id::INITIALIZE), movie_root::PRIORITY_INIT);
1729 bool
1730 MovieClip::unloadChildren()
1732 #ifdef GNASH_DEBUG
1733 log_debug(_("Unloading movieclip '%s'"), getTargetPath());
1734 #endif
1736 // stop any pending streaming sounds
1737 stopStreamSound();
1739 // We won't be displayed again, so worth releasing
1740 // some memory. The drawable might take a lot of memory
1741 // on itself.
1742 _drawable.clear();
1744 return _displayList.unload();
1747 void
1748 MovieClip::getLoadedMovie(Movie* extern_movie)
1750 DisplayObject* p = parent();
1751 if (p) {
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);
1764 // Copy own name
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();
1778 assert(parent_sp);
1779 parent_sp->_displayList.replaceDisplayObject(extern_movie, get_depth(),
1780 true, true);
1781 extern_movie->construct();
1783 else {
1784 // replaceLevel will set depth for us
1785 stage().replaceLevel(get_depth() - DisplayObject::staticDepthOffset,
1786 extern_movie);
1790 void
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));
1807 try {
1808 const StreamProvider& sp =
1809 getRunResources(*getObject(this)).streamProvider();
1811 if (sendVarsMethod == METHOD_POST) {
1812 // use POST method
1813 _loadVariableRequests.push_back(
1814 new LoadVariablesThread(sp, url, postdata));
1816 else {
1817 // use GET method
1818 if (sendVarsMethod == METHOD_GET) {
1819 // Append variables
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());
1833 void
1834 MovieClip::processCompletedLoadVariableRequest(LoadVariablesThread& request)
1836 assert(request.completed());
1838 MovieVariables& vals = request.getValues();
1839 setVariables(vals);
1841 // We want to call a clip-event too if available, see bug #22116
1842 notifyEvent(event_id(event_id::DATA));
1845 void
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);
1859 else ++it;
1863 void
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);
1876 void
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);
1886 return;
1889 MovieClip* p = dynamic_cast<MovieClip*>(parent());
1890 if (p) {
1891 // second argument is arbitrary, see comments above
1892 // the function declaration in MovieClip.h
1893 p->remove_display_object(depth, 0);
1895 else {
1896 // removing _level#
1897 stage().dropLevel(depth);
1898 // I guess this can only happen if someone uses
1899 // _swf.swapDepth([0..1048575])
1904 SWFRect
1905 MovieClip::getBounds() const
1907 SWFRect bounds;
1908 BoundsFinder f(bounds);
1909 _displayList.visitAll(f);
1910 SWFRect drawableBounds = _drawable.getBounds();
1911 bounds.expand_to_rect(drawableBounds);
1913 return bounds;
1916 bool
1917 MovieClip::isEnabled() const
1919 as_object* obj = getObject(this);
1920 assert(obj);
1922 as_value enabled;
1923 if (!obj->get_member(NSV::PROP_ENABLED, &enabled)) {
1924 // We're enabled if there's no 'enabled' member...
1925 return true;
1927 return toBool(enabled, getVM(*obj));
1931 void
1932 MovieClip::visitNonProperties(KeyVisitor& v) const
1934 DisplayListVisitor dv(v);
1935 _displayList.visitAll(dv);
1938 void
1939 MovieClip::cleanupDisplayList()
1941 _displayList.removeUnloaded();
1942 cleanup_textfield_variables();
1945 void
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();
1958 i!=e; ++i)
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();
1970 void
1971 MovieClip::destroy()
1973 stopStreamSound();
1974 _displayList.destroy();
1975 DisplayObject::destroy();
1978 Movie*
1979 MovieClip::get_root() const
1981 return _swf;
1984 MovieClip*
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
1994 // then recursion.
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();
2013 void
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);
2019 stopStreamSound();
2021 m_sound_stream_id = id;
2024 void
2025 MovieClip::stopStreamSound()
2027 if (m_sound_stream_id == -1) return; // nothing to do
2029 sound::sound_handler* handler = getRunResources(*getObject(this)).soundHandler();
2030 if (handler) {
2031 handler->stop_sound(m_sound_stream_id);
2034 m_sound_stream_id = -1;
2037 void
2038 MovieClip::setPlayState(PlayState s)
2040 if (s == _playState) return; // nothing to do
2041 if (s == PLAYSTATE_STOP) stopStreamSound();
2042 _playState = s;
2045 namespace {
2047 MovieClip::TextFields*
2048 textfieldVar(MovieClip::TextFieldIndex* t, const ObjectURI& name)
2050 // nothing allocated yet...
2051 if (!t) return 0;
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