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
23 #include "gnashconfig.h" // USE_SWFTREE
26 #include "smart_ptr.h" // GNASH_USE_GC
27 #include "DisplayObject.h"
28 #include "movie_root.h"
29 #include "MovieClip.h"
30 #include "drag_state.h" // for do_mouse_drag (to be moved in movie_root)
31 #include "VM.h" // for do_mouse_drag (to be moved in movie_root)
32 #include "fn_call.h" // for shared ActionScript getter-setters
33 #include "GnashException.h"
34 #include "ExecutableCode.h"
35 #include "namedStrings.h"
36 #include "gnash.h" // Quality
37 #include "GnashNumeric.h"
38 #include "Global_as.h"
45 #include <boost/algorithm/string/case_conv.hpp>
46 #include <boost/assign/list_of.hpp>
47 #include <boost/bind.hpp>
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 std::map
<string_table::key
, Getter
> Getters
;
64 typedef void(*Setter
)(DisplayObject
&, const as_value
&);
65 typedef std::map
<string_table::key
, Setter
> Setters
;
67 const Getters
& displayObjectGetters();
68 const Setters
& displayObjectSetters();
70 bool doSet(string_table::key prop
, DisplayObject
& o
, const as_value
& val
);
71 bool doGet(string_table::key prop
, DisplayObject
& o
, as_value
& val
);
72 string_table::key
getPropertyByIndex(size_t index
);
75 // Define static const members.
76 const int DisplayObject::lowerAccessibleBound
;
77 const int DisplayObject::upperAccessibleBound
;
78 const int DisplayObject::staticDepthOffset
;
79 const int DisplayObject::removedDepthOffset
;
80 const int DisplayObject::noClipDepthValue
;
82 DisplayObject::DisplayObject(movie_root
& mr
, as_object
* object
,
83 DisplayObject
* parent
)
95 m_clip_depth(noClipDepthValue
),
98 _blendMode(BLENDMODE_NORMAL
),
100 _scriptTransformed(false),
101 _dynamicallyCreated(false),
105 _child_invalidated(true)
107 assert(m_old_invalidated_ranges
.isNull());
109 // This informs the core that the object is a DisplayObject.
110 if (_object
) _object
->setDisplayObject(this);
114 DisplayObject::object() const
120 DisplayObject::unloaded() const
126 DisplayObject::getLoadedMovie(Movie
* extern_movie
)
129 log_unimpl("loadMovie against a %s DisplayObject", typeName(*this))
132 // TODO: look at the MovieClip implementation, but most importantly
133 // test all the event handlers copies etc..
135 UNUSED(extern_movie
);
139 DisplayObject::getNextUnnamedInstanceName()
142 movie_root
& mr
= getRoot(*_object
);
143 std::ostringstream ss
;
144 ss
<< "instance" << mr
.nextUnnamedInstance();
146 string_table
& st
= getStringTable(*_object
);
147 return st
.find(ss
.str());
152 DisplayObject::getWorldVolume() const
157 volume
= int(volume
*_parent
->getVolume()/100.0);
165 DisplayObject::pathElement(const ObjectURI
& uri
)
167 as_object
* obj
= getObject(this);
170 string_table::key key
= getName(uri
);
172 string_table
& st
= stage().getVM().getStringTable();
174 // TODO: put ".." and "." in namedStrings
175 if (key
== st
.find("..")) return getObject(parent());
176 if (key
== st
.find(".")) return obj
;
178 // The check is case-insensitive for SWF6 and below.
179 // TODO: cache ObjectURI(NSV::PROP_THIS) [as many others...]
180 if (equals(st
, uri
, ObjectURI(NSV::PROP_THIS
), caseless(*obj
))) {
187 DisplayObject::set_invalidated()
189 set_invalidated("unknown", -1);
193 DisplayObject::set_invalidated(const char* debug_file
, int debug_line
)
195 // Set the invalidated-flag of the parent. Note this does not mean that
196 // the parent must re-draw itself, it just means that one of it's childs
197 // needs to be re-drawn.
198 if ( _parent
) _parent
->set_child_invalidated();
200 // Ok, at this point the instance will change it's
201 // visual aspect after the
202 // call to set_invalidated(). We save the *current*
203 // position of the instance because this region must
204 // be updated even (or first of all) if the DisplayObject
205 // moves away from here.
207 if ( ! _invalidated
)
211 #ifdef DEBUG_SET_INVALIDATED
212 log_debug("%p set_invalidated() of %s in %s:%d",
213 (void*)this, get_name(), debug_file
, debug_line
);
219 // NOTE: the SnappingRanges instance used here is not initialized by the
220 // GUI and therefore uses the default settings. This should not be a
221 // problem but special snapping ranges configuration done in gui.cpp
222 // is ignored here...
224 m_old_invalidated_ranges
.setNull();
225 add_invalidated_bounds(m_old_invalidated_ranges
, true);
231 DisplayObject::add_invalidated_bounds(InvalidatedRanges
& ranges
, bool force
)
233 ranges
.add(m_old_invalidated_ranges
);
234 if (visible() && (_invalidated
||force
))
237 bounds
.expand_to_transformed_rect(getWorldMatrix(*this), getBounds());
238 ranges
.add(bounds
.getRange());
243 DisplayObject::set_child_invalidated()
245 if ( ! _child_invalidated
)
247 _child_invalidated
=true;
248 if ( _parent
) _parent
->set_child_invalidated();
253 DisplayObject::extend_invalidated_bounds(const InvalidatedRanges
& ranges
)
255 set_invalidated(__FILE__
, __LINE__
);
256 m_old_invalidated_ranges
.add(ranges
);
260 DisplayObject::blendMode(const fn_call
& fn
)
262 DisplayObject
* ch
= ensure
<IsDisplayObject
<> >(fn
);
264 // This is AS-correct, but doesn't do anything.
265 // TODO: implement in the renderers!
266 LOG_ONCE(log_unimpl(_("blendMode")));
271 BlendMode bm
= ch
->getBlendMode();
273 /// If the blend mode is undefined, it doesn't return a string.
274 if (bm
== BLENDMODE_UNDEFINED
) return as_value();
276 std::ostringstream blendMode
;
278 return as_value(blendMode
.str());
285 const as_value
& bm
= fn
.arg(0);
287 // Undefined argument sets blend mode to normal.
288 if (bm
.is_undefined()) {
289 ch
->setBlendMode(BLENDMODE_NORMAL
);
294 if (bm
.is_number()) {
295 double mode
= bm
.to_number();
297 // Hardlight is the last known value. This also performs range checking
298 // for float-to-int conversion.
299 if (mode
< 0 || mode
> BLENDMODE_HARDLIGHT
) {
301 // An invalid numeric argument becomes undefined.
302 ch
->setBlendMode(BLENDMODE_UNDEFINED
);
305 /// The extra static cast is required to keep OpenBSD happy.
306 ch
->setBlendMode(static_cast<BlendMode
>(static_cast<int>(mode
)));
311 // Other arguments use toString method.
312 const std::string
& mode
= bm
.to_string();
314 const BlendModeMap
& bmm
= getBlendModeMap();
315 BlendModeMap::const_iterator it
= std::find_if(bmm
.begin(), bmm
.end(),
316 boost::bind(blendModeMatches
, _1
, mode
));
318 if (it
!= bmm
.end()) {
319 ch
->setBlendMode(it
->first
);
322 // An invalid string argument has no effect.
329 DisplayObject::set_visible(bool visible
)
331 if (_visible
!= visible
) set_invalidated(__FILE__
, __LINE__
);
333 // Remove focus from this DisplayObject if it changes from visible to
334 // invisible (see Selection.as).
335 if (_visible
&& !visible
) {
337 movie_root
& mr
= getRoot(*_object
);
338 if (mr
.getFocus() == this) {
346 DisplayObject::setWidth(double newwidth
)
348 const SWFRect
& bounds
= getBounds();
349 const double oldwidth
= bounds
.width();
350 assert(oldwidth
>= 0);
352 const double xscale
= oldwidth
? (newwidth
/ oldwidth
) : 0;
353 const double rotation
= _rotation
* PI
/ 180.0;
355 SWFMatrix m
= getMatrix(*this);
356 const double yscale
= m
.get_y_scale();
357 m
.set_scale_rotation(xscale
, yscale
, rotation
);
362 getHeight(DisplayObject
& o
)
364 SWFRect bounds
= o
.getBounds();
365 const SWFMatrix m
= getMatrix(o
);
367 return twipsToPixels(bounds
.height());
371 setHeight(DisplayObject
& o
, const as_value
& val
)
373 const double newheight
= pixelsToTwips(val
.to_number());
374 if (newheight
<= 0) {
375 IF_VERBOSE_ASCODING_ERRORS(
376 log_aserror(_("Setting _height=%g of DisplayObject %s (%s)"),
377 newheight
/ 20, o
.getTarget(), typeName(o
));
380 o
.setHeight(newheight
);
384 DisplayObject::setHeight(double newheight
)
386 const SWFRect
& bounds
= getBounds();
388 const double oldheight
= bounds
.height();
389 assert(oldheight
>= 0);
391 const double yscale
= oldheight
? (newheight
/ oldheight
) : 0;
392 const double rotation
= _rotation
* PI
/ 180.0;
394 SWFMatrix m
= getMatrix(*this);
395 const double xscale
= m
.get_x_scale();
396 m
.set_scale_rotation(xscale
, yscale
, rotation
);
401 DisplayObject::setMatrix(const SWFMatrix
& m
, bool updateCache
)
404 if (m
== _transform
.matrix
) return;
406 //log_debug("setting SWFMatrix to: %s", m);
407 set_invalidated(__FILE__
, __LINE__
);
408 _transform
.matrix
= m
;
410 // don't update caches if SWFMatrix wasn't updated too
412 _xscale
= _transform
.matrix
.get_x_scale() * 100.0;
413 _yscale
= _transform
.matrix
.get_y_scale() * 100.0;
414 _rotation
= _transform
.matrix
.get_rotation() * 180.0 / PI
;
420 DisplayObject::set_event_handlers(const Events
& copyfrom
)
422 for (Events::const_iterator it
=copyfrom
.begin(), itE
=copyfrom
.end();
425 const event_id
& ev
= it
->first
;
426 const BufferList
& bufs
= it
->second
;
427 for (size_t i
= 0, e
= bufs
.size(); i
< e
; ++i
)
429 const action_buffer
* buf
= bufs
[i
];
431 add_event_handler(ev
, *buf
);
437 DisplayObject::add_event_handler(const event_id
& id
, const action_buffer
& code
)
439 _event_handlers
[id
].push_back(&code
);
441 // todo: drop the DisplayObject as a listener
442 // if it gets no valid handlers for
443 // mouse or Key events.
446 std::auto_ptr
<ExecutableCode
>
447 DisplayObject::get_event_handler(const event_id
& id
) const
449 std::auto_ptr
<ExecutableCode
> handler
;
451 Events::const_iterator it
= _event_handlers
.find(id
);
452 if ( it
== _event_handlers
.end() ) return handler
;
455 assert(get_ref_count() > 0);
456 #endif // GNASH_USE_GC
457 DisplayObject
* this_ptr
= const_cast<DisplayObject
*>(this);
459 handler
.reset( new EventCode(this_ptr
, it
->second
) );
464 DisplayObject::unload()
467 const bool childHandler
= unloadChildren();
470 queueEvent(event_id::UNLOAD
, movie_root::PRIORITY_DOACTION
);
473 // Unregister this DisplayObject as mask and/or maskee.
474 if (_maskee
) _maskee
->setMask(0);
475 if (_mask
) _mask
->setMaskee(0);
477 const bool hasEvent
= hasEventHandler(event_id::UNLOAD
) || childHandler
;
480 stage().removeQueuedConstructor(this);
489 DisplayObject::queueEvent(const event_id
& id
, int lvl
)
491 if (!_object
) return;
492 std::auto_ptr
<ExecutableCode
> event(new QueuedEvent(this, id
));
493 stage().pushAction(event
, lvl
);
497 DisplayObject::hasEventHandler(const event_id
& id
) const
499 Events::const_iterator it
= _event_handlers
.find(id
);
500 if (it
!= _event_handlers
.end()) return true;
502 if (!_object
) return false;
505 if (_object
->get_member(id
.functionKey(), &tmp
)) {
506 return tmp
.to_function();
512 /// Set the real and cached x scale.
514 /// Cached rotation and y scale are not updated.
516 DisplayObject::set_x_scale(double scale_percent
)
518 double xscale
= scale_percent
/ 100.0;
520 if (xscale
!= 0.0 && _xscale
!= 0.0)
522 if (scale_percent
* _xscale
< 0.0)
524 xscale
= -std::abs(xscale
);
526 else xscale
= std::abs(xscale
);
529 _xscale
= scale_percent
;
531 // As per misc-ming.all/SWFMatrix_test.{c,swf}
532 // we don't need to recompute the SWFMatrix from the
535 SWFMatrix m
= getMatrix(*this);
537 m
.set_x_scale(xscale
);
539 setMatrix(m
); // we updated the cache ourselves
541 transformedByScript();
544 /// Set the real and cached rotation.
546 /// Cached scale values are not updated.
548 DisplayObject::set_rotation(double rot
)
550 // Translate to the -180 .. 180 range
551 rot
= std::fmod(rot
, 360.0);
552 if (rot
> 180.0) rot
-= 360.0;
553 else if (rot
< -180.0) rot
+= 360.0;
555 double rotation
= rot
* PI
/ 180.0;
557 if (_xscale
< 0 ) rotation
+= PI
;
559 SWFMatrix m
= getMatrix(*this);
560 m
.set_rotation(rotation
);
562 // Update the matrix from the cached x scale to avoid accumulating
564 // TODO: also update y scale? The x scale update is needed to keep
565 // TextField correct; no tests for y scale.
566 m
.set_x_scale(std::abs(scaleX() / 100.0));
567 setMatrix(m
); // we update the cache ourselves
571 transformedByScript();
575 /// Set the real and cached y scale.
577 /// Cached rotation and x scale are not updated.
579 DisplayObject::set_y_scale(double scale_percent
)
581 double yscale
= scale_percent
/ 100.0;
583 if (yscale
!= 0.0 && _yscale
!= 0.0)
585 if (scale_percent
* _yscale
< 0.0) yscale
= -std::abs(yscale
);
586 else yscale
= std::abs(yscale
);
589 _yscale
= scale_percent
;
591 SWFMatrix m
= getMatrix(*this);
592 m
.set_y_scale(yscale
);
593 setMatrix(m
); // we updated the cache ourselves
595 transformedByScript();
600 DisplayObject::getTargetPath() const
602 // TODO: check what happens when this DisplayObject
603 // is a Movie loaded into another
606 typedef std::vector
<std::string
> Path
;
609 // Build parents stack
610 const DisplayObject
* topLevel
= 0;
611 const DisplayObject
* ch
= this;
613 string_table
& st
= getStringTable(*getObject(this));
616 const DisplayObject
* parent
= ch
->parent();
618 // Don't push the _root name on the stack
624 path
.push_back(ch
->get_name().toString(st
));
631 if (&stage().getRootMovie() == this) return "/";
632 std::stringstream ss
;
633 ss
<< "_level" << _depth
-DisplayObject::staticDepthOffset
;
637 // Build the target string from the parents stack
639 if (topLevel
!= &stage().getRootMovie()) {
640 std::stringstream ss
;
642 topLevel
->get_depth() - DisplayObject::staticDepthOffset
;
645 for (Path::reverse_iterator it
=path
.rbegin(), itEnd
=path
.rend();
654 DisplayObject::getTarget() const
657 // TODO: check what happens when this DisplayObject
658 // is a Movie loaded into another
661 typedef std::vector
<std::string
> Path
;
664 // Build parents stack
665 const DisplayObject
* ch
= this;
666 string_table
& st
= stage().getVM().getStringTable();
669 const DisplayObject
* parent
= ch
->parent();
671 // Don't push the _root name on the stack
674 std::stringstream ss
;
675 if (!dynamic_cast<const Movie
*>(ch
)) {
676 // must be an as-referenceable
677 // DisplayObject created using 'new'
678 // like, new MovieClip, new Video, new TextField...
679 //log_debug("DisplayObject %p (%s) doesn't have a parent and "
680 // "is not a Movie", ch, typeName(*ch));
681 ss
<< "<no parent, depth" << ch
->get_depth() << ">";
682 path
.push_back(ss
.str());
686 ch
->get_depth() - DisplayObject::staticDepthOffset
;
687 path
.push_back(ss
.str());
692 path
.push_back(ch
->get_name().toString(st
));
696 assert (!path
.empty());
698 // Build the target string from the parents stack
700 for (Path::const_reverse_iterator it
=path
.rbegin(), itEnd
=path
.rend();
703 if (!target
.empty()) target
+= ".";
712 DisplayObject::destroy()
714 // in case we are destroyed without being unloaded first
718 /// we may destory a DisplayObject that's not unloaded.
719 ///(we don't have chance to unload it in current model,
720 /// see new_child_in_unload_test.c)
721 /// We don't destroy ourself twice, right ?
723 if (_object
) _object
->clearProperties();
730 DisplayObject::markReachableResources() const
733 if (_object
) _object
->setReachable();
734 if (_parent
) _parent
->setReachable();
735 if (_mask
) _mask
->setReachable();
736 if (_maskee
) _maskee
->setReachable();
739 /// Whether to use a hand cursor when the mouse is over this DisplayObject
741 /// This depends on the useHandCursor AS property, but:
742 /// 1. Only AS-referenceable objects may use a hand cursor (TODO: check
744 /// 2. Only objects with a release event may use a hand cursor.
745 /// 3. The default value (if the property is not defined) is true.
747 DisplayObject::allowHandCursor() const
749 if (!getObject(this)) return false;
751 if (!hasEventHandler(event_id::RELEASE
)) return false;
754 if (!getObject(this)->get_member(NSV::PROP_USEHANDCURSOR
, &val
)) {
757 return val
.to_bool();
761 DisplayObject::setMask(DisplayObject
* mask
)
763 if ( _mask
== mask
) return;
767 // Backup this before setMaskee has a chance to change it..
768 DisplayObject
* prevMaskee
= _maskee
;
770 // If we had a previous mask unregister with it
771 if ( _mask
&& _mask
!= mask
)
773 // the mask will call setMask(NULL)
774 // on any previously registered maskee
775 // so we make sure to set our _mask to
776 // NULL before getting called again
780 // if we had a maskee, notify it to stop using
782 if (prevMaskee
) prevMaskee
->setMask(0);
784 // TODO: should we reset any original clip depth
785 // specified by PlaceObject tag ?
786 set_clip_depth(noClipDepthValue
);
791 /// Register as as masked by the mask
792 _mask
->setMaskee(this);
797 DisplayObject::setMaskee(DisplayObject
* maskee
)
799 if ( _maskee
== maskee
) { return; }
802 // We don't want the maskee to call setMaskee(null)
811 // TODO: should we reset any original clip depth
812 // specified by PlaceObject tag ?
813 set_clip_depth(noClipDepthValue
);
819 DisplayObject::boundsInClippingArea(Renderer
& renderer
) const
821 SWFRect mybounds
= getBounds();
822 getWorldMatrix(*this).transform(mybounds
);
824 return renderer
.bounds_in_clipping_area(mybounds
.getRange());
828 DisplayObject::InfoTree::iterator
829 DisplayObject::getMovieInfo(InfoTree
& tr
, InfoTree::iterator it
)
831 const std::string yes
= _("yes");
832 const std::string no
= _("no");
834 it
= tr
.append_child(it
, StringPair(getTarget(), typeName(*this)));
836 std::ostringstream os
;
838 tr
.append_child(it
, StringPair(_("Depth"), os
.str()));
840 /// Don't add if the DisplayObject has no ratio value
841 if (get_ratio() >= 0)
845 tr
.append_child(it
, StringPair(_("Ratio"), os
.str()));
848 /// Don't add if it's not a real clipping depth
849 if (int cd
= get_clip_depth() != noClipDepthValue
)
852 if (_maskee
) os
<< "Dynamic mask";
855 tr
.append_child(it
, StringPair(_("Clipping depth"), os
.str()));
859 os
<< getBounds().width() << "x" << getBounds().height();
860 tr
.append_child(it
, StringPair(_("Dimensions"), os
.str()));
862 tr
.append_child(it
, StringPair(_("Dynamic"), isDynamic() ? yes
: no
));
863 tr
.append_child(it
, StringPair(_("Mask"), isMaskLayer() ? yes
: no
));
864 tr
.append_child(it
, StringPair(_("Destroyed"), isDestroyed() ? yes
: no
));
865 tr
.append_child(it
, StringPair(_("Unloaded"), unloaded() ? yes
: no
));
869 tr
.append_child(it
, StringPair(_("Blend mode"), os
.str()));
871 // This probably isn't interesting for non-developers
872 tr
.append_child(it
, StringPair(_("Invalidated"), _invalidated
? yes
: no
));
873 tr
.append_child(it
, StringPair(_("Child invalidated"),
874 _child_invalidated
? yes
: no
));
881 DisplayObject::getAsRoot()
887 setIndexedProperty(size_t index
, DisplayObject
& o
, const as_value
& val
)
889 string_table::key prop
= getPropertyByIndex(index
);
895 getIndexedProperty(size_t index
, DisplayObject
& o
, as_value
& val
)
897 string_table::key prop
= getPropertyByIndex(index
);
906 /// DisplayObject property lookup
908 /// This function is only called on the first object in the inheritance chain
909 /// after the object's own properties have been checked.
910 /// In AS2, any DisplayObject marks the end of the inheritance chain for
915 /// 1. _level0.._level9
916 /// 2. Objects on the DisplayList of a MovieClip
917 /// 3. DisplayObject magic properties (_x, _y etc).
918 /// 4. MovieClips' TextField variables (this is probably not the best
919 /// way to do it, but as it is done like this, this must be called here.
920 /// It will cause an infinite recursion otherwise.
922 getDisplayObjectProperty(DisplayObject
& obj
, const ObjectURI
& uri
,
926 as_object
* o
= getObject(&obj
);
929 string_table
& st
= getStringTable(*o
);
930 const std::string
& propname
= uri
.toString(st
);
932 // Check _level0.._level9
933 movie_root
& mr
= getRoot(*getObject(&obj
));
934 unsigned int levelno
;
935 if (isLevelTarget(getSWFVersion(*o
), propname
, levelno
)) {
936 MovieClip
* mo
= mr
.getLevel(levelno
);
944 MovieClip
* mc
= dynamic_cast<MovieClip
*>(&obj
);
946 DisplayObject
* ch
= mc
->getDisplayListObject(uri
);
953 const string_table::key noCaseKey
= uri
.noCase(st
);
955 // These properties have normal case-sensitivity.
956 // They are tested to exist for TextField, MovieClip, and Button
957 // but do not belong to the inheritance chain.
958 switch (caseless(*o
) ? noCaseKey
: getName(uri
))
962 case NSV::PROP_uROOT
:
963 if (getSWFVersion(*o
) < 5) break;
964 val
= getObject(obj
.getAsRoot());
966 case NSV::PROP_uGLOBAL
:
967 // TODO: clean up this mess.
968 assert(getObject(&obj
));
969 if (getSWFVersion(*o
) < 6) break;
970 val
= &getGlobal(*o
);
974 // These magic properties are case insensitive in all versions!
975 if (doGet(noCaseKey
, obj
, val
)) return true;
977 // Check MovieClip such as TextField variables.
978 // TODO: check if there's a better way to find these properties.
979 if (mc
&& mc
->getTextFieldVariables(uri
, val
)) return true;
986 setDisplayObjectProperty(DisplayObject
& obj
, const ObjectURI
& uri
,
989 // These magic properties are case insensitive in all versions!
990 string_table
& st
= getStringTable(*getObject(&obj
));
991 return doSet(uri
.noCase(st
), obj
, val
);
994 DisplayObject::MaskRenderer::MaskRenderer(Renderer
& r
, const DisplayObject
& o
)
997 _mask(o
.visible() && o
.getMask() && !o
.getMask()->unloaded() ? o
.getMask()
1002 _renderer
.begin_submit_mask();
1003 DisplayObject
* p
= _mask
->parent();
1004 const Transform tr
= p
?
1005 Transform(getWorldMatrix(*p
), getWorldCxForm(*p
)) : Transform();
1006 _mask
->display(_renderer
, tr
);
1007 _renderer
.end_submit_mask();
1010 DisplayObject::MaskRenderer::~MaskRenderer()
1012 if (_mask
) _renderer
.disable_mask();
1018 getQuality(DisplayObject
& o
)
1020 movie_root
& mr
= getRoot(*getObject(&o
));
1021 switch (mr
.getQuality())
1024 return as_value("BEST");
1026 return as_value("HIGH");
1027 case QUALITY_MEDIUM
:
1028 return as_value("MEDIUM");
1030 return as_value("LOW");
1038 setQuality(DisplayObject
& o
, const as_value
& val
)
1040 movie_root
& mr
= getRoot(*getObject(&o
));
1042 if (!val
.is_string()) return;
1044 const std::string
& q
= val
.to_string();
1046 StringNoCaseEqual noCaseCompare
;
1048 if (noCaseCompare(q
, "BEST")) mr
.setQuality(QUALITY_BEST
);
1049 else if (noCaseCompare(q
, "HIGH")) {
1050 mr
.setQuality(QUALITY_HIGH
);
1052 else if (noCaseCompare(q
, "MEDIUM")) {
1053 mr
.setQuality(QUALITY_MEDIUM
);
1055 else if (noCaseCompare(q
, "LOW")) {
1056 mr
.setQuality(QUALITY_LOW
);
1063 getURL(DisplayObject
& o
)
1065 return as_value(o
.get_root()->url());
1069 getHighQuality(DisplayObject
& o
)
1071 movie_root
& mr
= getRoot(*getObject(&o
));
1072 switch (mr
.getQuality())
1075 return as_value(2.0);
1077 return as_value(1.0);
1078 case QUALITY_MEDIUM
:
1080 return as_value(0.0);
1086 setHighQuality(DisplayObject
& o
, const as_value
& val
)
1088 movie_root
& mr
= getRoot(*getObject(&o
));
1090 const double q
= val
.to_number();
1092 if (q
< 0) mr
.setQuality(QUALITY_HIGH
);
1093 else if (q
> 2) mr
.setQuality(QUALITY_BEST
);
1095 int i
= static_cast<int>(q
);
1099 mr
.setQuality(QUALITY_LOW
);
1102 mr
.setQuality(QUALITY_HIGH
);
1105 mr
.setQuality(QUALITY_BEST
);
1113 setY(DisplayObject
& o
, const as_value
& val
)
1116 const double newy
= val
.to_number();
1118 // NaN is skipped, Infinite isn't
1121 IF_VERBOSE_ASCODING_ERRORS(
1122 log_aserror(_("Attempt to set %s._y to %s "
1123 "(evaluating to number %g) refused"),
1124 o
.getTarget(), val
, newy
);
1129 SWFMatrix m
= getMatrix(o
);
1130 // NOTE: infinite_to_zero is wrong here, see actionscript.all/setProperty.as
1131 m
.set_y_translation(pixelsToTwips(infinite_to_zero(newy
)));
1133 o
.transformedByScript();
1137 getY(DisplayObject
& o
)
1139 SWFMatrix m
= getMatrix(o
);
1140 return twipsToPixels(m
.get_y_translation());
1144 setX(DisplayObject
& o
, const as_value
& val
)
1147 const double newx
= val
.to_number();
1149 // NaN is skipped, Infinite isn't
1152 IF_VERBOSE_ASCODING_ERRORS(
1153 log_aserror(_("Attempt to set %s._x to %s "
1154 "(evaluating to number %g) refused"),
1155 o
.getTarget(), val
, newx
);
1160 SWFMatrix m
= getMatrix(o
);
1161 // NOTE: infinite_to_zero is wrong here, see actionscript.all/setProperty.as
1162 m
.set_x_translation(pixelsToTwips(infinite_to_zero(newx
)));
1164 o
.transformedByScript();
1168 getX(DisplayObject
& o
)
1170 SWFMatrix m
= getMatrix(o
);
1171 return twipsToPixels(m
.get_x_translation());
1175 setScaleX(DisplayObject
& o
, const as_value
& val
)
1178 const double scale_percent
= val
.to_number();
1180 // NaN is skipped, Infinite is not, see actionscript.all/setProperty.as
1181 if (isNaN(scale_percent
))
1183 IF_VERBOSE_ASCODING_ERRORS(
1184 log_aserror(_("Attempt to set %s._xscale to %s "
1185 "(evaluating to number %g) refused"),
1186 o
.getTarget(), val
, scale_percent
);
1191 // input is in percent
1192 o
.set_x_scale(scale_percent
);
1197 getScaleX(DisplayObject
& o
)
1203 setScaleY(DisplayObject
& o
, const as_value
& val
)
1206 const double scale_percent
= val
.to_number();
1208 // NaN is skipped, Infinite is not, see actionscript.all/setProperty.as
1209 if (isNaN(scale_percent
))
1211 IF_VERBOSE_ASCODING_ERRORS(
1212 log_aserror(_("Attempt to set %s._yscale to %s "
1213 "(evaluating to number %g) refused"),
1214 o
.getTarget(), val
, scale_percent
);
1219 // input is in percent
1220 o
.set_y_scale(scale_percent
);
1225 getScaleY(DisplayObject
& o
)
1231 getVisible(DisplayObject
& o
)
1237 setVisible(DisplayObject
& o
, const as_value
& val
)
1240 /// We cast to number and rely (mostly) on C++'s automatic
1241 /// cast to bool, as string "0" should be converted to
1242 /// its numeric equivalent, not interpreted as 'true', which
1243 /// SWF7+ does for strings.
1244 double d
= val
.to_number();
1246 // Infinite or NaN is skipped
1247 if (isInf(d
) || isNaN(d
)) {
1248 IF_VERBOSE_ASCODING_ERRORS(
1249 log_aserror(_("Attempt to set %s._visible to %s "
1250 "(evaluating to number %g) refused"),
1251 o
.getTarget(), val
, d
);
1258 o
.transformedByScript();
1262 getAlpha(DisplayObject
& o
)
1264 return as_value(getCxForm(o
).aa
/ 2.56);
1268 setAlpha(DisplayObject
& o
, const as_value
& val
)
1271 // The new internal alpha value is input / 100.0 * 256.
1272 // We test for finiteness later, but the multiplication
1273 // won't make any difference.
1274 const double newAlpha
= val
.to_number() * 2.56;
1276 // NaN is skipped, Infinite is not, see actionscript.all/setProperty.as
1277 if (isNaN(newAlpha
)) {
1278 IF_VERBOSE_ASCODING_ERRORS(
1279 log_aserror(_("Attempt to set %s._alpha to %s "
1280 "(evaluating to number %g) refused"),
1281 o
.getTarget(), val
, newAlpha
);
1286 SWFCxForm cx
= getCxForm(o
);
1288 // Overflows are *not* truncated, but set to -32768.
1289 if (newAlpha
> std::numeric_limits
<boost::int16_t>::max() ||
1290 newAlpha
< std::numeric_limits
<boost::int16_t>::min()) {
1291 cx
.aa
= std::numeric_limits
<boost::int16_t>::min();
1294 cx
.aa
= static_cast<boost::int16_t>(newAlpha
);
1298 o
.transformedByScript();
1303 getMouseX(DisplayObject
& o
)
1305 // Local coord of mouse IN PIXELS.
1306 boost::int32_t x
, y
;
1307 getRoot(*getObject(&o
)).get_mouse_state(x
, y
);
1309 SWFMatrix m
= getWorldMatrix(o
);
1310 point
a(pixelsToTwips(x
), pixelsToTwips(y
));
1312 m
.invert().transform(a
);
1313 return as_value(twipsToPixels(a
.x
));
1317 getMouseY(DisplayObject
& o
)
1319 // Local coord of mouse IN PIXELS.
1320 boost::int32_t x
, y
;
1321 getRoot(*getObject(&o
)).get_mouse_state(x
, y
);
1323 SWFMatrix m
= getWorldMatrix(o
);
1324 point
a(pixelsToTwips(x
), pixelsToTwips(y
));
1325 m
.invert().transform(a
);
1326 return as_value(twipsToPixels(a
.y
));
1330 getRotation(DisplayObject
& o
)
1332 return o
.rotation();
1337 setRotation(DisplayObject
& o
, const as_value
& val
)
1340 // input is in degrees
1341 const double rotation_val
= val
.to_number();
1343 // NaN is skipped, Infinity isn't
1344 if (isNaN(rotation_val
))
1346 IF_VERBOSE_ASCODING_ERRORS(
1347 log_aserror(_("Attempt to set %s._rotation to %s "
1348 "(evaluating to number %g) refused"),
1349 o
.getTarget(), val
, rotation_val
);
1353 o
.set_rotation(rotation_val
);
1358 getParent(DisplayObject
& o
)
1360 as_object
* p
= getObject(o
.parent());
1361 return p
? p
: as_value();
1365 getTarget(DisplayObject
& o
)
1367 return o
.getTargetPath();
1371 getNameProperty(DisplayObject
& o
)
1373 string_table
& st
= getStringTable(*getObject(&o
));
1374 const std::string
& name
= o
.get_name().toString(st
);
1375 if (getSWFVersion(*getObject(&o
)) < 6 && name
.empty()) return as_value();
1376 return as_value(name
);
1380 setName(DisplayObject
& o
, const as_value
& val
)
1382 string_table
& st
= getStringTable(*getObject(&o
));
1383 o
.set_name(st
.find(val
.to_string().c_str()));
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(val
.to_number());
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
= dynamic_cast<MovieClip
*>(&o
);
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
= dynamic_cast<MovieClip
*>(&o
);
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
= dynamic_cast<MovieClip
*>(&o
);
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
= dynamic_cast<MovieClip
*>(&o
);
1467 if (!mc
) return as_value();
1468 return as_value(mc
->get_frame_count());
1473 getPropertyByIndex(size_t index
)
1476 // This is a magic number; defining it here makes sure that the
1477 // table is really this size.
1478 const size_t size
= 22;
1480 if (index
>= size
) return 0;
1482 static const string_table::key props
[size
] = {
1487 NSV::PROP_uCURRENTFRAME
,
1488 NSV::PROP_uTOTALFRAMES
,
1493 NSV::PROP_uROTATION
,
1495 NSV::PROP_uFRAMESLOADED
,
1497 NSV::PROP_uDROPTARGET
,
1499 NSV::PROP_uHIGHQUALITY
,
1500 NSV::PROP_uFOCUSRECT
,
1501 NSV::PROP_uSOUNDBUFTIME
,
1506 return props
[index
];
1510 doGet(string_table::key prop
, DisplayObject
& o
, as_value
& val
)
1512 const Getters
& getters
= displayObjectGetters();
1513 const Getters::const_iterator it
= getters
.find(prop
);
1514 if (it
== getters
.end()) return false;
1516 val
= (*it
->second
)(o
);
1521 /// Do the actual setProperty
1523 /// Return true if the property is a DisplayObject property, regardless of
1524 /// whether it was successfully set or not.
1526 /// @param prop The property to search for. Note that all special
1527 /// properties are lower-case, so for a caseless check
1528 /// it is sufficient for prop to be caseless.
1530 doSet(string_table::key prop
, DisplayObject
& o
, const as_value
& val
)
1532 const Setters
& setters
= displayObjectSetters();
1533 const Setters::const_iterator it
= setters
.find(prop
);
1534 if (it
== setters
.end()) return false;
1536 const Setter s
= it
->second
;
1539 if (!s
) return true;
1541 if (val
.is_undefined() || val
.is_null()) {
1542 IF_VERBOSE_ASCODING_ERRORS(
1543 log_aserror(_("Attempt to set property to %s, refused"),
1544 o
.getTarget(), val
);
1554 displayObjectGetters()
1556 static const Getters getters
= boost::assign::map_list_of
1557 (NSV::PROP_uX
, &getX
)
1558 (NSV::PROP_uY
, &getY
)
1559 (NSV::PROP_uXSCALE
, &getScaleX
)
1560 (NSV::PROP_uYSCALE
, &getScaleY
)
1561 (NSV::PROP_uROTATION
, &getRotation
)
1562 (NSV::PROP_uHIGHQUALITY
, &getHighQuality
)
1563 (NSV::PROP_uQUALITY
, &getQuality
)
1564 (NSV::PROP_uALPHA
, &getAlpha
)
1565 (NSV::PROP_uWIDTH
, &getWidth
)
1566 (NSV::PROP_uURL
, &getURL
)
1567 (NSV::PROP_uHEIGHT
, &getHeight
)
1568 (NSV::PROP_uNAME
, &getNameProperty
)
1569 (NSV::PROP_uVISIBLE
, &getVisible
)
1570 (NSV::PROP_uSOUNDBUFTIME
, &getSoundBufTime
)
1571 (NSV::PROP_uFOCUSRECT
, &getFocusRect
)
1572 (NSV::PROP_uDROPTARGET
, &getDropTarget
)
1573 (NSV::PROP_uCURRENTFRAME
, &getCurrentFrame
)
1574 (NSV::PROP_uFRAMESLOADED
, &getFramesLoaded
)
1575 (NSV::PROP_uTOTALFRAMES
, &getTotalFrames
)
1576 (NSV::PROP_uPARENT
, &getParent
)
1577 (NSV::PROP_uTARGET
, &getTarget
)
1578 (NSV::PROP_uXMOUSE
, &getMouseX
)
1579 (NSV::PROP_uYMOUSE
, &getMouseY
);
1584 displayObjectSetters()
1588 static const Setters setters
= boost::assign::map_list_of
1589 (NSV::PROP_uX
, &setX
)
1590 (NSV::PROP_uY
, &setY
)
1591 (NSV::PROP_uXSCALE
, &setScaleX
)
1592 (NSV::PROP_uYSCALE
, &setScaleY
)
1593 (NSV::PROP_uROTATION
, &setRotation
)
1594 (NSV::PROP_uHIGHQUALITY
, &setHighQuality
)
1595 (NSV::PROP_uQUALITY
, &setQuality
)
1596 (NSV::PROP_uALPHA
, &setAlpha
)
1597 (NSV::PROP_uWIDTH
, &setWidth
)
1598 (NSV::PROP_uHEIGHT
, &setHeight
)
1599 (NSV::PROP_uNAME
, &setName
)
1600 (NSV::PROP_uVISIBLE
, &setVisible
)
1601 (NSV::PROP_uSOUNDBUFTIME
, &setSoundBufTime
)
1602 (NSV::PROP_uFOCUSRECT
, &setFocusRect
)
1603 (NSV::PROP_uDROPTARGET
, n
)
1604 (NSV::PROP_uCURRENTFRAME
, n
)
1605 (NSV::PROP_uFRAMESLOADED
, n
)
1606 (NSV::PROP_uTOTALFRAMES
, n
)
1607 (NSV::PROP_uPARENT
, n
)
1609 (NSV::PROP_uTARGET
, n
)
1610 (NSV::PROP_uXMOUSE
, n
)
1611 (NSV::PROP_uYMOUSE
, n
);
1619 /// BLENDMODE_UNDEFINED has no matching string in AS. It is included
1620 /// here for logging purposes.
1621 static const BlendModeMap bm
= boost::assign::map_list_of
1622 (DisplayObject::BLENDMODE_UNDEFINED
, "undefined")
1623 (DisplayObject::BLENDMODE_NORMAL
, "normal")
1624 (DisplayObject::BLENDMODE_LAYER
, "layer")
1625 (DisplayObject::BLENDMODE_MULTIPLY
, "multiply")
1626 (DisplayObject::BLENDMODE_SCREEN
, "screen")
1627 (DisplayObject::BLENDMODE_LIGHTEN
, "lighten")
1628 (DisplayObject::BLENDMODE_DARKEN
, "darken")
1629 (DisplayObject::BLENDMODE_DIFFERENCE
, "difference")
1630 (DisplayObject::BLENDMODE_ADD
, "add")
1631 (DisplayObject::BLENDMODE_SUBTRACT
, "subtract")
1632 (DisplayObject::BLENDMODE_INVERT
, "invert")
1633 (DisplayObject::BLENDMODE_ALPHA
, "alpha")
1634 (DisplayObject::BLENDMODE_ERASE
, "erase")
1635 (DisplayObject::BLENDMODE_OVERLAY
, "overlay")
1636 (DisplayObject::BLENDMODE_HARDLIGHT
, "hardlight");
1641 // Match a blend mode to its string.
1643 blendModeMatches(const BlendModeMap::value_type
& val
, const std::string
& mode
)
1645 /// The match must be case-sensitive.
1646 if (mode
.empty()) return false;
1647 return (val
.second
== mode
);
1653 operator<<(std::ostream
& o
, DisplayObject::BlendMode bm
)
1655 const BlendModeMap
& bmm
= getBlendModeMap();
1656 return (o
<< bmm
.find(bm
)->second
);
1660 } // namespace gnash
1664 // indent-tabs-mode: t