Fix test for bug #32625
[gnash.git] / libcore / DisplayObject.cpp
blobfd43a9a94de1eb35cbd377f09f3573fc412f4a94
1 // DisplayObject.cpp: ActionScript DisplayObject class, for Gnash.
2 //
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
4 // Foundation, Inc
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 #ifdef HAVE_CONFIG_H
22 #include "gnashconfig.h" // USE_SWFTREE
23 #endif
25 #include "DisplayObject.h"
27 #include <utility>
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"
36 #include "VM.h"
37 #include "fn_call.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"
44 #include "Renderer.h"
45 #include "GnashAlgorithm.h"
46 #ifdef USE_SWFTREE
47 # include "tree.hh"
48 #endif
50 #undef set_invalidated
52 namespace gnash
55 // Forward declarations.
56 namespace {
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,
73 string_table& st);
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)
90 GcResource(mr.gc()),
91 _name(),
92 _parent(parent),
93 _object(object),
94 _stage(mr),
95 _xscale(100),
96 _yscale(100),
97 _rotation(0),
98 _depth(0),
99 _volume(100),
100 _ratio(0),
101 m_clip_depth(noClipDepthValue),
102 _mask(0),
103 _maskee(0),
104 _blendMode(BLENDMODE_NORMAL),
105 _visible(true),
106 _scriptTransformed(false),
107 _dynamicallyCreated(false),
108 _unloaded(false),
109 _destroyed(false),
110 _invalidated(true),
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);
119 void
120 DisplayObject::getLoadedMovie(Movie* extern_movie)
122 LOG_ONCE(
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);
132 ObjectURI
133 DisplayObject::getNextUnnamedInstanceName()
135 assert(_object);
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
148 int volume=_volume;
149 if (_parent != NULL)
151 volume = int(volume*_parent->getVolume()/100.0);
154 return volume;
158 as_object*
159 DisplayObject::pathElement(const ObjectURI& uri)
161 as_object* obj = getObject(this);
162 if (!obj) return 0;
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))) {
176 return obj;
178 return 0;
181 void
182 DisplayObject::set_invalidated()
184 set_invalidated("unknown", -1);
187 void
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 )
204 _invalidated = true;
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);
209 #else
210 UNUSED(debug_file);
211 UNUSED(debug_line);
212 #endif
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);
225 void
226 DisplayObject::add_invalidated_bounds(InvalidatedRanges& ranges, bool force)
228 ranges.add(m_old_invalidated_ranges);
229 if (visible() && (_invalidated||force))
231 SWFRect bounds;
232 bounds.expand_to_transformed_rect(getWorldMatrix(*this), getBounds());
233 ranges.add(bounds.getRange());
237 void
238 DisplayObject::set_child_invalidated()
240 if ( ! _child_invalidated )
242 _child_invalidated=true;
243 if ( _parent ) _parent->set_child_invalidated();
247 void
248 DisplayObject::extend_invalidated_bounds(const InvalidatedRanges& ranges)
250 set_invalidated(__FILE__, __LINE__);
251 m_old_invalidated_ranges.add(ranges);
254 as_value
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")));
263 if (!fn.nargs)
265 // Getter
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;
272 blendMode << bm;
273 return as_value(blendMode.str());
277 // Setter
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);
285 return as_value();
288 // Numeric argument.
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);
299 else {
300 /// The extra static cast is required to keep OpenBSD happy.
301 ch->setBlendMode(static_cast<BlendMode>(static_cast<int>(mode)));
303 return as_value();
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.
319 return as_value();
323 void
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) {
331 assert(_object);
332 movie_root& mr = getRoot(*_object);
333 if (mr.getFocus() == this) {
334 mr.setFocus(0);
337 _visible = visible;
340 void
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);
353 setMatrix(m, true);
356 as_value
357 getHeight(DisplayObject& o)
359 SWFRect bounds = o.getBounds();
360 const SWFMatrix m = getMatrix(o);
361 m.transform(bounds);
362 return twipsToPixels(bounds.height());
365 void
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);
378 void
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);
392 setMatrix(m, true);
395 void
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
405 if (updateCache) {
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;
413 void
414 DisplayObject::set_event_handlers(const Events& copyfrom)
416 for (Events::const_iterator it=copyfrom.begin(), itE=copyfrom.end();
417 it != itE; ++it)
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];
424 assert(buf);
425 add_event_handler(ev, *buf);
430 void
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) );
451 return handler;
454 bool
455 DisplayObject::unload()
458 const bool childHandler = unloadChildren();
460 if (!_unloaded) {
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;
471 if (!hasEvent) {
472 stage().removeQueuedConstructor(this);
475 _unloaded = true;
477 return hasEvent;
480 void
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);
488 bool
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();
500 return false;
504 /// Set the real and cached x scale.
506 /// Cached rotation and y scale are not updated.
507 void
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
525 // caches.
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.
539 void
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
555 // errors.
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
561 _rotation = rot;
563 transformedByScript();
567 /// Set the real and cached y scale.
569 /// Cached rotation and x scale are not updated.
570 void
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();
591 std::string
592 DisplayObject::getTargetPath() const
594 // TODO: check what happens when this DisplayObject
595 // is a Movie loaded into another
596 // running movie.
598 typedef std::vector<std::string> Path;
599 Path path;
601 // Build parents stack
602 const DisplayObject* topLevel = 0;
603 const DisplayObject* ch = this;
605 string_table& st = getStringTable(*getObject(this));
606 for (;;)
608 const DisplayObject* parent = ch->parent();
610 // Don't push the _root name on the stack
611 if (!parent) {
612 topLevel = ch;
613 break;
616 path.push_back(ch->get_name().toString(st));
617 ch = parent;
620 assert(topLevel);
622 if (path.empty()) {
623 if (&stage().getRootMovie() == this) return "/";
624 std::stringstream ss;
625 ss << "_level" << _depth-DisplayObject::staticDepthOffset;
626 return ss.str();
629 // Build the target string from the parents stack
630 std::string target;
631 if (topLevel != &stage().getRootMovie()) {
632 std::stringstream ss;
633 ss << "_level" <<
634 topLevel->get_depth() - DisplayObject::staticDepthOffset;
635 target = ss.str();
637 for (Path::reverse_iterator it=path.rbegin(), itEnd=path.rend();
638 it != itEnd; ++it) {
639 target += "/" + *it;
641 return target;
645 std::string
646 DisplayObject::getTarget() const
649 // TODO: check what happens when this DisplayObject
650 // is a Movie loaded into another
651 // running movie.
653 typedef std::vector<std::string> Path;
654 Path path;
656 // Build parents stack
657 const DisplayObject* ch = this;
658 string_table& st = stage().getVM().getStringTable();
659 for (;;) {
661 const DisplayObject* parent = ch->parent();
663 // Don't push the _root name on the stack
664 if (!parent) {
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());
674 else {
675 ss << "_level" <<
676 ch->get_depth() - DisplayObject::staticDepthOffset;
677 path.push_back(ss.str());
679 break;
682 path.push_back(ch->get_name().toString(st));
683 ch = parent;
686 assert (!path.empty());
688 // Build the target string from the parents stack
689 std::string target;
690 for (Path::const_reverse_iterator it=path.rbegin(), itEnd=path.rend();
691 it != itEnd; ++it) {
693 if (!target.empty()) target += ".";
694 target += *it;
697 return target;
701 void
702 DisplayObject::destroy()
704 // in case we are destroyed without being unloaded first
705 // see bug #21842
706 _unloaded = true;
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();
715 assert(!_destroyed);
716 _destroyed = true;
719 void
720 DisplayObject::markReachableResources() const
722 markOwnResources();
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
733 /// Video).
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.
737 bool
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;
748 as_value val;
749 if (!obj->get_member(NSV::PROP_USEHANDCURSOR, &val)) {
750 return true;
752 return toBool(val, getVM(*obj));
755 void
756 DisplayObject::setMask(DisplayObject* mask)
758 if ( _mask == mask ) return;
760 set_invalidated();
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
772 _mask->setMaskee(0);
775 // if we had a maskee, notify it to stop using
776 // us as a mask
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);
782 _mask = mask;
783 _maskee = 0;
785 if (_mask) {
786 /// Register as as masked by the mask
787 _mask->setMaskee(this);
791 void
792 DisplayObject::setMaskee(DisplayObject* maskee)
794 if ( _maskee == maskee ) { return; }
796 if (_maskee) {
797 // We don't want the maskee to call setMaskee(null)
798 // on us again
799 _maskee->_mask = 0;
802 _maskee = maskee;
804 if (!maskee)
806 // TODO: should we reset any original clip depth
807 // specified by PlaceObject tag ?
808 set_clip_depth(noClipDepthValue);
813 bool
814 DisplayObject::boundsInClippingArea(Renderer& renderer) const
816 SWFRect mybounds = getBounds();
817 getWorldMatrix(*this).transform(mybounds);
819 return renderer.bounds_in_clipping_area(mybounds.getRange());
822 #ifdef USE_SWFTREE
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;
832 os << get_depth();
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) {
837 os.str("");
838 os << get_ratio();
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) {
845 os.str("");
846 if (_maskee) os << "Dynamic mask";
847 else os << cd;
849 tr.append_child(it, std::make_pair(_("Clipping depth"), os.str()));
852 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));
862 os.str("");
863 os << _blendMode;
864 tr.append_child(it, std::make_pair(_("Blend mode"), os.str()));
865 #ifndef NDEBUG
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));
871 #endif
872 return it;
874 #endif
876 MovieClip*
877 DisplayObject::getAsRoot()
879 return get_root();
882 void
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"),
891 o.getTarget(), val);
893 return;
896 (*s)(o, val);
899 void
900 getIndexedProperty(size_t index, DisplayObject& o, as_value& val)
902 const Getter s = getGetterSetterByIndex(index).first;
903 if (!s) {
904 val.set_undefined();
905 return;
907 val = (*s)(o);
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
916 /// lookups.
918 /// Lookup order:
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.
926 bool
927 getDisplayObjectProperty(DisplayObject& obj, const ObjectURI& uri,
928 as_value& val)
931 as_object* o = getObject(&obj);
932 assert(o);
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);
942 if (mo) {
943 val = getObject(mo);
944 return true;
946 return false;
949 MovieClip* mc = obj.to_movie();
950 if (mc) {
951 DisplayObject* ch = mc->getDisplayListObject(uri);
952 if (ch) {
953 val = getObject(ch);
954 return true;
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))
965 default:
966 break;
967 case NSV::PROP_uROOT:
968 if (getSWFVersion(*o) < 5) break;
969 val = getObject(obj.getAsRoot());
970 return true;
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);
976 return true;
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;
986 return false;
990 bool
991 setDisplayObjectProperty(DisplayObject& obj, const ObjectURI& uri,
992 const as_value& val)
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)
1000 _renderer(r),
1001 _mask(o.visible() && o.getMask() && !o.getMask()->unloaded() ? o.getMask()
1002 : 0)
1004 if (!_mask) return;
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();
1019 namespace {
1021 as_value
1022 getQuality(DisplayObject& o)
1024 movie_root& mr = getRoot(*getObject(&o));
1025 switch (mr.getQuality())
1027 case QUALITY_BEST:
1028 return as_value("BEST");
1029 case QUALITY_HIGH:
1030 return as_value("HIGH");
1031 case QUALITY_MEDIUM:
1032 return as_value("MEDIUM");
1033 case QUALITY_LOW:
1034 return as_value("LOW");
1037 return as_value();
1041 void
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);
1063 return;
1066 as_value
1067 getURL(DisplayObject& o)
1069 return as_value(o.get_root()->url());
1072 as_value
1073 getHighQuality(DisplayObject& o)
1075 movie_root& mr = getRoot(*getObject(&o));
1076 switch (mr.getQuality())
1078 case QUALITY_BEST:
1079 return as_value(2.0);
1080 case QUALITY_HIGH:
1081 return as_value(1.0);
1082 case QUALITY_MEDIUM:
1083 case QUALITY_LOW:
1084 return as_value(0.0);
1086 return as_value();
1089 void
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);
1098 else {
1099 int i = static_cast<int>(q);
1100 switch(i)
1102 case 0:
1103 mr.setQuality(QUALITY_LOW);
1104 break;
1105 case 1:
1106 mr.setQuality(QUALITY_HIGH);
1107 break;
1108 case 2:
1109 mr.setQuality(QUALITY_BEST);
1110 break;
1116 void
1117 setY(DisplayObject& o, const as_value& val)
1120 const double newy = toNumber(val, getVM(*getObject(&o)));
1122 // NaN is skipped, Infinite isn't
1123 if (isNaN(newy))
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);
1130 return;
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)));
1136 o.setMatrix(m);
1137 o.transformedByScript();
1140 as_value
1141 getY(DisplayObject& o)
1143 const SWFMatrix m = getMatrix(o);
1144 return twipsToPixels(m.get_y_translation());
1147 void
1148 setX(DisplayObject& o, const as_value& val)
1151 const double newx = toNumber(val, getVM(*getObject(&o)));
1153 // NaN is skipped, Infinite isn't
1154 if (isNaN(newx))
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);
1161 return;
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)));
1167 o.setMatrix(m);
1168 o.transformedByScript();
1171 as_value
1172 getX(DisplayObject& o)
1174 const SWFMatrix m = getMatrix(o);
1175 return twipsToPixels(m.get_x_translation());
1178 void
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);
1191 return;
1194 // input is in percent
1195 o.set_x_scale(scale_percent);
1199 as_value
1200 getScaleX(DisplayObject& o)
1202 return o.scaleX();
1205 void
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);
1218 return;
1221 // input is in percent
1222 o.set_y_scale(scale_percent);
1226 as_value
1227 getScaleY(DisplayObject& o)
1229 return o.scaleY();
1232 as_value
1233 getVisible(DisplayObject& o)
1235 return o.visible();
1238 void
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);
1255 return;
1258 o.set_visible(d);
1260 o.transformedByScript();
1263 as_value
1264 getAlpha(DisplayObject& o)
1266 return as_value(getCxForm(o).aa / 2.56);
1269 void
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);
1285 return;
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();
1295 else {
1296 cx.aa = static_cast<boost::int16_t>(newAlpha);
1299 o.setCxForm(cx);
1300 o.transformedByScript();
1304 as_value
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));
1318 as_value
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));
1331 as_value
1332 getRotation(DisplayObject& o)
1334 return o.rotation();
1338 void
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);
1352 return;
1354 o.set_rotation(rotation_val);
1358 as_value
1359 getParent(DisplayObject& o)
1361 as_object* p = getObject(o.parent());
1362 return p ? p : as_value();
1365 as_value
1366 getTarget(DisplayObject& o)
1368 return o.getTargetPath();
1371 as_value
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);
1380 void
1381 setName(DisplayObject& o, const as_value& val)
1383 o.set_name(getURI(getVM(*getObject(&o)), val.to_string()));
1386 void
1387 setSoundBufTime(DisplayObject& /*o*/, const as_value& /*val*/)
1389 LOG_ONCE(log_unimpl("_soundbuftime setting"));
1392 as_value
1393 getSoundBufTime(DisplayObject& /*o*/)
1395 return as_value(0.0);
1398 as_value
1399 getWidth(DisplayObject& o)
1401 SWFRect bounds = o.getBounds();
1402 const SWFMatrix& m = getMatrix(o);
1403 m.transform(bounds);
1404 return twipsToPixels(bounds.width());
1407 void
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);
1420 as_value
1421 getFocusRect(DisplayObject& /*o*/)
1423 LOG_ONCE(log_unimpl("_focusrect"));
1424 return as_value(true);
1427 void
1428 setFocusRect(DisplayObject& /*o*/, const as_value& /*val*/)
1430 LOG_ONCE(log_unimpl("_focusrect setting"));
1433 as_value
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());
1442 as_value
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);
1453 as_value
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());
1462 as_value
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
1477 const GetterSetter&
1478 getGetterSetterByURI(const ObjectURI& uri, string_table& st)
1480 typedef std::map<ObjectURI, GetterSetter, ObjectURI::CaseLessThan>
1481 GetterSetters;
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);
1490 return none;
1493 return it->second;
1497 const GetterSetter&
1498 getGetterSetterByIndex(size_t index)
1500 const Setter n = 0;
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);
1536 return none;
1539 return props[index];
1543 bool
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;
1550 val = (*s)(o);
1551 return true;
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.
1563 bool
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);
1584 return true;
1587 (*s)(o, val);
1588 return true;
1592 const BlendModeMap&
1593 getBlendModeMap()
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");
1614 return bm;
1618 // Match a blend mode to its string.
1619 bool
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>
1632 const Map
1633 getURIMap(const typename Map::key_compare& cmp)
1635 const Setter n = 0;
1637 Map ret(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)));
1677 return ret;
1680 } // anonymous namespace
1682 std::ostream&
1683 operator<<(std::ostream& o, DisplayObject::BlendMode bm)
1685 const BlendModeMap& bmm = getBlendModeMap();
1686 return (o << bmm.find(bm)->second);
1689 } // namespace gnash
1691 // local variables:
1692 // mode: c++
1693 // indent-tabs-mode: t
1694 // end: