1 // DisplayObject.cpp: ActionScript DisplayObject class, for Gnash.
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
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"
28 #include <boost/tuple/tuple.hpp>
29 #include <boost/algorithm/string/case_conv.hpp>
30 #include <boost/assign/list_of.hpp>
31 #include <boost/bind.hpp>
33 #include "smart_ptr.h"
34 #include "movie_root.h"
35 #include "MovieClip.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
55 // Forward declarations.
57 /// Match blend modes.
58 typedef std::map
<DisplayObject::BlendMode
, std::string
> BlendModeMap
;
59 const BlendModeMap
& getBlendModeMap();
60 bool blendModeMatches(const BlendModeMap::value_type
& val
,
61 const std::string
& mode
);
63 typedef as_value(*Getter
)(DisplayObject
&);
64 typedef void(*Setter
)(DisplayObject
&, const as_value
&);
65 typedef std::pair
<Getter
, Setter
> GetterSetter
;
67 bool doSet(const ObjectURI
& uri
, DisplayObject
& o
, const as_value
& val
);
68 bool doGet(const ObjectURI
& uri
, DisplayObject
& o
, as_value
& val
);
69 const GetterSetter
& getGetterSetterByIndex(size_t index
);
71 // NOTE: comparison will be case-insensitive
72 const GetterSetter
& getGetterSetterByURI(const ObjectURI
& uri
,
75 // Convenience function to create a const URI-to-function map
76 template<typename Map
> const Map
getURIMap(
77 const typename
Map::key_compare
& cmp
);
80 // Define static const members.
81 const int DisplayObject::lowerAccessibleBound
;
82 const int DisplayObject::upperAccessibleBound
;
83 const int DisplayObject::staticDepthOffset
;
84 const int DisplayObject::removedDepthOffset
;
85 const int DisplayObject::noClipDepthValue
;
87 DisplayObject::DisplayObject(movie_root
& mr
, as_object
* object
,
88 DisplayObject
* parent
)
101 m_clip_depth(noClipDepthValue
),
104 _blendMode(BLENDMODE_NORMAL
),
106 _scriptTransformed(false),
107 _dynamicallyCreated(false),
111 _child_invalidated(true)
113 assert(m_old_invalidated_ranges
.isNull());
115 // This informs the core that the object is a DisplayObject.
116 if (_object
) _object
->setDisplayObject(this);
120 DisplayObject::getLoadedMovie(Movie
* extern_movie
)
123 log_unimpl("loadMovie against a %s DisplayObject", typeName(*this))
126 // TODO: look at the MovieClip implementation, but most importantly
127 // test all the event handlers copies etc..
129 UNUSED(extern_movie
);
133 DisplayObject::getNextUnnamedInstanceName()
136 movie_root
& mr
= getRoot(*_object
);
137 std::ostringstream ss
;
138 ss
<< "instance" << mr
.nextUnnamedInstance();
140 VM
& vm
= getVM(*_object
);
141 return getURI(vm
, ss
.str(), true);
146 DisplayObject::getWorldVolume() const
151 volume
= int(volume
*_parent
->getVolume()/100.0);
159 DisplayObject::pathElement(const ObjectURI
& uri
)
161 as_object
* obj
= getObject(this);
164 string_table::key key
= getName(uri
);
166 string_table
& st
= stage().getVM().getStringTable();
168 // TODO: put ".." and "." in namedStrings
169 if (key
== st
.find("..")) return getObject(parent());
170 if (key
== st
.find(".")) return obj
;
172 // The check is case-insensitive for SWF6 and below.
173 // TODO: cache ObjectURI(NSV::PROP_THIS) [as many others...]
174 if (ObjectURI::CaseEquals(st
, caseless(*obj
))
175 (uri
, ObjectURI(NSV::PROP_THIS
))) {
182 DisplayObject::set_invalidated()
184 set_invalidated("unknown", -1);
188 DisplayObject::set_invalidated(const char* debug_file
, int debug_line
)
190 // Set the invalidated-flag of the parent. Note this does not mean that
191 // the parent must re-draw itself, it just means that one of it's childs
192 // needs to be re-drawn.
193 if ( _parent
) _parent
->set_child_invalidated();
195 // Ok, at this point the instance will change it's
196 // visual aspect after the
197 // call to set_invalidated(). We save the *current*
198 // position of the instance because this region must
199 // be updated even (or first of all) if the DisplayObject
200 // moves away from here.
202 if ( ! _invalidated
)
206 #ifdef DEBUG_SET_INVALIDATED
207 log_debug("%p set_invalidated() of %s in %s:%d",
208 (void*)this, get_name(), debug_file
, debug_line
);
214 // NOTE: the SnappingRanges instance used here is not initialized by the
215 // GUI and therefore uses the default settings. This should not be a
216 // problem but special snapping ranges configuration done in gui.cpp
217 // is ignored here...
219 m_old_invalidated_ranges
.setNull();
220 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
)
242 _child_invalidated
=true;
243 if ( _parent
) _parent
->set_child_invalidated();
248 DisplayObject::extend_invalidated_bounds(const InvalidatedRanges
& ranges
)
250 set_invalidated(__FILE__
, __LINE__
);
251 m_old_invalidated_ranges
.add(ranges
);
255 DisplayObject::blendMode(const fn_call
& fn
)
257 DisplayObject
* ch
= ensure
<IsDisplayObject
<> >(fn
);
259 // This is AS-correct, but doesn't do anything.
260 // TODO: implement in the renderers!
261 LOG_ONCE(log_unimpl(_("blendMode")));
266 BlendMode bm
= ch
->getBlendMode();
268 /// If the blend mode is undefined, it doesn't return a string.
269 if (bm
== BLENDMODE_UNDEFINED
) return as_value();
271 std::ostringstream blendMode
;
273 return as_value(blendMode
.str());
280 const as_value
& bm
= fn
.arg(0);
282 // Undefined argument sets blend mode to normal.
283 if (bm
.is_undefined()) {
284 ch
->setBlendMode(BLENDMODE_NORMAL
);
289 if (bm
.is_number()) {
290 double mode
= toNumber(bm
, getVM(fn
));
292 // Hardlight is the last known value. This also performs range checking
293 // for float-to-int conversion.
294 if (mode
< 0 || mode
> BLENDMODE_HARDLIGHT
) {
296 // An invalid numeric argument becomes undefined.
297 ch
->setBlendMode(BLENDMODE_UNDEFINED
);
300 /// The extra static cast is required to keep OpenBSD happy.
301 ch
->setBlendMode(static_cast<BlendMode
>(static_cast<int>(mode
)));
306 // Other arguments use toString method.
307 const std::string
& mode
= bm
.to_string();
309 const BlendModeMap
& bmm
= getBlendModeMap();
310 BlendModeMap::const_iterator it
= std::find_if(bmm
.begin(), bmm
.end(),
311 boost::bind(blendModeMatches
, _1
, mode
));
313 if (it
!= bmm
.end()) {
314 ch
->setBlendMode(it
->first
);
317 // An invalid string argument has no effect.
324 DisplayObject::set_visible(bool visible
)
326 if (_visible
!= visible
) set_invalidated(__FILE__
, __LINE__
);
328 // Remove focus from this DisplayObject if it changes from visible to
329 // invisible (see Selection.as).
330 if (_visible
&& !visible
) {
332 movie_root
& mr
= getRoot(*_object
);
333 if (mr
.getFocus() == this) {
341 DisplayObject::setWidth(double newwidth
)
343 const SWFRect
& bounds
= getBounds();
344 const double oldwidth
= bounds
.width();
345 assert(oldwidth
>= 0);
347 const double xscale
= oldwidth
? (newwidth
/ oldwidth
) : 0;
348 const double rotation
= _rotation
* PI
/ 180.0;
350 SWFMatrix m
= getMatrix(*this);
351 const double yscale
= m
.get_y_scale();
352 m
.set_scale_rotation(xscale
, yscale
, rotation
);
357 getHeight(DisplayObject
& o
)
359 SWFRect bounds
= o
.getBounds();
360 const SWFMatrix m
= getMatrix(o
);
362 return twipsToPixels(bounds
.height());
366 setHeight(DisplayObject
& o
, const as_value
& val
)
368 const double newheight
= pixelsToTwips(toNumber(val
, getVM(*getObject(&o
))));
369 if (newheight
<= 0) {
370 IF_VERBOSE_ASCODING_ERRORS(
371 log_aserror(_("Setting _height=%g of DisplayObject %s (%s)"),
372 newheight
/ 20, o
.getTarget(), typeName(o
));
375 o
.setHeight(newheight
);
379 DisplayObject::setHeight(double newheight
)
381 const SWFRect
& bounds
= getBounds();
383 const double oldheight
= bounds
.height();
384 assert(oldheight
>= 0);
386 const double yscale
= oldheight
? (newheight
/ oldheight
) : 0;
387 const double rotation
= _rotation
* PI
/ 180.0;
389 SWFMatrix m
= getMatrix(*this);
390 const double xscale
= m
.get_x_scale();
391 m
.set_scale_rotation(xscale
, yscale
, rotation
);
396 DisplayObject::setMatrix(const SWFMatrix
& m
, bool updateCache
)
399 if (m
== _transform
.matrix
) return;
401 set_invalidated(__FILE__
, __LINE__
);
402 _transform
.matrix
= m
;
404 // don't update caches if SWFMatrix wasn't updated too
406 _xscale
= _transform
.matrix
.get_x_scale() * 100.0;
407 _yscale
= _transform
.matrix
.get_y_scale() * 100.0;
408 _rotation
= _transform
.matrix
.get_rotation() * 180.0 / PI
;
414 DisplayObject::set_event_handlers(const Events
& copyfrom
)
416 for (Events::const_iterator it
=copyfrom
.begin(), itE
=copyfrom
.end();
419 const event_id
& ev
= it
->first
;
420 const BufferList
& bufs
= it
->second
;
421 for (size_t i
= 0, e
= bufs
.size(); i
< e
; ++i
)
423 const action_buffer
* buf
= bufs
[i
];
425 add_event_handler(ev
, *buf
);
431 DisplayObject::add_event_handler(const event_id
& id
, const action_buffer
& code
)
433 _event_handlers
[id
].push_back(&code
);
435 // todo: drop the DisplayObject as a listener
436 // if it gets no valid handlers for
437 // mouse or Key events.
440 std::auto_ptr
<ExecutableCode
>
441 DisplayObject::get_event_handler(const event_id
& id
) const
443 std::auto_ptr
<ExecutableCode
> handler
;
445 Events::const_iterator it
= _event_handlers
.find(id
);
446 if ( it
== _event_handlers
.end() ) return handler
;
448 DisplayObject
* this_ptr
= const_cast<DisplayObject
*>(this);
450 handler
.reset( new EventCode(this_ptr
, it
->second
) );
455 DisplayObject::unload()
458 const bool childHandler
= unloadChildren();
461 queueEvent(event_id(event_id::UNLOAD
), movie_root::PRIORITY_DOACTION
);
464 // Unregister this DisplayObject as mask and/or maskee.
465 if (_maskee
) _maskee
->setMask(0);
466 if (_mask
) _mask
->setMaskee(0);
468 const bool hasEvent
=
469 hasEventHandler(event_id(event_id::UNLOAD
)) || childHandler
;
472 stage().removeQueuedConstructor(this);
481 DisplayObject::queueEvent(const event_id
& id
, int lvl
)
483 if (!_object
) return;
484 std::auto_ptr
<ExecutableCode
> event(new QueuedEvent(this, id
));
485 stage().pushAction(event
, lvl
);
489 DisplayObject::hasEventHandler(const event_id
& id
) const
491 Events::const_iterator it
= _event_handlers
.find(id
);
492 if (it
!= _event_handlers
.end()) return true;
494 if (!_object
) return false;
496 // Don't check resolve!
497 if (Property
* prop
= _object
->findProperty(id
.functionURI())) {
498 return prop
->getValue(*_object
).to_function();
504 /// Set the real and cached x scale.
506 /// Cached rotation and y scale are not updated.
508 DisplayObject::set_x_scale(double scale_percent
)
510 double xscale
= scale_percent
/ 100.0;
512 if (xscale
!= 0.0 && _xscale
!= 0.0)
514 if (scale_percent
* _xscale
< 0.0)
516 xscale
= -std::abs(xscale
);
518 else xscale
= std::abs(xscale
);
521 _xscale
= scale_percent
;
523 // As per misc-ming.all/SWFMatrix_test.{c,swf}
524 // we don't need to recompute the SWFMatrix from the
527 SWFMatrix m
= getMatrix(*this);
529 m
.set_x_scale(xscale
);
531 setMatrix(m
); // we updated the cache ourselves
533 transformedByScript();
536 /// Set the real and cached rotation.
538 /// Cached scale values are not updated.
540 DisplayObject::set_rotation(double rot
)
542 // Translate to the -180 .. 180 range
543 rot
= std::fmod(rot
, 360.0);
544 if (rot
> 180.0) rot
-= 360.0;
545 else if (rot
< -180.0) rot
+= 360.0;
547 double rotation
= rot
* PI
/ 180.0;
549 if (_xscale
< 0) rotation
+= PI
;
551 SWFMatrix m
= getMatrix(*this);
552 m
.set_rotation(rotation
);
554 // Update the matrix from the cached x scale to avoid accumulating
556 // TODO: also update y scale? The x scale update is needed to keep
557 // TextField correct; no tests for y scale.
558 m
.set_x_scale(std::abs(scaleX() / 100.0));
559 setMatrix(m
); // we update the cache ourselves
563 transformedByScript();
567 /// Set the real and cached y scale.
569 /// Cached rotation and x scale are not updated.
571 DisplayObject::set_y_scale(double scale_percent
)
573 double yscale
= scale_percent
/ 100.0;
575 if (yscale
!= 0.0 && _yscale
!= 0.0)
577 if (scale_percent
* _yscale
< 0.0) yscale
= -std::abs(yscale
);
578 else yscale
= std::abs(yscale
);
581 _yscale
= scale_percent
;
583 SWFMatrix m
= getMatrix(*this);
584 m
.set_y_scale(yscale
);
585 setMatrix(m
); // we updated the cache ourselves
587 transformedByScript();
592 DisplayObject::getTargetPath() const
594 // TODO: check what happens when this DisplayObject
595 // is a Movie loaded into another
598 typedef std::vector
<std::string
> Path
;
601 // Build parents stack
602 const DisplayObject
* topLevel
= 0;
603 const DisplayObject
* ch
= this;
605 string_table
& st
= getStringTable(*getObject(this));
608 const DisplayObject
* parent
= ch
->parent();
610 // Don't push the _root name on the stack
616 path
.push_back(ch
->get_name().toString(st
));
623 if (&stage().getRootMovie() == this) return "/";
624 std::stringstream ss
;
625 ss
<< "_level" << _depth
-DisplayObject::staticDepthOffset
;
629 // Build the target string from the parents stack
631 if (topLevel
!= &stage().getRootMovie()) {
632 std::stringstream ss
;
634 topLevel
->get_depth() - DisplayObject::staticDepthOffset
;
637 for (Path::reverse_iterator it
=path
.rbegin(), itEnd
=path
.rend();
646 DisplayObject::getTarget() const
649 // TODO: check what happens when this DisplayObject
650 // is a Movie loaded into another
653 typedef std::vector
<std::string
> Path
;
656 // Build parents stack
657 const DisplayObject
* ch
= this;
658 string_table
& st
= stage().getVM().getStringTable();
661 const DisplayObject
* parent
= ch
->parent();
663 // Don't push the _root name on the stack
666 std::stringstream ss
;
667 if (!dynamic_cast<const Movie
*>(ch
)) {
668 // must be an as-referenceable
669 // DisplayObject created using 'new'
670 // like, new MovieClip, new Video, new TextField...
671 ss
<< "<no parent, depth" << ch
->get_depth() << ">";
672 path
.push_back(ss
.str());
676 ch
->get_depth() - DisplayObject::staticDepthOffset
;
677 path
.push_back(ss
.str());
682 path
.push_back(ch
->get_name().toString(st
));
686 assert (!path
.empty());
688 // Build the target string from the parents stack
690 for (Path::const_reverse_iterator it
=path
.rbegin(), itEnd
=path
.rend();
693 if (!target
.empty()) target
+= ".";
702 DisplayObject::destroy()
704 // in case we are destroyed without being unloaded first
708 /// we may destory a DisplayObject that's not unloaded.
709 ///(we don't have chance to unload it in current model,
710 /// see new_child_in_unload_test.c)
711 /// We don't destroy ourself twice, right ?
713 if (_object
) _object
->clearProperties();
720 DisplayObject::markReachableResources() const
723 if (_object
) _object
->setReachable();
724 if (_parent
) _parent
->setReachable();
725 if (_mask
) _mask
->setReachable();
726 if (_maskee
) _maskee
->setReachable();
729 /// Whether to use a hand cursor when the mouse is over this DisplayObject
731 /// This depends on the useHandCursor AS property, but:
732 /// 1. Only AS-referenceable objects may use a hand cursor (TODO: check
734 /// 2. Only objects with a release event may use a hand cursor.
735 /// CANNOT CONFIRM THE ABOVE, SEE ButtonEventsTest.swf in misc-ming.all
736 /// 3. The default value (if the property is not defined) is true.
738 DisplayObject::allowHandCursor() const
740 as_object
* obj
= getObject(this);
741 if (!obj
) return false;
743 // Checking for RELEASE breaks ButtonEventsTest.
744 // I guess such an event would influence wheter or not this
745 // character would become an active one, despite hand cursor
746 //if (!hasEventHandler(event_id::RELEASE)) return false;
749 if (!obj
->get_member(NSV::PROP_USEHANDCURSOR
, &val
)) {
752 return toBool(val
, getVM(*obj
));
756 DisplayObject::setMask(DisplayObject
* mask
)
758 if ( _mask
== mask
) return;
762 // Backup this before setMaskee has a chance to change it..
763 DisplayObject
* prevMaskee
= _maskee
;
765 // If we had a previous mask unregister with it
766 if ( _mask
&& _mask
!= mask
)
768 // the mask will call setMask(NULL)
769 // on any previously registered maskee
770 // so we make sure to set our _mask to
771 // NULL before getting called again
775 // if we had a maskee, notify it to stop using
777 if (prevMaskee
) prevMaskee
->setMask(0);
779 // TODO: should we reset any original clip depth
780 // specified by PlaceObject tag ?
781 set_clip_depth(noClipDepthValue
);
786 /// Register as as masked by the mask
787 _mask
->setMaskee(this);
792 DisplayObject::setMaskee(DisplayObject
* maskee
)
794 if ( _maskee
== maskee
) { return; }
797 // We don't want the maskee to call setMaskee(null)
806 // TODO: should we reset any original clip depth
807 // specified by PlaceObject tag ?
808 set_clip_depth(noClipDepthValue
);
814 DisplayObject::boundsInClippingArea(Renderer
& renderer
) const
816 SWFRect mybounds
= getBounds();
817 getWorldMatrix(*this).transform(mybounds
);
819 return renderer
.bounds_in_clipping_area(mybounds
.getRange());
823 DisplayObject::InfoTree::iterator
824 DisplayObject::getMovieInfo(InfoTree
& tr
, InfoTree::iterator it
)
826 const std::string yes
= _("yes");
827 const std::string no
= _("no");
829 it
= tr
.append_child(it
, std::make_pair(getTarget(), typeName(*this)));
831 std::ostringstream os
;
833 tr
.append_child(it
, std::make_pair(_("Depth"), os
.str()));
835 /// Don't add if the DisplayObject has no ratio value
836 if (get_ratio() > 0) {
839 tr
.append_child(it
, std::make_pair(_("Ratio"), os
.str()));
842 /// Don't add if it's not a real clipping depth
843 const int cd
= get_clip_depth();
844 if (cd
!= noClipDepthValue
) {
846 if (_maskee
) os
<< "Dynamic mask";
849 tr
.append_child(it
, std::make_pair(_("Clipping depth"), os
.str()));
853 os
<< getBounds().width() << "x" << getBounds().height();
854 tr
.append_child(it
, std::make_pair(_("Dimensions"), os
.str()));
856 tr
.append_child(it
, std::make_pair(_("Dynamic"), isDynamic() ? yes
: no
));
857 tr
.append_child(it
, std::make_pair(_("Mask"), isMaskLayer() ? yes
: no
));
858 tr
.append_child(it
, std::make_pair(_("Destroyed"),
859 isDestroyed() ? yes
: no
));
860 tr
.append_child(it
, std::make_pair(_("Unloaded"), unloaded() ? yes
: no
));
864 tr
.append_child(it
, std::make_pair(_("Blend mode"), os
.str()));
866 // This probably isn't interesting for non-developers
867 tr
.append_child(it
, std::make_pair(_("Invalidated"),
868 _invalidated
? yes
: no
));
869 tr
.append_child(it
, std::make_pair(_("Child invalidated"),
870 _child_invalidated
? yes
: no
));
877 DisplayObject::getAsRoot()
883 setIndexedProperty(size_t index
, DisplayObject
& o
, const as_value
& val
)
885 const Setter s
= getGetterSetterByIndex(index
).second
;
886 if (!s
) return; // read-only (warn?)
888 if (val
.is_undefined() || val
.is_null()) {
889 IF_VERBOSE_ASCODING_ERRORS(
890 log_aserror(_("Attempt to set property to %s, refused"),
900 getIndexedProperty(size_t index
, DisplayObject
& o
, as_value
& val
)
902 const Getter s
= getGetterSetterByIndex(index
).first
;
911 /// DisplayObject property lookup
913 /// This function is only called on the first object in the inheritance chain
914 /// after the object's own properties have been checked.
915 /// In AS2, any DisplayObject marks the end of the inheritance chain for
920 /// 1. _level0.._level9
921 /// 2. Objects on the DisplayList of a MovieClip
922 /// 3. DisplayObject magic properties (_x, _y etc).
923 /// 4. MovieClips' TextField variables (this is probably not the best
924 /// way to do it, but as it is done like this, this must be called here.
925 /// It will cause an infinite recursion otherwise.
927 getDisplayObjectProperty(DisplayObject
& obj
, const ObjectURI
& uri
,
931 as_object
* o
= getObject(&obj
);
934 string_table
& st
= getStringTable(*o
);
935 const std::string
& propname
= uri
.toString(st
);
937 // Check _level0.._level9
938 unsigned int levelno
;
939 if (isLevelTarget(getSWFVersion(*o
), propname
, levelno
)) {
940 movie_root
& mr
= getRoot(*getObject(&obj
));
941 MovieClip
* mo
= mr
.getLevel(levelno
);
949 MovieClip
* mc
= obj
.to_movie();
951 DisplayObject
* ch
= mc
->getDisplayListObject(uri
);
958 const string_table::key noCaseKey
= uri
.noCase(st
);
960 // These properties have normal case-sensitivity.
961 // They are tested to exist for TextField, MovieClip, and Button
962 // but do not belong to the inheritance chain.
963 switch (caseless(*o
) ? noCaseKey
: getName(uri
))
967 case NSV::PROP_uROOT
:
968 if (getSWFVersion(*o
) < 5) break;
969 val
= getObject(obj
.getAsRoot());
971 case NSV::PROP_uGLOBAL
:
972 // TODO: clean up this mess.
973 assert(getObject(&obj
));
974 if (getSWFVersion(*o
) < 6) break;
975 val
= &getGlobal(*o
);
979 // These magic properties are case insensitive in all versions!
980 if (doGet(uri
, obj
, val
)) return true;
982 // Check MovieClip such as TextField variables.
983 // TODO: check if there's a better way to find these properties.
984 if (mc
&& mc
->getTextFieldVariables(uri
, val
)) return true;
991 setDisplayObjectProperty(DisplayObject
& obj
, const ObjectURI
& uri
,
994 // These magic properties are case insensitive in all versions!
995 return doSet(uri
, obj
, val
);
998 DisplayObject::MaskRenderer::MaskRenderer(Renderer
& r
, const DisplayObject
& o
)
1001 _mask(o
.visible() && o
.getMask() && !o
.getMask()->unloaded() ? o
.getMask()
1006 _renderer
.begin_submit_mask();
1007 DisplayObject
* p
= _mask
->parent();
1008 const Transform tr
= p
?
1009 Transform(getWorldMatrix(*p
), getWorldCxForm(*p
)) : Transform();
1010 _mask
->display(_renderer
, tr
);
1011 _renderer
.end_submit_mask();
1014 DisplayObject::MaskRenderer::~MaskRenderer()
1016 if (_mask
) _renderer
.disable_mask();
1022 getQuality(DisplayObject
& o
)
1024 movie_root
& mr
= getRoot(*getObject(&o
));
1025 switch (mr
.getQuality())
1028 return as_value("BEST");
1030 return as_value("HIGH");
1031 case QUALITY_MEDIUM
:
1032 return as_value("MEDIUM");
1034 return as_value("LOW");
1042 setQuality(DisplayObject
& o
, const as_value
& val
)
1044 movie_root
& mr
= getRoot(*getObject(&o
));
1046 if (!val
.is_string()) return;
1048 const std::string
& q
= val
.to_string();
1050 StringNoCaseEqual noCaseCompare
;
1052 if (noCaseCompare(q
, "BEST")) mr
.setQuality(QUALITY_BEST
);
1053 else if (noCaseCompare(q
, "HIGH")) {
1054 mr
.setQuality(QUALITY_HIGH
);
1056 else if (noCaseCompare(q
, "MEDIUM")) {
1057 mr
.setQuality(QUALITY_MEDIUM
);
1059 else if (noCaseCompare(q
, "LOW")) {
1060 mr
.setQuality(QUALITY_LOW
);
1067 getURL(DisplayObject
& o
)
1069 return as_value(o
.get_root()->url());
1073 getHighQuality(DisplayObject
& o
)
1075 movie_root
& mr
= getRoot(*getObject(&o
));
1076 switch (mr
.getQuality())
1079 return as_value(2.0);
1081 return as_value(1.0);
1082 case QUALITY_MEDIUM
:
1084 return as_value(0.0);
1090 setHighQuality(DisplayObject
& o
, const as_value
& val
)
1092 movie_root
& mr
= getRoot(*getObject(&o
));
1094 const double q
= toNumber(val
, getVM(*getObject(&o
)));
1096 if (q
< 0) mr
.setQuality(QUALITY_HIGH
);
1097 else if (q
> 2) mr
.setQuality(QUALITY_BEST
);
1099 int i
= static_cast<int>(q
);
1103 mr
.setQuality(QUALITY_LOW
);
1106 mr
.setQuality(QUALITY_HIGH
);
1109 mr
.setQuality(QUALITY_BEST
);
1117 setY(DisplayObject
& o
, const as_value
& val
)
1120 const double newy
= toNumber(val
, getVM(*getObject(&o
)));
1122 // NaN is skipped, Infinite isn't
1125 IF_VERBOSE_ASCODING_ERRORS(
1126 log_aserror(_("Attempt to set %s._y to %s "
1127 "(evaluating to number %g) refused"),
1128 o
.getTarget(), val
, newy
);
1133 SWFMatrix m
= getMatrix(o
);
1134 // NOTE: infinite_to_zero is wrong here, see actionscript.all/setProperty.as
1135 m
.set_y_translation(pixelsToTwips(infinite_to_zero(newy
)));
1137 o
.transformedByScript();
1141 getY(DisplayObject
& o
)
1143 const SWFMatrix m
= getMatrix(o
);
1144 return twipsToPixels(m
.get_y_translation());
1148 setX(DisplayObject
& o
, const as_value
& val
)
1151 const double newx
= toNumber(val
, getVM(*getObject(&o
)));
1153 // NaN is skipped, Infinite isn't
1156 IF_VERBOSE_ASCODING_ERRORS(
1157 log_aserror(_("Attempt to set %s._x to %s "
1158 "(evaluating to number %g) refused"),
1159 o
.getTarget(), val
, newx
);
1164 SWFMatrix m
= getMatrix(o
);
1165 // NOTE: infinite_to_zero is wrong here, see actionscript.all/setProperty.as
1166 m
.set_x_translation(pixelsToTwips(infinite_to_zero(newx
)));
1168 o
.transformedByScript();
1172 getX(DisplayObject
& o
)
1174 const SWFMatrix m
= getMatrix(o
);
1175 return twipsToPixels(m
.get_x_translation());
1179 setScaleX(DisplayObject
& o
, const as_value
& val
)
1182 const double scale_percent
= toNumber(val
, getVM(*getObject(&o
)));
1184 // NaN is skipped, Infinite is not, see actionscript.all/setProperty.as
1185 if (isNaN(scale_percent
)) {
1186 IF_VERBOSE_ASCODING_ERRORS(
1187 log_aserror(_("Attempt to set %s._xscale to %s "
1188 "(evaluating to number %g) refused"),
1189 o
.getTarget(), val
, scale_percent
);
1194 // input is in percent
1195 o
.set_x_scale(scale_percent
);
1200 getScaleX(DisplayObject
& o
)
1206 setScaleY(DisplayObject
& o
, const as_value
& val
)
1209 const double scale_percent
= toNumber(val
, getVM(*getObject(&o
)));
1211 // NaN is skipped, Infinite is not, see actionscript.all/setProperty.as
1212 if (isNaN(scale_percent
)) {
1213 IF_VERBOSE_ASCODING_ERRORS(
1214 log_aserror(_("Attempt to set %s._yscale to %s "
1215 "(evaluating to number %g) refused"),
1216 o
.getTarget(), val
, scale_percent
);
1221 // input is in percent
1222 o
.set_y_scale(scale_percent
);
1227 getScaleY(DisplayObject
& o
)
1233 getVisible(DisplayObject
& o
)
1239 setVisible(DisplayObject
& o
, const as_value
& val
)
1242 /// We cast to number and rely (mostly) on C++'s automatic
1243 /// cast to bool, as string "0" should be converted to
1244 /// its numeric equivalent, not interpreted as 'true', which
1245 /// SWF7+ does for strings.
1246 const double d
= toNumber(val
, getVM(*getObject(&o
)));
1248 // Infinite or NaN is skipped
1249 if (isInf(d
) || isNaN(d
)) {
1250 IF_VERBOSE_ASCODING_ERRORS(
1251 log_aserror(_("Attempt to set %s._visible to %s "
1252 "(evaluating to number %g) refused"),
1253 o
.getTarget(), val
, d
);
1260 o
.transformedByScript();
1264 getAlpha(DisplayObject
& o
)
1266 return as_value(getCxForm(o
).aa
/ 2.56);
1270 setAlpha(DisplayObject
& o
, const as_value
& val
)
1273 // The new internal alpha value is input / 100.0 * 256.
1274 // We test for finiteness later, but the multiplication
1275 // won't make any difference.
1276 const double newAlpha
= toNumber(val
, getVM(*getObject(&o
))) * 2.56;
1278 // NaN is skipped, Infinite is not, see actionscript.all/setProperty.as
1279 if (isNaN(newAlpha
)) {
1280 IF_VERBOSE_ASCODING_ERRORS(
1281 log_aserror(_("Attempt to set %s._alpha to %s "
1282 "(evaluating to number %g) refused"),
1283 o
.getTarget(), val
, newAlpha
);
1288 SWFCxForm cx
= getCxForm(o
);
1290 // Overflows are *not* truncated, but set to -32768.
1291 if (newAlpha
> std::numeric_limits
<boost::int16_t>::max() ||
1292 newAlpha
< std::numeric_limits
<boost::int16_t>::min()) {
1293 cx
.aa
= std::numeric_limits
<boost::int16_t>::min();
1296 cx
.aa
= static_cast<boost::int16_t>(newAlpha
);
1300 o
.transformedByScript();
1305 getMouseX(DisplayObject
& o
)
1307 // Local coord of mouse IN PIXELS.
1308 boost::int32_t x
, y
;
1309 boost::tie(x
, y
) = getRoot(*getObject(&o
)).mousePosition();
1311 SWFMatrix m
= getWorldMatrix(o
);
1312 point
a(pixelsToTwips(x
), pixelsToTwips(y
));
1314 m
.invert().transform(a
);
1315 return as_value(twipsToPixels(a
.x
));
1319 getMouseY(DisplayObject
& o
)
1321 // Local coord of mouse IN PIXELS.
1322 boost::int32_t x
, y
;
1323 boost::tie(x
, y
) = getRoot(*getObject(&o
)).mousePosition();
1325 SWFMatrix m
= getWorldMatrix(o
);
1326 point
a(pixelsToTwips(x
), pixelsToTwips(y
));
1327 m
.invert().transform(a
);
1328 return as_value(twipsToPixels(a
.y
));
1332 getRotation(DisplayObject
& o
)
1334 return o
.rotation();
1339 setRotation(DisplayObject
& o
, const as_value
& val
)
1342 // input is in degrees
1343 const double rotation_val
= toNumber(val
, getVM(*getObject(&o
)));
1345 // NaN is skipped, Infinity isn't
1346 if (isNaN(rotation_val
)) {
1347 IF_VERBOSE_ASCODING_ERRORS(
1348 log_aserror(_("Attempt to set %s._rotation to %s "
1349 "(evaluating to number %g) refused"),
1350 o
.getTarget(), val
, rotation_val
);
1354 o
.set_rotation(rotation_val
);
1359 getParent(DisplayObject
& o
)
1361 as_object
* p
= getObject(o
.parent());
1362 return p
? p
: as_value();
1366 getTarget(DisplayObject
& o
)
1368 return o
.getTargetPath();
1372 getNameProperty(DisplayObject
& o
)
1374 string_table
& st
= getStringTable(*getObject(&o
));
1375 const std::string
& name
= o
.get_name().toString(st
);
1376 if (getSWFVersion(*getObject(&o
)) < 6 && name
.empty()) return as_value();
1377 return as_value(name
);
1381 setName(DisplayObject
& o
, const as_value
& val
)
1383 o
.set_name(getURI(getVM(*getObject(&o
)), val
.to_string()));
1387 setSoundBufTime(DisplayObject
& /*o*/, const as_value
& /*val*/)
1389 LOG_ONCE(log_unimpl("_soundbuftime setting"));
1393 getSoundBufTime(DisplayObject
& /*o*/)
1395 return as_value(0.0);
1399 getWidth(DisplayObject
& o
)
1401 SWFRect bounds
= o
.getBounds();
1402 const SWFMatrix
& m
= getMatrix(o
);
1403 m
.transform(bounds
);
1404 return twipsToPixels(bounds
.width());
1408 setWidth(DisplayObject
& o
, const as_value
& val
)
1410 const double newwidth
= pixelsToTwips(toNumber(val
, getVM(*getObject(&o
))));
1411 if (newwidth
<= 0) {
1412 IF_VERBOSE_ASCODING_ERRORS(
1413 log_aserror(_("Setting _width=%g of DisplayObject %s (%s)"),
1414 newwidth
/20, o
.getTarget(), typeName(o
));
1417 o
.setWidth(newwidth
);
1421 getFocusRect(DisplayObject
& /*o*/)
1423 LOG_ONCE(log_unimpl("_focusrect"));
1424 return as_value(true);
1428 setFocusRect(DisplayObject
& /*o*/, const as_value
& /*val*/)
1430 LOG_ONCE(log_unimpl("_focusrect setting"));
1434 getDropTarget(DisplayObject
& o
)
1436 // This property only applies to MovieClips.
1437 MovieClip
* mc
= o
.to_movie();
1438 if (!mc
) return as_value();
1439 return as_value(mc
->getDropTarget());
1443 getCurrentFrame(DisplayObject
& o
)
1445 // This property only applies to MovieClips.
1446 MovieClip
* mc
= o
.to_movie();
1447 if (!mc
) return as_value();
1448 const int currframe
=
1449 std::min(mc
->get_loaded_frames(), mc
->get_current_frame() + 1);
1450 return as_value(currframe
);
1454 getFramesLoaded(DisplayObject
& o
)
1456 // This property only applies to MovieClips.
1457 MovieClip
* mc
= o
.to_movie();
1458 if (!mc
) return as_value();
1459 return as_value(mc
->get_loaded_frames());
1463 getTotalFrames(DisplayObject
& o
)
1465 // This property only applies to MovieClips.
1466 MovieClip
* mc
= o
.to_movie();
1467 if (!mc
) return as_value();
1468 return as_value(mc
->get_frame_count());
1472 /// @param uri The property to search for. Note that all special
1473 /// properties are lower-case.
1475 /// NOTE that all properties have getters so you can recognize a
1476 /// 'not-found' condition by checking .first = 0
1478 getGetterSetterByURI(const ObjectURI
& uri
, string_table
& st
)
1480 typedef std::map
<ObjectURI
, GetterSetter
, ObjectURI::CaseLessThan
>
1483 static const GetterSetters gs
=
1484 getURIMap
<GetterSetters
>(ObjectURI::CaseLessThan(st
, true));
1486 const GetterSetters::const_iterator it
= gs
.find(uri
);
1488 if (it
== gs
.end()) {
1489 static const GetterSetter
none(0, 0);
1498 getGetterSetterByIndex(size_t index
)
1502 static const GetterSetter props
[] = {
1503 GetterSetter(&getX
, &setX
),
1504 GetterSetter(&getY
, &setY
),
1505 GetterSetter(&getScaleX
, &setScaleX
),
1506 GetterSetter(&getScaleY
, &setScaleY
),
1508 GetterSetter(&getCurrentFrame
, n
),
1509 GetterSetter(&getTotalFrames
, n
),
1510 GetterSetter(&getAlpha
, &setAlpha
),
1511 GetterSetter(&getVisible
, &setVisible
),
1513 GetterSetter(&getWidth
, &setWidth
),
1514 GetterSetter(&getHeight
, &setHeight
),
1515 GetterSetter(&getRotation
, &setRotation
),
1516 GetterSetter(&getTarget
, n
),
1518 GetterSetter(&getFramesLoaded
, n
),
1519 GetterSetter(&getNameProperty
, &setName
),
1520 GetterSetter(&getDropTarget
, n
),
1521 GetterSetter(&getURL
, n
),
1523 GetterSetter(&getHighQuality
, &setHighQuality
),
1524 GetterSetter(&getFocusRect
, &setFocusRect
),
1525 GetterSetter(&getSoundBufTime
, &setSoundBufTime
),
1526 GetterSetter(&getQuality
, &setQuality
),
1528 GetterSetter(&getMouseX
, n
),
1529 GetterSetter(&getMouseY
, n
)
1533 if (index
>= arraySize(props
)) {
1534 const Getter ng
= 0;
1535 static const GetterSetter
none(ng
, n
);
1539 return props
[index
];
1544 doGet(const ObjectURI
& uri
, DisplayObject
& o
, as_value
& val
)
1546 string_table
& st
= getStringTable(*getObject(&o
));
1547 const Getter s
= getGetterSetterByURI(uri
, st
).first
;
1548 if (!s
) return false;
1555 /// Do the actual setProperty
1557 /// Return true if the property is a DisplayObject property, regardless of
1558 /// whether it was successfully set or not.
1560 /// @param uri The property to search for. Note that all special
1561 /// properties are lower-case, so for a caseless check
1562 /// it is sufficient for prop to be caseless.
1564 doSet(const ObjectURI
& uri
, DisplayObject
& o
, const as_value
& val
)
1566 string_table
& st
= getStringTable(*getObject(&o
));
1568 const GetterSetter gs
= getGetterSetterByURI(uri
, st
);
1570 // not found (all props have getters)
1571 if (!gs
.first
) return false;
1573 const Setter s
= gs
.second
;
1575 // read-only (TODO: aserror ?)
1576 if (!s
) return true;
1578 if (val
.is_undefined() || val
.is_null()) {
1579 IF_VERBOSE_ASCODING_ERRORS(
1580 // TODO: add property name to this log...
1581 log_aserror(_("Attempt to set property to %s, refused"),
1582 o
.getTarget(), val
);
1595 /// BLENDMODE_UNDEFINED has no matching string in AS. It is included
1596 /// here for logging purposes.
1597 static const BlendModeMap bm
= boost::assign::map_list_of
1598 (DisplayObject::BLENDMODE_UNDEFINED
, "undefined")
1599 (DisplayObject::BLENDMODE_NORMAL
, "normal")
1600 (DisplayObject::BLENDMODE_LAYER
, "layer")
1601 (DisplayObject::BLENDMODE_MULTIPLY
, "multiply")
1602 (DisplayObject::BLENDMODE_SCREEN
, "screen")
1603 (DisplayObject::BLENDMODE_LIGHTEN
, "lighten")
1604 (DisplayObject::BLENDMODE_DARKEN
, "darken")
1605 (DisplayObject::BLENDMODE_DIFFERENCE
, "difference")
1606 (DisplayObject::BLENDMODE_ADD
, "add")
1607 (DisplayObject::BLENDMODE_SUBTRACT
, "subtract")
1608 (DisplayObject::BLENDMODE_INVERT
, "invert")
1609 (DisplayObject::BLENDMODE_ALPHA
, "alpha")
1610 (DisplayObject::BLENDMODE_ERASE
, "erase")
1611 (DisplayObject::BLENDMODE_OVERLAY
, "overlay")
1612 (DisplayObject::BLENDMODE_HARDLIGHT
, "hardlight");
1618 // Match a blend mode to its string.
1620 blendModeMatches(const BlendModeMap::value_type
& val
, const std::string
& mode
)
1622 /// The match must be case-sensitive.
1623 if (mode
.empty()) return false;
1624 return (val
.second
== mode
);
1627 /// Return a const map of property URI to function.
1629 /// This function takes advantage of NRVO to allow the map to
1630 /// be constructed in the caller.
1631 template<typename Map
>
1633 getURIMap(const typename
Map::key_compare
& cmp
)
1638 ret
.insert(std::make_pair(NSV::PROP_uX
, GetterSetter(&getX
, &setX
)));
1639 ret
.insert(std::make_pair(NSV::PROP_uY
, GetterSetter(&getY
, &setY
)));
1640 ret
.insert(std::make_pair(NSV::PROP_uXSCALE
,
1641 GetterSetter(&getScaleX
, &setScaleX
)));
1642 ret
.insert(std::make_pair(NSV::PROP_uYSCALE
,
1643 GetterSetter(&getScaleY
, &setScaleY
)));
1644 ret
.insert(std::make_pair(NSV::PROP_uROTATION
,
1645 GetterSetter(&getRotation
, &setRotation
)));
1646 ret
.insert(std::make_pair(NSV::PROP_uHIGHQUALITY
,
1647 GetterSetter(&getHighQuality
, &setHighQuality
)));
1648 ret
.insert(std::make_pair(NSV::PROP_uQUALITY
,
1649 GetterSetter(&getQuality
, &setQuality
)));
1650 ret
.insert(std::make_pair(NSV::PROP_uALPHA
,
1651 GetterSetter(&getAlpha
, &setAlpha
)));
1652 ret
.insert(std::make_pair(NSV::PROP_uWIDTH
,
1653 GetterSetter(&getWidth
, &setWidth
)));
1654 ret
.insert(std::make_pair(NSV::PROP_uHEIGHT
,
1655 GetterSetter(&getHeight
, &setHeight
)));
1656 ret
.insert(std::make_pair(NSV::PROP_uNAME
,
1657 GetterSetter(&getNameProperty
, &setName
)));
1658 ret
.insert(std::make_pair(NSV::PROP_uVISIBLE
,
1659 GetterSetter(&getVisible
, &setVisible
)));
1660 ret
.insert(std::make_pair(NSV::PROP_uSOUNDBUFTIME
,
1661 GetterSetter(&getSoundBufTime
, &setSoundBufTime
)));
1662 ret
.insert(std::make_pair(NSV::PROP_uFOCUSRECT
,
1663 GetterSetter(&getFocusRect
, &setFocusRect
)));
1664 ret
.insert(std::make_pair(NSV::PROP_uDROPTARGET
,
1665 GetterSetter(&getDropTarget
, n
)));
1666 ret
.insert(std::make_pair(NSV::PROP_uCURRENTFRAME
,
1667 GetterSetter(&getCurrentFrame
, n
)));
1668 ret
.insert(std::make_pair(NSV::PROP_uFRAMESLOADED
,
1669 GetterSetter(&getFramesLoaded
, n
)));
1670 ret
.insert(std::make_pair(NSV::PROP_uTOTALFRAMES
,
1671 GetterSetter(&getTotalFrames
, n
)));
1672 ret
.insert(std::make_pair(NSV::PROP_uURL
, GetterSetter(&getURL
, n
)));
1673 ret
.insert(std::make_pair(NSV::PROP_uTARGET
, GetterSetter(&getTarget
, n
)));
1674 ret
.insert(std::make_pair(NSV::PROP_uXMOUSE
, GetterSetter(&getMouseX
, n
)));
1675 ret
.insert(std::make_pair(NSV::PROP_uYMOUSE
, GetterSetter(&getMouseY
, n
)));
1676 ret
.insert(std::make_pair(NSV::PROP_uPARENT
, GetterSetter(&getParent
, n
)));
1680 } // anonymous namespace
1683 operator<<(std::ostream
& o
, DisplayObject::BlendMode bm
)
1685 const BlendModeMap
& bmm
= getBlendModeMap();
1686 return (o
<< bmm
.find(bm
)->second
);
1689 } // namespace gnash
1693 // indent-tabs-mode: t