Drop extra semicolon
[gnash.git] / libcore / MovieClip.cpp
blob20ce020880b5a83682777616d7185bdf1deed752
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 // Execute the actions in the action list, in the given
509 // environment. The list of action will be consumed
510 // starting from the first element. When the function returns
511 // the list should be empty.
512 void
513 MovieClip::execute_actions(MovieClip::ActionList& action_list)
515 // action_list may be changed due to actions (appended-to)
516 // This loop is probably quicker than using an iterator
517 // and a final call to .clear(), as repeated calls to
518 // .size() or .end() are no quicker (and probably slower)
519 // than pop_front(), which is constant time.
520 while (!action_list.empty()) {
521 const action_buffer* ab = action_list.front();
522 action_list.pop_front();
524 execute_action(*ab);
528 DisplayObject*
529 MovieClip::getDisplayObjectAtDepth(int depth)
531 return _displayList.getDisplayObjectAtDepth(depth);
534 /// This handles special properties of MovieClip.
536 /// The only genuine special properties are DisplayList members. These
537 /// are accessible as properties and are enumerated, but not ownProperties
538 /// of a MovieClip.
540 /// The TextField variables should probably be handled in a more generic
541 /// way.
542 bool
543 MovieClip::getTextFieldVariables(const ObjectURI& uri, as_value& val)
545 // Try textfield variables
546 TextFields* etc = textfieldVar(_text_variables.get(), uri);
547 if (etc) {
548 for (TextFields::const_iterator i=etc->begin(), e=etc->end();
549 i!=e; ++i) {
551 TextField* tf = *i;
552 if (tf->getTextDefined()) {
553 val = tf->get_text_value();
554 return true;
558 return false;
561 bool
562 MovieClip::get_frame_number(const as_value& frame_spec, size_t& frameno) const
564 // If there is no definition, this is a dynamically-created MovieClip
565 // and has no frames.
566 if (!_def) return false;
568 std::string fspecStr = frame_spec.to_string();
570 as_value str(fspecStr);
572 const double num = toNumber(str, getVM(*getObject(this)));
574 if (!isFinite(num) || int(num) != num || num == 0) {
575 bool ret = _def->get_labeled_frame(fspecStr, frameno);
576 return ret;
579 if (num < 0) return false;
581 // all frame numbers > 0 are valid, but a valid frame number may still
582 // reference a non-exist frame(eg. frameno > total_frames).
583 frameno = size_t(num) - 1;
585 return true;
588 /// Execute the actions for the specified frame.
590 /// The frame_spec could be an integer or a string.
592 void
593 MovieClip::call_frame_actions(const as_value& frame_spec)
595 // If there is no definition, this is a dynamically-created MovieClip
596 // and has no frames.
597 if (!_def) return;
599 size_t frame_number;
600 if (!get_frame_number(frame_spec, frame_number)) {
601 // No dice.
602 IF_VERBOSE_ASCODING_ERRORS(
603 log_aserror(_("call_frame('%s') -- invalid frame"),
604 frame_spec);
606 return;
609 // Execute the ControlTag actions
610 // We set _callingFrameActions to true so that add_action_buffer
611 // will execute immediately instead of queuing them.
612 // NOTE: in case gotoFrame is executed by code in the called frame
613 // we'll temporarly clear the _callingFrameActions flag
614 // to properly queue actions back on the global queue.
616 _callingFrameActions = true;
617 const PlayList* playlist = _def->getPlaylist(frame_number);
618 if (playlist) {
619 PlayList::const_iterator it = playlist->begin();
620 const PlayList::const_iterator e = playlist->end();
621 for (; it != e; it++) {
622 (*it)->executeActions(this, _displayList);
625 _callingFrameActions = false;
629 DisplayObject*
630 MovieClip::addDisplayListObject(DisplayObject* obj, int depth)
632 // TODO: only call set_invalidated if this DisplayObject actually overrides
633 // an existing one !
634 set_invalidated();
635 _displayList.placeDisplayObject(obj, depth);
636 obj->construct();
637 return obj;
641 MovieClip*
642 MovieClip::duplicateMovieClip(const std::string& newname, int depth,
643 as_object* initObject)
645 DisplayObject* parent_ch = parent();
646 if (!parent_ch) {
647 IF_VERBOSE_ASCODING_ERRORS(
648 log_aserror(_("Can't clone root of the movie"));
650 return 0;
653 MovieClip* parent = parent_ch->to_movie();
654 if (!parent) {
655 IF_VERBOSE_ASCODING_ERRORS(
656 log_error(_("%s parent is not a movieclip, can't clone"),
657 getTarget());
659 return 0;
662 as_object* o = getObjectWithPrototype(getGlobal(*getObject(this)),
663 NSV::CLASS_MOVIE_CLIP);
665 MovieClip* newmovieclip = new MovieClip(o, _def.get(), _swf, parent);
667 const ObjectURI& nn = getURI(getVM(*getObject(this)), newname);
668 newmovieclip->set_name(nn);
670 newmovieclip->setDynamic();
672 // Copy event handlers from movieclip
673 // We should not copy 'm_action_buffer' since the
674 // 'm_method' already contains it
675 newmovieclip->set_event_handlers(get_event_handlers());
677 // Copy drawable
678 newmovieclip->_drawable = _drawable;
680 newmovieclip->setCxForm(getCxForm(*this));
681 newmovieclip->setMatrix(getMatrix(*this), true);
682 newmovieclip->set_ratio(get_ratio());
683 newmovieclip->set_clip_depth(get_clip_depth());
685 parent->_displayList.placeDisplayObject(newmovieclip, depth);
686 newmovieclip->construct(initObject);
688 return newmovieclip;
691 void
692 MovieClip::queueAction(const action_buffer& action)
694 stage().pushAction(action, this);
697 void
698 MovieClip::notifyEvent(const event_id& id)
700 #ifdef GNASH_DEBUG
701 log_debug(_("Event %s invoked for movieclip %s"), id, getTarget());
702 #endif
704 // We do not execute ENTER_FRAME if unloaded
705 if (id.id() == event_id::ENTER_FRAME && unloaded()) {
706 #ifdef GNASH_DEBUG
707 log_debug(_("Sprite %s ignored ENTER_FRAME event (is unloaded)"), getTarget());
708 #endif
709 return;
712 if (isButtonEvent(id) && !isEnabled()) {
713 #ifdef GNASH_DEBUG
714 log_debug(_("Sprite %s ignored button-like event %s as not 'enabled'"),
715 getTarget(), id);
716 #endif
717 return;
720 std::auto_ptr<ExecutableCode> code (get_event_handler(id));
721 if (code.get()) {
722 // Dispatch.
723 code->execute();
726 // user-defined onInitialize is never called
727 if (id.id() == event_id::INITIALIZE) return;
729 // NOTE: user-defined onLoad is not invoked for static
730 // clips on which no clip-events are defined.
731 // see testsuite/misc-ming.all/action_execution_order_extend_test.swf
733 // Note that this can't be true for movieclips
734 // not placed by PlaceObject, see
735 // testsuite/misc-ming.all/registerClassTest.swf
737 // Note that this is also not true for movieclips which have
738 // a registered class on them, see
739 // testsuite/misc-ming.all/registerClassTest2.swf
741 // TODO: test the case in which it's MovieClip.prototype.onLoad
742 // defined !
743 if (id.id() == event_id::LOAD) {
745 // TODO: we're likely making too much noise for nothing here,
746 // there must be some action-execution-order related problem instead....
747 // See testsuite/misc-ming.all/registerClassTest2.swf for an onLoad
748 // execution order related problem ...
749 do {
750 // we don't skip calling user-defined onLoad for top-level movies
751 if (!parent()) break;
752 // nor if there are clip-defined handler
753 if (!get_event_handlers().empty()) break;
754 // nor if it's dynamic
755 if (isDynamic()) break;
757 const sprite_definition* def =
758 dynamic_cast<const sprite_definition*>(_def.get());
760 // must be a loaded movie (loadMovie doesn't mark it as
761 // "dynamic" - should it? no, or getBytesLoaded will always
762 // return 0)
763 if (!def) break;
765 // if it has a registered class it can have an onLoad
766 // in prototype...
767 if (def->getRegisteredClass()) break;
769 #ifdef GNASH_DEBUG
770 log_debug(_("Sprite %s (depth %d) won't check for user-defined "
771 "LOAD event (is not dynamic, has a parent, "
772 "no registered class and no clip events defined)"),
773 getTarget(), get_depth());
774 #endif
775 return;
776 } while (0);
780 // Call the appropriate member function.
781 if (!isKeyEvent(id)) {
782 sendEvent(*getObject(this), get_environment(), id.functionURI());
787 as_object*
788 MovieClip::pathElement(const ObjectURI& uri)
790 as_object* obj = DisplayObject::pathElement(uri);
791 if (obj) return obj;
793 // See if we have a match on the display list.
794 obj = getObject(getDisplayListObject(uri));
795 if (obj) return obj;
797 obj = getObject(this);
798 assert(obj);
800 // See if it's a member
801 as_value tmp;
802 if (!obj->as_object::get_member(uri, &tmp)) return 0;
803 if (!tmp.is_object()) return 0;
805 if (tmp.is_sprite()) {
806 return getObject(tmp.toDisplayObject(true));
809 return toObject(tmp, getVM(*getObject(this)));
812 bool
813 MovieClip::setTextFieldVariables(const ObjectURI& uri, const as_value& val)
815 // Try textfield variables
816 TextFields* etc = textfieldVar(_text_variables.get(), uri);
818 if (!etc) return false;
820 for (TextFields::iterator i=etc->begin(), e=etc->end(); i!=e; ++i) {
821 (*i)->updateText(val.to_string(getSWFVersion(*getObject(this))));
823 return true;
826 /// Remove the 'contents' of the MovieClip, but leave properties and
827 /// event handlers intact.
828 void
829 MovieClip::unloadMovie()
831 LOG_ONCE(log_unimpl("MovieClip.unloadMovie()"));
834 // child movieclip advance
835 void
836 MovieClip::advance()
838 #ifdef GNASH_DEBUG
839 log_debug(_("Advance movieclip '%s' at frame %u/%u"),
840 getTargetPath(), _currentFrame,
841 get_frame_count());
842 #endif
844 assert(!unloaded());
846 // call_frame should never trigger advance_movieclip
847 assert(!_callingFrameActions);
849 // We might have loaded NO frames !
850 if (get_loaded_frames() == 0) {
851 IF_VERBOSE_MALFORMED_SWF(
852 LOG_ONCE( log_swferror(_("advance_movieclip: no frames loaded "
853 "for movieclip/movie %s"), getTarget()) );
855 return;
858 // Process any pending loadVariables request
859 processCompletedLoadVariableRequests();
861 #ifdef GNASH_DEBUG
862 size_t frame_count = _def->get_frame_count();
864 log_debug(_("Advance_movieclip for movieclip '%s' - frame %u/%u "),
865 getTarget(), _currentFrame,
866 frame_count);
867 #endif
869 // I'm not sure ENTERFRAME goes in a different queue then DOACTION...
870 queueEvent(event_id(event_id::ENTER_FRAME), movie_root::PRIORITY_DOACTION);
872 // Update current and next frames.
873 if (_playState == PLAYSTATE_PLAY) {
874 #ifdef GNASH_DEBUG
875 log_debug(_("MovieClip::advance_movieclip we're in PLAYSTATE_PLAY mode"));
876 #endif
878 const size_t prev_frame = _currentFrame;
880 #ifdef GNASH_DEBUG
881 log_debug(_("on_event_load called, incrementing"));
882 #endif
883 increment_frame_and_check_for_loop();
884 #ifdef GNASH_DEBUG
885 log_debug(_("after increment we are at frame %u/%u"), _currentFrame, frame_count);
886 #endif
888 // Execute the current frame's tags.
889 // First time executeFrameTags(0) executed in dlist.cpp(child) or
890 // SWFMovieDefinition(root)
891 if (_currentFrame != prev_frame) {
893 if (_currentFrame == 0 && _hasLooped) {
894 #ifdef GNASH_DEBUG
895 log_debug(_("Jumping back to frame 0 of movieclip %s"),
896 getTarget());
897 #endif
898 restoreDisplayList(0); // seems OK to me.
900 else {
901 #ifdef GNASH_DEBUG
902 log_debug(_("Executing frame%d (0-based) tags of movieclip "
903 "%s"), _currentFrame, getTarget());
904 #endif
905 // Make sure _currentFrame is 0-based during execution of
906 // DLIST tags
907 executeFrameTags(_currentFrame, _displayList,
908 SWF::ControlTag::TAG_DLIST |
909 SWF::ControlTag::TAG_ACTION);
914 #ifdef GNASH_DEBUG
915 else {
916 log_debug(_("MovieClip::advance_movieclip we're in STOP mode"));
918 #endif
921 void
922 MovieClip::execute_init_action_buffer(const action_buffer& a, int cid)
924 assert(cid >= 0);
926 if (_swf->initializeCharacter(cid)) {
927 #ifdef GNASH_DEBUG
928 log_debug(_("Queuing init actions in frame %d of movieclip %s"),
929 _currentFrame, getTarget());
930 #endif
931 std::auto_ptr<ExecutableCode> code(new GlobalCode(a, this));
933 stage().pushAction(code, movie_root::PRIORITY_INIT);
935 else {
936 #ifdef GNASH_DEBUG
937 log_debug(_("Init actions for DisplayObject %d already executed"), cid);
938 #endif
942 void
943 MovieClip::execute_action(const action_buffer& ab)
945 ActionExec exec(ab, _environment);
946 exec();
949 void
950 MovieClip::restoreDisplayList(size_t tgtFrame)
952 // This is not tested as usable for jump-forwards (yet)...
953 // TODO: I guess just moving here the code currently in goto_frame
954 // for jump-forwards would do
955 assert(tgtFrame <= _currentFrame);
957 // Just invalidate this DisplayObject before jumping back.
958 // Should be optimized, but the invalidating model is not clear enough,
959 // and there are some old questions spreading the source files.
960 set_invalidated();
962 DisplayList tmplist;
963 for (size_t f = 0; f < tgtFrame; ++f) {
964 _currentFrame = f;
965 executeFrameTags(f, tmplist, SWF::ControlTag::TAG_DLIST);
968 // Execute both action tags and DLIST tags of the target frame
969 _currentFrame = tgtFrame;
970 executeFrameTags(tgtFrame, tmplist, SWF::ControlTag::TAG_DLIST |
971 SWF::ControlTag::TAG_ACTION);
973 _displayList.mergeDisplayList(tmplist);
976 // 0-based frame number !
977 void
978 MovieClip::executeFrameTags(size_t frame, DisplayList& dlist, int typeflags)
980 // If there is no definition, this is a dynamically-created MovieClip
981 // and has no frames.
982 if (!_def) return;
984 assert(typeflags);
986 const PlayList* playlist = _def->getPlaylist(frame);
987 if (playlist) {
989 IF_VERBOSE_ACTION(
990 // Use 1-based frame numbers
991 log_action(_("Executing %d tags in frame %d/%d of movieclip %s"),
992 playlist->size(), frame + 1, get_frame_count(),
993 getTargetPath());
996 // Generally tags should be executed in the order they are found in.
997 for (PlayList::const_iterator it = playlist->begin(),
998 e = playlist->end(); it != e; ++it) {
1000 if (typeflags & SWF::ControlTag::TAG_DLIST) {
1001 (*it)->executeState(this, dlist);
1004 if (typeflags & SWF::ControlTag::TAG_ACTION) {
1005 (*it)->executeActions(this, _displayList);
1011 void
1012 MovieClip::goto_frame(size_t target_frame_number)
1014 #if defined(DEBUG_GOTOFRAME) || defined(GNASH_DEBUG_TIMELINE)
1015 log_debug(_("movieclip %s ::goto_frame(%d) - current frame is %d"),
1016 getTargetPath(), target_frame_number, _currentFrame);
1017 #endif
1019 // goto_frame stops by default.
1020 // ActionGotoFrame tells the movieClip to go to the target frame
1021 // and stop at that frame.
1022 setPlayState(PLAYSTATE_STOP);
1024 if (target_frame_number > _def->get_frame_count() - 1) {
1026 target_frame_number = _def->get_frame_count() - 1;
1028 if (!_def->ensure_frame_loaded(target_frame_number + 1)) {
1029 log_error(_("Target frame of a gotoFrame(%d) was never loaded,"
1030 "although frame count in header (%d) said we "
1031 "should have found it"),
1032 target_frame_number+1, _def->get_frame_count());
1033 return;
1036 // Just set _currentframe and return.
1037 _currentFrame = target_frame_number;
1039 // don't push actions, already tested.
1040 return;
1043 if (target_frame_number == _currentFrame) {
1044 // don't push actions
1045 return;
1048 // Unless the target frame is the next one, stop playback of soundstream
1049 if (target_frame_number != _currentFrame + 1) {
1050 stopStreamSound();
1053 const size_t loaded_frames = get_loaded_frames();
1055 // target_frame_number is 0-based, get_loaded_frames() is 1-based
1056 // so in order to goto_frame(3) loaded_frames must be at least 4
1057 // if goto_frame(4) is called, and loaded_frames is 4 we're jumping
1058 // forward
1059 if (target_frame_number >= loaded_frames) {
1060 IF_VERBOSE_ASCODING_ERRORS(
1061 log_aserror(_("GotoFrame(%d) targets a yet "
1062 "to be loaded frame (%d) loaded). "
1063 "We'll wait for it but a more correct form "
1064 "is explicitly using WaitForFrame instead"),
1065 target_frame_number+1,
1066 loaded_frames);
1069 if (!_def->ensure_frame_loaded(target_frame_number + 1)) {
1070 log_error(_("Target frame of a gotoFrame(%d) was never loaded, "
1071 "although frame count in header (%d) said we should"
1072 " have found it"),
1073 target_frame_number + 1, _def->get_frame_count());
1074 return;
1078 // Construct the DisplayList of the target frame
1079 if (target_frame_number < _currentFrame) {
1081 // Go backward to a previous frame
1082 // NOTE: just in case we're being called by code in a called frame
1083 // we'll backup and resume the _callingFrameActions flag
1084 bool callingFrameActionsBackup = _callingFrameActions;
1085 _callingFrameActions = false;
1087 // restoreDisplayList takes care of properly setting the
1088 // _currentFrame variable
1089 restoreDisplayList(target_frame_number);
1090 assert(_currentFrame == target_frame_number);
1091 _callingFrameActions = callingFrameActionsBackup;
1093 else {
1094 // Go forward to a later frame
1095 // We'd immediately return if target_frame_number == _currentFrame
1096 assert(target_frame_number > _currentFrame);
1097 while (++_currentFrame < target_frame_number) {
1098 //for (size_t f = _currentFrame+1; f<target_frame_number; ++f)
1099 // Second argument requests that only "DisplayList" tags
1100 // are executed. This means NO actions will be
1101 // pushed on m_action_list.
1102 executeFrameTags(_currentFrame, _displayList,
1103 SWF::ControlTag::TAG_DLIST);
1105 assert(_currentFrame == target_frame_number);
1107 // Now execute target frame tags (queuing actions)
1108 // NOTE: just in case we're being called by code in a called frame
1109 // we'll backup and resume the _callingFrameActions flag
1110 bool callingFrameActionsBackup = _callingFrameActions;
1111 _callingFrameActions = false;
1112 executeFrameTags(target_frame_number, _displayList,
1113 SWF::ControlTag::TAG_DLIST | SWF::ControlTag::TAG_ACTION);
1114 _callingFrameActions = callingFrameActionsBackup;
1117 assert(_currentFrame == target_frame_number);
1120 bool
1121 MovieClip::goto_labeled_frame(const std::string& label)
1123 // If there is no definition, this is a dynamically-created MovieClip
1124 // and has no frames. (We are also probably not called in this case).
1125 if (!_def) return false;
1127 size_t target_frame;
1128 if (_def->get_labeled_frame(label, target_frame)) {
1129 goto_frame(target_frame);
1130 return true;
1133 IF_VERBOSE_MALFORMED_SWF(
1134 log_swferror(_("MovieClip::goto_labeled_frame('%s') "
1135 "unknown label"), label);
1137 return false;
1140 void
1141 MovieClip::draw(Renderer& renderer, const Transform& xform)
1143 const DisplayObject::MaskRenderer mr(renderer, *this);
1145 _drawable.finalize();
1146 _drawable.display(renderer, xform);
1147 _displayList.display(renderer, xform);
1150 void
1151 MovieClip::display(Renderer& renderer, const Transform& base)
1153 // Note: DisplayList::display() will take care of the visibility checking.
1155 // Whether a DisplayObject should be rendered or not is dependent
1156 // on its parent: i.e. if its parent is a mask, this DisplayObject
1157 // should be rendered to the mask buffer even it is invisible.
1159 // Draw everything with our own transform.
1160 const Transform xform = base * transform();
1161 draw(renderer, xform);
1162 clear_invalidated();
1165 void MovieClip::omit_display()
1167 if (childInvalidated()) _displayList.omit_display();
1168 clear_invalidated();
1171 void
1172 MovieClip::attachCharacter(DisplayObject& newch, int depth, as_object* initObj)
1174 _displayList.placeDisplayObject(&newch, depth);
1175 newch.construct(initObj);
1178 DisplayObject*
1179 MovieClip::add_display_object(const SWF::PlaceObject2Tag* tag,
1180 DisplayList& dlist)
1182 // If this MovieClip has no definition, it should also have no ControlTags,
1183 // and this shouldn't be called.
1184 assert(_def);
1185 assert(tag);
1187 SWF::DefinitionTag* cdef = _def->getDefinitionTag(tag->getID());
1188 if (!cdef) {
1189 IF_VERBOSE_MALFORMED_SWF(
1190 log_swferror(_("MovieClip::add_display_object(): "
1191 "unknown cid = %d"), tag->getID());
1193 return NULL;
1196 DisplayObject* existing_char = dlist.getDisplayObjectAtDepth(tag->getDepth());
1198 if (existing_char) return NULL;
1200 Global_as& gl = getGlobal(*getObject(this));
1201 VM& vm = getVM(*getObject(this));
1202 DisplayObject* ch = cdef->createDisplayObject(gl, this);
1204 if (tag->hasName()) ch->set_name(getURI(vm, tag->getName()));
1205 else if (isReferenceable(*ch)) {
1206 const ObjectURI& instance_name = getNextUnnamedInstanceName();
1207 ch->set_name(instance_name);
1210 if (tag->hasBlendMode()) {
1211 boost::uint8_t bm = tag->getBlendMode();
1212 ch->setBlendMode(static_cast<DisplayObject::BlendMode>(bm));
1215 // Attach event handlers (if any).
1216 const SWF::PlaceObject2Tag::EventHandlers& event_handlers =
1217 tag->getEventHandlers();
1219 for (size_t i = 0, n = event_handlers.size(); i < n; ++i) {
1220 const swf_event& ev = event_handlers[i];
1221 ch->add_event_handler(ev.event(), ev.action());
1224 // TODO: check if we should check those has_xxx flags first.
1225 ch->setCxForm(tag->getCxform());
1226 ch->setMatrix(tag->getMatrix(), true); // update caches
1227 ch->set_ratio(tag->getRatio());
1228 ch->set_clip_depth(tag->getClipDepth());
1230 dlist.placeDisplayObject(ch, tag->getDepth());
1231 ch->construct();
1232 return ch;
1235 void
1236 MovieClip::move_display_object(const SWF::PlaceObject2Tag* tag, DisplayList& dlist)
1238 boost::uint16_t ratio = tag->getRatio();
1239 // clip_depth is not used in MOVE tag(at least no related tests).
1240 dlist.moveDisplayObject(
1241 tag->getDepth(),
1242 tag->hasCxform() ? &tag->getCxform() : NULL,
1243 tag->hasMatrix() ? &tag->getMatrix() : NULL,
1244 tag->hasRatio() ? &ratio : NULL);
1247 void
1248 MovieClip::replace_display_object(const SWF::PlaceObject2Tag* tag,
1249 DisplayList& dlist)
1251 // A MovieClip without a definition cannot have any ControlTags, so this
1252 // should not be called.
1253 assert(_def);
1254 assert(tag != NULL);
1256 const boost::uint16_t id = tag->getID();
1258 SWF::DefinitionTag* cdef = _def->getDefinitionTag(id);
1259 if (!cdef) {
1260 log_error(_("movieclip::replace_display_object(): "
1261 "unknown cid = %d"), id);
1262 return;
1264 assert(cdef);
1266 DisplayObject* existing_char = dlist.getDisplayObjectAtDepth(tag->getDepth());
1268 if (!existing_char) {
1269 log_error(_("MovieClip::replace_display_object: could not "
1270 "find any DisplayObject at depth %d"), tag->getDepth());
1271 return;
1274 // if the existing DisplayObject is not a shape, move it instead
1275 // of replacing.
1276 if (isReferenceable(*existing_char)) {
1277 move_display_object(tag, dlist);
1278 return;
1281 Global_as& gl = getGlobal(*getObject(this));
1282 DisplayObject* ch = cdef->createDisplayObject(gl, this);
1285 // TODO: check if we can drop this for REPLACE!
1286 // should we rename the DisplayObject when it's REPLACE tag?
1287 if (tag->hasName()) {
1288 VM& vm = getVM(*getObject(this));
1289 ch->set_name(getURI(vm, tag->getName()));
1291 else if (isReferenceable(*ch)) {
1292 ch->set_name(getNextUnnamedInstanceName());
1294 if (tag->hasRatio()) {
1295 ch->set_ratio(tag->getRatio());
1297 if (tag->hasCxform()) {
1298 ch->setCxForm(tag->getCxform());
1300 if (tag->hasMatrix()) {
1301 ch->setMatrix(tag->getMatrix(), true);
1304 // use SWFMatrix from the old DisplayObject if tag doesn't provide one.
1305 dlist.replaceDisplayObject(ch, tag->getDepth(),
1306 !tag->hasCxform(), !tag->hasMatrix());
1307 ch->construct();
1310 void
1311 MovieClip::remove_display_object(const SWF::PlaceObject2Tag* tag,
1312 DisplayList& dlist)
1314 set_invalidated();
1315 dlist.removeDisplayObject(tag->getDepth());
1318 void
1319 MovieClip::remove_display_object(int depth, int)
1321 set_invalidated();
1322 _displayList.removeDisplayObject(depth);
1325 void
1326 MovieClip::increment_frame_and_check_for_loop()
1328 const size_t frame_count = get_loaded_frames();
1329 if (++_currentFrame >= frame_count) {
1330 // Loop.
1331 _currentFrame = 0;
1332 _hasLooped = true;
1336 bool
1337 MovieClip::handleFocus()
1339 as_object* obj = getObject(this);
1340 assert(obj);
1342 // For SWF6 and above: the MovieClip can always receive focus if
1343 // focusEnabled evaluates to true.
1344 if (getSWFVersion(*obj) > 5) {
1345 as_value focusEnabled;
1346 if (obj->get_member(NSV::PROP_FOCUS_ENABLED, &focusEnabled)) {
1347 if (toBool(focusEnabled, getVM(*obj))) return true;
1351 // If focusEnabled doesn't evaluate to true or for SWF5, return true
1352 // only if at least one mouse event handler is defined.
1353 return mouseEnabled();
1356 bool
1357 MovieClip::pointInShape(boost::int32_t x, boost::int32_t y) const
1359 ShapeContainerFinder finder(x, y);
1360 _displayList.visitBackward(finder);
1361 if ( finder.hitFound() ) return true;
1362 return hitTestDrawable(x, y);
1365 bool
1366 MovieClip::pointInVisibleShape(boost::int32_t x, boost::int32_t y) const
1368 if (! visible()) return false;
1369 if (isDynamicMask() && ! mouseEnabled()) {
1370 // see testsuite/misc-ming.all/masks_test.swf
1371 #ifdef GNASH_DEBUG_HITTEST
1372 log_debug(_("%s is a dynamic mask and can't handle mouse "
1373 "events, no point will hit it"), getTarget());
1374 #endif
1375 return false;
1377 const DisplayObject* mask = getMask(); // dynamic one
1378 if (mask && mask->visible() && !mask->pointInShape(x, y)) {
1379 #ifdef GNASH_DEBUG_HITTEST
1380 log_debug(_("%s is dynamically masked by %s, which "
1381 "doesn't hit point %g,%g"), getTarget(),
1382 mask->getTarget(), x, y);
1383 #endif
1384 return false;
1386 VisibleShapeContainerFinder finder(x, y);
1387 _displayList.visitBackward(finder);
1388 if (finder.hitFound()) return true;
1389 return hitTestDrawable(x, y);
1392 inline bool
1393 MovieClip::hitTestDrawable(boost::int32_t x, boost::int32_t y) const
1395 const SWFMatrix wm = getWorldMatrix(*this).invert();
1396 point lp(x, y);
1397 wm.transform(lp);
1398 if (!_drawable.getBounds().point_test(lp.x, lp.y)) return false;
1399 return _drawable.pointTestLocal(lp.x, lp.y, wm);
1402 bool
1403 MovieClip::pointInHitableShape(boost::int32_t x, boost::int32_t y) const
1405 if (isDynamicMask() && !mouseEnabled()) return false;
1407 const DisplayObject* mask = getMask();
1408 if (mask && !mask->pointInShape(x, y)) return false;
1410 HitableShapeContainerFinder finder(x, y);
1411 _displayList.visitBackward(finder);
1412 if (finder.hitFound()) return true;
1414 return hitTestDrawable(x, y);
1417 InteractiveObject*
1418 MovieClip::topmostMouseEntity(boost::int32_t x, boost::int32_t y)
1420 if (!visible()) return 0;
1422 // point is in parent's space, we need to convert it in world space
1423 point wp(x, y);
1424 DisplayObject* p = parent();
1425 if (p) {
1426 // WARNING: if we have NO parent, our parent is the Stage (movie_root)
1427 // so, in case we'll add a "stage" matrix, we'll need to take
1428 // it into account here.
1429 // TODO: actually, why are we insisting in using parent's
1430 // coordinates for this method at all ?
1431 getWorldMatrix(*p).transform(wp);
1434 if (mouseEnabled()) {
1435 if (pointInVisibleShape(wp.x, wp.y)) return this;
1436 return 0;
1439 SWFMatrix m = getMatrix(*this);
1440 m.invert();
1441 point pp(x, y);
1442 m.transform(pp);
1444 MouseEntityFinder finder(wp, pp);
1445 _displayList.visitAll(finder);
1446 InteractiveObject* ch = finder.getEntity();
1448 // It doesn't make any sense to query _drawable, as it's
1449 // not an InteractiveObject.
1450 return ch;
1453 const DisplayObject*
1454 MovieClip::findDropTarget(boost::int32_t x, boost::int32_t y,
1455 DisplayObject* dragging) const
1457 if (this == dragging) return 0; // not here...
1459 if (!visible()) return 0; // isn't me !
1461 DropTargetFinder finder(x, y, dragging);
1462 _displayList.visitAll(finder);
1464 // does it hit any child ?
1465 const DisplayObject* ch = finder.getDropChar();
1466 if (ch) {
1467 // TODO: find closest actionscript referenceable container
1468 // (possibly itself)
1469 return ch;
1472 // does it hit us ?
1473 if (hitTestDrawable(x, y)) return this;
1475 return 0;
1478 bool
1479 MovieClip::trackAsMenu()
1481 as_object* obj = getObject(this);
1482 assert(obj);
1484 as_value track;
1485 VM& vm = getVM(*obj);
1486 // TODO: use namedStrings here
1487 return (obj->get_member(getURI(vm, "trackAsMenu"), &track) &&
1488 toBool(track, vm));
1491 bool
1492 MovieClip::mouseEnabled() const
1494 if (!isEnabled()) return false;
1496 // Event handlers that qualify as mouse event handlers.
1497 static const event_id EH[] = {
1498 event_id(event_id::PRESS),
1499 event_id(event_id::RELEASE),
1500 event_id(event_id::RELEASE_OUTSIDE),
1501 event_id(event_id::ROLL_OVER),
1502 event_id(event_id::ROLL_OUT),
1503 event_id(event_id::DRAG_OVER),
1504 event_id(event_id::DRAG_OUT),
1507 const size_t size = arraySize(EH);
1509 for (size_t i = 0; i < size; ++i) {
1510 const event_id &event = EH[i];
1512 // Check event handlers
1513 if (hasEventHandler(event_id(event.id()))) {
1514 return true;
1517 return false;
1520 void
1521 MovieClip::stop_drag()
1523 stage().stop_drag();
1526 void
1527 MovieClip::set_background_color(const rgba& color)
1529 stage().set_background_color(color);
1532 void
1533 MovieClip::cleanup_textfield_variables()
1535 // nothing to do
1536 if (!_text_variables.get()) return;
1538 TextFieldIndex& m = *_text_variables;
1540 for (TextFieldIndex::iterator i=m.begin(), ie=m.end(); i!=ie; ++i)
1542 TextFields& v=i->second;
1543 TextFields::iterator lastValid = std::remove_if(v.begin(), v.end(),
1544 boost::mem_fn(&DisplayObject::unloaded));
1545 v.erase(lastValid, v.end());
1550 void
1551 MovieClip::set_textfield_variable(const ObjectURI& name, TextField* ch)
1553 assert(ch);
1555 // lazy allocation
1556 if (!_text_variables.get()) {
1557 _text_variables.reset(new TextFieldIndex);
1560 (*_text_variables)[name].push_back(ch);
1563 DisplayObject*
1564 MovieClip::getDisplayListObject(const ObjectURI& uri)
1566 as_object* obj = getObject(this);
1567 assert(obj);
1569 string_table& st = getStringTable(*obj);
1571 // Try items on our display list.
1572 DisplayObject* ch = _displayList.getDisplayObjectByName(st, uri,
1573 caseless(*obj));
1575 if (!ch) return 0;
1577 // Found object.
1579 // If the object is an ActionScript referenciable one we
1580 // return it, otherwise we return ourselves
1581 if (isReferenceable(*ch)) {
1582 return ch;
1584 return this;
1587 void
1588 MovieClip::add_invalidated_bounds(InvalidatedRanges& ranges, bool force)
1590 // nothing to do if this movieclip is not visible
1591 if (!visible() || invisible(getCxForm(*this))) {
1592 ranges.add(m_old_invalidated_ranges);
1593 return;
1596 if (!invalidated() && !childInvalidated() && !force) return;
1598 // m_child_invalidated does not require our own bounds
1599 if (invalidated() || force) {
1600 // Add old invalidated bounds
1601 ranges.add(m_old_invalidated_ranges);
1604 _displayList.add_invalidated_bounds(ranges, force || invalidated());
1606 /// Add drawable.
1607 SWFRect bounds;
1608 bounds.expand_to_transformed_rect(getWorldMatrix(*this),
1609 _drawable.getBounds());
1611 ranges.add(bounds.getRange());
1615 void
1616 MovieClip::constructAsScriptObject()
1618 as_object* mc = getObject(this);
1620 // A MovieClip should always have an associated object.
1621 assert(mc);
1623 if (!parent()) {
1624 mc->init_member("$version", getVM(*mc).getPlayerVersion(), 0);
1627 const sprite_definition* def =
1628 dynamic_cast<const sprite_definition*>(_def.get());
1630 // We won't "construct" top-level movies
1631 as_function* ctor = def ? def->getRegisteredClass() : 0;
1633 #ifdef GNASH_DEBUG
1634 log_debug(_("Attached movieclips %s registered class is %p"),
1635 getTarget(), (void*)ctor);
1636 #endif
1638 // Set this MovieClip object to be an instance of the class.
1639 if (ctor) {
1640 Property* proto = ctor->getOwnProperty(NSV::PROP_PROTOTYPE);
1641 if (proto) mc->set_prototype(proto->getValue(*ctor));
1644 // Send the construct event. This must be done after the __proto__
1645 // member is set. It is always done.
1646 notifyEvent(event_id(event_id::CONSTRUCT));
1648 if (ctor) {
1649 const int swfversion = getSWFVersion(*mc);
1650 if (swfversion > 5) {
1651 fn_call::Args args;
1652 ctor->construct(*mc, get_environment(), args);
1657 void
1658 MovieClip::construct(as_object* initObj)
1660 assert(!unloaded());
1662 saveOriginalTarget();
1664 #ifdef GNASH_DEBUG
1665 log_debug(_("Sprite '%s' placed on stage"), getTarget());
1666 #endif
1668 // Register this movieclip as a live one
1669 stage().addLiveChar(this);
1671 // It seems it's legal to place 0-framed movieclips on stage.
1672 // See testsuite/misc-swfmill.all/zeroframe_definemovieclip.swf
1674 // Now execute frame tags and take care of queuing the LOAD event.
1676 // DLIST tags are executed immediately while ACTION tags are queued.
1678 // For _root movie, LOAD event is invoked *after* actions in first frame
1679 // See misc-ming.all/action_execution_order_test4.{c,swf}
1681 assert(!_callingFrameActions); // or will not be queuing actions
1682 if (!parent()) {
1684 executeFrameTags(0, _displayList, SWF::ControlTag::TAG_DLIST |
1685 SWF::ControlTag::TAG_ACTION);
1687 if (getSWFVersion(*getObject(this)) > 5) {
1688 queueEvent(event_id(event_id::LOAD),
1689 movie_root::PRIORITY_DOACTION);
1693 else {
1694 queueEvent(event_id(event_id::LOAD), movie_root::PRIORITY_DOACTION);
1695 executeFrameTags(0, _displayList, SWF::ControlTag::TAG_DLIST |
1696 SWF::ControlTag::TAG_ACTION);
1699 as_object* mc = getObject(this);
1701 // A MovieClip should always have an associated object.
1702 assert(mc);
1704 // We execute events immediately when the stage-placed DisplayObject
1705 // is dynamic, This is becase we assume that this means that
1706 // the DisplayObject is placed during processing of actions (opposed
1707 // that during advancement iteration).
1709 // A more general implementation might ask movie_root about its state
1710 // (iterating or processing actions?)
1711 // Another possibility to inspect could be letting movie_root decide
1712 // when to really queue and when rather to execute immediately the
1713 // events with priority INITIALIZE or CONSTRUCT ...
1714 if (!isDynamic()) {
1716 #ifdef GNASH_DEBUG
1717 log_debug(_("Queuing INITIALIZE and CONSTRUCT events for movieclip %s"),
1718 getTarget());
1719 #endif
1721 std::auto_ptr<ExecutableCode> code(new ConstructEvent(this));
1722 stage().pushAction(code, movie_root::PRIORITY_CONSTRUCT);
1725 else {
1727 // Properties from an initObj must be copied before construction, but
1728 // after the display list has been populated, so that _height and
1729 // _width (which depend on bounds) are correct.
1730 if (initObj) {
1731 mc->copyProperties(*initObj);
1733 constructAsScriptObject();
1736 // Tested in testsuite/swfdec/duplicateMovieclip-events.c and
1737 // testsuite/swfdec/clone-sprite-events.c not to call notifyEvent
1738 // immediately.
1739 queueEvent(event_id(event_id::INITIALIZE), movie_root::PRIORITY_INIT);
1742 bool
1743 MovieClip::unloadChildren()
1745 #ifdef GNASH_DEBUG
1746 log_debug(_("Unloading movieclip '%s'"), getTargetPath());
1747 #endif
1749 // stop any pending streaming sounds
1750 stopStreamSound();
1752 // We won't be displayed again, so worth releasing
1753 // some memory. The drawable might take a lot of memory
1754 // on itself.
1755 _drawable.clear();
1757 return _displayList.unload();
1760 void
1761 MovieClip::getLoadedMovie(Movie* extern_movie)
1763 DisplayObject* p = parent();
1764 if (p) {
1765 extern_movie->set_parent(p);
1767 // Copy own lockroot value
1768 extern_movie->setLockRoot(getLockRoot());
1770 // Copy own event handlers
1771 // see testsuite/misc-ming.all/loadMovieTest.swf
1772 const Events& clipEvs = get_event_handlers();
1773 // top-level movies can't have clip events, right ?
1774 assert (extern_movie->get_event_handlers().empty());
1775 extern_movie->set_event_handlers(clipEvs);
1777 // Copy own name
1778 // TODO: check empty != none...
1779 const ObjectURI& name = get_name();
1780 if (!name.empty()) extern_movie->set_name(name);
1782 // Copy own clip depth (TODO: check this)
1783 extern_movie->set_clip_depth(get_clip_depth());
1785 // Replace ourselves in parent
1786 // TODO: don't pretend our parent is a MovieClip,
1787 // could as well be a button I guess...
1788 // At most we should require it to be a
1789 // DisplayObjectContainer and log an error if it's not.
1790 MovieClip* parent_sp = p->to_movie();
1791 assert(parent_sp);
1792 parent_sp->_displayList.replaceDisplayObject(extern_movie, get_depth(),
1793 true, true);
1794 extern_movie->construct();
1796 else {
1797 // replaceLevel will set depth for us
1798 stage().replaceLevel(get_depth() - DisplayObject::staticDepthOffset,
1799 extern_movie);
1803 void
1804 MovieClip::loadVariables(const std::string& urlstr,
1805 VariablesMethod sendVarsMethod)
1807 // Host security check will be will be done by LoadVariablesThread
1808 // (down by getStream, that is)
1810 const movie_root& mr = stage();
1811 URL url(urlstr, mr.runResources().streamProvider().baseURL());
1813 std::string postdata;
1815 // Encode our vars for sending.
1816 if (sendVarsMethod != METHOD_NONE) {
1817 postdata = getURLEncodedVars(*getObject(this));
1820 try {
1821 const StreamProvider& sp =
1822 getRunResources(*getObject(this)).streamProvider();
1824 if (sendVarsMethod == METHOD_POST) {
1825 // use POST method
1826 _loadVariableRequests.push_back(
1827 new LoadVariablesThread(sp, url, postdata));
1829 else {
1830 // use GET method
1831 if (sendVarsMethod == METHOD_GET) {
1832 // Append variables
1833 std::string qs = url.querystring();
1834 if (qs.empty()) url.set_querystring(postdata);
1835 else url.set_querystring(qs + "&" + postdata);
1837 _loadVariableRequests.push_back(new LoadVariablesThread(sp, url));
1839 _loadVariableRequests.back().process();
1841 catch (const NetworkException& ex) {
1842 log_error(_("Could not load variables from %s"), url.str());
1846 void
1847 MovieClip::processCompletedLoadVariableRequest(LoadVariablesThread& request)
1849 assert(request.completed());
1851 MovieVariables& vals = request.getValues();
1852 setVariables(vals);
1854 // We want to call a clip-event too if available, see bug #22116
1855 notifyEvent(event_id(event_id::DATA));
1858 void
1859 MovieClip::processCompletedLoadVariableRequests()
1861 // Nothing to do (just for clarity)
1862 if (_loadVariableRequests.empty()) return;
1864 for (LoadVariablesThreads::iterator it=_loadVariableRequests.begin();
1865 it != _loadVariableRequests.end();) {
1867 LoadVariablesThread& request = *it;
1868 if (request.completed()) {
1869 processCompletedLoadVariableRequest(request);
1870 it = _loadVariableRequests.erase(it);
1872 else ++it;
1876 void
1877 MovieClip::setVariables(const MovieVariables& vars)
1879 VM& vm = getVM(*getObject(this));
1880 for (MovieVariables::const_iterator it=vars.begin(), itEnd=vars.end();
1881 it != itEnd; ++it) {
1883 const std::string& name = it->first;
1884 const std::string& val = it->second;
1885 getObject(this)->set_member(getURI(vm, name), val);
1889 void
1890 MovieClip::removeMovieClip()
1892 const int depth = get_depth();
1893 if (depth < 0 || depth > 1048575) {
1894 IF_VERBOSE_ASCODING_ERRORS(
1895 log_aserror(_("removeMovieClip(%s): movieclip depth (%d) out of "
1896 "the 'dynamic' zone [0..1048575], won't remove"),
1897 getTarget(), depth);
1899 return;
1902 MovieClip* p = dynamic_cast<MovieClip*>(parent());
1903 if (p) {
1904 // second argument is arbitrary, see comments above
1905 // the function declaration in MovieClip.h
1906 p->remove_display_object(depth, 0);
1908 else {
1909 // removing _level#
1910 stage().dropLevel(depth);
1911 // I guess this can only happen if someone uses
1912 // _swf.swapDepth([0..1048575])
1917 SWFRect
1918 MovieClip::getBounds() const
1920 SWFRect bounds;
1921 BoundsFinder f(bounds);
1922 _displayList.visitAll(f);
1923 SWFRect drawableBounds = _drawable.getBounds();
1924 bounds.expand_to_rect(drawableBounds);
1926 return bounds;
1929 bool
1930 MovieClip::isEnabled() const
1932 as_object* obj = getObject(this);
1933 assert(obj);
1935 as_value enabled;
1936 if (!obj->get_member(NSV::PROP_ENABLED, &enabled)) {
1937 // We're enabled if there's no 'enabled' member...
1938 return true;
1940 return toBool(enabled, getVM(*obj));
1944 void
1945 MovieClip::visitNonProperties(KeyVisitor& v) const
1947 DisplayListVisitor dv(v);
1948 _displayList.visitAll(dv);
1951 void
1952 MovieClip::cleanupDisplayList()
1954 _displayList.removeUnloaded();
1955 cleanup_textfield_variables();
1958 void
1959 MovieClip::markOwnResources() const
1961 ReachableMarker marker;
1963 _displayList.visitAll(marker);
1965 _environment.markReachableResources();
1967 // Mark textfields in the TextFieldIndex
1968 if (_text_variables.get()) {
1969 for (TextFieldIndex::const_iterator i=_text_variables->begin(),
1970 e=_text_variables->end();
1971 i!=e; ++i)
1973 const TextFields& tfs=i->second;
1974 std::for_each(tfs.begin(), tfs.end(),
1975 boost::mem_fn(&DisplayObject::setReachable));
1979 // Mark our relative root
1980 _swf->setReachable();
1983 void
1984 MovieClip::destroy()
1986 stopStreamSound();
1987 _displayList.destroy();
1988 DisplayObject::destroy();
1991 Movie*
1992 MovieClip::get_root() const
1994 return _swf;
1997 MovieClip*
1998 MovieClip::getAsRoot()
2001 // TODO1: as an optimization, if swf version < 7
2002 // we might as well just return _swf,
2003 // the whole chain from this movieclip to it's
2004 // _swf should have the same version...
2006 // TODO2: implement this with iteration rather
2007 // then recursion.
2010 DisplayObject* p = parent();
2011 if (!p) return this; // no parent, we're the root
2013 // If we have a parent, we descend to it unless
2014 // our _lockroot is true AND our or the VM's
2015 // SWF version is > 6
2016 int topSWFVersion = stage().getRootMovie().version();
2018 if (getDefinitionVersion() > 6 || topSWFVersion > 6) {
2019 if (getLockRoot()) return this;
2022 return p->getAsRoot();
2026 void
2027 MovieClip::setStreamSoundId(int id)
2029 if (id != m_sound_stream_id) {
2030 log_debug(_("Stream sound id from %d to %d, stopping old"),
2031 m_sound_stream_id, id);
2032 stopStreamSound();
2034 m_sound_stream_id = id;
2037 void
2038 MovieClip::stopStreamSound()
2040 if (m_sound_stream_id == -1) return; // nothing to do
2042 sound::sound_handler* handler = getRunResources(*getObject(this)).soundHandler();
2043 if (handler) {
2044 handler->stop_sound(m_sound_stream_id);
2047 m_sound_stream_id = -1;
2050 void
2051 MovieClip::setPlayState(PlayState s)
2053 if (s == _playState) return; // nothing to do
2054 if (s == PLAYSTATE_STOP) stopStreamSound();
2055 _playState = s;
2058 namespace {
2060 MovieClip::TextFields*
2061 textfieldVar(MovieClip::TextFieldIndex* t, const ObjectURI& name)
2063 // nothing allocated yet...
2064 if (!t) return 0;
2066 // TODO: should variable name be considered case-insensitive ?
2067 MovieClip::TextFieldIndex::iterator it = t->find(name);
2068 if (it == t->end()) return 0;
2069 return &(it->second);
2072 } // unnamed namespace
2073 } // namespace gnash