1 // DisplayObject.cpp: ActionScript DisplayObject class, for Gnash.
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
4 // Free Software Foundation, Inc
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22 #include "gnashconfig.h" // USE_SWFTREE
25 #include "DisplayObject.h"
29 #include <boost/logic/tribool.hpp>
31 #include "movie_root.h"
32 #include "MovieClip.h"
34 #include "DisplayObject.h"
38 #include "GnashException.h"
39 #include "ExecutableCode.h"
40 #include "namedStrings.h"
41 #include "GnashEnums.h"
42 #include "GnashNumeric.h"
43 #include "Global_as.h"
45 #include "GnashAlgorithm.h"
50 #undef set_invalidated
54 // Forward declarations.
56 /// Match blend modes.
57 typedef std::map
<DisplayObject::BlendMode
, std::string
> BlendModeMap
;
58 const BlendModeMap
& getBlendModeMap();
59 bool blendModeMatches(const BlendModeMap::value_type
& val
,
60 const std::string
& mode
);
62 typedef as_value(*Getter
)(DisplayObject
&);
63 typedef void(*Setter
)(DisplayObject
&, const as_value
&);
64 typedef std::pair
<Getter
, Setter
> GetterSetter
;
66 bool doSet(const ObjectURI
& uri
, DisplayObject
& o
, const as_value
& val
);
67 bool doGet(const ObjectURI
& uri
, DisplayObject
& o
, as_value
& val
);
68 const GetterSetter
& getGetterSetterByIndex(size_t index
);
70 // NOTE: comparison will be case-insensitive
71 const GetterSetter
& getGetterSetterByURI(const ObjectURI
& uri
,
74 // Convenience function to create a const URI-to-function map
75 template<typename Map
> const Map
getURIMap(
76 const typename
Map::key_compare
& cmp
);
79 // Define static const members.
80 const int DisplayObject::lowerAccessibleBound
;
81 const int DisplayObject::upperAccessibleBound
;
82 const int DisplayObject::staticDepthOffset
;
83 const int DisplayObject::removedDepthOffset
;
84 const int DisplayObject::noClipDepthValue
;
86 DisplayObject::DisplayObject(movie_root
& mr
, as_object
* object
,
87 DisplayObject
* parent
)
98 _focusRect(parent
? boost::tribool(boost::indeterminate
) :
99 boost::tribool(true)),
102 m_clip_depth(noClipDepthValue
),
105 _blendMode(BLENDMODE_NORMAL
),
107 _scriptTransformed(false),
108 _dynamicallyCreated(false),
112 _child_invalidated(true)
114 assert(m_old_invalidated_ranges
.isNull());
116 // This informs the core that the object is a DisplayObject.
117 if (_object
) _object
->setDisplayObject(this);
121 DisplayObject::getLoadedMovie(Movie
* extern_movie
)
123 LOG_ONCE(log_unimpl(_("loadMovie against a %s DisplayObject"),
127 // TODO: look at the MovieClip implementation, but most importantly
128 // test all the event handlers copies etc..
130 UNUSED(extern_movie
);
134 DisplayObject::getNextUnnamedInstanceName()
137 movie_root
& mr
= stage();
139 std::ostringstream ss
;
140 ss
<< "instance" << mr
.nextUnnamedInstance();
143 return getURI(vm
, ss
.str(), true);
148 DisplayObject::getWorldVolume() const
150 int volume
= _volume
;
152 volume
= int(volume
*_parent
->getVolume()/100.0);
160 DisplayObject::pathElement(const ObjectURI
& uri
)
162 as_object
* obj
= getObject(this);
163 if (!obj
) return nullptr;
165 string_table::key key
= getName(uri
);
167 string_table
& st
= stage().getVM().getStringTable();
169 // TODO: put ".." and "." in namedStrings
170 if (key
== st
.find("..")) return getObject(parent());
171 if (key
== st
.find(".")) return obj
;
173 // The check is case-insensitive for SWF6 and below.
174 // TODO: cache ObjectURI(NSV::PROP_THIS) [as many others...]
175 if (ObjectURI::CaseEquals(st
, caseless(*obj
))
176 (uri
, ObjectURI(NSV::PROP_THIS
))) {
183 DisplayObject::set_invalidated()
185 set_invalidated("unknown", -1);
189 DisplayObject::set_invalidated(const char* debug_file
, int debug_line
)
191 // Set the invalidated-flag of the parent. Note this does not mean that
192 // the parent must re-draw itself, it just means that one of it's childs
193 // needs to be re-drawn.
194 if ( _parent
) _parent
->set_child_invalidated();
196 // Ok, at this point the instance will change it's
197 // visual aspect after the
198 // call to set_invalidated(). We save the *current*
199 // position of the instance because this region must
200 // be updated even (or first of all) if the DisplayObject
201 // moves away from here.
203 if ( ! _invalidated
)
207 #ifdef DEBUG_SET_INVALIDATED
208 log_debug("%p set_invalidated() of %s in %s:%d",
209 (void*)this, getTarget(), debug_file
, debug_line
);
215 // NOTE: the SnappingRanges instance used here is not initialized by the
216 // GUI and therefore uses the default settings. This should not be a
217 // problem but special snapping ranges configuration done in gui.cpp
218 // is ignored here...
220 m_old_invalidated_ranges
.setNull();
221 add_invalidated_bounds(m_old_invalidated_ranges
, true);
226 DisplayObject::add_invalidated_bounds(InvalidatedRanges
& ranges
, bool force
)
228 ranges
.add(m_old_invalidated_ranges
);
229 if (visible() && (_invalidated
||force
))
232 bounds
.expand_to_transformed_rect(getWorldMatrix(*this), getBounds());
233 ranges
.add(bounds
.getRange());
238 DisplayObject::set_child_invalidated()
240 if (!_child_invalidated
) {
241 _child_invalidated
=true;
242 if (_parent
) _parent
->set_child_invalidated();
247 DisplayObject::extend_invalidated_bounds(const InvalidatedRanges
& ranges
)
249 set_invalidated(__FILE__
, __LINE__
);
250 m_old_invalidated_ranges
.add(ranges
);
254 DisplayObject::blendMode(const fn_call
& fn
)
256 DisplayObject
* ch
= ensure
<IsDisplayObject
<> >(fn
);
258 // This is AS-correct, but doesn't do anything.
259 // TODO: implement in the renderers!
260 LOG_ONCE(log_unimpl(_("blendMode")));
265 BlendMode bm
= ch
->getBlendMode();
267 /// If the blend mode is undefined, it doesn't return a string.
268 if (bm
== BLENDMODE_UNDEFINED
) return as_value();
270 std::ostringstream blendMode
;
272 return as_value(blendMode
.str());
279 const as_value
& bm
= fn
.arg(0);
281 // Undefined argument sets blend mode to normal.
282 if (bm
.is_undefined()) {
283 ch
->setBlendMode(BLENDMODE_NORMAL
);
288 if (bm
.is_number()) {
289 double mode
= toNumber(bm
, getVM(fn
));
291 // Hardlight is the last known value. This also performs range checking
292 // for float-to-int conversion.
293 if (mode
< 0 || mode
> BLENDMODE_HARDLIGHT
) {
295 // An invalid numeric argument becomes undefined.
296 ch
->setBlendMode(BLENDMODE_UNDEFINED
);
299 /// The extra static cast is required to keep OpenBSD happy.
300 ch
->setBlendMode(static_cast<BlendMode
>(static_cast<int>(mode
)));
305 // Other arguments use toString method.
306 const std::string
& mode
= bm
.to_string();
308 const BlendModeMap
& bmm
= getBlendModeMap();
309 BlendModeMap::const_iterator it
= std::find_if(bmm
.begin(), bmm
.end(),
310 std::bind(blendModeMatches
, std::placeholders::_1
, mode
));
312 if (it
!= bmm
.end()) {
313 ch
->setBlendMode(it
->first
);
316 // An invalid string argument has no effect.
323 DisplayObject::set_visible(bool visible
)
325 if (_visible
!= visible
) set_invalidated(__FILE__
, __LINE__
);
327 // Remove focus from this DisplayObject if it changes from visible to
328 // invisible (see Selection.as).
329 if (_visible
&& !visible
) {
331 movie_root
& mr
= stage();
332 if (mr
.getFocus() == this) {
333 mr
.setFocus(nullptr);
340 DisplayObject::setWidth(double newwidth
)
342 const SWFRect
& bounds
= getBounds();
343 const double oldwidth
= bounds
.width();
344 assert(oldwidth
>= 0);
346 const double xscale
= oldwidth
? (newwidth
/ oldwidth
) : 0;
347 const double rotation
= _rotation
* PI
/ 180.0;
349 SWFMatrix m
= getMatrix(*this);
350 const double yscale
= m
.get_y_scale();
351 m
.set_scale_rotation(xscale
, yscale
, rotation
);
356 getHeight(DisplayObject
& o
)
358 SWFRect bounds
= o
.getBounds();
359 const SWFMatrix
& m
= getMatrix(o
);
361 return twipsToPixels(bounds
.height());
365 setHeight(DisplayObject
& o
, const as_value
& val
)
367 const double newheight
= pixelsToTwips(toNumber(val
, getVM(*getObject(&o
))));
368 if (newheight
<= 0) {
369 IF_VERBOSE_ASCODING_ERRORS(
370 log_aserror(_("Setting _height=%g of DisplayObject %s (%s)"),
371 newheight
/ 20, o
.getTarget(), typeName(o
));
374 o
.setHeight(newheight
);
378 DisplayObject::setHeight(double newheight
)
380 const SWFRect
& bounds
= getBounds();
382 const double oldheight
= bounds
.height();
383 assert(oldheight
>= 0);
385 const double yscale
= oldheight
? (newheight
/ oldheight
) : 0;
386 const double rotation
= _rotation
* PI
/ 180.0;
388 SWFMatrix m
= getMatrix(*this);
389 const double xscale
= m
.get_x_scale();
390 m
.set_scale_rotation(xscale
, yscale
, rotation
);
395 DisplayObject::setMatrix(const SWFMatrix
& m
, bool updateCache
)
398 if (m
== _transform
.matrix
) return;
400 set_invalidated(__FILE__
, __LINE__
);
401 _transform
.matrix
= m
;
403 // don't update caches if SWFMatrix wasn't updated too
405 _xscale
= _transform
.matrix
.get_x_scale() * 100.0;
406 _yscale
= _transform
.matrix
.get_y_scale() * 100.0;
407 _rotation
= _transform
.matrix
.get_rotation() * 180.0 / PI
;
413 DisplayObject::set_event_handlers(const Events
& copyfrom
)
415 for (const auto& event
: copyfrom
)
417 const event_id
& ev
= event
.first
;
418 const BufferList
& bufs
= event
.second
;
419 for (const action_buffer
* buf
: bufs
)
422 add_event_handler(ev
, *buf
);
428 DisplayObject::add_event_handler(const event_id
& id
, const action_buffer
& code
)
430 _event_handlers
[id
].push_back(&code
);
433 std::unique_ptr
<ExecutableCode
>
434 DisplayObject::get_event_handler(const event_id
& id
) const
436 std::unique_ptr
<ExecutableCode
> handler
;
438 Events::const_iterator it
= _event_handlers
.find(id
);
439 if ( it
== _event_handlers
.end() ) return handler
;
441 DisplayObject
* this_ptr
= const_cast<DisplayObject
*>(this);
443 handler
.reset( new EventCode(this_ptr
, it
->second
) );
448 DisplayObject::unload()
450 const bool unloadHandler
= unloadChildren();
452 // Unregister this DisplayObject as mask and/or maskee.
453 if (_maskee
) _maskee
->setMask(nullptr);
454 if (_mask
) _mask
->setMaskee(nullptr);
458 return unloadHandler
;
462 DisplayObject::hasEventHandler(const event_id
& id
) const
464 Events::const_iterator it
= _event_handlers
.find(id
);
465 if (it
!= _event_handlers
.end()) return true;
467 if (!_object
) return false;
469 // Don't check resolve! Also don't check if it's a function, as
470 // the swfdec testsuite (onUnload-prototype.as) shows that it
472 if (Property
* prop
= _object
->findProperty(id
.functionURI())) {
479 /// Set the real and cached x scale.
481 /// Cached rotation and y scale are not updated.
483 DisplayObject::set_x_scale(double scale_percent
)
485 double xscale
= scale_percent
/ 100.0;
487 if (xscale
!= 0.0 && _xscale
!= 0.0)
489 if (scale_percent
* _xscale
< 0.0)
491 xscale
= -std::abs(xscale
);
493 else xscale
= std::abs(xscale
);
496 _xscale
= scale_percent
;
498 // As per misc-ming.all/SWFMatrix_test.{c,swf}
499 // we don't need to recompute the SWFMatrix from the
502 SWFMatrix m
= getMatrix(*this);
504 m
.set_x_scale(xscale
);
506 setMatrix(m
); // we updated the cache ourselves
508 transformedByScript();
511 /// Set the real and cached rotation.
513 /// Cached scale values are not updated.
515 DisplayObject::set_rotation(double rot
)
517 // Translate to the -180 .. 180 range
518 rot
= std::fmod(rot
, 360.0);
519 if (rot
> 180.0) rot
-= 360.0;
520 else if (rot
< -180.0) rot
+= 360.0;
522 double rotation
= rot
* PI
/ 180.0;
524 if (_xscale
< 0) rotation
+= PI
;
526 SWFMatrix m
= getMatrix(*this);
527 m
.set_rotation(rotation
);
529 // Update the matrix from the cached x scale to avoid accumulating
531 // TODO: also update y scale? The x scale update is needed to keep
532 // TextField correct; no tests for y scale.
533 m
.set_x_scale(std::abs(scaleX() / 100.0));
534 setMatrix(m
); // we update the cache ourselves
538 transformedByScript();
542 /// Set the real and cached y scale.
544 /// Cached rotation and x scale are not updated.
546 DisplayObject::set_y_scale(double scale_percent
)
548 double yscale
= scale_percent
/ 100.0;
550 if (yscale
!= 0.0 && _yscale
!= 0.0)
552 if (scale_percent
* _yscale
< 0.0) yscale
= -std::abs(yscale
);
553 else yscale
= std::abs(yscale
);
556 _yscale
= scale_percent
;
558 SWFMatrix m
= getMatrix(*this);
559 m
.set_y_scale(yscale
);
560 setMatrix(m
); // we updated the cache ourselves
562 transformedByScript();
567 DisplayObject::getTargetPath() const
569 // TODO: check what happens when this DisplayObject
570 // is a Movie loaded into another
573 typedef std::vector
<std::string
> Path
;
576 // Build parents stack
577 const DisplayObject
* topLevel
= nullptr;
578 const DisplayObject
* ch
= this;
580 string_table
& st
= getStringTable(*getObject(this));
583 const DisplayObject
* parent
= ch
->parent();
585 // Don't push the _root name on the stack
591 path
.push_back(ch
->get_name().toString(st
));
598 if (&stage().getRootMovie() == this) return "/";
599 std::stringstream ss
;
600 ss
<< "_level" << _depth
-DisplayObject::staticDepthOffset
;
604 // Build the target string from the parents stack
606 if (topLevel
!= &stage().getRootMovie()) {
607 std::stringstream ss
;
609 topLevel
->get_depth() - DisplayObject::staticDepthOffset
;
612 for (Path::reverse_iterator it
=path
.rbegin(), itEnd
=path
.rend();
621 DisplayObject::getTarget() const
624 // TODO: check what happens when this DisplayObject
625 // is a Movie loaded into another
628 typedef std::vector
<std::string
> Path
;
631 // Build parents stack
632 const DisplayObject
* ch
= this;
633 string_table
& st
= stage().getVM().getStringTable();
636 const DisplayObject
* parent
= ch
->parent();
638 // Don't push the _root name on the stack
641 std::stringstream ss
;
642 if (!dynamic_cast<const Movie
*>(ch
)) {
643 // must be an as-referenceable
644 // DisplayObject created using 'new'
645 // like, new MovieClip, new Video, new TextField...
646 ss
<< "<no parent, depth" << ch
->get_depth() << ">";
647 path
.push_back(ss
.str());
651 ch
->get_depth() - DisplayObject::staticDepthOffset
;
652 path
.push_back(ss
.str());
657 path
.push_back(ch
->get_name().toString(st
));
661 assert (!path
.empty());
663 // Build the target string from the parents stack
665 for (Path::const_reverse_iterator it
=path
.rbegin(), itEnd
=path
.rend();
668 if (!target
.empty()) target
+= ".";
677 DisplayObject::destroy()
679 // in case we are destroyed without being unloaded first
683 /// we may destory a DisplayObject that's not unloaded.
684 ///(we don't have chance to unload it in current model,
685 /// see new_child_in_unload_test.c)
686 /// We don't destroy ourself twice, right ?
688 if (_object
) _object
->clearProperties();
695 DisplayObject::markReachableResources() const
698 if (_object
) _object
->setReachable();
699 if (_parent
) _parent
->setReachable();
700 if (_mask
) _mask
->setReachable();
701 if (_maskee
) _maskee
->setReachable();
704 /// Whether to use a hand cursor when the mouse is over this DisplayObject
706 /// This depends on the useHandCursor AS property, but:
707 /// 1. Only AS-referenceable objects may use a hand cursor (TODO: check
709 /// 2. Only objects with a release event may use a hand cursor.
710 /// CANNOT CONFIRM THE ABOVE, SEE ButtonEventsTest.swf in misc-ming.all
711 /// 3. The default value (if the property is not defined) is true.
713 DisplayObject::allowHandCursor() const
715 as_object
* obj
= getObject(this);
716 if (!obj
) return false;
719 if (!obj
->get_member(NSV::PROP_USEHANDCURSOR
, &val
)) {
722 return toBool(val
, getVM(*obj
));
726 DisplayObject::setMask(DisplayObject
* mask
)
728 if ( _mask
== mask
) return;
732 // Backup this before setMaskee has a chance to change it..
733 DisplayObject
* prevMaskee
= _maskee
;
735 // If we had a previous mask unregister with it
736 if ( _mask
&& _mask
!= mask
)
738 // the mask will call setMask(NULL)
739 // on any previously registered maskee
740 // so we make sure to set our _mask to
741 // NULL before getting called again
742 _mask
->setMaskee(nullptr);
745 // if we had a maskee, notify it to stop using
747 if (prevMaskee
) prevMaskee
->setMask(nullptr);
749 // TODO: should we reset any original clip depth
750 // specified by PlaceObject tag ?
751 set_clip_depth(noClipDepthValue
);
756 /// Register as as masked by the mask
757 _mask
->setMaskee(this);
762 DisplayObject::setMaskee(DisplayObject
* maskee
)
764 if ( _maskee
== maskee
) { return; }
767 // We don't want the maskee to call setMaskee(null)
769 _maskee
->_mask
= nullptr;
776 // TODO: should we reset any original clip depth
777 // specified by PlaceObject tag ?
778 set_clip_depth(noClipDepthValue
);
784 DisplayObject::boundsInClippingArea(Renderer
& renderer
) const
786 SWFRect mybounds
= getBounds();
787 getWorldMatrix(*this).transform(mybounds
);
789 return renderer
.bounds_in_clipping_area(mybounds
.getRange());
793 DisplayObject::InfoTree::iterator
794 DisplayObject::getMovieInfo(InfoTree
& tr
, InfoTree::iterator it
)
796 const std::string yes
= _("yes");
797 const std::string no
= _("no");
799 it
= tr
.append_child(it
, std::make_pair(getTarget(), typeName(*this)));
801 std::ostringstream os
;
803 tr
.append_child(it
, std::make_pair(_("Depth"), os
.str()));
805 /// Don't add if the DisplayObject has no ratio value
806 if (get_ratio() > 0) {
809 tr
.append_child(it
, std::make_pair(_("Ratio"), os
.str()));
812 /// Don't add if it's not a real clipping depth
813 const int cd
= get_clip_depth();
814 if (cd
!= noClipDepthValue
) {
816 if (_maskee
) os
<< "Dynamic mask";
819 tr
.append_child(it
, std::make_pair(_("Clipping depth"), os
.str()));
823 os
<< getBounds().width() << "x" << getBounds().height();
824 tr
.append_child(it
, std::make_pair(_("Dimensions"), os
.str()));
826 tr
.append_child(it
, std::make_pair(_("Dynamic"), isDynamic() ? yes
: no
));
827 tr
.append_child(it
, std::make_pair(_("Mask"), isMaskLayer() ? yes
: no
));
828 tr
.append_child(it
, std::make_pair(_("Destroyed"),
829 isDestroyed() ? yes
: no
));
830 tr
.append_child(it
, std::make_pair(_("Unloaded"), unloaded() ? yes
: no
));
834 tr
.append_child(it
, std::make_pair(_("Blend mode"), os
.str()));
836 // This probably isn't interesting for non-developers
837 tr
.append_child(it
, std::make_pair(_("Invalidated"),
838 _invalidated
? yes
: no
));
839 tr
.append_child(it
, std::make_pair(_("Child invalidated"),
840 _child_invalidated
? yes
: no
));
847 DisplayObject::getAsRoot()
853 setIndexedProperty(size_t index
, DisplayObject
& o
, const as_value
& val
)
855 const Setter s
= getGetterSetterByIndex(index
).second
;
856 if (!s
) return; // read-only (warn?)
858 if (val
.is_undefined() || val
.is_null()) {
859 IF_VERBOSE_ASCODING_ERRORS(
860 log_aserror(_("Attempt to set property to %s, refused"),
870 getIndexedProperty(size_t index
, DisplayObject
& o
, as_value
& val
)
872 const Getter s
= getGetterSetterByIndex(index
).first
;
881 /// DisplayObject property lookup
883 /// This function is only called on the first object in the inheritance chain
884 /// after the object's own properties have been checked.
885 /// In AS2, any DisplayObject marks the end of the inheritance chain for
890 /// 1. _level0.._level9
891 /// 2. Objects on the DisplayList of a MovieClip
892 /// 3. DisplayObject magic properties (_x, _y etc).
893 /// 4. MovieClips' TextField variables (this is probably not the best
894 /// way to do it, but as it is done like this, this must be called here.
895 /// It will cause an infinite recursion otherwise.
897 getDisplayObjectProperty(DisplayObject
& obj
, const ObjectURI
& uri
,
901 as_object
* o
= getObject(&obj
);
904 string_table
& st
= getStringTable(*o
);
905 const std::string
& propname
= uri
.toString(st
);
907 // Check _level0.._level9
908 unsigned int levelno
;
909 if (isLevelTarget(getSWFVersion(*o
), propname
, levelno
)) {
910 movie_root
& mr
= getRoot(*getObject(&obj
));
911 MovieClip
* mo
= mr
.getLevel(levelno
);
919 MovieClip
* mc
= obj
.to_movie();
921 DisplayObject
* ch
= mc
->getDisplayListObject(uri
);
928 const string_table::key noCaseKey
= uri
.noCase(st
);
930 // These properties have normal case-sensitivity.
931 // They are tested to exist for TextField, MovieClip, and Button
932 // but do not belong to the inheritance chain.
933 switch (caseless(*o
) ? noCaseKey
: getName(uri
))
937 case NSV::PROP_uROOT
:
938 if (getSWFVersion(*o
) < 5) break;
939 val
= getObject(obj
.getAsRoot());
941 case NSV::PROP_uGLOBAL
:
942 // TODO: clean up this mess.
943 assert(getObject(&obj
));
944 if (getSWFVersion(*o
) < 6) break;
945 val
= &getGlobal(*o
);
949 // These magic properties are case insensitive in all versions!
950 if (doGet(uri
, obj
, val
)) return true;
952 // Check MovieClip such as TextField variables.
953 // TODO: check if there's a better way to find these properties.
954 if (mc
&& mc
->getTextFieldVariables(uri
, val
)) return true;
961 setDisplayObjectProperty(DisplayObject
& obj
, const ObjectURI
& uri
,
964 // These magic properties are case insensitive in all versions!
965 return doSet(uri
, obj
, val
);
968 DisplayObject::MaskRenderer::MaskRenderer(Renderer
& r
, const DisplayObject
& o
)
971 _mask(o
.visible() && o
.getMask() && !o
.getMask()->unloaded() ? o
.getMask()
976 _renderer
.begin_submit_mask();
977 DisplayObject
* p
= _mask
->parent();
978 const Transform tr
= p
?
979 Transform(getWorldMatrix(*p
), getWorldCxForm(*p
)) : Transform();
980 _mask
->display(_renderer
, tr
);
981 _renderer
.end_submit_mask();
984 DisplayObject::MaskRenderer::~MaskRenderer()
986 if (_mask
) _renderer
.disable_mask();
992 getQuality(DisplayObject
& o
)
994 movie_root
& mr
= getRoot(*getObject(&o
));
995 switch (mr
.getQuality())
998 return as_value("BEST");
1000 return as_value("HIGH");
1001 case QUALITY_MEDIUM
:
1002 return as_value("MEDIUM");
1004 return as_value("LOW");
1012 setQuality(DisplayObject
& o
, const as_value
& val
)
1014 movie_root
& mr
= getRoot(*getObject(&o
));
1016 if (!val
.is_string()) return;
1018 const std::string
& q
= val
.to_string();
1020 StringNoCaseEqual noCaseCompare
;
1022 if (noCaseCompare(q
, "BEST")) mr
.setQuality(QUALITY_BEST
);
1023 else if (noCaseCompare(q
, "HIGH")) {
1024 mr
.setQuality(QUALITY_HIGH
);
1026 else if (noCaseCompare(q
, "MEDIUM")) {
1027 mr
.setQuality(QUALITY_MEDIUM
);
1029 else if (noCaseCompare(q
, "LOW")) {
1030 mr
.setQuality(QUALITY_LOW
);
1037 getURL(DisplayObject
& o
)
1039 return as_value(o
.get_root()->url());
1043 getHighQuality(DisplayObject
& o
)
1045 movie_root
& mr
= getRoot(*getObject(&o
));
1046 switch (mr
.getQuality())
1049 return as_value(2.0);
1051 return as_value(1.0);
1052 case QUALITY_MEDIUM
:
1054 return as_value(0.0);
1060 setHighQuality(DisplayObject
& o
, const as_value
& val
)
1062 movie_root
& mr
= getRoot(*getObject(&o
));
1064 const double q
= toNumber(val
, getVM(*getObject(&o
)));
1066 if (q
< 0) mr
.setQuality(QUALITY_HIGH
);
1067 else if (q
> 2) mr
.setQuality(QUALITY_BEST
);
1069 int i
= static_cast<int>(q
);
1073 mr
.setQuality(QUALITY_LOW
);
1076 mr
.setQuality(QUALITY_HIGH
);
1079 mr
.setQuality(QUALITY_BEST
);
1087 setY(DisplayObject
& o
, const as_value
& val
)
1089 const double newy
= toNumber(val
, getVM(*getObject(&o
)));
1091 // NaN is skipped, Infinite isn't
1094 IF_VERBOSE_ASCODING_ERRORS(
1095 log_aserror(_("Attempt to set %s._y to %s "
1096 "(evaluating to number %g) refused"),
1097 o
.getTarget(), val
, newy
);
1102 SWFMatrix m
= getMatrix(o
);
1103 // NOTE: infinite_to_zero is wrong here, see actionscript.all/setProperty.as
1104 m
.set_y_translation(pixelsToTwips(infinite_to_zero(newy
)));
1106 o
.transformedByScript();
1110 getY(DisplayObject
& o
)
1112 const SWFMatrix
& m
= getMatrix(o
);
1113 return twipsToPixels(m
.get_y_translation());
1117 setX(DisplayObject
& o
, const as_value
& val
)
1120 const double newx
= toNumber(val
, getVM(*getObject(&o
)));
1122 // NaN is skipped, Infinite isn't
1125 IF_VERBOSE_ASCODING_ERRORS(
1126 log_aserror(_("Attempt to set %s._x to %s "
1127 "(evaluating to number %g) refused"),
1128 o
.getTarget(), val
, newx
);
1133 SWFMatrix m
= getMatrix(o
);
1134 // NOTE: infinite_to_zero is wrong here, see actionscript.all/setProperty.as
1135 m
.set_x_translation(pixelsToTwips(infinite_to_zero(newx
)));
1137 o
.transformedByScript();
1141 getX(DisplayObject
& o
)
1143 const SWFMatrix
& m
= getMatrix(o
);
1144 return twipsToPixels(m
.get_x_translation());
1148 setScaleX(DisplayObject
& o
, const as_value
& val
)
1150 const double scale_percent
= toNumber(val
, getVM(*getObject(&o
)));
1152 // NaN is skipped, Infinite is not, see actionscript.all/setProperty.as
1153 if (isNaN(scale_percent
)) {
1154 IF_VERBOSE_ASCODING_ERRORS(
1155 log_aserror(_("Attempt to set %s._xscale to %s "
1156 "(evaluating to number %g) refused"),
1157 o
.getTarget(), val
, scale_percent
);
1162 // input is in percent
1163 o
.set_x_scale(scale_percent
);
1168 getScaleX(DisplayObject
& o
)
1174 setScaleY(DisplayObject
& o
, const as_value
& val
)
1176 const double scale_percent
= toNumber(val
, getVM(*getObject(&o
)));
1178 // NaN is skipped, Infinite is not, see actionscript.all/setProperty.as
1179 if (isNaN(scale_percent
)) {
1180 IF_VERBOSE_ASCODING_ERRORS(
1181 log_aserror(_("Attempt to set %s._yscale to %s "
1182 "(evaluating to number %g) refused"),
1183 o
.getTarget(), val
, scale_percent
);
1188 // input is in percent
1189 o
.set_y_scale(scale_percent
);
1194 getScaleY(DisplayObject
& o
)
1200 getVisible(DisplayObject
& o
)
1206 setVisible(DisplayObject
& o
, const as_value
& val
)
1208 /// We cast to number and rely (mostly) on C++'s automatic
1209 /// cast to bool, as string "0" should be converted to
1210 /// its numeric equivalent, not interpreted as 'true', which
1211 /// SWF7+ does for strings.
1212 const double d
= toNumber(val
, getVM(*getObject(&o
)));
1214 // Infinite or NaN is skipped
1215 if (isInf(d
) || isNaN(d
)) {
1216 IF_VERBOSE_ASCODING_ERRORS(
1217 log_aserror(_("Attempt to set %s._visible to %s "
1218 "(evaluating to number %g) refused"),
1219 o
.getTarget(), val
, d
);
1226 o
.transformedByScript();
1230 getAlpha(DisplayObject
& o
)
1232 return as_value(getCxForm(o
).aa
/ 2.56);
1236 setAlpha(DisplayObject
& o
, const as_value
& val
)
1238 // The new internal alpha value is input / 100.0 * 256.
1239 // We test for finiteness later, but the multiplication
1240 // won't make any difference.
1241 const double newAlpha
= toNumber(val
, getVM(*getObject(&o
))) * 2.56;
1243 // NaN is skipped, Infinite is not, see actionscript.all/setProperty.as
1244 if (isNaN(newAlpha
)) {
1245 IF_VERBOSE_ASCODING_ERRORS(
1246 log_aserror(_("Attempt to set %s._alpha to %s "
1247 "(evaluating to number %g) refused"),
1248 o
.getTarget(), val
, newAlpha
);
1253 SWFCxForm cx
= getCxForm(o
);
1255 // Overflows are *not* truncated, but set to -32768.
1256 if (newAlpha
> std::numeric_limits
<std::int16_t>::max() ||
1257 newAlpha
< std::numeric_limits
<std::int16_t>::min()) {
1258 cx
.aa
= std::numeric_limits
<std::int16_t>::min();
1261 cx
.aa
= static_cast<std::int16_t>(newAlpha
);
1265 o
.transformedByScript();
1270 getMouseX(DisplayObject
& o
)
1272 // Local coord of mouse IN PIXELS.
1274 boost::tie(x
, y
) = getRoot(*getObject(&o
)).mousePosition();
1276 SWFMatrix m
= getWorldMatrix(o
);
1277 point
a(pixelsToTwips(x
), pixelsToTwips(y
));
1279 m
.invert().transform(a
);
1280 return as_value(twipsToPixels(a
.x
));
1284 getMouseY(DisplayObject
& o
)
1286 // Local coord of mouse IN PIXELS.
1288 boost::tie(x
, y
) = getRoot(*getObject(&o
)).mousePosition();
1290 SWFMatrix m
= getWorldMatrix(o
);
1291 point
a(pixelsToTwips(x
), pixelsToTwips(y
));
1292 m
.invert().transform(a
);
1293 return as_value(twipsToPixels(a
.y
));
1297 getRotation(DisplayObject
& o
)
1299 return o
.rotation();
1304 setRotation(DisplayObject
& o
, const as_value
& val
)
1306 // input is in degrees
1307 const double rotation_val
= toNumber(val
, getVM(*getObject(&o
)));
1309 // NaN is skipped, Infinity isn't
1310 if (isNaN(rotation_val
)) {
1311 IF_VERBOSE_ASCODING_ERRORS(
1312 log_aserror(_("Attempt to set %s._rotation to %s "
1313 "(evaluating to number %g) refused"),
1314 o
.getTarget(), val
, rotation_val
);
1318 o
.set_rotation(rotation_val
);
1323 getParent(DisplayObject
& o
)
1325 as_object
* p
= getObject(o
.parent());
1326 return p
? p
: as_value();
1330 getTarget(DisplayObject
& o
)
1332 return o
.getTargetPath();
1336 getNameProperty(DisplayObject
& o
)
1338 string_table
& st
= getStringTable(*getObject(&o
));
1339 const std::string
& name
= o
.get_name().toString(st
);
1340 return as_value(name
);
1344 setName(DisplayObject
& o
, const as_value
& val
)
1346 o
.set_name(getURI(getVM(*getObject(&o
)), val
.to_string()));
1350 setSoundBufTime(DisplayObject
& /*o*/, const as_value
& /*val*/)
1352 LOG_ONCE(log_unimpl(_("_soundbuftime setting")));
1356 getSoundBufTime(DisplayObject
& /*o*/)
1358 return as_value(0.0);
1362 getWidth(DisplayObject
& o
)
1364 SWFRect bounds
= o
.getBounds();
1365 const SWFMatrix
& m
= getMatrix(o
);
1366 m
.transform(bounds
);
1367 return twipsToPixels(bounds
.width());
1371 setWidth(DisplayObject
& o
, const as_value
& val
)
1373 const double newwidth
= pixelsToTwips(toNumber(val
, getVM(*getObject(&o
))));
1374 if (newwidth
<= 0) {
1375 IF_VERBOSE_ASCODING_ERRORS(
1376 log_aserror(_("Setting _width=%g of DisplayObject %s (%s)"),
1377 newwidth
/20, o
.getTarget(), typeName(o
));
1380 o
.setWidth(newwidth
);
1384 getFocusRect(DisplayObject
& o
)
1386 LOG_ONCE(log_unimpl(_("_focusrect")));
1388 const boost::tribool fr
= o
.focusRect();
1389 if (boost::indeterminate(fr
)) {
1394 const bool ret
= static_cast<bool>(fr
);
1395 if (getSWFVersion(*getObject(&o
)) == 5) {
1396 return as_value(static_cast<double>(ret
));
1398 return as_value(ret
);
1402 setFocusRect(DisplayObject
& o
, const as_value
& val
)
1404 LOG_ONCE(log_unimpl(_("_focusrect")));
1406 VM
& vm
= getVM(*getObject(&o
));
1408 const double d
= toNumber(val
, vm
);
1409 if (isNaN(d
)) return;
1413 o
.focusRect(toBool(val
, vm
));
1417 getDropTarget(DisplayObject
& o
)
1419 // This property only applies to MovieClips.
1420 MovieClip
* mc
= o
.to_movie();
1421 if (!mc
) return as_value();
1422 return as_value(mc
->getDropTarget());
1426 getCurrentFrame(DisplayObject
& o
)
1428 // This property only applies to MovieClips.
1429 MovieClip
* mc
= o
.to_movie();
1430 if (!mc
) return as_value();
1431 const int currframe
=
1432 std::min(mc
->get_loaded_frames(), mc
->get_current_frame() + 1);
1433 return as_value(currframe
);
1437 getFramesLoaded(DisplayObject
& o
)
1439 // This property only applies to MovieClips.
1440 MovieClip
* mc
= o
.to_movie();
1441 if (!mc
) return as_value();
1442 return as_value(mc
->get_loaded_frames());
1446 getTotalFrames(DisplayObject
& o
)
1448 // This property only applies to MovieClips.
1449 MovieClip
* mc
= o
.to_movie();
1450 if (!mc
) return as_value();
1451 return as_value(mc
->get_frame_count());
1455 /// @param uri The property to search for. Note that all special
1456 /// properties are lower-case.
1458 /// NOTE that all properties have getters so you can recognize a
1459 /// 'not-found' condition by checking .first = 0
1461 getGetterSetterByURI(const ObjectURI
& uri
, string_table
& st
)
1463 typedef std::map
<ObjectURI
, GetterSetter
, ObjectURI::CaseLessThan
>
1466 static const GetterSetters gs
=
1467 getURIMap
<GetterSetters
>(ObjectURI::CaseLessThan(st
, true));
1469 const GetterSetters::const_iterator it
= gs
.find(uri
);
1471 if (it
== gs
.end()) {
1472 static const GetterSetter
none(nullptr, nullptr);
1481 getGetterSetterByIndex(size_t index
)
1483 const Setter n
= nullptr;
1485 static const GetterSetter props
[] = {
1486 GetterSetter(&getX
, &setX
),
1487 GetterSetter(&getY
, &setY
),
1488 GetterSetter(&getScaleX
, &setScaleX
),
1489 GetterSetter(&getScaleY
, &setScaleY
),
1491 GetterSetter(&getCurrentFrame
, n
),
1492 GetterSetter(&getTotalFrames
, n
),
1493 GetterSetter(&getAlpha
, &setAlpha
),
1494 GetterSetter(&getVisible
, &setVisible
),
1496 GetterSetter(&getWidth
, &setWidth
),
1497 GetterSetter(&getHeight
, &setHeight
),
1498 GetterSetter(&getRotation
, &setRotation
),
1499 GetterSetter(&getTarget
, n
),
1501 GetterSetter(&getFramesLoaded
, n
),
1502 GetterSetter(&getNameProperty
, &setName
),
1503 GetterSetter(&getDropTarget
, n
),
1504 GetterSetter(&getURL
, n
),
1506 GetterSetter(&getHighQuality
, &setHighQuality
),
1507 GetterSetter(&getFocusRect
, &setFocusRect
),
1508 GetterSetter(&getSoundBufTime
, &setSoundBufTime
),
1509 GetterSetter(&getQuality
, &setQuality
),
1511 GetterSetter(&getMouseX
, n
),
1512 GetterSetter(&getMouseY
, n
)
1516 if (index
>= arraySize(props
)) {
1517 const Getter ng
= nullptr;
1518 static const GetterSetter
none(ng
, n
);
1522 return props
[index
];
1527 doGet(const ObjectURI
& uri
, DisplayObject
& o
, as_value
& val
)
1529 string_table
& st
= getStringTable(*getObject(&o
));
1530 const Getter s
= getGetterSetterByURI(uri
, st
).first
;
1531 if (!s
) return false;
1538 /// Do the actual setProperty
1540 /// Return true if the property is a DisplayObject property, regardless of
1541 /// whether it was successfully set or not.
1543 /// @param uri The property to search for. Note that all special
1544 /// properties are lower-case, so for a caseless check
1545 /// it is sufficient for prop to be caseless.
1547 doSet(const ObjectURI
& uri
, DisplayObject
& o
, const as_value
& val
)
1549 string_table
& st
= getStringTable(*getObject(&o
));
1551 const GetterSetter
& gs
= getGetterSetterByURI(uri
, st
);
1553 // not found (all props have getters)
1554 if (!gs
.first
) return false;
1556 const Setter
& s
= gs
.second
;
1558 // read-only (TODO: aserror ?)
1559 if (!s
) return true;
1561 if (val
.is_undefined() || val
.is_null()) {
1562 IF_VERBOSE_ASCODING_ERRORS(
1563 // TODO: add property name to this log...
1564 log_aserror(_("Attempt to set property to %s, refused"),
1565 o
.getTarget(), val
);
1578 /// BLENDMODE_UNDEFINED has no matching string in AS. It is included
1579 /// here for logging purposes.
1580 static const BlendModeMap bm
= {
1581 {DisplayObject::BLENDMODE_UNDEFINED
, "undefined"},
1582 {DisplayObject::BLENDMODE_NORMAL
, "normal"},
1583 {DisplayObject::BLENDMODE_LAYER
, "layer"},
1584 {DisplayObject::BLENDMODE_MULTIPLY
, "multiply"},
1585 {DisplayObject::BLENDMODE_SCREEN
, "screen"},
1586 {DisplayObject::BLENDMODE_LIGHTEN
, "lighten"},
1587 {DisplayObject::BLENDMODE_DARKEN
, "darken"},
1588 {DisplayObject::BLENDMODE_DIFFERENCE
, "difference"},
1589 {DisplayObject::BLENDMODE_ADD
, "add"},
1590 {DisplayObject::BLENDMODE_SUBTRACT
, "subtract"},
1591 {DisplayObject::BLENDMODE_INVERT
, "invert"},
1592 {DisplayObject::BLENDMODE_ALPHA
, "alpha"},
1593 {DisplayObject::BLENDMODE_ERASE
, "erase"},
1594 {DisplayObject::BLENDMODE_OVERLAY
, "overlay"},
1595 {DisplayObject::BLENDMODE_HARDLIGHT
, "hardlight"}
1602 // Match a blend mode to its string.
1604 blendModeMatches(const BlendModeMap::value_type
& val
, const std::string
& mode
)
1606 /// The match must be case-sensitive.
1607 if (mode
.empty()) return false;
1608 return (val
.second
== mode
);
1611 /// Return a const map of property URI to function.
1613 /// This function takes advantage of NRVO to allow the map to
1614 /// be constructed in the caller.
1615 template<typename Map
>
1617 getURIMap(const typename
Map::key_compare
& cmp
)
1619 const Setter n
= nullptr;
1622 ret
.insert(std::make_pair(NSV::PROP_uX
, GetterSetter(&getX
, &setX
)));
1623 ret
.insert(std::make_pair(NSV::PROP_uY
, GetterSetter(&getY
, &setY
)));
1624 ret
.insert(std::make_pair(NSV::PROP_uXSCALE
,
1625 GetterSetter(&getScaleX
, &setScaleX
)));
1626 ret
.insert(std::make_pair(NSV::PROP_uYSCALE
,
1627 GetterSetter(&getScaleY
, &setScaleY
)));
1628 ret
.insert(std::make_pair(NSV::PROP_uROTATION
,
1629 GetterSetter(&getRotation
, &setRotation
)));
1630 ret
.insert(std::make_pair(NSV::PROP_uHIGHQUALITY
,
1631 GetterSetter(&getHighQuality
, &setHighQuality
)));
1632 ret
.insert(std::make_pair(NSV::PROP_uQUALITY
,
1633 GetterSetter(&getQuality
, &setQuality
)));
1634 ret
.insert(std::make_pair(NSV::PROP_uALPHA
,
1635 GetterSetter(&getAlpha
, &setAlpha
)));
1636 ret
.insert(std::make_pair(NSV::PROP_uWIDTH
,
1637 GetterSetter(&getWidth
, &setWidth
)));
1638 ret
.insert(std::make_pair(NSV::PROP_uHEIGHT
,
1639 GetterSetter(&getHeight
, &setHeight
)));
1640 ret
.insert(std::make_pair(NSV::PROP_uNAME
,
1641 GetterSetter(&getNameProperty
, &setName
)));
1642 ret
.insert(std::make_pair(NSV::PROP_uVISIBLE
,
1643 GetterSetter(&getVisible
, &setVisible
)));
1644 ret
.insert(std::make_pair(NSV::PROP_uSOUNDBUFTIME
,
1645 GetterSetter(&getSoundBufTime
, &setSoundBufTime
)));
1646 ret
.insert(std::make_pair(NSV::PROP_uFOCUSRECT
,
1647 GetterSetter(&getFocusRect
, &setFocusRect
)));
1648 ret
.insert(std::make_pair(NSV::PROP_uDROPTARGET
,
1649 GetterSetter(&getDropTarget
, n
)));
1650 ret
.insert(std::make_pair(NSV::PROP_uCURRENTFRAME
,
1651 GetterSetter(&getCurrentFrame
, n
)));
1652 ret
.insert(std::make_pair(NSV::PROP_uFRAMESLOADED
,
1653 GetterSetter(&getFramesLoaded
, n
)));
1654 ret
.insert(std::make_pair(NSV::PROP_uTOTALFRAMES
,
1655 GetterSetter(&getTotalFrames
, n
)));
1656 ret
.insert(std::make_pair(NSV::PROP_uURL
, GetterSetter(&getURL
, n
)));
1657 ret
.insert(std::make_pair(NSV::PROP_uTARGET
, GetterSetter(&getTarget
, n
)));
1658 ret
.insert(std::make_pair(NSV::PROP_uXMOUSE
, GetterSetter(&getMouseX
, n
)));
1659 ret
.insert(std::make_pair(NSV::PROP_uYMOUSE
, GetterSetter(&getMouseY
, n
)));
1660 ret
.insert(std::make_pair(NSV::PROP_uPARENT
, GetterSetter(&getParent
, n
)));
1664 } // anonymous namespace
1667 operator<<(std::ostream
& o
, DisplayObject::BlendMode bm
)
1669 const BlendModeMap
& bmm
= getBlendModeMap();
1670 return (o
<< bmm
.find(bm
)->second
);
1673 } // namespace gnash
1677 // indent-tabs-mode: t