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"
27 #include <boost/algorithm/string/case_conv.hpp>
28 #include <boost/assign/list_of.hpp>
29 #include <boost/bind.hpp>
32 #include "smart_ptr.h" // GNASH_USE_GC
33 #include "movie_root.h"
34 #include "MovieClip.h"
37 #include "GnashException.h"
38 #include "ExecutableCode.h"
39 #include "namedStrings.h"
40 #include "GnashEnums.h"
41 #include "GnashNumeric.h"
42 #include "Global_as.h"
44 #include "GnashAlgorithm.h"
49 #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
)
100 m_clip_depth(noClipDepthValue
),
103 _blendMode(BLENDMODE_NORMAL
),
105 _scriptTransformed(false),
106 _dynamicallyCreated(false),
110 _child_invalidated(true)
112 assert(m_old_invalidated_ranges
.isNull());
114 // This informs the core that the object is a DisplayObject.
115 if (_object
) _object
->setDisplayObject(this);
119 DisplayObject::getLoadedMovie(Movie
* extern_movie
)
122 log_unimpl("loadMovie against a %s DisplayObject", typeName(*this))
125 // TODO: look at the MovieClip implementation, but most importantly
126 // test all the event handlers copies etc..
128 UNUSED(extern_movie
);
132 DisplayObject::getNextUnnamedInstanceName()
135 movie_root
& mr
= getRoot(*_object
);
136 std::ostringstream ss
;
137 ss
<< "instance" << mr
.nextUnnamedInstance();
139 VM
& vm
= getVM(*_object
);
140 return getURI(vm
, ss
.str(), true);
145 DisplayObject::getWorldVolume() const
150 volume
= int(volume
*_parent
->getVolume()/100.0);
158 DisplayObject::pathElement(const ObjectURI
& uri
)
160 as_object
* obj
= getObject(this);
163 string_table::key key
= getName(uri
);
165 string_table
& st
= stage().getVM().getStringTable();
167 // TODO: put ".." and "." in namedStrings
168 if (key
== st
.find("..")) return getObject(parent());
169 if (key
== st
.find(".")) return obj
;
171 // The check is case-insensitive for SWF6 and below.
172 // TODO: cache ObjectURI(NSV::PROP_THIS) [as many others...]
173 if (ObjectURI::CaseEquals(st
, caseless(*obj
))
174 (uri
, ObjectURI(NSV::PROP_THIS
))) {
181 DisplayObject::set_invalidated()
183 set_invalidated("unknown", -1);
187 DisplayObject::set_invalidated(const char* debug_file
, int debug_line
)
189 // Set the invalidated-flag of the parent. Note this does not mean that
190 // the parent must re-draw itself, it just means that one of it's childs
191 // needs to be re-drawn.
192 if ( _parent
) _parent
->set_child_invalidated();
194 // Ok, at this point the instance will change it's
195 // visual aspect after the
196 // call to set_invalidated(). We save the *current*
197 // position of the instance because this region must
198 // be updated even (or first of all) if the DisplayObject
199 // moves away from here.
201 if ( ! _invalidated
)
205 #ifdef DEBUG_SET_INVALIDATED
206 log_debug("%p set_invalidated() of %s in %s:%d",
207 (void*)this, get_name(), debug_file
, debug_line
);
213 // NOTE: the SnappingRanges instance used here is not initialized by the
214 // GUI and therefore uses the default settings. This should not be a
215 // problem but special snapping ranges configuration done in gui.cpp
216 // is ignored here...
218 m_old_invalidated_ranges
.setNull();
219 add_invalidated_bounds(m_old_invalidated_ranges
, true);
225 DisplayObject::add_invalidated_bounds(InvalidatedRanges
& ranges
, bool force
)
227 ranges
.add(m_old_invalidated_ranges
);
228 if (visible() && (_invalidated
||force
))
231 bounds
.expand_to_transformed_rect(getWorldMatrix(*this), getBounds());
232 ranges
.add(bounds
.getRange());
237 DisplayObject::set_child_invalidated()
239 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 boost::bind(blendModeMatches
, _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
= getRoot(*_object
);
332 if (mr
.getFocus() == this) {
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 //log_debug("setting SWFMatrix to: %s", m);
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
;
449 assert(get_ref_count() > 0);
450 #endif // GNASH_USE_GC
451 DisplayObject
* this_ptr
= const_cast<DisplayObject
*>(this);
453 handler
.reset( new EventCode(this_ptr
, it
->second
) );
458 DisplayObject::unload()
461 const bool childHandler
= unloadChildren();
464 queueEvent(event_id::UNLOAD
, movie_root::PRIORITY_DOACTION
);
467 // Unregister this DisplayObject as mask and/or maskee.
468 if (_maskee
) _maskee
->setMask(0);
469 if (_mask
) _mask
->setMaskee(0);
471 const bool hasEvent
= hasEventHandler(event_id::UNLOAD
) || childHandler
;
474 stage().removeQueuedConstructor(this);
483 DisplayObject::queueEvent(const event_id
& id
, int lvl
)
485 if (!_object
) return;
486 std::auto_ptr
<ExecutableCode
> event(new QueuedEvent(this, id
));
487 stage().pushAction(event
, lvl
);
491 DisplayObject::hasEventHandler(const event_id
& id
) const
493 Events::const_iterator it
= _event_handlers
.find(id
);
494 if (it
!= _event_handlers
.end()) return true;
496 if (!_object
) return false;
498 // Don't check resolve!
499 if (Property
* prop
= _object
->findProperty(id
.functionURI())) {
500 return prop
->getValue(*_object
).to_function();
506 /// Set the real and cached x scale.
508 /// Cached rotation and y scale are not updated.
510 DisplayObject::set_x_scale(double scale_percent
)
512 double xscale
= scale_percent
/ 100.0;
514 if (xscale
!= 0.0 && _xscale
!= 0.0)
516 if (scale_percent
* _xscale
< 0.0)
518 xscale
= -std::abs(xscale
);
520 else xscale
= std::abs(xscale
);
523 _xscale
= scale_percent
;
525 // As per misc-ming.all/SWFMatrix_test.{c,swf}
526 // we don't need to recompute the SWFMatrix from the
529 SWFMatrix m
= getMatrix(*this);
531 m
.set_x_scale(xscale
);
533 setMatrix(m
); // we updated the cache ourselves
535 transformedByScript();
538 /// Set the real and cached rotation.
540 /// Cached scale values are not updated.
542 DisplayObject::set_rotation(double rot
)
544 // Translate to the -180 .. 180 range
545 rot
= std::fmod(rot
, 360.0);
546 if (rot
> 180.0) rot
-= 360.0;
547 else if (rot
< -180.0) rot
+= 360.0;
549 double rotation
= rot
* PI
/ 180.0;
551 if (_xscale
< 0) rotation
+= PI
;
553 SWFMatrix m
= getMatrix(*this);
554 m
.set_rotation(rotation
);
556 // Update the matrix from the cached x scale to avoid accumulating
558 // TODO: also update y scale? The x scale update is needed to keep
559 // TextField correct; no tests for y scale.
560 m
.set_x_scale(std::abs(scaleX() / 100.0));
561 setMatrix(m
); // we update the cache ourselves
565 transformedByScript();
569 /// Set the real and cached y scale.
571 /// Cached rotation and x scale are not updated.
573 DisplayObject::set_y_scale(double scale_percent
)
575 double yscale
= scale_percent
/ 100.0;
577 if (yscale
!= 0.0 && _yscale
!= 0.0)
579 if (scale_percent
* _yscale
< 0.0) yscale
= -std::abs(yscale
);
580 else yscale
= std::abs(yscale
);
583 _yscale
= scale_percent
;
585 SWFMatrix m
= getMatrix(*this);
586 m
.set_y_scale(yscale
);
587 setMatrix(m
); // we updated the cache ourselves
589 transformedByScript();
594 DisplayObject::getTargetPath() const
596 // TODO: check what happens when this DisplayObject
597 // is a Movie loaded into another
600 typedef std::vector
<std::string
> Path
;
603 // Build parents stack
604 const DisplayObject
* topLevel
= 0;
605 const DisplayObject
* ch
= this;
607 string_table
& st
= getStringTable(*getObject(this));
610 const DisplayObject
* parent
= ch
->parent();
612 // Don't push the _root name on the stack
618 path
.push_back(ch
->get_name().toString(st
));
625 if (&stage().getRootMovie() == this) return "/";
626 std::stringstream ss
;
627 ss
<< "_level" << _depth
-DisplayObject::staticDepthOffset
;
631 // Build the target string from the parents stack
633 if (topLevel
!= &stage().getRootMovie()) {
634 std::stringstream ss
;
636 topLevel
->get_depth() - DisplayObject::staticDepthOffset
;
639 for (Path::reverse_iterator it
=path
.rbegin(), itEnd
=path
.rend();
648 DisplayObject::getTarget() const
651 // TODO: check what happens when this DisplayObject
652 // is a Movie loaded into another
655 typedef std::vector
<std::string
> Path
;
658 // Build parents stack
659 const DisplayObject
* ch
= this;
660 string_table
& st
= stage().getVM().getStringTable();
663 const DisplayObject
* parent
= ch
->parent();
665 // Don't push the _root name on the stack
668 std::stringstream ss
;
669 if (!dynamic_cast<const Movie
*>(ch
)) {
670 // must be an as-referenceable
671 // DisplayObject created using 'new'
672 // like, new MovieClip, new Video, new TextField...
673 //log_debug("DisplayObject %p (%s) doesn't have a parent and "
674 // "is not a Movie", ch, typeName(*ch));
675 ss
<< "<no parent, depth" << ch
->get_depth() << ">";
676 path
.push_back(ss
.str());
680 ch
->get_depth() - DisplayObject::staticDepthOffset
;
681 path
.push_back(ss
.str());
686 path
.push_back(ch
->get_name().toString(st
));
690 assert (!path
.empty());
692 // Build the target string from the parents stack
694 for (Path::const_reverse_iterator it
=path
.rbegin(), itEnd
=path
.rend();
697 if (!target
.empty()) target
+= ".";
706 DisplayObject::destroy()
708 // in case we are destroyed without being unloaded first
712 /// we may destory a DisplayObject that's not unloaded.
713 ///(we don't have chance to unload it in current model,
714 /// see new_child_in_unload_test.c)
715 /// We don't destroy ourself twice, right ?
717 if (_object
) _object
->clearProperties();
724 DisplayObject::markReachableResources() const
727 if (_object
) _object
->setReachable();
728 if (_parent
) _parent
->setReachable();
729 if (_mask
) _mask
->setReachable();
730 if (_maskee
) _maskee
->setReachable();
733 /// Whether to use a hand cursor when the mouse is over this DisplayObject
735 /// This depends on the useHandCursor AS property, but:
736 /// 1. Only AS-referenceable objects may use a hand cursor (TODO: check
738 /// 2. Only objects with a release event may use a hand cursor.
739 /// CANNOT CONFIRM THE ABOVE, SEE ButtonEventsTest.swf in misc-ming.all
740 /// 3. The default value (if the property is not defined) is true.
742 DisplayObject::allowHandCursor() const
744 as_object
* obj
= getObject(this);
745 if (!obj
) return false;
747 // Checking for RELEASE breaks ButtonEventsTest.
748 // I guess such an event would influence wheter or not this
749 // character would become an active one, despite hand cursor
750 //if (!hasEventHandler(event_id::RELEASE)) return false;
753 if (!obj
->get_member(NSV::PROP_USEHANDCURSOR
, &val
)) {
756 return toBool(val
, getVM(*obj
));
760 DisplayObject::setMask(DisplayObject
* mask
)
762 if ( _mask
== mask
) return;
766 // Backup this before setMaskee has a chance to change it..
767 DisplayObject
* prevMaskee
= _maskee
;
769 // If we had a previous mask unregister with it
770 if ( _mask
&& _mask
!= mask
)
772 // the mask will call setMask(NULL)
773 // on any previously registered maskee
774 // so we make sure to set our _mask to
775 // NULL before getting called again
779 // if we had a maskee, notify it to stop using
781 if (prevMaskee
) prevMaskee
->setMask(0);
783 // TODO: should we reset any original clip depth
784 // specified by PlaceObject tag ?
785 set_clip_depth(noClipDepthValue
);
790 /// Register as as masked by the mask
791 _mask
->setMaskee(this);
796 DisplayObject::setMaskee(DisplayObject
* maskee
)
798 if ( _maskee
== maskee
) { return; }
801 // We don't want the maskee to call setMaskee(null)
810 // TODO: should we reset any original clip depth
811 // specified by PlaceObject tag ?
812 set_clip_depth(noClipDepthValue
);
818 DisplayObject::boundsInClippingArea(Renderer
& renderer
) const
820 SWFRect mybounds
= getBounds();
821 getWorldMatrix(*this).transform(mybounds
);
823 return renderer
.bounds_in_clipping_area(mybounds
.getRange());
827 DisplayObject::InfoTree::iterator
828 DisplayObject::getMovieInfo(InfoTree
& tr
, InfoTree::iterator it
)
830 const std::string yes
= _("yes");
831 const std::string no
= _("no");
833 it
= tr
.append_child(it
, std::make_pair(getTarget(), typeName(*this)));
835 std::ostringstream os
;
837 tr
.append_child(it
, std::make_pair(_("Depth"), os
.str()));
839 /// Don't add if the DisplayObject has no ratio value
840 if (get_ratio() >= 0)
844 tr
.append_child(it
, std::make_pair(_("Ratio"), os
.str()));
847 /// Don't add if it's not a real clipping depth
848 const int cd
= get_clip_depth();
849 if (cd
!= noClipDepthValue
) {
851 if (_maskee
) os
<< "Dynamic mask";
854 tr
.append_child(it
, std::make_pair(_("Clipping depth"), os
.str()));
858 os
<< getBounds().width() << "x" << getBounds().height();
859 tr
.append_child(it
, std::make_pair(_("Dimensions"), os
.str()));
861 tr
.append_child(it
, std::make_pair(_("Dynamic"), isDynamic() ? yes
: no
));
862 tr
.append_child(it
, std::make_pair(_("Mask"), isMaskLayer() ? yes
: no
));
863 tr
.append_child(it
, std::make_pair(_("Destroyed"),
864 isDestroyed() ? yes
: no
));
865 tr
.append_child(it
, std::make_pair(_("Unloaded"), unloaded() ? yes
: no
));
869 tr
.append_child(it
, std::make_pair(_("Blend mode"), os
.str()));
871 // This probably isn't interesting for non-developers
872 tr
.append_child(it
, std::make_pair(_("Invalidated"),
873 _invalidated
? yes
: no
));
874 tr
.append_child(it
, std::make_pair(_("Child invalidated"),
875 _child_invalidated
? yes
: no
));
882 DisplayObject::getAsRoot()
888 setIndexedProperty(size_t index
, DisplayObject
& o
, const as_value
& val
)
890 const Setter s
= getGetterSetterByIndex(index
).second
;
891 if (!s
) return; // read-only (warn?)
893 if (val
.is_undefined() || val
.is_null()) {
894 IF_VERBOSE_ASCODING_ERRORS(
895 log_aserror(_("Attempt to set property to %s, refused"),
905 getIndexedProperty(size_t index
, DisplayObject
& o
, as_value
& val
)
907 const Getter s
= getGetterSetterByIndex(index
).first
;
916 /// DisplayObject property lookup
918 /// This function is only called on the first object in the inheritance chain
919 /// after the object's own properties have been checked.
920 /// In AS2, any DisplayObject marks the end of the inheritance chain for
925 /// 1. _level0.._level9
926 /// 2. Objects on the DisplayList of a MovieClip
927 /// 3. DisplayObject magic properties (_x, _y etc).
928 /// 4. MovieClips' TextField variables (this is probably not the best
929 /// way to do it, but as it is done like this, this must be called here.
930 /// It will cause an infinite recursion otherwise.
932 getDisplayObjectProperty(DisplayObject
& obj
, const ObjectURI
& uri
,
936 as_object
* o
= getObject(&obj
);
939 string_table
& st
= getStringTable(*o
);
940 const std::string
& propname
= uri
.toString(st
);
942 // Check _level0.._level9
943 movie_root
& mr
= getRoot(*getObject(&obj
));
944 unsigned int levelno
;
945 if (isLevelTarget(getSWFVersion(*o
), propname
, levelno
)) {
946 MovieClip
* mo
= mr
.getLevel(levelno
);
954 MovieClip
* mc
= dynamic_cast<MovieClip
*>(&obj
);
956 DisplayObject
* ch
= mc
->getDisplayListObject(uri
);
963 const string_table::key noCaseKey
= uri
.noCase(st
);
965 // These properties have normal case-sensitivity.
966 // They are tested to exist for TextField, MovieClip, and Button
967 // but do not belong to the inheritance chain.
968 switch (caseless(*o
) ? noCaseKey
: getName(uri
))
972 case NSV::PROP_uROOT
:
973 if (getSWFVersion(*o
) < 5) break;
974 val
= getObject(obj
.getAsRoot());
976 case NSV::PROP_uGLOBAL
:
977 // TODO: clean up this mess.
978 assert(getObject(&obj
));
979 if (getSWFVersion(*o
) < 6) break;
980 val
= &getGlobal(*o
);
984 // These magic properties are case insensitive in all versions!
985 if (doGet(uri
, obj
, val
)) return true;
987 // Check MovieClip such as TextField variables.
988 // TODO: check if there's a better way to find these properties.
989 if (mc
&& mc
->getTextFieldVariables(uri
, val
)) return true;
996 setDisplayObjectProperty(DisplayObject
& obj
, const ObjectURI
& uri
,
999 // These magic properties are case insensitive in all versions!
1000 return doSet(uri
, obj
, val
);
1003 DisplayObject::MaskRenderer::MaskRenderer(Renderer
& r
, const DisplayObject
& o
)
1006 _mask(o
.visible() && o
.getMask() && !o
.getMask()->unloaded() ? o
.getMask()
1011 _renderer
.begin_submit_mask();
1012 DisplayObject
* p
= _mask
->parent();
1013 const Transform tr
= p
?
1014 Transform(getWorldMatrix(*p
), getWorldCxForm(*p
)) : Transform();
1015 _mask
->display(_renderer
, tr
);
1016 _renderer
.end_submit_mask();
1019 DisplayObject::MaskRenderer::~MaskRenderer()
1021 if (_mask
) _renderer
.disable_mask();
1027 getQuality(DisplayObject
& o
)
1029 movie_root
& mr
= getRoot(*getObject(&o
));
1030 switch (mr
.getQuality())
1033 return as_value("BEST");
1035 return as_value("HIGH");
1036 case QUALITY_MEDIUM
:
1037 return as_value("MEDIUM");
1039 return as_value("LOW");
1047 setQuality(DisplayObject
& o
, const as_value
& val
)
1049 movie_root
& mr
= getRoot(*getObject(&o
));
1051 if (!val
.is_string()) return;
1053 const std::string
& q
= val
.to_string();
1055 StringNoCaseEqual noCaseCompare
;
1057 if (noCaseCompare(q
, "BEST")) mr
.setQuality(QUALITY_BEST
);
1058 else if (noCaseCompare(q
, "HIGH")) {
1059 mr
.setQuality(QUALITY_HIGH
);
1061 else if (noCaseCompare(q
, "MEDIUM")) {
1062 mr
.setQuality(QUALITY_MEDIUM
);
1064 else if (noCaseCompare(q
, "LOW")) {
1065 mr
.setQuality(QUALITY_LOW
);
1072 getURL(DisplayObject
& o
)
1074 return as_value(o
.get_root()->url());
1078 getHighQuality(DisplayObject
& o
)
1080 movie_root
& mr
= getRoot(*getObject(&o
));
1081 switch (mr
.getQuality())
1084 return as_value(2.0);
1086 return as_value(1.0);
1087 case QUALITY_MEDIUM
:
1089 return as_value(0.0);
1095 setHighQuality(DisplayObject
& o
, const as_value
& val
)
1097 movie_root
& mr
= getRoot(*getObject(&o
));
1099 const double q
= toNumber(val
, getVM(*getObject(&o
)));
1101 if (q
< 0) mr
.setQuality(QUALITY_HIGH
);
1102 else if (q
> 2) mr
.setQuality(QUALITY_BEST
);
1104 int i
= static_cast<int>(q
);
1108 mr
.setQuality(QUALITY_LOW
);
1111 mr
.setQuality(QUALITY_HIGH
);
1114 mr
.setQuality(QUALITY_BEST
);
1122 setY(DisplayObject
& o
, const as_value
& val
)
1125 const double newy
= toNumber(val
, getVM(*getObject(&o
)));
1127 // NaN is skipped, Infinite isn't
1130 IF_VERBOSE_ASCODING_ERRORS(
1131 log_aserror(_("Attempt to set %s._y to %s "
1132 "(evaluating to number %g) refused"),
1133 o
.getTarget(), val
, newy
);
1138 SWFMatrix m
= getMatrix(o
);
1139 // NOTE: infinite_to_zero is wrong here, see actionscript.all/setProperty.as
1140 m
.set_y_translation(pixelsToTwips(infinite_to_zero(newy
)));
1142 o
.transformedByScript();
1146 getY(DisplayObject
& o
)
1148 const SWFMatrix m
= getMatrix(o
);
1149 return twipsToPixels(m
.get_y_translation());
1153 setX(DisplayObject
& o
, const as_value
& val
)
1156 const double newx
= toNumber(val
, getVM(*getObject(&o
)));
1158 // NaN is skipped, Infinite isn't
1161 IF_VERBOSE_ASCODING_ERRORS(
1162 log_aserror(_("Attempt to set %s._x to %s "
1163 "(evaluating to number %g) refused"),
1164 o
.getTarget(), val
, newx
);
1169 SWFMatrix m
= getMatrix(o
);
1170 // NOTE: infinite_to_zero is wrong here, see actionscript.all/setProperty.as
1171 m
.set_x_translation(pixelsToTwips(infinite_to_zero(newx
)));
1173 o
.transformedByScript();
1177 getX(DisplayObject
& o
)
1179 const SWFMatrix m
= getMatrix(o
);
1180 return twipsToPixels(m
.get_x_translation());
1184 setScaleX(DisplayObject
& o
, const as_value
& val
)
1187 const double scale_percent
= toNumber(val
, getVM(*getObject(&o
)));
1189 // NaN is skipped, Infinite is not, see actionscript.all/setProperty.as
1190 if (isNaN(scale_percent
)) {
1191 IF_VERBOSE_ASCODING_ERRORS(
1192 log_aserror(_("Attempt to set %s._xscale to %s "
1193 "(evaluating to number %g) refused"),
1194 o
.getTarget(), val
, scale_percent
);
1199 // input is in percent
1200 o
.set_x_scale(scale_percent
);
1205 getScaleX(DisplayObject
& o
)
1211 setScaleY(DisplayObject
& o
, const as_value
& val
)
1214 const double scale_percent
= toNumber(val
, getVM(*getObject(&o
)));
1216 // NaN is skipped, Infinite is not, see actionscript.all/setProperty.as
1217 if (isNaN(scale_percent
)) {
1218 IF_VERBOSE_ASCODING_ERRORS(
1219 log_aserror(_("Attempt to set %s._yscale to %s "
1220 "(evaluating to number %g) refused"),
1221 o
.getTarget(), val
, scale_percent
);
1226 // input is in percent
1227 o
.set_y_scale(scale_percent
);
1232 getScaleY(DisplayObject
& o
)
1238 getVisible(DisplayObject
& o
)
1244 setVisible(DisplayObject
& o
, const as_value
& val
)
1247 /// We cast to number and rely (mostly) on C++'s automatic
1248 /// cast to bool, as string "0" should be converted to
1249 /// its numeric equivalent, not interpreted as 'true', which
1250 /// SWF7+ does for strings.
1251 const double d
= toNumber(val
, getVM(*getObject(&o
)));
1253 // Infinite or NaN is skipped
1254 if (isInf(d
) || isNaN(d
)) {
1255 IF_VERBOSE_ASCODING_ERRORS(
1256 log_aserror(_("Attempt to set %s._visible to %s "
1257 "(evaluating to number %g) refused"),
1258 o
.getTarget(), val
, d
);
1265 o
.transformedByScript();
1269 getAlpha(DisplayObject
& o
)
1271 return as_value(getCxForm(o
).aa
/ 2.56);
1275 setAlpha(DisplayObject
& o
, const as_value
& val
)
1278 // The new internal alpha value is input / 100.0 * 256.
1279 // We test for finiteness later, but the multiplication
1280 // won't make any difference.
1281 const double newAlpha
= toNumber(val
, getVM(*getObject(&o
))) * 2.56;
1283 // NaN is skipped, Infinite is not, see actionscript.all/setProperty.as
1284 if (isNaN(newAlpha
)) {
1285 IF_VERBOSE_ASCODING_ERRORS(
1286 log_aserror(_("Attempt to set %s._alpha to %s "
1287 "(evaluating to number %g) refused"),
1288 o
.getTarget(), val
, newAlpha
);
1293 SWFCxForm cx
= getCxForm(o
);
1295 // Overflows are *not* truncated, but set to -32768.
1296 if (newAlpha
> std::numeric_limits
<boost::int16_t>::max() ||
1297 newAlpha
< std::numeric_limits
<boost::int16_t>::min()) {
1298 cx
.aa
= std::numeric_limits
<boost::int16_t>::min();
1301 cx
.aa
= static_cast<boost::int16_t>(newAlpha
);
1305 o
.transformedByScript();
1310 getMouseX(DisplayObject
& o
)
1312 // Local coord of mouse IN PIXELS.
1313 boost::int32_t x
, y
;
1314 getRoot(*getObject(&o
)).get_mouse_state(x
, y
);
1316 SWFMatrix m
= getWorldMatrix(o
);
1317 point
a(pixelsToTwips(x
), pixelsToTwips(y
));
1319 m
.invert().transform(a
);
1320 return as_value(twipsToPixels(a
.x
));
1324 getMouseY(DisplayObject
& o
)
1326 // Local coord of mouse IN PIXELS.
1327 boost::int32_t x
, y
;
1328 getRoot(*getObject(&o
)).get_mouse_state(x
, y
);
1330 SWFMatrix m
= getWorldMatrix(o
);
1331 point
a(pixelsToTwips(x
), pixelsToTwips(y
));
1332 m
.invert().transform(a
);
1333 return as_value(twipsToPixels(a
.y
));
1337 getRotation(DisplayObject
& o
)
1339 return o
.rotation();
1344 setRotation(DisplayObject
& o
, const as_value
& val
)
1347 // input is in degrees
1348 const double rotation_val
= toNumber(val
, getVM(*getObject(&o
)));
1350 // NaN is skipped, Infinity isn't
1351 if (isNaN(rotation_val
)) {
1352 IF_VERBOSE_ASCODING_ERRORS(
1353 log_aserror(_("Attempt to set %s._rotation to %s "
1354 "(evaluating to number %g) refused"),
1355 o
.getTarget(), val
, rotation_val
);
1359 o
.set_rotation(rotation_val
);
1364 getParent(DisplayObject
& o
)
1366 as_object
* p
= getObject(o
.parent());
1367 return p
? p
: as_value();
1371 getTarget(DisplayObject
& o
)
1373 return o
.getTargetPath();
1377 getNameProperty(DisplayObject
& o
)
1379 string_table
& st
= getStringTable(*getObject(&o
));
1380 const std::string
& name
= o
.get_name().toString(st
);
1381 if (getSWFVersion(*getObject(&o
)) < 6 && name
.empty()) return as_value();
1382 return as_value(name
);
1386 setName(DisplayObject
& o
, const as_value
& val
)
1388 o
.set_name(getURI(getVM(*getObject(&o
)), val
.to_string()));
1392 setSoundBufTime(DisplayObject
& /*o*/, const as_value
& /*val*/)
1394 LOG_ONCE(log_unimpl("_soundbuftime setting"));
1398 getSoundBufTime(DisplayObject
& /*o*/)
1400 return as_value(0.0);
1404 getWidth(DisplayObject
& o
)
1406 SWFRect bounds
= o
.getBounds();
1407 const SWFMatrix
& m
= getMatrix(o
);
1408 m
.transform(bounds
);
1409 return twipsToPixels(bounds
.width());
1413 setWidth(DisplayObject
& o
, const as_value
& val
)
1415 const double newwidth
= pixelsToTwips(toNumber(val
, getVM(*getObject(&o
))));
1416 if (newwidth
<= 0) {
1417 IF_VERBOSE_ASCODING_ERRORS(
1418 log_aserror(_("Setting _width=%g of DisplayObject %s (%s)"),
1419 newwidth
/20, o
.getTarget(), typeName(o
));
1422 o
.setWidth(newwidth
);
1426 getFocusRect(DisplayObject
& /*o*/)
1428 LOG_ONCE(log_unimpl("_focusrect"));
1429 return as_value(true);
1433 setFocusRect(DisplayObject
& /*o*/, const as_value
& /*val*/)
1435 LOG_ONCE(log_unimpl("_focusrect setting"));
1439 getDropTarget(DisplayObject
& o
)
1441 // This property only applies to MovieClips.
1442 MovieClip
* mc
= dynamic_cast<MovieClip
*>(&o
);
1443 if (!mc
) return as_value();
1444 return as_value(mc
->getDropTarget());
1448 getCurrentFrame(DisplayObject
& o
)
1450 // This property only applies to MovieClips.
1451 MovieClip
* mc
= dynamic_cast<MovieClip
*>(&o
);
1452 if (!mc
) return as_value();
1453 const int currframe
=
1454 std::min(mc
->get_loaded_frames(), mc
->get_current_frame() + 1);
1455 return as_value(currframe
);
1459 getFramesLoaded(DisplayObject
& o
)
1461 // This property only applies to MovieClips.
1462 MovieClip
* mc
= dynamic_cast<MovieClip
*>(&o
);
1463 if (!mc
) return as_value();
1464 return as_value(mc
->get_loaded_frames());
1468 getTotalFrames(DisplayObject
& o
)
1470 // This property only applies to MovieClips.
1471 MovieClip
* mc
= dynamic_cast<MovieClip
*>(&o
);
1472 if (!mc
) return as_value();
1473 return as_value(mc
->get_frame_count());
1477 /// @param uri The property to search for. Note that all special
1478 /// properties are lower-case.
1480 /// NOTE that all properties have getters so you can recognize a
1481 /// 'not-found' condition by checking .first = 0
1483 getGetterSetterByURI(const ObjectURI
& uri
, string_table
& st
)
1485 typedef std::map
<ObjectURI
, GetterSetter
, ObjectURI::CaseLessThan
>
1488 static const GetterSetters gs
=
1489 getURIMap
<GetterSetters
>(ObjectURI::CaseLessThan(st
, true));
1491 const GetterSetters::const_iterator it
= gs
.find(uri
);
1493 if (it
== gs
.end()) {
1494 static const GetterSetter
none(0, 0);
1503 getGetterSetterByIndex(size_t index
)
1507 static const GetterSetter props
[] = {
1508 GetterSetter(&getX
, &setX
),
1509 GetterSetter(&getY
, &setY
),
1510 GetterSetter(&getScaleX
, &setScaleX
),
1511 GetterSetter(&getScaleY
, &setScaleY
),
1513 GetterSetter(&getCurrentFrame
, n
),
1514 GetterSetter(&getTotalFrames
, n
),
1515 GetterSetter(&getAlpha
, &setAlpha
),
1516 GetterSetter(&getVisible
, &setVisible
),
1518 GetterSetter(&getWidth
, &setWidth
),
1519 GetterSetter(&getHeight
, &setHeight
),
1520 GetterSetter(&getRotation
, &setRotation
),
1521 GetterSetter(&getTarget
, n
),
1523 GetterSetter(&getFramesLoaded
, n
),
1524 GetterSetter(&getNameProperty
, &setName
),
1525 GetterSetter(&getDropTarget
, n
),
1526 GetterSetter(&getURL
, n
),
1528 GetterSetter(&getHighQuality
, &setHighQuality
),
1529 GetterSetter(&getFocusRect
, &setFocusRect
),
1530 GetterSetter(&getSoundBufTime
, &setSoundBufTime
),
1531 GetterSetter(&getQuality
, &setQuality
),
1533 GetterSetter(&getMouseX
, n
),
1534 GetterSetter(&getMouseY
, n
)
1538 if (index
>= arraySize(props
)) {
1539 const Getter ng
= 0;
1540 static const GetterSetter
none(ng
, n
);
1544 return props
[index
];
1549 doGet(const ObjectURI
& uri
, DisplayObject
& o
, as_value
& val
)
1551 string_table
& st
= getStringTable(*getObject(&o
));
1552 const Getter s
= getGetterSetterByURI(uri
, st
).first
;
1553 if (!s
) return false;
1560 /// Do the actual setProperty
1562 /// Return true if the property is a DisplayObject property, regardless of
1563 /// whether it was successfully set or not.
1565 /// @param uri The property to search for. Note that all special
1566 /// properties are lower-case, so for a caseless check
1567 /// it is sufficient for prop to be caseless.
1569 doSet(const ObjectURI
& uri
, DisplayObject
& o
, const as_value
& val
)
1571 string_table
& st
= getStringTable(*getObject(&o
));
1573 const GetterSetter gs
= getGetterSetterByURI(uri
, st
);
1575 // not found (all props have getters)
1576 if (!gs
.first
) return false;
1578 const Setter s
= gs
.second
;
1580 // read-only (TODO: aserror ?)
1581 if (!s
) return true;
1583 if (val
.is_undefined() || val
.is_null()) {
1584 IF_VERBOSE_ASCODING_ERRORS(
1585 // TODO: add property name to this log...
1586 log_aserror(_("Attempt to set property to %s, refused"),
1587 o
.getTarget(), val
);
1600 /// BLENDMODE_UNDEFINED has no matching string in AS. It is included
1601 /// here for logging purposes.
1602 static const BlendModeMap bm
= boost::assign::map_list_of
1603 (DisplayObject::BLENDMODE_UNDEFINED
, "undefined")
1604 (DisplayObject::BLENDMODE_NORMAL
, "normal")
1605 (DisplayObject::BLENDMODE_LAYER
, "layer")
1606 (DisplayObject::BLENDMODE_MULTIPLY
, "multiply")
1607 (DisplayObject::BLENDMODE_SCREEN
, "screen")
1608 (DisplayObject::BLENDMODE_LIGHTEN
, "lighten")
1609 (DisplayObject::BLENDMODE_DARKEN
, "darken")
1610 (DisplayObject::BLENDMODE_DIFFERENCE
, "difference")
1611 (DisplayObject::BLENDMODE_ADD
, "add")
1612 (DisplayObject::BLENDMODE_SUBTRACT
, "subtract")
1613 (DisplayObject::BLENDMODE_INVERT
, "invert")
1614 (DisplayObject::BLENDMODE_ALPHA
, "alpha")
1615 (DisplayObject::BLENDMODE_ERASE
, "erase")
1616 (DisplayObject::BLENDMODE_OVERLAY
, "overlay")
1617 (DisplayObject::BLENDMODE_HARDLIGHT
, "hardlight");
1623 // Match a blend mode to its string.
1625 blendModeMatches(const BlendModeMap::value_type
& val
, const std::string
& mode
)
1627 /// The match must be case-sensitive.
1628 if (mode
.empty()) return false;
1629 return (val
.second
== mode
);
1632 /// Return a const map of property URI to function.
1634 /// This function takes advantage of NRVO to allow the map to
1635 /// be constructed in the caller.
1636 template<typename Map
>
1638 getURIMap(const typename
Map::key_compare
& cmp
)
1643 ret
.insert(std::make_pair(NSV::PROP_uX
, GetterSetter(&getX
, &setX
)));
1644 ret
.insert(std::make_pair(NSV::PROP_uY
, GetterSetter(&getY
, &setY
)));
1645 ret
.insert(std::make_pair(NSV::PROP_uXSCALE
,
1646 GetterSetter(&getScaleX
, &setScaleX
)));
1647 ret
.insert(std::make_pair(NSV::PROP_uYSCALE
,
1648 GetterSetter(&getScaleY
, &setScaleY
)));
1649 ret
.insert(std::make_pair(NSV::PROP_uROTATION
,
1650 GetterSetter(&getRotation
, &setRotation
)));
1651 ret
.insert(std::make_pair(NSV::PROP_uHIGHQUALITY
,
1652 GetterSetter(&getHighQuality
, &setHighQuality
)));
1653 ret
.insert(std::make_pair(NSV::PROP_uQUALITY
,
1654 GetterSetter(&getQuality
, &setQuality
)));
1655 ret
.insert(std::make_pair(NSV::PROP_uALPHA
,
1656 GetterSetter(&getAlpha
, &setAlpha
)));
1657 ret
.insert(std::make_pair(NSV::PROP_uWIDTH
,
1658 GetterSetter(&getWidth
, &setWidth
)));
1659 ret
.insert(std::make_pair(NSV::PROP_uHEIGHT
,
1660 GetterSetter(&getHeight
, &setHeight
)));
1661 ret
.insert(std::make_pair(NSV::PROP_uNAME
,
1662 GetterSetter(&getNameProperty
, &setName
)));
1663 ret
.insert(std::make_pair(NSV::PROP_uVISIBLE
,
1664 GetterSetter(&getVisible
, &setVisible
)));
1665 ret
.insert(std::make_pair(NSV::PROP_uSOUNDBUFTIME
,
1666 GetterSetter(&getSoundBufTime
, &setSoundBufTime
)));
1667 ret
.insert(std::make_pair(NSV::PROP_uFOCUSRECT
,
1668 GetterSetter(&getFocusRect
, &setFocusRect
)));
1669 ret
.insert(std::make_pair(NSV::PROP_uDROPTARGET
,
1670 GetterSetter(&getDropTarget
, n
)));
1671 ret
.insert(std::make_pair(NSV::PROP_uCURRENTFRAME
,
1672 GetterSetter(&getCurrentFrame
, n
)));
1673 ret
.insert(std::make_pair(NSV::PROP_uFRAMESLOADED
,
1674 GetterSetter(&getFramesLoaded
, n
)));
1675 ret
.insert(std::make_pair(NSV::PROP_uTOTALFRAMES
,
1676 GetterSetter(&getTotalFrames
, n
)));
1677 ret
.insert(std::make_pair(NSV::PROP_uURL
, GetterSetter(&getURL
, n
)));
1678 ret
.insert(std::make_pair(NSV::PROP_uTARGET
, GetterSetter(&getTarget
, n
)));
1679 ret
.insert(std::make_pair(NSV::PROP_uXMOUSE
, GetterSetter(&getMouseX
, n
)));
1680 ret
.insert(std::make_pair(NSV::PROP_uYMOUSE
, GetterSetter(&getMouseY
, n
)));
1681 ret
.insert(std::make_pair(NSV::PROP_uPARENT
, GetterSetter(&getParent
, n
)));
1685 } // anonymous namespace
1688 operator<<(std::ostream
& o
, DisplayObject::BlendMode bm
)
1690 const BlendModeMap
& bmm
= getBlendModeMap();
1691 return (o
<< bmm
.find(bm
)->second
);
1694 } // namespace gnash
1698 // indent-tabs-mode: t