drop libxv, add lsb-release
[gnash.git] / libcore / MovieClip.cpp
blob0afc117cb80e67d10ee4bcdccaacf4601f82e3d3
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" // GNASH_USE_GC
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 // Anonymous namespace for module-private definitions
88 namespace {
90 /// ConstructEvent, used for queuing construction
92 /// Its execution will call constructAsScriptObject()
93 /// on the target movieclip
94 ///
95 class ConstructEvent: public ExecutableCode {
97 public:
99 explicit ConstructEvent(MovieClip* nTarget)
101 ExecutableCode(nTarget)
104 virtual void execute()
106 static_cast<MovieClip*>(target())->constructAsScriptObject();
111 /// Find a DisplayObject hit by the given coordinates.
113 /// This class takes care about taking masks layers into
114 /// account, but nested masks aren't properly tested yet.
116 class MouseEntityFinder
118 public:
120 /// @param wp
121 /// Query point in world coordinate space
123 /// @param pp
124 /// Query point in parent coordinate space
126 MouseEntityFinder(point wp, point pp)
128 _highestHiddenDepth(std::numeric_limits<int>::min()),
129 _m(NULL),
130 _candidates(),
131 _wp(wp),
132 _pp(pp),
133 _checked(false)
136 void operator() (DisplayObject* ch)
138 assert(!_checked);
139 if ( ch->get_depth() <= _highestHiddenDepth )
141 if ( ch->isMaskLayer() )
143 log_debug(_("CHECKME: nested mask in MouseEntityFinder. "
144 "This mask is %s at depth %d outer mask masked "
145 "up to depth %d."),
146 ch->getTarget(), ch->get_depth(),
147 _highestHiddenDepth);
148 // Hiding mask still in effect...
150 return;
153 if ( ch->isMaskLayer() )
155 if ( ! ch->pointInShape(_wp.x, _wp.y) )
157 #ifdef DEBUG_MOUSE_ENTITY_FINDING
158 log_debug(_("Character %s at depth %d is a mask not hitting "
159 "the query point %g,%g and masking up to "
160 "depth %d"), ch->getTarget(), ch->get_depth(),
161 _wp.x, _wp.y, ch->get_clip_depth());
162 #endif
163 _highestHiddenDepth = ch->get_clip_depth();
165 else
167 #ifdef DEBUG_MOUSE_ENTITY_FINDING
168 log_debug(_("Character %s at depth %d is a mask hitting the "
169 "query point %g,%g"),
170 ch->getTarget(), ch->get_depth(), _wp.x, _wp.y);
171 #endif
173 return;
175 if (! ch->visible()) return;
177 _candidates.push_back(ch);
180 void checkCandidates()
182 if (_checked) return;
183 for (Candidates::reverse_iterator i=_candidates.rbegin(),
184 e=_candidates.rend(); i!=e; ++i) {
185 DisplayObject* ch = *i;
186 InteractiveObject* te = ch->topmostMouseEntity(_pp.x, _pp.y);
187 if (te) {
188 _m = te;
189 break;
192 _checked = true;
195 InteractiveObject* getEntity()
197 checkCandidates();
198 #ifdef DEBUG_MOUSE_ENTITY_FINDING
199 if ( _m )
201 log_debug(_("MouseEntityFinder found DisplayObject %s (depth %d) "
202 "hitting point %g,%g"),
203 _m->getTarget(), _m->get_depth(), _wp.x, _wp.y);
205 #endif // DEBUG_MOUSE_ENTITY_FINDING
206 return _m;
209 private:
211 /// Highest depth hidden by a mask
213 /// This will be -1 initially, and set
214 /// the the depth of a mask when the mask
215 /// doesn't contain the query point, while
216 /// scanning a DisplayList bottom-up
218 int _highestHiddenDepth;
220 InteractiveObject* _m;
222 typedef std::vector<DisplayObject*> Candidates;
223 Candidates _candidates;
225 /// Query point in world coordinate space
226 point _wp;
228 /// Query point in parent coordinate space
229 point _pp;
231 bool _checked;
235 /// Find the first DisplayObject whose shape contain the point
237 /// Point coordinates in world TWIPS
239 class ShapeContainerFinder
241 public:
243 ShapeContainerFinder(boost::int32_t x, boost::int32_t y)
245 _found(false),
246 _x(x),
247 _y(y)
250 bool operator()(const DisplayObject* ch) {
251 if (ch->pointInShape(_x, _y)) {
252 _found = true;
253 return false;
255 return true;
258 bool hitFound() const { return _found; }
260 private:
261 bool _found;
262 const boost::int32_t _x;
263 const boost::int32_t _y;
266 /// Find the first visible DisplayObject whose shape contain the point
268 /// Point coordinates in world TWIPS
270 class VisibleShapeContainerFinder
272 public:
274 VisibleShapeContainerFinder(boost::int32_t x, boost::int32_t y)
276 _found(false),
277 _x(x),
278 _y(y)
281 bool operator()(const DisplayObject* ch)
283 if (ch->pointInVisibleShape(_x, _y)) {
284 _found = true;
285 return false;
287 return true;
290 bool hitFound() const { return _found; }
292 private:
293 bool _found;
294 const boost::int32_t _x;
295 const boost::int32_t _y;
298 /// Find the first hitable DisplayObject whose shape contain the point
300 /// Point coordinates in world TWIPS
301 ///
302 class HitableShapeContainerFinder
304 public:
305 HitableShapeContainerFinder(boost::int32_t x, boost::int32_t y)
307 _found(false),
308 _x(x),
309 _y(y)
312 bool operator()(const DisplayObject* ch)
314 if (ch->isDynamicMask()) return true;
315 if (ch->pointInShape(_x, _y)) {
316 _found = true;
317 return false;
319 return true;
322 bool hitFound() const { return _found; }
324 private:
326 bool _found;
328 // x position in twips.
329 const boost::int32_t _x;
331 // y position in twips.
332 const boost::int32_t _y;
335 /// A DisplayList visitor used to compute its overall bounds.
337 class BoundsFinder
339 public:
340 explicit BoundsFinder(SWFRect& b) : _bounds(b) {}
342 void operator()(DisplayObject* ch) {
343 // don't include bounds of unloaded DisplayObjects
344 if (ch->unloaded()) return;
345 SWFRect chb = ch->getBounds();
346 SWFMatrix m = getMatrix(*ch);
347 _bounds.expand_to_transformed_rect(m, chb);
350 private:
351 SWFRect& _bounds;
354 struct ReachableMarker
356 void operator()(DisplayObject *ch) const {
357 ch->setReachable();
361 /// Find the first visible DisplayObject whose shape contain the point
362 /// and is not the DisplayObject being dragged or any of its childs
364 /// Point coordinates in world TWIPS
366 class DropTargetFinder {
368 /// Highest depth hidden by a mask
370 /// This will be -1 initially, and set
371 /// the the depth of a mask when the mask
372 /// doesn't contain the query point, while
373 /// scanning a DisplayList bottom-up
375 int _highestHiddenDepth;
377 boost::int32_t _x;
378 boost::int32_t _y;
379 DisplayObject* _dragging;
380 mutable const DisplayObject* _dropch;
382 typedef std::vector<const DisplayObject*> Candidates;
383 Candidates _candidates;
385 mutable bool _checked;
387 public:
389 DropTargetFinder(boost::int32_t x, boost::int32_t y, DisplayObject* dragging)
391 _highestHiddenDepth(std::numeric_limits<int>::min()),
392 _x(x),
393 _y(y),
394 _dragging(dragging),
395 _dropch(0),
396 _candidates(),
397 _checked(false)
400 void operator() (const DisplayObject* ch)
402 assert(!_checked);
403 if ( ch->get_depth() <= _highestHiddenDepth )
405 if ( ch->isMaskLayer() )
407 log_debug(_("CHECKME: nested mask in DropTargetFinder. "
408 "This mask is %s at depth %d outer mask masked "
409 "up to depth %d."),
410 ch->getTarget(), ch->get_depth(), _highestHiddenDepth);
411 // Hiding mask still in effect...
413 return;
416 if ( ch->isMaskLayer() )
418 if ( ! ch->visible() )
420 log_debug(_("FIXME: invisible mask in MouseEntityFinder."));
422 if ( ! ch->pointInShape(_x, _y) )
424 #ifdef DEBUG_MOUSE_ENTITY_FINDING
425 log_debug(_("Character %s at depth %d is a mask not hitting "
426 "the query point %g,%g and masking up to depth %d"),
427 ch->getTarget(), ch->get_depth(), _x, _y,
428 ch->get_clip_depth());
429 #endif
430 _highestHiddenDepth = ch->get_clip_depth();
432 else
434 #ifdef DEBUG_MOUSE_ENTITY_FINDING
435 log_debug(_("Character %s at depth %d is a mask "
436 "hitting the query point %g,%g"),
437 ch->getTarget(), ch->get_depth(), _x, _y);
438 #endif
441 return;
444 _candidates.push_back(ch);
448 void checkCandidates() const
450 if ( _checked ) return;
451 for (Candidates::const_reverse_iterator i=_candidates.rbegin(),
452 e=_candidates.rend(); i!=e; ++i)
454 const DisplayObject* ch = *i;
455 const DisplayObject* dropChar = ch->findDropTarget(_x, _y, _dragging);
456 if ( dropChar )
458 _dropch = dropChar;
459 break;
462 _checked = true;
465 const DisplayObject* getDropChar() const
467 checkCandidates();
468 return _dropch;
472 class DisplayListVisitor
474 public:
475 DisplayListVisitor(KeyVisitor& v) : _v(v) {}
477 void operator()(DisplayObject* ch) const {
478 if (!isReferenceable(*ch)) return;
479 // Don't enumerate unloaded DisplayObjects
480 if (ch->unloaded()) return;
482 const ObjectURI& name = ch->get_name();
483 // Don't enumerate unnamed DisplayObjects
484 if (name.empty()) return;
486 // Referenceable DisplayObject always have an object.
487 assert(getObject(ch));
488 _v(name);
490 private:
491 KeyVisitor& _v;
494 } // anonymous namespace
497 MovieClip::MovieClip(as_object* object, const movie_definition* def,
498 Movie* r, DisplayObject* parent)
500 DisplayObjectContainer(object, parent),
501 _def(def),
502 _swf(r),
503 _playState(PLAYSTATE_PLAY),
504 _environment(getVM(*object)),
505 _currentFrame(0),
506 m_sound_stream_id(-1),
507 _hasLooped(false),
508 _callingFrameActions(false),
509 _lockroot(false)
511 assert(_swf);
512 assert(object);
514 _environment.set_target(this);
518 MovieClip::~MovieClip()
520 stopStreamSound();
521 deleteChecked(_loadVariableRequests.begin(), _loadVariableRequests.end());
525 MovieClip::getDefinitionVersion() const
527 return _swf->version();
530 // Execute the actions in the action list, in the given
531 // environment. The list of action will be consumed
532 // starting from the first element. When the function returns
533 // the list should be empty.
534 void
535 MovieClip::execute_actions(MovieClip::ActionList& action_list)
537 // action_list may be changed due to actions (appended-to)
538 // This loop is probably quicker than using an iterator
539 // and a final call to .clear(), as repeated calls to
540 // .size() or .end() are no quicker (and probably slower)
541 // than pop_front(), which is constant time.
542 while (!action_list.empty()) {
543 const action_buffer* ab = action_list.front();
544 action_list.pop_front();
546 execute_action(*ab);
550 DisplayObject*
551 MovieClip::getDisplayObjectAtDepth(int depth)
553 return _displayList.getDisplayObjectAtDepth(depth);
556 /// This handles special properties of MovieClip.
558 /// The only genuine special properties are DisplayList members. These
559 /// are accessible as properties and are enumerated, but not ownProperties
560 /// of a MovieClip.
562 /// The TextField variables should probably be handled in a more generic
563 /// way.
564 bool
565 MovieClip::getTextFieldVariables(const ObjectURI& uri, as_value& val)
567 const string_table::key name_key = getName(uri);
569 const std::string& name = getStringTable(*getObject(this)).value(name_key);
571 // Try textfield variables
572 TextFields* etc = get_textfield_variable(name);
573 if ( etc )
575 for (TextFields::const_iterator i=etc->begin(), e=etc->end();
576 i!=e; ++i)
578 TextField* tf = *i;
579 if ( tf->getTextDefined() )
581 val = tf->get_text_value();
582 return true;
587 return false;
591 bool
592 MovieClip::get_frame_number(const as_value& frame_spec, size_t& frameno) const
595 // If there is no definition, this is a dynamically-created MovieClip
596 // and has no frames.
597 if (!_def) return false;
599 std::string fspecStr = frame_spec.to_string();
601 as_value str(fspecStr);
603 const double num = toNumber(str, getVM(*getObject(this)));
605 if (!isFinite(num) || int(num) != num || num == 0)
607 bool ret = _def->get_labeled_frame(fspecStr, frameno);
608 return ret;
611 if (num < 0) return false;
613 // all frame numbers > 0 are valid, but a valid frame number may still
614 // reference a non-exist frame(eg. frameno > total_frames).
615 frameno = size_t(num) - 1;
617 return true;
620 /// Execute the actions for the specified frame.
622 /// The frame_spec could be an integer or a string.
624 void
625 MovieClip::call_frame_actions(const as_value& frame_spec)
627 // If there is no definition, this is a dynamically-created MovieClip
628 // and has no frames.
629 if (!_def) return;
631 size_t frame_number;
632 if (!get_frame_number(frame_spec, frame_number)) {
633 // No dice.
634 IF_VERBOSE_ASCODING_ERRORS(
635 log_aserror(_("call_frame('%s') -- invalid frame"),
636 frame_spec);
638 return;
641 // Execute the ControlTag actions
642 // We set _callingFrameActions to true so that add_action_buffer
643 // will execute immediately instead of queuing them.
644 // NOTE: in case gotoFrame is executed by code in the called frame
645 // we'll temporarly clear the _callingFrameActions flag
646 // to properly queue actions back on the global queue.
648 _callingFrameActions = true;
649 const PlayList* playlist = _def->getPlaylist(frame_number);
650 if (playlist) {
651 PlayList::const_iterator it = playlist->begin();
652 const PlayList::const_iterator e = playlist->end();
653 for (; it != e; it++) {
654 (*it)->executeActions(this, _displayList);
657 _callingFrameActions = false;
661 DisplayObject*
662 MovieClip::addDisplayListObject(DisplayObject* obj, int depth)
664 // TODO: only call set_invalidated if this DisplayObject actually overrides
665 // an existing one !
666 set_invalidated();
667 _displayList.placeDisplayObject(obj, depth);
668 obj->construct();
669 return obj;
673 MovieClip*
674 MovieClip::duplicateMovieClip(const std::string& newname, int depth,
675 as_object* initObject)
677 DisplayObject* parent_ch = parent();
678 if (!parent_ch) {
679 log_error(_("Can't clone root of the movie"));
680 return NULL;
682 MovieClip* parent = parent_ch->to_movie();
683 if ( ! parent )
685 log_error(_("%s parent is not a movieclip, can't clone"), getTarget());
686 return NULL;
689 as_object* o = getObjectWithPrototype(getGlobal(*getObject(this)),
690 NSV::CLASS_MOVIE_CLIP);
692 MovieClip* newmovieclip = new MovieClip(o, _def.get(), _swf, parent);
694 const string_table::key nn = getStringTable(*getObject(this)).find(newname);
695 newmovieclip->set_name(nn);
697 newmovieclip->setDynamic();
699 // Copy event handlers from movieclip
700 // We should not copy 'm_action_buffer' since the
701 // 'm_method' already contains it
702 newmovieclip->set_event_handlers(get_event_handlers());
704 // Copy drawable
705 newmovieclip->_drawable = _drawable;
707 newmovieclip->setCxForm(getCxForm(*this));
708 newmovieclip->setMatrix(getMatrix(*this), true);
709 newmovieclip->set_ratio(get_ratio());
710 newmovieclip->set_clip_depth(get_clip_depth());
712 parent->_displayList.placeDisplayObject(newmovieclip, depth);
713 newmovieclip->construct(initObject);
715 return newmovieclip;
718 void
719 MovieClip::queueAction(const action_buffer& action)
721 stage().pushAction(action, this);
724 void
725 MovieClip::queueActions(ActionList& actions)
727 for(ActionList::const_iterator it=actions.begin(), itEnd=actions.end();
728 it != itEnd; ++it)
730 const action_buffer* buf = *it;
731 queueAction(*buf);
736 void
737 MovieClip::notifyEvent(const event_id& id)
740 #ifdef GNASH_DEBUG
741 log_debug(_("Event %s invoked for movieclip %s"), id, getTarget());
742 #endif
744 // We do not execute ENTER_FRAME if unloaded
745 if (id.id() == event_id::ENTER_FRAME && unloaded()) {
746 #ifdef GNASH_DEBUG
747 log_debug(_("Sprite %s ignored ENTER_FRAME event (is unloaded)"), getTarget());
748 #endif
749 return;
752 if (isButtonEvent(id) && !isEnabled()) {
753 #ifdef GNASH_DEBUG
754 log_debug(_("Sprite %s ignored button-like event %s as not 'enabled'"),
755 getTarget(), id);
756 #endif
757 return;
760 std::auto_ptr<ExecutableCode> code (get_event_handler(id));
761 if (code.get()) {
762 // Dispatch.
763 code->execute();
766 // user-defined onInitialize is never called
767 if (id.id() == event_id::INITIALIZE) return;
769 // NOTE: user-defined onLoad is not invoked for static
770 // clips on which no clip-events are defined.
771 // see testsuite/misc-ming.all/action_execution_order_extend_test.swf
773 // Note that this can't be true for movieclips
774 // not placed by PlaceObject, see
775 // testsuite/misc-ming.all/registerClassTest.swf
777 // Note that this is also not true for movieclips which have
778 // a registered class on them, see
779 // testsuite/misc-ming.all/registerClassTest2.swf
781 // TODO: test the case in which it's MovieClip.prototype.onLoad
782 // defined !
783 if (id.id() == event_id::LOAD) {
785 // TODO: we're likely making too much noise for nothing here,
786 // there must be some action-execution-order related problem instead....
787 // See testsuite/misc-ming.all/registerClassTest2.swf for an onLoad
788 // execution order related problem ...
791 // we don't skip calling user-defined onLoad for top-level movies
792 if ( ! parent() ) break;
793 // nor if there are clip-defined handler
794 if ( ! get_event_handlers().empty() ) break;
795 // nor if it's dynamic
796 if ( isDynamic() ) break;
798 const sprite_definition* def =
799 dynamic_cast<const sprite_definition*>(_def.get());
801 // must be a loaded movie (loadMovie doesn't mark it as
802 // "dynamic" - should it? no, or getBytesLoaded will always
803 // return 0)
804 if (!def) break;
806 // if it has a registered class it can have an onLoad
807 // in prototype...
808 if (def->getRegisteredClass()) break;
810 #ifdef GNASH_DEBUG
811 log_debug(_("Sprite %s (depth %d) won't check for user-defined "
812 "LOAD event (is not dynamic, has a parent, "
813 "no registered class and no clip events defined)"),
814 getTarget(), get_depth());
815 #endif
816 return;
817 } while (0);
821 // Call the appropriate member function.
822 if (!isKeyEvent(id)) {
823 sendEvent(*getObject(this), get_environment(), id.functionURI());
828 as_object*
829 MovieClip::pathElement(const ObjectURI& uri)
831 as_object* obj = DisplayObject::pathElement(uri);
832 if (obj) return obj;
834 // See if we have a match on the display list.
835 obj = getObject(getDisplayListObject(uri));
836 if (obj) return obj;
838 obj = getObject(this);
839 assert(obj);
841 // See if it's a member
842 as_value tmp;
843 if (!obj->as_object::get_member(uri, &tmp)) {
844 return NULL;
846 if (!tmp.is_object()) {
847 return NULL;
849 if (tmp.is_sprite())
851 return getObject(tmp.toDisplayObject(true));
854 return toObject(tmp, getVM(*getObject(this)));
857 bool
858 MovieClip::setTextFieldVariables(const ObjectURI& uri, const as_value& val)
861 const string_table::key name = getName(uri);
863 // Try textfield variables
865 // FIXME: Turn textfield variables into Getter/Setters (Properties)
866 // so that as_object::set_member will do this automatically.
867 // The problem is that setting a TextVariable named after
868 // a builtin property will prevent *any* setting for the
869 // property (ie: have a textfield use _x as variable name and
870 // be scared)
871 TextFields* etc = get_textfield_variable(
872 getStringTable(*getObject(this)).value(name));
874 if (!etc) return false;
876 for (TextFields::iterator i=etc->begin(), e=etc->end(); i!=e; ++i) {
877 (*i)->updateText(val.to_string(getSWFVersion(*getObject(this))));
879 return true;
882 /// Remove the 'contents' of the MovieClip, but leave properties and
883 /// event handlers intact.
884 void
885 MovieClip::unloadMovie()
887 LOG_ONCE(log_unimpl("MovieClip.unloadMovie()"));
890 // child movieclip advance
891 void
892 MovieClip::advance()
895 #ifdef GNASH_DEBUG
896 log_debug(_("Advance movieclip '%s' at frame %u/%u"),
897 getTargetPath(), _currentFrame,
898 get_frame_count());
899 #endif
901 assert(!unloaded());
903 // call_frame should never trigger advance_movieclip
904 assert(!_callingFrameActions);
906 // We might have loaded NO frames !
907 if (get_loaded_frames() == 0) {
908 IF_VERBOSE_MALFORMED_SWF(
909 LOG_ONCE( log_swferror(_("advance_movieclip: no frames loaded "
910 "for movieclip/movie %s"), getTarget()) );
912 return;
915 // Process any pending loadVariables request
916 processCompletedLoadVariableRequests();
918 #ifdef GNASH_DEBUG
919 size_t frame_count = _def->get_frame_count();
921 log_debug(_("Advance_movieclip for movieclip '%s' - frame %u/%u "),
922 getTarget(), _currentFrame,
923 frame_count);
924 #endif
926 // I'm not sure ENTERFRAME goes in a different queue then DOACTION...
927 queueEvent(event_id::ENTER_FRAME, movie_root::PRIORITY_DOACTION);
929 // Update current and next frames.
930 if (_playState == PLAYSTATE_PLAY)
932 #ifdef GNASH_DEBUG
933 log_debug(_("MovieClip::advance_movieclip we're in PLAYSTATE_PLAY mode"));
934 #endif
936 int prev_frame = _currentFrame;
938 #ifdef GNASH_DEBUG
939 log_debug(_("on_event_load called, incrementing"));
940 #endif
941 increment_frame_and_check_for_loop();
942 #ifdef GNASH_DEBUG
943 log_debug(_("after increment we are at frame %u/%u"), _currentFrame, frame_count);
944 #endif
946 // Execute the current frame's tags.
947 // First time executeFrameTags(0) executed in dlist.cpp(child) or
948 // SWFMovieDefinition(root)
949 if (_currentFrame != (size_t)prev_frame)
951 if (_currentFrame == 0 && _hasLooped)
953 #ifdef GNASH_DEBUG
954 log_debug(_("Jumping back to frame 0 of movieclip %s"),
955 getTarget());
956 #endif
957 restoreDisplayList(0); // seems OK to me.
959 else
961 #ifdef GNASH_DEBUG
962 log_debug(_("Executing frame%d (0-based) tags of movieclip "
963 "%s"), _currentFrame, getTarget());
964 #endif
965 // Make sure _currentFrame is 0-based during execution of
966 // DLIST tags
967 executeFrameTags(_currentFrame, _displayList,
968 SWF::ControlTag::TAG_DLIST |
969 SWF::ControlTag::TAG_ACTION);
974 #ifdef GNASH_DEBUG
975 else
977 log_debug(_("MovieClip::advance_movieclip we're in STOP mode"));
979 #endif
983 void
984 MovieClip::execute_init_action_buffer(const action_buffer& a, int cid)
986 assert(cid >= 0);
988 if ( _swf->initializeCharacter(cid) )
990 #ifdef GNASH_DEBUG
991 log_debug(_("Queuing init actions in frame %d of movieclip %s"),
992 _currentFrame, getTarget());
993 #endif
994 std::auto_ptr<ExecutableCode> code(new GlobalCode(a, this));
996 stage().pushAction(code, movie_root::PRIORITY_INIT);
998 else
1000 #ifdef GNASH_DEBUG
1001 log_debug(_("Init actions for DisplayObject %d already executed"), cid);
1002 #endif
1006 void
1007 MovieClip::execute_action(const action_buffer& ab)
1009 ActionExec exec(ab, _environment);
1010 exec();
1013 void
1014 MovieClip::restoreDisplayList(size_t tgtFrame)
1017 // This is not tested as usable for jump-forwards (yet)...
1018 // TODO: I guess just moving here the code currently in goto_frame
1019 // for jump-forwards would do
1020 assert(tgtFrame <= _currentFrame);
1022 // Just invalidate this DisplayObject before jumping back.
1023 // Should be optimized, but the invalidating model is not clear enough,
1024 // and there are some old questions spreading the source files.
1025 set_invalidated();
1027 DisplayList tmplist;
1028 for (size_t f = 0; f < tgtFrame; ++f)
1030 _currentFrame = f;
1031 executeFrameTags(f, tmplist, SWF::ControlTag::TAG_DLIST);
1034 // Execute both action tags and DLIST tags of the target frame
1035 _currentFrame = tgtFrame;
1036 executeFrameTags(tgtFrame, tmplist, SWF::ControlTag::TAG_DLIST |
1037 SWF::ControlTag::TAG_ACTION);
1039 _displayList.mergeDisplayList(tmplist);
1042 // 0-based frame number !
1043 void
1044 MovieClip::executeFrameTags(size_t frame, DisplayList& dlist, int typeflags)
1046 // If there is no definition, this is a dynamically-created MovieClip
1047 // and has no frames.
1048 if (!_def) return;
1050 assert(typeflags);
1052 const PlayList* playlist = _def->getPlaylist(frame);
1053 if (playlist) {
1055 IF_VERBOSE_ACTION(
1056 // Use 1-based frame numbers
1057 log_action(_("Executing %d tags in frame %d/%d of movieclip %s"),
1058 playlist->size(), frame + 1, get_frame_count(),
1059 getTargetPath());
1062 // Generally tags should be executed in the order they are found in.
1063 for (PlayList::const_iterator it = playlist->begin(),
1064 e = playlist->end(); it != e; ++it) {
1066 if (typeflags & SWF::ControlTag::TAG_DLIST) {
1067 (*it)->executeState(this, dlist);
1070 if (typeflags & SWF::ControlTag::TAG_ACTION) {
1071 (*it)->executeActions(this, _displayList);
1080 void
1081 MovieClip::goto_frame(size_t target_frame_number)
1083 #if defined(DEBUG_GOTOFRAME) || defined(GNASH_DEBUG_TIMELINE)
1084 log_debug(_("movieclip %s ::goto_frame(%d) - current frame is %d"),
1085 getTargetPath(), target_frame_number, _currentFrame);
1086 #endif
1088 // goto_frame stops by default.
1089 // ActionGotoFrame tells the movieClip to go to the target frame
1090 // and stop at that frame.
1091 setPlayState(PLAYSTATE_STOP);
1093 if (target_frame_number > _def->get_frame_count() - 1) {
1095 target_frame_number = _def->get_frame_count() - 1;
1097 if (!_def->ensure_frame_loaded(target_frame_number + 1)) {
1098 log_error(_("Target frame of a gotoFrame(%d) was never loaded,"
1099 "although frame count in header (%d) said we "
1100 "should have found it"),
1101 target_frame_number+1, _def->get_frame_count());
1102 return;
1105 // Just set _currentframe and return.
1106 _currentFrame = target_frame_number;
1108 // don't push actions, already tested.
1109 return;
1112 if (target_frame_number == _currentFrame) {
1113 // don't push actions
1114 return;
1117 // Unless the target frame is the next one, stop playback of soundstream
1118 if (target_frame_number != _currentFrame + 1) {
1119 stopStreamSound();
1122 const size_t loaded_frames = get_loaded_frames();
1124 // target_frame_number is 0-based, get_loaded_frames() is 1-based
1125 // so in order to goto_frame(3) loaded_frames must be at least 4
1126 // if goto_frame(4) is called, and loaded_frames is 4 we're jumping
1127 // forward
1128 if (target_frame_number >= loaded_frames) {
1129 IF_VERBOSE_ASCODING_ERRORS(
1130 log_aserror(_("GotoFrame(%d) targets a yet "
1131 "to be loaded frame (%d) loaded). "
1132 "We'll wait for it but a more correct form "
1133 "is explicitly using WaitForFrame instead"),
1134 target_frame_number+1,
1135 loaded_frames);
1138 if (!_def->ensure_frame_loaded(target_frame_number + 1)) {
1139 log_error(_("Target frame of a gotoFrame(%d) was never loaded, "
1140 "although frame count in header (%d) said we should"
1141 " have found it"),
1142 target_frame_number + 1, _def->get_frame_count());
1143 return;
1149 // Construct the DisplayList of the target frame
1152 if (target_frame_number < _currentFrame) {
1154 // Go backward to a previous frame
1155 // NOTE: just in case we're being called by code in a called frame
1156 // we'll backup and resume the _callingFrameActions flag
1157 bool callingFrameActionsBackup = _callingFrameActions;
1158 _callingFrameActions = false;
1160 // restoreDisplayList takes care of properly setting the
1161 // _currentFrame variable
1162 restoreDisplayList(target_frame_number);
1163 assert(_currentFrame == target_frame_number);
1164 _callingFrameActions = callingFrameActionsBackup;
1166 else {
1167 // Go forward to a later frame
1168 // We'd immediately return if target_frame_number == _currentFrame
1169 assert(target_frame_number > _currentFrame);
1170 while (++_currentFrame < target_frame_number) {
1171 //for (size_t f = _currentFrame+1; f<target_frame_number; ++f)
1172 // Second argument requests that only "DisplayList" tags
1173 // are executed. This means NO actions will be
1174 // pushed on m_action_list.
1175 executeFrameTags(_currentFrame, _displayList,
1176 SWF::ControlTag::TAG_DLIST);
1178 assert(_currentFrame == target_frame_number);
1180 // Now execute target frame tags (queuing actions)
1181 // NOTE: just in case we're being called by code in a called frame
1182 // we'll backup and resume the _callingFrameActions flag
1183 bool callingFrameActionsBackup = _callingFrameActions;
1184 _callingFrameActions = false;
1185 executeFrameTags(target_frame_number, _displayList,
1186 SWF::ControlTag::TAG_DLIST | SWF::ControlTag::TAG_ACTION);
1187 _callingFrameActions = callingFrameActionsBackup;
1190 assert(_currentFrame == target_frame_number);
1193 bool
1194 MovieClip::goto_labeled_frame(const std::string& label)
1197 // If there is no definition, this is a dynamically-created MovieClip
1198 // and has no frames. (We are also probably not called in this case).
1199 if (!_def) return false;
1201 size_t target_frame;
1202 if (_def->get_labeled_frame(label, target_frame)) {
1203 goto_frame(target_frame);
1204 return true;
1207 IF_VERBOSE_MALFORMED_SWF(
1208 log_swferror(_("MovieClip::goto_labeled_frame('%s') "
1209 "unknown label"), label);
1211 return false;
1214 void
1215 MovieClip::draw(Renderer& renderer, const Transform& xform)
1217 const DisplayObject::MaskRenderer mr(renderer, *this);
1219 _drawable.finalize();
1220 _drawable.display(renderer, xform);
1221 _displayList.display(renderer, xform);
1224 void
1225 MovieClip::display(Renderer& renderer, const Transform& base)
1227 // Note: DisplayList::display() will take care of the visibility checking.
1229 // Whether a DisplayObject should be rendered or not is dependent
1230 // on its parent: i.e. if its parent is a mask, this DisplayObject
1231 // should be rendered to the mask buffer even it is invisible.
1233 // Draw everything with our own transform.
1234 const Transform xform = base * transform();
1235 draw(renderer, xform);
1236 clear_invalidated();
1239 void MovieClip::omit_display()
1241 if (childInvalidated()) _displayList.omit_display();
1243 clear_invalidated();
1246 bool
1247 MovieClip::attachCharacter(DisplayObject& newch, int depth, as_object* initObj)
1249 _displayList.placeDisplayObject(&newch, depth);
1250 newch.construct(initObj);
1252 // FIXME: check return from placeDisplayObject above ?
1253 return true;
1256 DisplayObject*
1257 MovieClip::add_display_object(const SWF::PlaceObject2Tag* tag,
1258 DisplayList& dlist)
1261 // If this MovieClip has no definition, it should also have no ControlTags,
1262 // and this shouldn't be called.
1263 assert(_def);
1264 assert(tag);
1266 SWF::DefinitionTag* cdef = _def->getDefinitionTag(tag->getID());
1267 if (!cdef)
1269 IF_VERBOSE_MALFORMED_SWF(
1270 log_swferror(_("MovieClip::add_display_object(): "
1271 "unknown cid = %d"), tag->getID());
1273 return NULL;
1276 DisplayObject* existing_char = dlist.getDisplayObjectAtDepth(tag->getDepth());
1278 if (existing_char) return NULL;
1280 Global_as& gl = getGlobal(*getObject(this));
1281 DisplayObject* ch = cdef->createDisplayObject(gl, this);
1283 string_table& st = getStringTable(*getObject(this));
1285 if (tag->hasName()) ch->set_name(st.find(tag->getName()));
1286 else if (isReferenceable(*ch))
1288 const string_table::key instance_name = getNextUnnamedInstanceName();
1289 ch->set_name(instance_name);
1292 if (tag->hasBlendMode()) {
1293 boost::uint8_t bm = tag->getBlendMode();
1294 ch->setBlendMode(static_cast<DisplayObject::BlendMode>(bm));
1297 // Attach event handlers (if any).
1298 const std::vector<swf_event*>& event_handlers = tag->getEventHandlers();
1299 for (size_t i = 0, n = event_handlers.size(); i < n; i++)
1301 swf_event* ev = event_handlers[i];
1302 ch->add_event_handler(ev->event(), ev->action());
1305 // TODO: check if we should check those has_xxx flags first.
1306 ch->setCxForm(tag->getCxform());
1307 ch->setMatrix(tag->getMatrix(), true); // update caches
1308 ch->set_ratio(tag->getRatio());
1309 ch->set_clip_depth(tag->getClipDepth());
1311 dlist.placeDisplayObject(ch, tag->getDepth());
1312 ch->construct();
1313 return ch;
1316 void
1317 MovieClip::move_display_object(const SWF::PlaceObject2Tag* tag, DisplayList& dlist)
1319 int ratio = tag->getRatio();
1320 // clip_depth is not used in MOVE tag(at least no related tests).
1321 dlist.moveDisplayObject(
1322 tag->getDepth(),
1323 tag->hasCxform() ? &tag->getCxform() : NULL,
1324 tag->hasMatrix() ? &tag->getMatrix() : NULL,
1325 tag->hasRatio() ? &ratio : NULL,
1326 NULL);
1329 void
1330 MovieClip::replace_display_object(const SWF::PlaceObject2Tag* tag,
1331 DisplayList& dlist)
1333 // A MovieClip without a definition cannot have any ControlTags, so this
1334 // should not be called.
1335 assert(_def);
1336 assert(tag != NULL);
1338 const boost::uint16_t id = tag->getID();
1340 SWF::DefinitionTag* cdef = _def->getDefinitionTag(id);
1341 if (cdef == NULL)
1343 log_error(_("movieclip::replace_display_object(): "
1344 "unknown cid = %d"), id);
1345 return;
1347 assert(cdef);
1349 DisplayObject* existing_char = dlist.getDisplayObjectAtDepth(tag->getDepth());
1351 if (!existing_char) {
1352 log_error(_("MovieClip::replace_display_object: could not "
1353 "find any DisplayObject at depth %d"), tag->getDepth());
1354 return;
1357 // if the existing DisplayObject is not a shape, move it instead
1358 // of replacing.
1359 if (isReferenceable(*existing_char)) {
1360 move_display_object(tag, dlist);
1361 return;
1364 Global_as& gl = getGlobal(*getObject(this));
1365 DisplayObject* ch = cdef->createDisplayObject(gl, this);
1368 // TODO: check if we can drop this for REPLACE!
1369 // should we rename the DisplayObject when it's REPLACE tag?
1370 if (tag->hasName()) {
1371 string_table& st = getStringTable(*getObject(this));
1372 ch->set_name(st.find(tag->getName()));
1374 else if (isReferenceable(*ch)) {
1375 const string_table::key instance_name = getNextUnnamedInstanceName();
1376 ch->set_name(instance_name);
1378 if (tag->hasRatio()) {
1379 ch->set_ratio(tag->getRatio());
1381 if (tag->hasCxform()) {
1382 ch->setCxForm(tag->getCxform());
1384 if (tag->hasMatrix()) {
1385 ch->setMatrix(tag->getMatrix(), true);
1388 // use SWFMatrix from the old DisplayObject if tag doesn't provide one.
1389 dlist.replaceDisplayObject(ch, tag->getDepth(),
1390 !tag->hasCxform(), !tag->hasMatrix());
1391 ch->construct();
1394 void
1395 MovieClip::remove_display_object(const SWF::PlaceObject2Tag* tag,
1396 DisplayList& dlist)
1398 set_invalidated();
1399 dlist.removeDisplayObject(tag->getDepth());
1402 void
1403 MovieClip::remove_display_object(int depth, int)
1405 set_invalidated();
1406 _displayList.removeDisplayObject(depth);
1409 void
1410 MovieClip::increment_frame_and_check_for_loop()
1412 const size_t frame_count = get_loaded_frames();
1413 if (++_currentFrame >= frame_count) {
1414 // Loop.
1415 _currentFrame = 0;
1416 _hasLooped = true;
1421 bool
1422 MovieClip::handleFocus()
1425 as_object* obj = getObject(this);
1426 assert(obj);
1428 // For SWF6 and above: the MovieClip can always receive focus if
1429 // focusEnabled evaluates to true.
1430 if (getSWFVersion(*obj) > 5) {
1431 as_value focusEnabled;
1432 if (obj->get_member(NSV::PROP_FOCUS_ENABLED, &focusEnabled)) {
1433 if (toBool(focusEnabled, getVM(*obj))) return true;
1437 // If focusEnabled doesn't evaluate to true or for SWF5, return true
1438 // only if at least one mouse event handler is defined.
1439 return mouseEnabled();
1442 bool
1443 MovieClip::pointInShape(boost::int32_t x, boost::int32_t y) const
1445 ShapeContainerFinder finder(x, y);
1446 _displayList.visitBackward(finder);
1447 if ( finder.hitFound() ) return true;
1448 return hitTestDrawable(x, y);
1451 bool
1452 MovieClip::pointInVisibleShape(boost::int32_t x, boost::int32_t y) const
1454 if ( ! visible() ) return false;
1455 if ( isDynamicMask() && ! mouseEnabled() )
1457 // see testsuite/misc-ming.all/masks_test.swf
1458 #ifdef GNASH_DEBUG_HITTEST
1459 log_debug(_("%s is a dynamic mask and can't handle mouse "
1460 "events, no point will hit it"), getTarget());
1461 #endif
1462 return false;
1464 const DisplayObject* mask = getMask(); // dynamic one
1465 if ( mask && mask->visible() && ! mask->pointInShape(x, y) )
1467 #ifdef GNASH_DEBUG_HITTEST
1468 log_debug(_("%s is dynamically masked by %s, which "
1469 "doesn't hit point %g,%g"), getTarget(),
1470 mask->getTarget(), x, y);
1471 #endif
1472 return false;
1474 VisibleShapeContainerFinder finder(x, y);
1475 _displayList.visitBackward(finder);
1476 if (finder.hitFound()) return true;
1477 return hitTestDrawable(x, y);
1480 inline bool
1481 MovieClip::hitTestDrawable(boost::int32_t x, boost::int32_t y) const
1483 const SWFMatrix wm = getWorldMatrix(*this).invert();
1484 point lp(x, y);
1485 wm.transform(lp);
1486 if (!_drawable.getBounds().point_test(lp.x, lp.y)) return false;
1487 return _drawable.pointTestLocal(lp.x, lp.y, wm);
1490 bool
1491 MovieClip::pointInHitableShape(boost::int32_t x, boost::int32_t y) const
1493 if (isDynamicMask() && !mouseEnabled()) return false;
1495 const DisplayObject* mask = getMask();
1496 if (mask && !mask->pointInShape(x, y)) return false;
1498 HitableShapeContainerFinder finder(x, y);
1499 _displayList.visitBackward(finder);
1500 if (finder.hitFound()) return true;
1502 return hitTestDrawable(x, y);
1505 InteractiveObject*
1506 MovieClip::topmostMouseEntity(boost::int32_t x, boost::int32_t y)
1508 //GNASH_REPORT_FUNCTION;
1510 if (!visible()) return 0;
1512 // point is in parent's space, we need to convert it in world space
1513 point wp(x, y);
1514 DisplayObject* p = parent();
1515 if (p) {
1516 // WARNING: if we have NO parent, our parent is the Stage (movie_root)
1517 // so, in case we'll add a "stage" matrix, we'll need to take
1518 // it into account here.
1519 // TODO: actually, why are we insisting in using parent's
1520 // coordinates for this method at all ?
1521 getWorldMatrix(*p).transform(wp);
1524 if (mouseEnabled())
1526 if (pointInVisibleShape(wp.x, wp.y)) return this;
1527 else return NULL;
1530 SWFMatrix m = getMatrix(*this);
1531 m.invert();
1532 point pp(x, y);
1533 m.transform(pp);
1535 MouseEntityFinder finder(wp, pp);
1536 _displayList.visitAll(finder);
1537 InteractiveObject* ch = finder.getEntity();
1539 // It doesn't make any sense to query _drawable, as it's
1540 // not an InteractiveObject.
1541 return ch;
1544 const DisplayObject*
1545 MovieClip::findDropTarget(boost::int32_t x, boost::int32_t y,
1546 DisplayObject* dragging) const
1548 if ( this == dragging ) return 0; // not here...
1550 if ( ! visible() ) return 0; // isn't me !
1552 DropTargetFinder finder(x, y, dragging);
1553 _displayList.visitAll(finder);
1555 // does it hit any child ?
1556 const DisplayObject* ch = finder.getDropChar();
1557 if ( ch )
1559 // TODO: find closest actionscript referenceable container
1560 // (possibly itself)
1561 return ch;
1564 // does it hit us ?
1565 if (hitTestDrawable(x, y)) return this;
1567 return 0;
1570 bool
1571 MovieClip::trackAsMenu()
1573 as_object* obj = getObject(this);
1574 assert(obj);
1576 string_table& st = getStringTable(*obj);
1578 as_value track;
1579 return (obj->get_member(st.find("trackAsMenu"), &track) &&
1580 toBool(track, getVM(*obj)));
1583 bool
1584 MovieClip::mouseEnabled() const
1586 if ( ! isEnabled() ) return false;
1588 // Event handlers that qualify as mouse event handlers.
1589 static const event_id EH[] =
1591 event_id(event_id::PRESS),
1592 event_id(event_id::RELEASE),
1593 event_id(event_id::RELEASE_OUTSIDE),
1594 event_id(event_id::ROLL_OVER),
1595 event_id(event_id::ROLL_OUT),
1596 event_id(event_id::DRAG_OVER),
1597 event_id(event_id::DRAG_OUT),
1600 const size_t size = arraySize(EH);
1602 for (size_t i = 0; i < size; ++i) {
1603 const event_id &event = EH[i];
1605 // Check event handlers
1606 if (hasEventHandler(event.id())) {
1607 return true;
1611 return false;
1614 void
1615 MovieClip::stop_drag()
1617 stage().stop_drag();
1620 void
1621 MovieClip::set_background_color(const rgba& color)
1623 stage().set_background_color(color);
1626 void
1627 MovieClip::cleanup_textfield_variables()
1629 // nothing to do
1630 if (!_text_variables.get()) return;
1632 TextFieldIndex& m = *_text_variables;
1634 for (TextFieldIndex::iterator i=m.begin(), ie=m.end(); i!=ie; ++i)
1636 TextFields& v=i->second;
1637 TextFields::iterator lastValid = std::remove_if(v.begin(), v.end(),
1638 boost::mem_fn(&DisplayObject::unloaded));
1639 v.erase(lastValid, v.end());
1644 void
1645 MovieClip::set_textfield_variable(const std::string& name, TextField* ch)
1647 assert(ch);
1649 // lazy allocation
1650 if ( ! _text_variables.get() )
1652 _text_variables.reset(new TextFieldIndex);
1655 (*_text_variables)[name].push_back(ch);
1658 MovieClip::TextFields*
1659 MovieClip::get_textfield_variable(const std::string& name)
1661 // nothing allocated yet...
1662 if ( ! _text_variables.get() ) return NULL;
1664 // TODO: should variable name be considered case-insensitive ?
1665 TextFieldIndex::iterator it = _text_variables->find(name);
1666 if (it == _text_variables->end()) return 0;
1667 else return &(it->second);
1671 DisplayObject*
1672 MovieClip::getDisplayListObject(const ObjectURI& uri)
1675 as_object* obj = getObject(this);
1676 assert(obj);
1678 string_table& st = getStringTable(*obj);
1680 // Try items on our display list.
1681 DisplayObject* ch = _displayList.getDisplayObjectByName(st, uri,
1682 caseless(*obj));
1684 if (!ch) return 0;
1686 // Found object.
1688 // If the object is an ActionScript referenciable one we
1689 // return it, otherwise we return ourselves
1690 if (isReferenceable(*ch)) {
1691 return ch;
1693 return this;
1696 void
1697 MovieClip::add_invalidated_bounds(InvalidatedRanges& ranges, bool force)
1700 // nothing to do if this movieclip is not visible
1701 if (!visible() || invisible(getCxForm(*this))) {
1702 ranges.add(m_old_invalidated_ranges);
1703 return;
1706 if (!invalidated() && !childInvalidated() && !force) return;
1709 // m_child_invalidated does not require our own bounds
1710 if (invalidated() || force) {
1711 // Add old invalidated bounds
1712 ranges.add(m_old_invalidated_ranges);
1715 _displayList.add_invalidated_bounds(ranges, force || invalidated());
1717 /// Add drawable.
1718 SWFRect bounds;
1719 bounds.expand_to_transformed_rect(getWorldMatrix(*this),
1720 _drawable.getBounds());
1722 ranges.add(bounds.getRange());
1727 void
1728 MovieClip::constructAsScriptObject()
1730 as_object* mc = getObject(this);
1732 // A MovieClip should always have an associated object.
1733 assert(mc);
1735 if (!parent()) {
1736 mc->init_member("$version", getVM(*mc).getPlayerVersion(), 0);
1739 const sprite_definition* def =
1740 dynamic_cast<const sprite_definition*>(_def.get());
1742 // We won't "construct" top-level movies
1743 as_function* ctor = def ? def->getRegisteredClass() : 0;
1745 #ifdef GNASH_DEBUG
1746 log_debug(_("Attached movieclips %s registered class is %p"),
1747 getTarget(), (void*)ctor);
1748 #endif
1750 // Set this MovieClip object to be an instance of the class.
1751 if (ctor) {
1752 Property* proto = ctor->getOwnProperty(NSV::PROP_PROTOTYPE);
1753 if (proto) mc->set_prototype(proto->getValue(*ctor));
1756 // Send the construct event. This must be done after the __proto__
1757 // member is set. It is always done.
1758 notifyEvent(event_id::CONSTRUCT);
1760 if (ctor) {
1761 const int swfversion = getSWFVersion(*mc);
1762 if (swfversion > 5) {
1763 fn_call::Args args;
1764 ctor->construct(*mc, get_environment(), args);
1770 void
1771 MovieClip::construct(as_object* initObj)
1774 assert(!unloaded());
1776 saveOriginalTarget();
1778 #ifdef GNASH_DEBUG
1779 log_debug(_("Sprite '%s' placed on stage"), getTarget());
1780 #endif
1782 // Register this movieclip as a live one
1783 stage().addLiveChar(this);
1785 // It seems it's legal to place 0-framed movieclips on stage.
1786 // See testsuite/misc-swfmill.all/zeroframe_definemovieclip.swf
1788 // Now execute frame tags and take care of queuing the LOAD event.
1790 // DLIST tags are executed immediately while ACTION tags are queued.
1792 // For _root movie, LOAD event is invoked *after* actions in first frame
1793 // See misc-ming.all/action_execution_order_test4.{c,swf}
1795 assert(!_callingFrameActions); // or will not be queuing actions
1796 if (!parent()) {
1797 executeFrameTags(0, _displayList, SWF::ControlTag::TAG_DLIST |
1798 SWF::ControlTag::TAG_ACTION);
1799 if (getSWFVersion(*getObject(this)) > 5) {
1800 queueEvent(event_id::LOAD, movie_root::PRIORITY_DOACTION);
1804 else {
1805 queueEvent(event_id::LOAD, movie_root::PRIORITY_DOACTION);
1806 executeFrameTags(0, _displayList, SWF::ControlTag::TAG_DLIST |
1807 SWF::ControlTag::TAG_ACTION);
1810 as_object* mc = getObject(this);
1812 // A MovieClip should always have an associated object.
1813 assert(mc);
1815 // We execute events immediately when the stage-placed DisplayObject
1816 // is dynamic, This is becase we assume that this means that
1817 // the DisplayObject is placed during processing of actions (opposed
1818 // that during advancement iteration).
1820 // A more general implementation might ask movie_root about its state
1821 // (iterating or processing actions?)
1822 // Another possibility to inspect could be letting movie_root decide
1823 // when to really queue and when rather to execute immediately the
1824 // events with priority INITIALIZE or CONSTRUCT ...
1825 if (!isDynamic()) {
1827 #ifdef GNASH_DEBUG
1828 log_debug(_("Queuing INITIALIZE and CONSTRUCT events for movieclip %s"),
1829 getTarget());
1830 #endif
1832 std::auto_ptr<ExecutableCode> code(new ConstructEvent(this));
1833 stage().pushAction(code, movie_root::PRIORITY_CONSTRUCT);
1836 else {
1838 // Properties from an initObj must be copied before construction, but
1839 // after the display list has been populated, so that _height and
1840 // _width (which depend on bounds) are correct.
1841 if (initObj) {
1842 mc->copyProperties(*initObj);
1845 constructAsScriptObject();
1849 // Tested in testsuite/swfdec/duplicateMovieclip-events.c and
1850 // testsuite/swfdec/clone-sprite-events.c not to call notifyEvent
1851 // immediately.
1852 queueEvent(event_id::INITIALIZE, movie_root::PRIORITY_INIT);
1856 bool
1857 MovieClip::unloadChildren()
1859 #ifdef GNASH_DEBUG
1860 log_debug(_("Unloading movieclip '%s'"), getTargetPath());
1861 #endif
1863 // stop any pending streaming sounds
1864 stopStreamSound();
1866 // We won't be displayed again, so worth releasing
1867 // some memory. The drawable might take a lot of memory
1868 // on itself.
1869 _drawable.clear();
1871 return _displayList.unload();
1875 void
1876 MovieClip::getLoadedMovie(Movie* extern_movie)
1878 DisplayObject* p = parent();
1879 if (p) {
1880 extern_movie->set_parent(p);
1882 // Copy own lockroot value
1883 extern_movie->setLockRoot(getLockRoot());
1885 // Copy own event handlers
1886 // see testsuite/misc-ming.all/loadMovieTest.swf
1887 const Events& clipEvs = get_event_handlers();
1888 // top-level movies can't have clip events, right ?
1889 assert (extern_movie->get_event_handlers().empty());
1890 extern_movie->set_event_handlers(clipEvs);
1892 // Copy own name
1893 // TODO: check empty != none...
1894 const ObjectURI& name = get_name();
1895 if (!name.empty()) extern_movie->set_name(name);
1897 // Copy own clip depth (TODO: check this)
1898 extern_movie->set_clip_depth(get_clip_depth());
1900 // Replace ourselves in parent
1901 // TODO: don't pretend our parent is a MovieClip,
1902 // could as well be a button I guess...
1903 // At most we should require it to be a
1904 // DisplayObjectContainer and log an error if it's not.
1905 MovieClip* parent_sp = p->to_movie();
1906 assert(parent_sp);
1907 parent_sp->_displayList.replaceDisplayObject(extern_movie, get_depth(),
1908 true, true);
1909 extern_movie->construct();
1911 else
1913 // replaceLevel will set depth for us
1914 stage().replaceLevel(get_depth() - DisplayObject::staticDepthOffset,
1915 extern_movie);
1919 void
1920 MovieClip::loadVariables(const std::string& urlstr,
1921 VariablesMethod sendVarsMethod)
1923 // Host security check will be will be done by LoadVariablesThread
1924 // (down by getStream, that is)
1926 const movie_root& mr = stage();
1927 URL url(urlstr, mr.runResources().streamProvider().originalURL());
1929 std::string postdata;
1931 // Encode our vars for sending.
1932 if (sendVarsMethod != METHOD_NONE) {
1933 postdata = getURLEncodedVars(*getObject(this));
1936 try {
1937 const StreamProvider& sp =
1938 getRunResources(*getObject(this)).streamProvider();
1940 if (sendVarsMethod == METHOD_POST) {
1941 // use POST method
1942 _loadVariableRequests.push_back(
1943 new LoadVariablesThread(sp, url, postdata));
1945 else {
1946 // use GET method
1947 if (sendVarsMethod == METHOD_GET) {
1948 // Append variables
1949 std::string qs = url.querystring();
1950 if (qs.empty()) url.set_querystring(postdata);
1951 else url.set_querystring(qs + "&" + postdata);
1953 _loadVariableRequests.push_back(new LoadVariablesThread(sp, url));
1955 _loadVariableRequests.back()->process();
1957 catch (NetworkException& ex)
1959 log_error(_("Could not load variables from %s"), url.str());
1964 void
1965 MovieClip::processCompletedLoadVariableRequest(LoadVariablesThread& request)
1967 assert(request.completed());
1969 MovieVariables& vals = request.getValues();
1970 setVariables(vals);
1972 // We want to call a clip-event too if available, see bug #22116
1973 notifyEvent(event_id::DATA);
1976 /*private*/
1977 void
1978 MovieClip::processCompletedLoadVariableRequests()
1980 // Nothing to do (just for clarity)
1981 if ( _loadVariableRequests.empty() ) return;
1983 for (LoadVariablesThreads::iterator it=_loadVariableRequests.begin();
1984 it != _loadVariableRequests.end(); )
1986 LoadVariablesThread& request = *(*it);
1987 if (request.completed())
1989 processCompletedLoadVariableRequest(request);
1990 delete *it;
1991 it = _loadVariableRequests.erase(it);
1993 else ++it;
1997 void
1998 MovieClip::setVariables(const MovieVariables& vars)
2000 string_table& st = getStringTable(*getObject(this));
2001 for (MovieVariables::const_iterator it=vars.begin(), itEnd=vars.end();
2002 it != itEnd; ++it)
2004 const std::string& name = it->first;
2005 const std::string& val = it->second;
2006 getObject(this)->set_member(st.find(name), val);
2010 void
2011 MovieClip::removeMovieClip()
2013 int depth = get_depth();
2014 if ( depth < 0 || depth > 1048575 )
2016 IF_VERBOSE_ASCODING_ERRORS(
2017 log_aserror(_("removeMovieClip(%s): movieclip depth (%d) out of the "
2018 "'dynamic' zone [0..1048575], won't remove"),
2019 getTarget(), depth);
2021 return;
2024 MovieClip* p = dynamic_cast<MovieClip*>(parent());
2025 if (p) {
2026 // second argument is arbitrary, see comments above
2027 // the function declaration in MovieClip.h
2028 p->remove_display_object(depth, 0);
2030 else {
2031 // removing _level#
2032 stage().dropLevel(depth);
2033 // I guess this can only happen if someone uses
2034 // _swf.swapDepth([0..1048575])
2039 SWFRect
2040 MovieClip::getBounds() const
2042 SWFRect bounds;
2043 BoundsFinder f(bounds);
2044 _displayList.visitAll(f);
2045 SWFRect drawableBounds = _drawable.getBounds();
2046 bounds.expand_to_rect(drawableBounds);
2048 return bounds;
2051 bool
2052 MovieClip::isEnabled() const
2054 as_object* obj = getObject(this);
2055 assert(obj);
2057 as_value enabled;
2058 if (!obj->get_member(NSV::PROP_ENABLED, &enabled)) {
2059 // We're enabled if there's no 'enabled' member...
2060 return true;
2062 return toBool(enabled, getVM(*obj));
2066 void
2067 MovieClip::visitNonProperties(KeyVisitor& v) const
2069 DisplayListVisitor dv(v);
2070 _displayList.visitAll(dv);
2073 void
2074 MovieClip::cleanupDisplayList()
2076 _displayList.removeUnloaded();
2077 cleanup_textfield_variables();
2080 void
2081 MovieClip::markOwnResources() const
2083 ReachableMarker marker;
2085 _displayList.visitAll(marker);
2087 _environment.markReachableResources();
2089 // Mark textfields in the TextFieldIndex
2090 if ( _text_variables.get() )
2092 for (TextFieldIndex::const_iterator i=_text_variables->begin(),
2093 e=_text_variables->end();
2094 i!=e; ++i)
2096 const TextFields& tfs=i->second;
2097 std::for_each(tfs.begin(), tfs.end(),
2098 boost::mem_fn(&DisplayObject::setReachable));
2102 // Mark our relative root
2103 _swf->setReachable();
2107 void
2108 MovieClip::destroy()
2110 stopStreamSound();
2111 _displayList.destroy();
2112 DisplayObject::destroy();
2115 Movie*
2116 MovieClip::get_root() const
2118 return _swf;
2121 MovieClip*
2122 MovieClip::getAsRoot()
2125 // TODO1: as an optimization, if swf version < 7
2126 // we might as well just return _swf,
2127 // the whole chain from this movieclip to it's
2128 // _swf should have the same version...
2130 // TODO2: implement this with iteration rather
2131 // then recursion.
2134 DisplayObject* p = parent();
2135 if (!p) return this; // no parent, we're the root
2137 // If we have a parent, we descend to it unless
2138 // our _lockroot is true AND our or the VM's
2139 // SWF version is > 6
2140 int topSWFVersion = stage().getRootMovie().version();
2142 if (getDefinitionVersion() > 6 || topSWFVersion > 6) {
2143 if (getLockRoot()) return this;
2146 return p->getAsRoot();
2150 void
2151 MovieClip::setStreamSoundId(int id)
2153 if ( id != m_sound_stream_id )
2155 log_debug(_("Stream sound id from %d to %d, stopping old"),
2156 m_sound_stream_id, id);
2157 stopStreamSound();
2159 m_sound_stream_id = id;
2162 void
2163 MovieClip::stopStreamSound()
2165 if ( m_sound_stream_id == -1 ) return; // nothing to do
2167 sound::sound_handler* handler = getRunResources(*getObject(this)).soundHandler();
2168 if (handler)
2170 handler->stop_sound(m_sound_stream_id);
2173 m_sound_stream_id = -1;
2176 void
2177 MovieClip::setPlayState(PlayState s)
2179 if (s == _playState) return; // nothing to do
2180 if (s == PLAYSTATE_STOP) stopStreamSound();
2181 _playState = s;
2184 } // namespace gnash