1 // DisplayObject.cpp:  ActionScript DisplayObject class, for Gnash.
2 //
3 //   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
4 //   Free Software 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.
15 //
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
19 
20 
21 #ifdef HAVE_CONFIG_H
22 #include "gnashconfig.h" // USE_SWFTREE
23 #endif
24 
25 #include "DisplayObject.h"
26 
27 #include <utility>
28 #include <functional>
29 #include <boost/logic/tribool.hpp>
30 
31 #include "movie_root.h"
32 #include "MovieClip.h"
33 #include "Movie.h"
34 #include "DisplayObject.h"
35 #include "Object.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
49 
50 #undef set_invalidated
51 
52 namespace gnash {
53 
54 // Forward declarations.
55 namespace {
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);
61 
62     typedef as_value(*Getter)(DisplayObject&);
63     typedef void(*Setter)(DisplayObject&, const as_value&);
64     typedef std::pair<Getter, Setter> GetterSetter;
65 
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);
69 
70     // NOTE: comparison will be case-insensitive
71     const GetterSetter& getGetterSetterByURI(const ObjectURI& uri,
72             string_table& st);
73 
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);
77 }
78 
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;
85 
DisplayObject(movie_root & mr,as_object * object,DisplayObject * parent)86 DisplayObject::DisplayObject(movie_root& mr, as_object* object,
87         DisplayObject* parent)
88     :
89     GcResource(mr.gc()),
90     _name(),
91     _parent(parent),
92     _object(object),
93     _stage(mr),
94     _xscale(100),
95     _yscale(100),
96     _rotation(0),
97     _depth(0),
98     _focusRect(parent ? boost::tribool(boost::indeterminate) :
99                         boost::tribool(true)),
100     _volume(100),
101     _ratio(0),
102     m_clip_depth(noClipDepthValue),
103     _mask(nullptr),
104     _maskee(nullptr),
105     _blendMode(BLENDMODE_NORMAL),
106     _visible(true),
107     _scriptTransformed(false),
108     _dynamicallyCreated(false),
109     _unloaded(false),
110     _destroyed(false),
111     _invalidated(true),
112     _child_invalidated(true)
113 {
114     assert(m_old_invalidated_ranges.isNull());
115 
116     // This informs the core that the object is a DisplayObject.
117     if (_object) _object->setDisplayObject(this);
118 }
119 
120 void
getLoadedMovie(Movie * extern_movie)121 DisplayObject::getLoadedMovie(Movie* extern_movie)
122 {
123     LOG_ONCE(log_unimpl(_("loadMovie against a %s DisplayObject"),
124 			  typeName(*this))
125     );
126 
127     // TODO: look at the MovieClip implementation, but most importantly
128     //       test all the event handlers copies etc..
129 
130     UNUSED(extern_movie);
131 }
132 
133 ObjectURI
getNextUnnamedInstanceName()134 DisplayObject::getNextUnnamedInstanceName()
135 {
136     assert(_object);
137     movie_root& mr = stage();
138 
139     std::ostringstream ss;
140     ss << "instance" << mr.nextUnnamedInstance();
141 
142     VM& vm = mr.getVM();
143     return getURI(vm, ss.str(), true);
144 }
145 
146 
147 int
getWorldVolume() const148 DisplayObject::getWorldVolume() const
149 {
150     int volume = _volume;
151     if (_parent) {
152         volume = int(volume*_parent->getVolume()/100.0);
153     }
154 
155     return volume;
156 }
157 
158 
159 as_object*
pathElement(const ObjectURI & uri)160 DisplayObject::pathElement(const ObjectURI& uri)
161 {
162     as_object* obj = getObject(this);
163     if (!obj) return nullptr;
164 
165     string_table::key key = getName(uri);
166 
167     string_table& st = stage().getVM().getStringTable();
168 
169     // TODO: put ".." and "." in namedStrings
170     if (key == st.find("..")) return getObject(parent());
171     if (key == st.find(".")) return obj;
172 
173     // The check is case-insensitive for SWF6 and below.
174     // TODO: cache ObjectURI(NSV::PROP_THIS) [as many others...]
175     if (ObjectURI::CaseEquals(st, caseless(*obj))
176             (uri, ObjectURI(NSV::PROP_THIS))) {
177         return obj;
178     }
179     return nullptr;
180 }
181 
182 void
set_invalidated()183 DisplayObject::set_invalidated()
184 {
185     set_invalidated("unknown", -1);
186 }
187 
188 void
set_invalidated(const char * debug_file,int debug_line)189 DisplayObject::set_invalidated(const char* debug_file, int debug_line)
190 {
191     // Set the invalidated-flag of the parent. Note this does not mean that
192     // the parent must re-draw itself, it just means that one of it's childs
193     // needs to be re-drawn.
194     if ( _parent ) _parent->set_child_invalidated();
195 
196     // Ok, at this point the instance will change it's
197     // visual aspect after the
198     // call to set_invalidated(). We save the *current*
199     // position of the instance because this region must
200     // be updated even (or first of all) if the DisplayObject
201     // moves away from here.
202     //
203     if ( ! _invalidated )
204     {
205         _invalidated = true;
206 
207 #ifdef DEBUG_SET_INVALIDATED
208         log_debug("%p set_invalidated() of %s in %s:%d",
209             (void*)this, getTarget(), debug_file, debug_line);
210 #else
211         UNUSED(debug_file);
212         UNUSED(debug_line);
213 #endif
214 
215         // NOTE: the SnappingRanges instance used here is not initialized by the
216         // GUI and therefore uses the default settings. This should not be a
217         // problem but special snapping ranges configuration done in gui.cpp
218         // is ignored here...
219 
220         m_old_invalidated_ranges.setNull();
221         add_invalidated_bounds(m_old_invalidated_ranges, true);
222     }
223 }
224 
225 void
add_invalidated_bounds(InvalidatedRanges & ranges,bool force)226 DisplayObject::add_invalidated_bounds(InvalidatedRanges& ranges, bool force)
227 {
228     ranges.add(m_old_invalidated_ranges);
229     if (visible() && (_invalidated||force))
230     {
231         SWFRect bounds;
232         bounds.expand_to_transformed_rect(getWorldMatrix(*this), getBounds());
233         ranges.add(bounds.getRange());
234     }
235 }
236 
237 void
set_child_invalidated()238 DisplayObject::set_child_invalidated()
239 {
240     if (!_child_invalidated) {
241         _child_invalidated=true;
242         if (_parent) _parent->set_child_invalidated();
243     }
244 }
245 
246 void
extend_invalidated_bounds(const InvalidatedRanges & ranges)247 DisplayObject::extend_invalidated_bounds(const InvalidatedRanges& ranges)
248 {
249     set_invalidated(__FILE__, __LINE__);
250     m_old_invalidated_ranges.add(ranges);
251 }
252 
253 as_value
blendMode(const fn_call & fn)254 DisplayObject::blendMode(const fn_call& fn)
255 {
256     DisplayObject* ch = ensure<IsDisplayObject<> >(fn);
257 
258     // This is AS-correct, but doesn't do anything.
259     // TODO: implement in the renderers!
260     LOG_ONCE(log_unimpl(_("blendMode")));
261 
262     if (!fn.nargs)
263     {
264         // Getter
265         BlendMode bm = ch->getBlendMode();
266 
267         /// If the blend mode is undefined, it doesn't return a string.
268         if (bm == BLENDMODE_UNDEFINED) return as_value();
269 
270         std::ostringstream blendMode;
271         blendMode << bm;
272         return as_value(blendMode.str());
273     }
274 
275     //
276     // Setter
277     //
278 
279     const as_value& bm = fn.arg(0);
280 
281     // Undefined argument sets blend mode to normal.
282     if (bm.is_undefined()) {
283         ch->setBlendMode(BLENDMODE_NORMAL);
284         return as_value();
285     }
286 
287     // Numeric argument.
288     if (bm.is_number()) {
289         double mode = toNumber(bm, getVM(fn));
290 
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) {
294 
295             // An invalid numeric argument becomes undefined.
296             ch->setBlendMode(BLENDMODE_UNDEFINED);
297         }
298         else {
299             /// The extra static cast is required to keep OpenBSD happy.
300             ch->setBlendMode(static_cast<BlendMode>(static_cast<int>(mode)));
301         }
302         return as_value();
303     }
304 
305     // Other arguments use toString method.
306     const std::string& mode = bm.to_string();
307 
308     const BlendModeMap& bmm = getBlendModeMap();
309     BlendModeMap::const_iterator it = std::find_if(bmm.begin(), bmm.end(),
310             std::bind(blendModeMatches, std::placeholders::_1, mode));
311 
312     if (it != bmm.end()) {
313         ch->setBlendMode(it->first);
314     }
315 
316     // An invalid string argument has no effect.
317 
318     return as_value();
319 
320 }
321 
322 void
set_visible(bool visible)323 DisplayObject::set_visible(bool visible)
324 {
325     if (_visible != visible) set_invalidated(__FILE__, __LINE__);
326 
327     // Remove focus from this DisplayObject if it changes from visible to
328     // invisible (see Selection.as).
329     if (_visible && !visible) {
330         assert(_object);
331         movie_root& mr = stage();
332         if (mr.getFocus() == this) {
333             mr.setFocus(nullptr);
334         }
335     }
336     _visible = visible;
337 }
338 
339 void
setWidth(double newwidth)340 DisplayObject::setWidth(double newwidth)
341 {
342     const SWFRect& bounds = getBounds();
343     const double oldwidth = bounds.width();
344     assert(oldwidth >= 0);
345 
346     const double xscale = oldwidth ? (newwidth / oldwidth) : 0;
347     const double rotation = _rotation * PI / 180.0;
348 
349     SWFMatrix m = getMatrix(*this);
350     const double yscale = m.get_y_scale();
351     m.set_scale_rotation(xscale, yscale, rotation);
352     setMatrix(m, true);
353 }
354 
355 as_value
getHeight(DisplayObject & o)356 getHeight(DisplayObject& o)
357 {
358     SWFRect bounds = o.getBounds();
359     const SWFMatrix& m = getMatrix(o);
360     m.transform(bounds);
361     return twipsToPixels(bounds.height());
362 }
363 
364 void
setHeight(DisplayObject & o,const as_value & val)365 setHeight(DisplayObject& o, const as_value& val)
366 {
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));
372         );
373     }
374     o.setHeight(newheight);
375 }
376 
377 void
setHeight(double newheight)378 DisplayObject::setHeight(double newheight)
379 {
380     const SWFRect& bounds = getBounds();
381 
382     const double oldheight = bounds.height();
383     assert(oldheight >= 0);
384 
385     const double yscale = oldheight ? (newheight / oldheight) : 0;
386     const double rotation = _rotation * PI / 180.0;
387 
388     SWFMatrix m = getMatrix(*this);
389     const double xscale = m.get_x_scale();
390     m.set_scale_rotation(xscale, yscale, rotation);
391     setMatrix(m, true);
392 }
393 
394 void
setMatrix(const SWFMatrix & m,bool updateCache)395 DisplayObject::setMatrix(const SWFMatrix& m, bool updateCache)
396 {
397 
398     if (m == _transform.matrix) return;
399 
400     set_invalidated(__FILE__, __LINE__);
401     _transform.matrix = m;
402 
403     // don't update caches if SWFMatrix wasn't updated too
404     if (updateCache) {
405         _xscale = _transform.matrix.get_x_scale() * 100.0;
406         _yscale = _transform.matrix.get_y_scale() * 100.0;
407         _rotation = _transform.matrix.get_rotation() * 180.0 / PI;
408     }
409 
410 }
411 
412 void
set_event_handlers(const Events & copyfrom)413 DisplayObject::set_event_handlers(const Events& copyfrom)
414 {
415     for (const auto& event : copyfrom)
416     {
417         const event_id& ev = event.first;
418         const BufferList& bufs = event.second;
419         for (const action_buffer* buf : bufs)
420         {
421             assert(buf);
422             add_event_handler(ev, *buf);
423         }
424     }
425 }
426 
427 void
add_event_handler(const event_id & id,const action_buffer & code)428 DisplayObject::add_event_handler(const event_id& id, const action_buffer& code)
429 {
430     _event_handlers[id].push_back(&code);
431 }
432 
433 std::unique_ptr<ExecutableCode>
get_event_handler(const event_id & id) const434 DisplayObject::get_event_handler(const event_id& id) const
435 {
436     std::unique_ptr<ExecutableCode> handler;
437 
438     Events::const_iterator it = _event_handlers.find(id);
439     if ( it == _event_handlers.end() ) return handler;
440 
441     DisplayObject* this_ptr = const_cast<DisplayObject*>(this);
442 
443     handler.reset( new EventCode(this_ptr, it->second) );
444     return handler;
445 }
446 
447 bool
unload()448 DisplayObject::unload()
449 {
450     const bool unloadHandler = unloadChildren();
451 
452     // Unregister this DisplayObject as mask and/or maskee.
453     if (_maskee) _maskee->setMask(nullptr);
454     if (_mask) _mask->setMaskee(nullptr);
455 
456     _unloaded = true;
457 
458     return unloadHandler;
459 }
460 
461 bool
hasEventHandler(const event_id & id) const462 DisplayObject::hasEventHandler(const event_id& id) const
463 {
464     Events::const_iterator it = _event_handlers.find(id);
465     if (it != _event_handlers.end()) return true;
466 
467     if (!_object) return false;
468 
469     // Don't check resolve! Also don't check if it's a function, as
470     // the swfdec testsuite (onUnload-prototype.as) shows that it
471     // doesn't matter.
472     if (Property* prop = _object->findProperty(id.functionURI())) {
473         return prop;
474     }
475     return false;
476 
477 }
478 
479 /// Set the real and cached x scale.
480 //
481 /// Cached rotation and y scale are not updated.
482 void
set_x_scale(double scale_percent)483 DisplayObject::set_x_scale(double scale_percent)
484 {
485     double xscale = scale_percent / 100.0;
486 
487     if (xscale != 0.0 && _xscale != 0.0)
488     {
489         if (scale_percent * _xscale < 0.0)
490         {
491             xscale = -std::abs(xscale);
492         }
493         else xscale = std::abs(xscale);
494     }
495 
496     _xscale = scale_percent;
497 
498     // As per misc-ming.all/SWFMatrix_test.{c,swf}
499     // we don't need to recompute the SWFMatrix from the
500     // caches.
501 
502     SWFMatrix m = getMatrix(*this);
503 
504     m.set_x_scale(xscale);
505 
506     setMatrix(m); // we updated the cache ourselves
507 
508     transformedByScript();
509 }
510 
511 /// Set the real and cached rotation.
512 //
513 /// Cached scale values are not updated.
514 void
set_rotation(double rot)515 DisplayObject::set_rotation(double rot)
516 {
517     // Translate to the -180 .. 180 range
518     rot = std::fmod(rot, 360.0);
519     if (rot > 180.0) rot -= 360.0;
520     else if (rot < -180.0) rot += 360.0;
521 
522     double rotation = rot * PI / 180.0;
523 
524     if (_xscale < 0) rotation += PI;
525 
526     SWFMatrix m = getMatrix(*this);
527     m.set_rotation(rotation);
528 
529     // Update the matrix from the cached x scale to avoid accumulating
530     // errors.
531     // TODO: also update y scale? The x scale update is needed to keep
532     // TextField correct; no tests for y scale.
533     m.set_x_scale(std::abs(scaleX() / 100.0));
534     setMatrix(m); // we update the cache ourselves
535 
536     _rotation = rot;
537 
538     transformedByScript();
539 }
540 
541 
542 /// Set the real and cached y scale.
543 //
544 /// Cached rotation and x scale are not updated.
545 void
set_y_scale(double scale_percent)546 DisplayObject::set_y_scale(double scale_percent)
547 {
548     double yscale = scale_percent / 100.0;
549 
550     if (yscale != 0.0 && _yscale != 0.0)
551     {
552         if (scale_percent * _yscale < 0.0) yscale = -std::abs(yscale);
553         else yscale = std::abs(yscale);
554     }
555 
556     _yscale = scale_percent;
557 
558     SWFMatrix m = getMatrix(*this);
559     m.set_y_scale(yscale);
560     setMatrix(m); // we updated the cache ourselves
561 
562     transformedByScript();
563 }
564 
565 
566 std::string
getTargetPath() const567 DisplayObject::getTargetPath() const
568 {
569     // TODO: check what happens when this DisplayObject
570     //       is a Movie loaded into another
571     //       running movie.
572 
573     typedef std::vector<std::string> Path;
574     Path path;
575 
576     // Build parents stack
577     const DisplayObject* topLevel = nullptr;
578     const DisplayObject* ch = this;
579 
580     string_table& st = getStringTable(*getObject(this));
581     for (;;)
582     {
583         const DisplayObject* parent = ch->parent();
584 
585         // Don't push the _root name on the stack
586         if (!parent) {
587             topLevel = ch;
588             break;
589         }
590 
591         path.push_back(ch->get_name().toString(st));
592         ch = parent;
593     }
594 
595     assert(topLevel);
596 
597     if (path.empty()) {
598         if (&stage().getRootMovie() == this) return "/";
599         std::stringstream ss;
600         ss << "_level" << _depth-DisplayObject::staticDepthOffset;
601         return ss.str();
602     }
603 
604     // Build the target string from the parents stack
605     std::string target;
606     if (topLevel != &stage().getRootMovie()) {
607         std::stringstream ss;
608         ss << "_level" <<
609             topLevel->get_depth() - DisplayObject::staticDepthOffset;
610         target = ss.str();
611     }
612     for (Path::reverse_iterator it=path.rbegin(), itEnd=path.rend();
613             it != itEnd; ++it) {
614         target += "/" + *it;
615     }
616     return target;
617 }
618 
619 
620 std::string
getTarget() const621 DisplayObject::getTarget() const
622 {
623 
624     // TODO: check what happens when this DisplayObject
625     //       is a Movie loaded into another
626     //       running movie.
627 
628     typedef std::vector<std::string> Path;
629     Path path;
630 
631     // Build parents stack
632     const DisplayObject* ch = this;
633     string_table& st = stage().getVM().getStringTable();
634     for (;;) {
635 
636         const DisplayObject* parent = ch->parent();
637 
638         // Don't push the _root name on the stack
639         if (!parent) {
640 
641             std::stringstream ss;
642             if (!dynamic_cast<const Movie*>(ch)) {
643                 // must be an as-referenceable
644                 // DisplayObject created using 'new'
645                 // like, new MovieClip, new Video, new TextField...
646                 ss << "<no parent, depth" << ch->get_depth() << ">";
647                 path.push_back(ss.str());
648             }
649             else {
650                 ss << "_level" <<
651                     ch->get_depth() - DisplayObject::staticDepthOffset;
652                 path.push_back(ss.str());
653             }
654             break;
655         }
656 
657         path.push_back(ch->get_name().toString(st));
658         ch = parent;
659     }
660 
661     assert (!path.empty());
662 
663     // Build the target string from the parents stack
664     std::string target;
665     for (Path::const_reverse_iterator it=path.rbegin(), itEnd=path.rend();
666             it != itEnd; ++it) {
667 
668         if (!target.empty()) target += ".";
669         target += *it;
670     }
671 
672     return target;
673 }
674 
675 
676 void
destroy()677 DisplayObject::destroy()
678 {
679     // in case we are destroyed without being unloaded first
680     // see bug #21842
681     _unloaded = true;
682 
683     /// we may destory a DisplayObject that's not unloaded.
684     ///(we don't have chance to unload it in current model,
685     /// see new_child_in_unload_test.c)
686     /// We don't destroy ourself twice, right ?
687 
688     if (_object) _object->clearProperties();
689 
690     assert(!_destroyed);
691     _destroyed = true;
692 }
693 
694 void
markReachableResources() const695 DisplayObject::markReachableResources() const
696 {
697     markOwnResources();
698     if (_object) _object->setReachable();
699     if (_parent) _parent->setReachable();
700     if (_mask) _mask->setReachable();
701     if (_maskee) _maskee->setReachable();
702 }
703 
704 /// Whether to use a hand cursor when the mouse is over this DisplayObject
705 //
706 /// This depends on the useHandCursor AS property, but:
707 /// 1. Only AS-referenceable objects may use a hand cursor (TODO: check
708 ///    Video).
709 /// 2. Only objects with a release event may use a hand cursor.
710 ///    CANNOT CONFIRM THE ABOVE, SEE ButtonEventsTest.swf in misc-ming.all
711 /// 3. The default value (if the property is not defined) is true.
712 bool
allowHandCursor() const713 DisplayObject::allowHandCursor() const
714 {
715     as_object* obj = getObject(this);
716     if (!obj) return false;
717 
718     as_value val;
719     if (!obj->get_member(NSV::PROP_USEHANDCURSOR, &val)) {
720          return true;
721     }
722     return toBool(val, getVM(*obj));
723 }
724 
725 void
setMask(DisplayObject * mask)726 DisplayObject::setMask(DisplayObject* mask)
727 {
728     if ( _mask == mask ) return;
729 
730     set_invalidated();
731 
732     // Backup this before setMaskee has a chance to change it..
733     DisplayObject* prevMaskee = _maskee;
734 
735     // If we had a previous mask unregister with it
736     if ( _mask && _mask != mask )
737     {
738         // the mask will call setMask(NULL)
739         // on any previously registered maskee
740         // so we make sure to set our _mask to
741         // NULL before getting called again
742         _mask->setMaskee(nullptr);
743     }
744 
745     // if we had a maskee, notify it to stop using
746     // us as a mask
747     if (prevMaskee) prevMaskee->setMask(nullptr);
748 
749     // TODO: should we reset any original clip depth
750     //       specified by PlaceObject tag ?
751     set_clip_depth(noClipDepthValue);
752     _mask = mask;
753     _maskee = nullptr;
754 
755     if (_mask) {
756         /// Register as as masked by the mask
757         _mask->setMaskee(this);
758     }
759 }
760 
761 void
setMaskee(DisplayObject * maskee)762 DisplayObject::setMaskee(DisplayObject* maskee)
763 {
764     if ( _maskee == maskee ) { return; }
765 
766     if (_maskee) {
767         // We don't want the maskee to call setMaskee(null)
768         // on us again
769         _maskee->_mask = nullptr;
770     }
771 
772     _maskee = maskee;
773 
774     if (!maskee)
775     {
776         // TODO: should we reset any original clip depth
777         //       specified by PlaceObject tag ?
778         set_clip_depth(noClipDepthValue);
779     }
780 }
781 
782 
783 bool
boundsInClippingArea(Renderer & renderer) const784 DisplayObject::boundsInClippingArea(Renderer& renderer) const
785 {
786     SWFRect mybounds = getBounds();
787     getWorldMatrix(*this).transform(mybounds);
788 
789     return renderer.bounds_in_clipping_area(mybounds.getRange());
790 }
791 
792 #ifdef USE_SWFTREE
793 DisplayObject::InfoTree::iterator
getMovieInfo(InfoTree & tr,InfoTree::iterator it)794 DisplayObject::getMovieInfo(InfoTree& tr, InfoTree::iterator it)
795 {
796     const std::string yes = _("yes");
797     const std::string no = _("no");
798 
799     it = tr.append_child(it, std::make_pair(getTarget(), typeName(*this)));
800 
801     std::ostringstream os;
802     os << get_depth();
803     tr.append_child(it, std::make_pair(_("Depth"), os.str()));
804 
805     /// Don't add if the DisplayObject has no ratio value
806     if (get_ratio() > 0) {
807         os.str("");
808         os << get_ratio();
809         tr.append_child(it, std::make_pair(_("Ratio"), os.str()));
810     }
811 
812     /// Don't add if it's not a real clipping depth
813     const int cd = get_clip_depth();
814     if (cd != noClipDepthValue) {
815         os.str("");
816         if (_maskee) os << "Dynamic mask";
817         else os << cd;
818 
819         tr.append_child(it, std::make_pair(_("Clipping depth"), os.str()));
820     }
821 
822     os.str("");
823     os << getBounds().width() << "x" << getBounds().height();
824     tr.append_child(it, std::make_pair(_("Dimensions"), os.str()));
825 
826     tr.append_child(it, std::make_pair(_("Dynamic"), isDynamic() ? yes : no));
827     tr.append_child(it, std::make_pair(_("Mask"), isMaskLayer() ? yes : no));
828     tr.append_child(it, std::make_pair(_("Destroyed"),
829                 isDestroyed() ? yes : no));
830     tr.append_child(it, std::make_pair(_("Unloaded"), unloaded() ? yes : no));
831 
832     os.str("");
833     os << _blendMode;
834     tr.append_child(it, std::make_pair(_("Blend mode"), os.str()));
835 #ifndef NDEBUG
836     // This probably isn't interesting for non-developers
837     tr.append_child(it, std::make_pair(_("Invalidated"),
838                 _invalidated ? yes : no));
839     tr.append_child(it, std::make_pair(_("Child invalidated"),
840                 _child_invalidated ? yes : no));
841 #endif
842     return it;
843 }
844 #endif
845 
846 MovieClip*
getAsRoot()847 DisplayObject::getAsRoot()
848 {
849     return get_root();
850 }
851 
852 void
setIndexedProperty(size_t index,DisplayObject & o,const as_value & val)853 setIndexedProperty(size_t index, DisplayObject& o, const as_value& val)
854 {
855     const Setter s = getGetterSetterByIndex(index).second;
856     if (!s) return; // read-only (warn?)
857 
858     if (val.is_undefined() || val.is_null()) {
859         IF_VERBOSE_ASCODING_ERRORS(
860             log_aserror(_("Attempt to set property to %s, refused"),
861                 o.getTarget(), val);
862         );
863         return;
864     }
865 
866     (*s)(o, val);
867 }
868 
869 void
getIndexedProperty(size_t index,DisplayObject & o,as_value & val)870 getIndexedProperty(size_t index, DisplayObject& o, as_value& val)
871 {
872     const Getter s = getGetterSetterByIndex(index).first;
873     if (!s) {
874         val.set_undefined();
875         return;
876     }
877     val = (*s)(o);
878 }
879 
880 
881 /// DisplayObject property lookup
882 //
883 /// This function is only called on the first object in the inheritance chain
884 /// after the object's own properties have been checked.
885 /// In AS2, any DisplayObject marks the end of the inheritance chain for
886 /// lookups.
887 //
888 /// Lookup order:
889 //
890 /// 1. _level0.._level9
891 /// 2. Objects on the DisplayList of a MovieClip
892 /// 3. DisplayObject magic properties (_x, _y etc).
893 /// 4. MovieClips' TextField variables (this is probably not the best
894 ///    way to do it, but as it is done like this, this must be called here.
895 ///    It will cause an infinite recursion otherwise.
896 bool
getDisplayObjectProperty(DisplayObject & obj,const ObjectURI & uri,as_value & val)897 getDisplayObjectProperty(DisplayObject& obj, const ObjectURI& uri,
898         as_value& val)
899 {
900 
901     as_object* o = getObject(&obj);
902     assert(o);
903 
904     string_table& st = getStringTable(*o);
905     const std::string& propname = uri.toString(st);
906 
907     // Check _level0.._level9
908     unsigned int levelno;
909     if (isLevelTarget(getSWFVersion(*o), propname, levelno)) {
910         movie_root& mr = getRoot(*getObject(&obj));
911         MovieClip* mo = mr.getLevel(levelno);
912         if (mo) {
913             val = getObject(mo);
914             return true;
915         }
916         return false;
917     }
918 
919     MovieClip* mc = obj.to_movie();
920     if (mc) {
921         DisplayObject* ch = mc->getDisplayListObject(uri);
922         if (ch) {
923            val = getObject(ch);
924            return true;
925         }
926     }
927 
928     const string_table::key noCaseKey = uri.noCase(st);
929 
930     // These properties have normal case-sensitivity.
931     // They are tested to exist for TextField, MovieClip, and Button
932     // but do not belong to the inheritance chain.
933     switch (caseless(*o) ? noCaseKey : getName(uri))
934     {
935         default:
936             break;
937         case NSV::PROP_uROOT:
938             if (getSWFVersion(*o) < 5) break;
939             val = getObject(obj.getAsRoot());
940             return true;
941         case NSV::PROP_uGLOBAL:
942             // TODO: clean up this mess.
943             assert(getObject(&obj));
944             if (getSWFVersion(*o) < 6) break;
945             val = &getGlobal(*o);
946             return true;
947     }
948 
949     // These magic properties are case insensitive in all versions!
950     if (doGet(uri, obj, val)) return true;
951 
952     // Check MovieClip such as TextField variables.
953     // TODO: check if there's a better way to find these properties.
954     if (mc && mc->getTextFieldVariables(uri, val)) return true;
955 
956     return false;
957 }
958 
959 
960 bool
setDisplayObjectProperty(DisplayObject & obj,const ObjectURI & uri,const as_value & val)961 setDisplayObjectProperty(DisplayObject& obj, const ObjectURI& uri,
962         const as_value& val)
963 {
964     // These magic properties are case insensitive in all versions!
965     return doSet(uri, obj, val);
966 }
967 
MaskRenderer(Renderer & r,const DisplayObject & o)968 DisplayObject::MaskRenderer::MaskRenderer(Renderer& r, const DisplayObject& o)
969     :
970     _renderer(r),
971     _mask(o.visible() && o.getMask() && !o.getMask()->unloaded() ? o.getMask()
972                                                                  : nullptr)
973 {
974     if (!_mask) return;
975 
976     _renderer.begin_submit_mask();
977     DisplayObject* p = _mask->parent();
978     const Transform tr = p ?
979         Transform(getWorldMatrix(*p), getWorldCxForm(*p)) : Transform();
980     _mask->display(_renderer, tr);
981     _renderer.end_submit_mask();
982 }
983 
~MaskRenderer()984 DisplayObject::MaskRenderer::~MaskRenderer()
985 {
986     if (_mask) _renderer.disable_mask();
987 }
988 
989 namespace {
990 
991 as_value
getQuality(DisplayObject & o)992 getQuality(DisplayObject& o)
993 {
994     movie_root& mr = getRoot(*getObject(&o));
995     switch (mr.getQuality())
996     {
997         case QUALITY_BEST:
998             return as_value("BEST");
999         case QUALITY_HIGH:
1000             return as_value("HIGH");
1001         case QUALITY_MEDIUM:
1002             return as_value("MEDIUM");
1003         case QUALITY_LOW:
1004             return as_value("LOW");
1005     }
1006 
1007     return as_value();
1008 
1009 }
1010 
1011 void
setQuality(DisplayObject & o,const as_value & val)1012 setQuality(DisplayObject& o, const as_value& val)
1013 {
1014     movie_root& mr = getRoot(*getObject(&o));
1015 
1016     if (!val.is_string()) return;
1017 
1018     const std::string& q = val.to_string();
1019 
1020     StringNoCaseEqual noCaseCompare;
1021 
1022     if (noCaseCompare(q, "BEST")) mr.setQuality(QUALITY_BEST);
1023     else if (noCaseCompare(q, "HIGH")) {
1024         mr.setQuality(QUALITY_HIGH);
1025     }
1026     else if (noCaseCompare(q, "MEDIUM")) {
1027         mr.setQuality(QUALITY_MEDIUM);
1028     }
1029     else if (noCaseCompare(q, "LOW")) {
1030             mr.setQuality(QUALITY_LOW);
1031     }
1032 
1033     return;
1034 }
1035 
1036 as_value
getURL(DisplayObject & o)1037 getURL(DisplayObject& o)
1038 {
1039     return as_value(o.get_root()->url());
1040 }
1041 
1042 as_value
getHighQuality(DisplayObject & o)1043 getHighQuality(DisplayObject& o)
1044 {
1045     movie_root& mr = getRoot(*getObject(&o));
1046     switch (mr.getQuality())
1047     {
1048         case QUALITY_BEST:
1049             return as_value(2.0);
1050         case QUALITY_HIGH:
1051             return as_value(1.0);
1052         case QUALITY_MEDIUM:
1053         case QUALITY_LOW:
1054             return as_value(0.0);
1055     }
1056     return as_value();
1057 }
1058 
1059 void
setHighQuality(DisplayObject & o,const as_value & val)1060 setHighQuality(DisplayObject& o, const as_value& val)
1061 {
1062     movie_root& mr = getRoot(*getObject(&o));
1063 
1064     const double q = toNumber(val, getVM(*getObject(&o)));
1065 
1066     if (q < 0) mr.setQuality(QUALITY_HIGH);
1067     else if (q > 2) mr.setQuality(QUALITY_BEST);
1068     else {
1069         int i = static_cast<int>(q);
1070         switch(i)
1071         {
1072             case 0:
1073                 mr.setQuality(QUALITY_LOW);
1074                 break;
1075             case 1:
1076                 mr.setQuality(QUALITY_HIGH);
1077                 break;
1078             case 2:
1079                 mr.setQuality(QUALITY_BEST);
1080                 break;
1081         }
1082     }
1083 
1084 }
1085 
1086 void
setY(DisplayObject & o,const as_value & val)1087 setY(DisplayObject& o, const as_value& val)
1088 {
1089     const double newy = toNumber(val, getVM(*getObject(&o)));
1090 
1091     // NaN is skipped, Infinite isn't
1092     if (isNaN(newy))
1093     {
1094         IF_VERBOSE_ASCODING_ERRORS(
1095         log_aserror(_("Attempt to set %s._y to %s "
1096             "(evaluating to number %g) refused"),
1097             o.getTarget(), val, newy);
1098         );
1099         return;
1100     }
1101 
1102     SWFMatrix m = getMatrix(o);
1103     // NOTE: infinite_to_zero is wrong here, see actionscript.all/setProperty.as
1104     m.set_y_translation(pixelsToTwips(infinite_to_zero(newy)));
1105     o.setMatrix(m);
1106     o.transformedByScript();
1107 }
1108 
1109 as_value
getY(DisplayObject & o)1110 getY(DisplayObject& o)
1111 {
1112     const SWFMatrix& m = getMatrix(o);
1113     return twipsToPixels(m.get_y_translation());
1114 }
1115 
1116 void
setX(DisplayObject & o,const as_value & val)1117 setX(DisplayObject& o, const as_value& val)
1118 {
1119 
1120     const double newx = toNumber(val, getVM(*getObject(&o)));
1121 
1122     // NaN is skipped, Infinite isn't
1123     if (isNaN(newx))
1124     {
1125         IF_VERBOSE_ASCODING_ERRORS(
1126         log_aserror(_("Attempt to set %s._x to %s "
1127             "(evaluating to number %g) refused"),
1128             o.getTarget(), val, newx);
1129         );
1130         return;
1131     }
1132 
1133     SWFMatrix m = getMatrix(o);
1134     // NOTE: infinite_to_zero is wrong here, see actionscript.all/setProperty.as
1135     m.set_x_translation(pixelsToTwips(infinite_to_zero(newx)));
1136     o.setMatrix(m);
1137     o.transformedByScript();
1138 }
1139 
1140 as_value
getX(DisplayObject & o)1141 getX(DisplayObject& o)
1142 {
1143     const SWFMatrix& m = getMatrix(o);
1144     return twipsToPixels(m.get_x_translation());
1145 }
1146 
1147 void
setScaleX(DisplayObject & o,const as_value & val)1148 setScaleX(DisplayObject& o, const as_value& val)
1149 {
1150     const double scale_percent = toNumber(val, getVM(*getObject(&o)));
1151 
1152     // NaN is skipped, Infinite is not, see actionscript.all/setProperty.as
1153     if (isNaN(scale_percent)) {
1154         IF_VERBOSE_ASCODING_ERRORS(
1155         log_aserror(_("Attempt to set %s._xscale to %s "
1156             "(evaluating to number %g) refused"),
1157             o.getTarget(), val, scale_percent);
1158         );
1159         return;
1160     }
1161 
1162     // input is in percent
1163     o.set_x_scale(scale_percent);
1164 
1165 }
1166 
1167 as_value
getScaleX(DisplayObject & o)1168 getScaleX(DisplayObject& o)
1169 {
1170     return o.scaleX();
1171 }
1172 
1173 void
setScaleY(DisplayObject & o,const as_value & val)1174 setScaleY(DisplayObject& o, const as_value& val)
1175 {
1176     const double scale_percent = toNumber(val, getVM(*getObject(&o)));
1177 
1178     // NaN is skipped, Infinite is not, see actionscript.all/setProperty.as
1179     if (isNaN(scale_percent)) {
1180         IF_VERBOSE_ASCODING_ERRORS(
1181         log_aserror(_("Attempt to set %s._yscale to %s "
1182             "(evaluating to number %g) refused"),
1183             o.getTarget(), val, scale_percent);
1184         );
1185         return;
1186     }
1187 
1188     // input is in percent
1189     o.set_y_scale(scale_percent);
1190 
1191 }
1192 
1193 as_value
getScaleY(DisplayObject & o)1194 getScaleY(DisplayObject& o)
1195 {
1196     return o.scaleY();
1197 }
1198 
1199 as_value
getVisible(DisplayObject & o)1200 getVisible(DisplayObject& o)
1201 {
1202     return o.visible();
1203 }
1204 
1205 void
setVisible(DisplayObject & o,const as_value & val)1206 setVisible(DisplayObject& o, const as_value& val)
1207 {
1208     /// We cast to number and rely (mostly) on C++'s automatic
1209     /// cast to bool, as string "0" should be converted to
1210     /// its numeric equivalent, not interpreted as 'true', which
1211     /// SWF7+ does for strings.
1212     const double d = toNumber(val, getVM(*getObject(&o)));
1213 
1214     // Infinite or NaN is skipped
1215     if (isInf(d) || isNaN(d)) {
1216         IF_VERBOSE_ASCODING_ERRORS(
1217         log_aserror(_("Attempt to set %s._visible to %s "
1218             "(evaluating to number %g) refused"),
1219             o.getTarget(), val, d);
1220         );
1221         return;
1222     }
1223 
1224     o.set_visible(d);
1225 
1226     o.transformedByScript();
1227 }
1228 
1229 as_value
getAlpha(DisplayObject & o)1230 getAlpha(DisplayObject& o)
1231 {
1232     return as_value(getCxForm(o).aa / 2.56);
1233 }
1234 
1235 void
setAlpha(DisplayObject & o,const as_value & val)1236 setAlpha(DisplayObject& o, const as_value& val)
1237 {
1238     // The new internal alpha value is input / 100.0 * 256.
1239     // We test for finiteness later, but the multiplication
1240     // won't make any difference.
1241     const double newAlpha = toNumber(val, getVM(*getObject(&o))) * 2.56;
1242 
1243     // NaN is skipped, Infinite is not, see actionscript.all/setProperty.as
1244     if (isNaN(newAlpha)) {
1245         IF_VERBOSE_ASCODING_ERRORS(
1246         log_aserror(_("Attempt to set %s._alpha to %s "
1247             "(evaluating to number %g) refused"),
1248             o.getTarget(), val, newAlpha);
1249         );
1250         return;
1251     }
1252 
1253     SWFCxForm cx = getCxForm(o);
1254 
1255     // Overflows are *not* truncated, but set to -32768.
1256     if (newAlpha > std::numeric_limits<std::int16_t>::max() ||
1257         newAlpha < std::numeric_limits<std::int16_t>::min()) {
1258         cx.aa = std::numeric_limits<std::int16_t>::min();
1259     }
1260     else {
1261         cx.aa = static_cast<std::int16_t>(newAlpha);
1262     }
1263 
1264     o.setCxForm(cx);
1265     o.transformedByScript();
1266 
1267 }
1268 
1269 as_value
getMouseX(DisplayObject & o)1270 getMouseX(DisplayObject& o)
1271 {
1272     // Local coord of mouse IN PIXELS.
1273     std::int32_t x, y;
1274     boost::tie(x, y) = getRoot(*getObject(&o)).mousePosition();
1275 
1276     SWFMatrix m = getWorldMatrix(o);
1277     point a(pixelsToTwips(x), pixelsToTwips(y));
1278 
1279     m.invert().transform(a);
1280     return as_value(twipsToPixels(a.x));
1281 }
1282 
1283 as_value
getMouseY(DisplayObject & o)1284 getMouseY(DisplayObject& o)
1285 {
1286     // Local coord of mouse IN PIXELS.
1287     std::int32_t x, y;
1288     boost::tie(x, y) = getRoot(*getObject(&o)).mousePosition();
1289 
1290     SWFMatrix m = getWorldMatrix(o);
1291     point a(pixelsToTwips(x), pixelsToTwips(y));
1292     m.invert().transform(a);
1293     return as_value(twipsToPixels(a.y));
1294 }
1295 
1296 as_value
getRotation(DisplayObject & o)1297 getRotation(DisplayObject& o)
1298 {
1299     return o.rotation();
1300 }
1301 
1302 
1303 void
setRotation(DisplayObject & o,const as_value & val)1304 setRotation(DisplayObject& o, const as_value& val)
1305 {
1306     // input is in degrees
1307     const double rotation_val = toNumber(val, getVM(*getObject(&o)));
1308 
1309     // NaN is skipped, Infinity isn't
1310     if (isNaN(rotation_val)) {
1311         IF_VERBOSE_ASCODING_ERRORS(
1312         log_aserror(_("Attempt to set %s._rotation to %s "
1313             "(evaluating to number %g) refused"),
1314             o.getTarget(), val, rotation_val);
1315         );
1316         return;
1317     }
1318     o.set_rotation(rotation_val);
1319 }
1320 
1321 
1322 as_value
getParent(DisplayObject & o)1323 getParent(DisplayObject& o)
1324 {
1325     as_object* p = getObject(o.parent());
1326     return p ? p : as_value();
1327 }
1328 
1329 as_value
getTarget(DisplayObject & o)1330 getTarget(DisplayObject& o)
1331 {
1332     return o.getTargetPath();
1333 }
1334 
1335 as_value
getNameProperty(DisplayObject & o)1336 getNameProperty(DisplayObject& o)
1337 {
1338     string_table& st = getStringTable(*getObject(&o));
1339     const std::string& name = o.get_name().toString(st);
1340     return as_value(name);
1341 }
1342 
1343 void
setName(DisplayObject & o,const as_value & val)1344 setName(DisplayObject& o, const as_value& val)
1345 {
1346     o.set_name(getURI(getVM(*getObject(&o)), val.to_string()));
1347 }
1348 
1349 void
setSoundBufTime(DisplayObject &,const as_value &)1350 setSoundBufTime(DisplayObject& /*o*/, const as_value& /*val*/)
1351 {
1352     LOG_ONCE(log_unimpl(_("_soundbuftime setting")));
1353 }
1354 
1355 as_value
getSoundBufTime(DisplayObject &)1356 getSoundBufTime(DisplayObject& /*o*/)
1357 {
1358     return as_value(0.0);
1359 }
1360 
1361 as_value
getWidth(DisplayObject & o)1362 getWidth(DisplayObject& o)
1363 {
1364     SWFRect bounds = o.getBounds();
1365     const SWFMatrix& m = getMatrix(o);
1366     m.transform(bounds);
1367     return twipsToPixels(bounds.width());
1368 }
1369 
1370 void
setWidth(DisplayObject & o,const as_value & val)1371 setWidth(DisplayObject& o, const as_value& val)
1372 {
1373     const double newwidth = pixelsToTwips(toNumber(val, getVM(*getObject(&o))));
1374     if (newwidth <= 0) {
1375         IF_VERBOSE_ASCODING_ERRORS(
1376         log_aserror(_("Setting _width=%g of DisplayObject %s (%s)"),
1377             newwidth/20, o.getTarget(), typeName(o));
1378         );
1379     }
1380     o.setWidth(newwidth);
1381 }
1382 
1383 as_value
getFocusRect(DisplayObject & o)1384 getFocusRect(DisplayObject& o)
1385 {
1386     LOG_ONCE(log_unimpl(_("_focusrect")));
1387 
1388     const boost::tribool fr = o.focusRect();
1389     if (boost::indeterminate(fr)) {
1390         as_value null;
1391         null.set_null();
1392         return null;
1393     }
1394     const bool ret = static_cast<bool>(fr);
1395     if (getSWFVersion(*getObject(&o)) == 5) {
1396         return as_value(static_cast<double>(ret));
1397     }
1398     return as_value(ret);
1399 }
1400 
1401 void
setFocusRect(DisplayObject & o,const as_value & val)1402 setFocusRect(DisplayObject& o, const as_value& val)
1403 {
1404     LOG_ONCE(log_unimpl(_("_focusrect")));
1405 
1406     VM& vm = getVM(*getObject(&o));
1407     if (!o.parent()) {
1408         const double d = toNumber(val, vm);
1409         if (isNaN(d)) return;
1410         o.focusRect(d);
1411         return;
1412     }
1413     o.focusRect(toBool(val, vm));
1414 }
1415 
1416 as_value
getDropTarget(DisplayObject & o)1417 getDropTarget(DisplayObject& o)
1418 {
1419     // This property only applies to MovieClips.
1420     MovieClip* mc = o.to_movie();
1421     if (!mc) return as_value();
1422     return as_value(mc->getDropTarget());
1423 }
1424 
1425 as_value
getCurrentFrame(DisplayObject & o)1426 getCurrentFrame(DisplayObject& o)
1427 {
1428     // This property only applies to MovieClips.
1429     MovieClip* mc = o.to_movie();
1430     if (!mc) return as_value();
1431     const int currframe =
1432         std::min(mc->get_loaded_frames(), mc->get_current_frame() + 1);
1433     return as_value(currframe);
1434 }
1435 
1436 as_value
getFramesLoaded(DisplayObject & o)1437 getFramesLoaded(DisplayObject& o)
1438 {
1439     // This property only applies to MovieClips.
1440     MovieClip* mc = o.to_movie();
1441     if (!mc) return as_value();
1442     return as_value(mc->get_loaded_frames());
1443 }
1444 
1445 as_value
getTotalFrames(DisplayObject & o)1446 getTotalFrames(DisplayObject& o)
1447 {
1448     // This property only applies to MovieClips.
1449     MovieClip* mc = o.to_movie();
1450     if (!mc) return as_value();
1451     return as_value(mc->get_frame_count());
1452 }
1453 
1454 
1455 /// @param uri     The property to search for. Note that all special
1456 ///                properties are lower-case.
1457 ///
1458 /// NOTE that all properties have getters so you can recognize a
1459 /// 'not-found' condition by checking .first = 0
1460 const GetterSetter&
getGetterSetterByURI(const ObjectURI & uri,string_table & st)1461 getGetterSetterByURI(const ObjectURI& uri, string_table& st)
1462 {
1463     typedef std::map<ObjectURI, GetterSetter, ObjectURI::CaseLessThan>
1464         GetterSetters;
1465 
1466     static const GetterSetters gs =
1467         getURIMap<GetterSetters>(ObjectURI::CaseLessThan(st, true));
1468 
1469     const GetterSetters::const_iterator it = gs.find(uri);
1470 
1471     if (it == gs.end()) {
1472         static const GetterSetter none(nullptr, nullptr);
1473         return none;
1474     }
1475 
1476     return it->second;
1477 }
1478 
1479 
1480 const GetterSetter&
getGetterSetterByIndex(size_t index)1481 getGetterSetterByIndex(size_t index)
1482 {
1483     const Setter n = nullptr;
1484 
1485     static const GetterSetter props[] = {
1486         GetterSetter(&getX, &setX),
1487         GetterSetter(&getY, &setY),
1488         GetterSetter(&getScaleX, &setScaleX),
1489         GetterSetter(&getScaleY, &setScaleY),
1490 
1491         GetterSetter(&getCurrentFrame, n),
1492         GetterSetter(&getTotalFrames, n),
1493         GetterSetter(&getAlpha, &setAlpha),
1494         GetterSetter(&getVisible, &setVisible),
1495 
1496         GetterSetter(&getWidth, &setWidth),
1497         GetterSetter(&getHeight, &setHeight),
1498         GetterSetter(&getRotation, &setRotation),
1499         GetterSetter(&getTarget, n),
1500 
1501         GetterSetter(&getFramesLoaded, n),
1502         GetterSetter(&getNameProperty, &setName),
1503         GetterSetter(&getDropTarget, n),
1504         GetterSetter(&getURL, n),
1505 
1506         GetterSetter(&getHighQuality, &setHighQuality),
1507         GetterSetter(&getFocusRect, &setFocusRect),
1508         GetterSetter(&getSoundBufTime, &setSoundBufTime),
1509         GetterSetter(&getQuality, &setQuality),
1510 
1511         GetterSetter(&getMouseX, n),
1512         GetterSetter(&getMouseY, n)
1513 
1514     };
1515 
1516     if (index >= arraySize(props)) {
1517         const Getter ng = nullptr;
1518         static const GetterSetter none(ng, n);
1519         return none;
1520     }
1521 
1522     return props[index];
1523 }
1524 
1525 
1526 bool
doGet(const ObjectURI & uri,DisplayObject & o,as_value & val)1527 doGet(const ObjectURI& uri, DisplayObject& o, as_value& val)
1528 {
1529     string_table& st = getStringTable(*getObject(&o));
1530     const Getter s = getGetterSetterByURI(uri, st).first;
1531     if (!s) return false;
1532 
1533     val = (*s)(o);
1534     return true;
1535 }
1536 
1537 
1538 /// Do the actual setProperty
1539 //
1540 /// Return true if the property is a DisplayObject property, regardless of
1541 /// whether it was successfully set or not.
1542 //
1543 /// @param uri     The property to search for. Note that all special
1544 ///                properties are lower-case, so for a caseless check
1545 ///                it is sufficient for prop to be caseless.
1546 bool
doSet(const ObjectURI & uri,DisplayObject & o,const as_value & val)1547 doSet(const ObjectURI& uri, DisplayObject& o, const as_value& val)
1548 {
1549     string_table& st = getStringTable(*getObject(&o));
1550 
1551     const GetterSetter& gs = getGetterSetterByURI(uri, st);
1552 
1553     // not found (all props have getters)
1554     if (!gs.first) return false;
1555 
1556     const Setter& s = gs.second;
1557 
1558     // read-only (TODO: aserror ?)
1559     if (!s) return true;
1560 
1561     if (val.is_undefined() || val.is_null()) {
1562         IF_VERBOSE_ASCODING_ERRORS(
1563             // TODO: add property  name to this log...
1564             log_aserror(_("Attempt to set property to %s, refused"),
1565                 o.getTarget(), val);
1566         );
1567         return true;
1568     }
1569 
1570     (*s)(o, val);
1571     return true;
1572 }
1573 
1574 
1575 const BlendModeMap&
getBlendModeMap()1576 getBlendModeMap()
1577 {
1578     /// BLENDMODE_UNDEFINED has no matching string in AS. It is included
1579     /// here for logging purposes.
1580     static const BlendModeMap bm = {
1581         {DisplayObject::BLENDMODE_UNDEFINED, "undefined"},
1582         {DisplayObject::BLENDMODE_NORMAL, "normal"},
1583         {DisplayObject::BLENDMODE_LAYER, "layer"},
1584         {DisplayObject::BLENDMODE_MULTIPLY, "multiply"},
1585         {DisplayObject::BLENDMODE_SCREEN, "screen"},
1586         {DisplayObject::BLENDMODE_LIGHTEN, "lighten"},
1587         {DisplayObject::BLENDMODE_DARKEN, "darken"},
1588         {DisplayObject::BLENDMODE_DIFFERENCE, "difference"},
1589         {DisplayObject::BLENDMODE_ADD, "add"},
1590         {DisplayObject::BLENDMODE_SUBTRACT, "subtract"},
1591         {DisplayObject::BLENDMODE_INVERT, "invert"},
1592         {DisplayObject::BLENDMODE_ALPHA, "alpha"},
1593         {DisplayObject::BLENDMODE_ERASE, "erase"},
1594         {DisplayObject::BLENDMODE_OVERLAY, "overlay"},
1595         {DisplayObject::BLENDMODE_HARDLIGHT, "hardlight"}
1596     };
1597 
1598     return bm;
1599 }
1600 
1601 
1602 // Match a blend mode to its string.
1603 bool
blendModeMatches(const BlendModeMap::value_type & val,const std::string & mode)1604 blendModeMatches(const BlendModeMap::value_type& val, const std::string& mode)
1605 {
1606     /// The match must be case-sensitive.
1607     if (mode.empty()) return false;
1608     return (val.second == mode);
1609 }
1610 
1611 /// Return a const map of property URI to function.
1612 //
1613 /// This function takes advantage of NRVO to allow the map to
1614 /// be constructed in the caller.
1615 template<typename Map>
1616 const Map
getURIMap(const typename Map::key_compare & cmp)1617 getURIMap(const typename Map::key_compare& cmp)
1618 {
1619     const Setter n = nullptr;
1620 
1621     Map ret(cmp);
1622     ret.insert(std::make_pair(NSV::PROP_uX, GetterSetter(&getX, &setX)));
1623     ret.insert(std::make_pair(NSV::PROP_uY, GetterSetter(&getY, &setY)));
1624     ret.insert(std::make_pair(NSV::PROP_uXSCALE,
1625                 GetterSetter(&getScaleX, &setScaleX)));
1626     ret.insert(std::make_pair(NSV::PROP_uYSCALE,
1627                 GetterSetter(&getScaleY, &setScaleY)));
1628     ret.insert(std::make_pair(NSV::PROP_uROTATION,
1629                 GetterSetter(&getRotation, &setRotation)));
1630     ret.insert(std::make_pair(NSV::PROP_uHIGHQUALITY,
1631                 GetterSetter(&getHighQuality, &setHighQuality)));
1632     ret.insert(std::make_pair(NSV::PROP_uQUALITY,
1633                 GetterSetter(&getQuality, &setQuality)));
1634     ret.insert(std::make_pair(NSV::PROP_uALPHA,
1635                 GetterSetter(&getAlpha, &setAlpha)));
1636     ret.insert(std::make_pair(NSV::PROP_uWIDTH,
1637                 GetterSetter(&getWidth, &setWidth)));
1638     ret.insert(std::make_pair(NSV::PROP_uHEIGHT,
1639                 GetterSetter(&getHeight, &setHeight)));
1640     ret.insert(std::make_pair(NSV::PROP_uNAME,
1641                 GetterSetter(&getNameProperty, &setName)));
1642     ret.insert(std::make_pair(NSV::PROP_uVISIBLE,
1643                 GetterSetter(&getVisible, &setVisible)));
1644     ret.insert(std::make_pair(NSV::PROP_uSOUNDBUFTIME,
1645                 GetterSetter(&getSoundBufTime, &setSoundBufTime)));
1646     ret.insert(std::make_pair(NSV::PROP_uFOCUSRECT,
1647                 GetterSetter(&getFocusRect, &setFocusRect)));
1648     ret.insert(std::make_pair(NSV::PROP_uDROPTARGET,
1649                 GetterSetter(&getDropTarget, n)));
1650     ret.insert(std::make_pair(NSV::PROP_uCURRENTFRAME,
1651                 GetterSetter(&getCurrentFrame, n)));
1652     ret.insert(std::make_pair(NSV::PROP_uFRAMESLOADED,
1653                 GetterSetter(&getFramesLoaded, n)));
1654     ret.insert(std::make_pair(NSV::PROP_uTOTALFRAMES,
1655                 GetterSetter(&getTotalFrames, n)));
1656     ret.insert(std::make_pair(NSV::PROP_uURL, GetterSetter(&getURL, n)));
1657     ret.insert(std::make_pair(NSV::PROP_uTARGET, GetterSetter(&getTarget, n)));
1658     ret.insert(std::make_pair(NSV::PROP_uXMOUSE, GetterSetter(&getMouseX, n)));
1659     ret.insert(std::make_pair(NSV::PROP_uYMOUSE, GetterSetter(&getMouseY, n)));
1660     ret.insert(std::make_pair(NSV::PROP_uPARENT, GetterSetter(&getParent, n)));
1661     return ret;
1662 }
1663 
1664 } // anonymous namespace
1665 
1666 std::ostream&
operator <<(std::ostream & o,DisplayObject::BlendMode bm)1667 operator<<(std::ostream& o, DisplayObject::BlendMode bm)
1668 {
1669     const BlendModeMap& bmm = getBlendModeMap();
1670     return (o << bmm.find(bm)->second);
1671 }
1672 
1673 } // namespace gnash
1674 
1675 // local variables:
1676 // mode: c++
1677 // indent-tabs-mode: t
1678 // end:
1679