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