Drop the bool operator for ObjectURI, to avoid getting the kind of side-effect that...
[gnash.git] / libcore / DisplayObject.cpp
blob14a6540a15ce2e3bc86de16ffa696a81bacb77c8
1 // DisplayObject.cpp: ActionScript DisplayObject class, for Gnash.
2 //
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
4 // Foundation, Inc
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 //
22 #ifdef HAVE_CONFIG_H
23 #include "gnashconfig.h" // USE_SWFTREE
24 #endif
26 #include "smart_ptr.h" // GNASH_USE_GC
27 #include "DisplayObject.h"
28 #include "movie_root.h"
29 #include "MovieClip.h"
30 #include "drag_state.h" // for do_mouse_drag (to be moved in movie_root)
31 #include "VM.h" // for do_mouse_drag (to be moved in movie_root)
32 #include "fn_call.h" // for shared ActionScript getter-setters
33 #include "GnashException.h"
34 #include "ExecutableCode.h"
35 #include "namedStrings.h"
36 #include "gnash.h" // Quality
37 #include "GnashNumeric.h"
38 #include "Global_as.h"
39 #include "Renderer.h"
41 #ifdef USE_SWFTREE
42 # include "tree.hh"
43 #endif
45 #include <boost/algorithm/string/case_conv.hpp>
46 #include <boost/assign/list_of.hpp>
47 #include <boost/bind.hpp>
49 #undef set_invalidated
51 namespace gnash
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);
62 typedef as_value(*Getter)(DisplayObject&);
63 typedef std::map<string_table::key, Getter> Getters;
64 typedef void(*Setter)(DisplayObject&, const as_value&);
65 typedef std::map<string_table::key, Setter> Setters;
67 const Getters& displayObjectGetters();
68 const Setters& displayObjectSetters();
70 bool doSet(string_table::key prop, DisplayObject& o, const as_value& val);
71 bool doGet(string_table::key prop, DisplayObject& o, as_value& val);
72 string_table::key getPropertyByIndex(size_t index);
75 // Define static const members.
76 const int DisplayObject::lowerAccessibleBound;
77 const int DisplayObject::upperAccessibleBound;
78 const int DisplayObject::staticDepthOffset;
79 const int DisplayObject::removedDepthOffset;
80 const int DisplayObject::noClipDepthValue;
82 DisplayObject::DisplayObject(movie_root& mr, as_object* object,
83 DisplayObject* parent)
85 _name(),
86 _parent(parent),
87 _object(object),
88 _stage(mr),
89 _xscale(100),
90 _yscale(100),
91 _rotation(0),
92 _depth(0),
93 _volume(100),
94 _ratio(0),
95 m_clip_depth(noClipDepthValue),
96 _mask(0),
97 _maskee(0),
98 _blendMode(BLENDMODE_NORMAL),
99 _visible(true),
100 _scriptTransformed(false),
101 _dynamicallyCreated(false),
102 _unloaded(false),
103 _destroyed(false),
104 _invalidated(true),
105 _child_invalidated(true)
107 assert(m_old_invalidated_ranges.isNull());
109 // This informs the core that the object is a DisplayObject.
110 if (_object) _object->setDisplayObject(this);
113 as_object*
114 DisplayObject::object() const
116 return _object;
119 bool
120 DisplayObject::unloaded() const
122 return _unloaded;
125 void
126 DisplayObject::getLoadedMovie(Movie* extern_movie)
128 LOG_ONCE(
129 log_unimpl("loadMovie against a %s DisplayObject", typeName(*this))
132 // TODO: look at the MovieClip implementation, but most importantly
133 // test all the event handlers copies etc..
135 UNUSED(extern_movie);
138 string_table::key
139 DisplayObject::getNextUnnamedInstanceName()
141 assert(_object);
142 movie_root& mr = getRoot(*_object);
143 std::ostringstream ss;
144 ss << "instance" << mr.nextUnnamedInstance();
146 string_table& st = getStringTable(*_object);
147 return st.find(ss.str());
152 DisplayObject::getWorldVolume() const
154 int volume=_volume;
155 if (_parent != NULL)
157 volume = int(volume*_parent->getVolume()/100.0);
160 return volume;
164 as_object*
165 DisplayObject::pathElement(const ObjectURI& uri)
167 as_object* obj = getObject(this);
168 if (!obj) return 0;
170 string_table::key key = getName(uri);
172 string_table& st = stage().getVM().getStringTable();
174 // TODO: put ".." and "." in namedStrings
175 if (key == st.find("..")) return getObject(parent());
176 if (key == st.find(".")) return obj;
178 // The check is case-insensitive for SWF6 and below.
179 // TODO: cache ObjectURI(NSV::PROP_THIS) [as many others...]
180 if (equals(st, uri, ObjectURI(NSV::PROP_THIS), caseless(*obj))) {
181 return obj;
183 return 0;
186 void
187 DisplayObject::set_invalidated()
189 set_invalidated("unknown", -1);
192 void
193 DisplayObject::set_invalidated(const char* debug_file, int debug_line)
195 // Set the invalidated-flag of the parent. Note this does not mean that
196 // the parent must re-draw itself, it just means that one of it's childs
197 // needs to be re-drawn.
198 if ( _parent ) _parent->set_child_invalidated();
200 // Ok, at this point the instance will change it's
201 // visual aspect after the
202 // call to set_invalidated(). We save the *current*
203 // position of the instance because this region must
204 // be updated even (or first of all) if the DisplayObject
205 // moves away from here.
207 if ( ! _invalidated )
209 _invalidated = true;
211 #ifdef DEBUG_SET_INVALIDATED
212 log_debug("%p set_invalidated() of %s in %s:%d",
213 (void*)this, get_name(), debug_file, debug_line);
214 #else
215 UNUSED(debug_file);
216 UNUSED(debug_line);
217 #endif
219 // NOTE: the SnappingRanges instance used here is not initialized by the
220 // GUI and therefore uses the default settings. This should not be a
221 // problem but special snapping ranges configuration done in gui.cpp
222 // is ignored here...
224 m_old_invalidated_ranges.setNull();
225 add_invalidated_bounds(m_old_invalidated_ranges, true);
230 void
231 DisplayObject::add_invalidated_bounds(InvalidatedRanges& ranges, bool force)
233 ranges.add(m_old_invalidated_ranges);
234 if (visible() && (_invalidated||force))
236 SWFRect bounds;
237 bounds.expand_to_transformed_rect(getWorldMatrix(*this), getBounds());
238 ranges.add(bounds.getRange());
242 void
243 DisplayObject::set_child_invalidated()
245 if ( ! _child_invalidated )
247 _child_invalidated=true;
248 if ( _parent ) _parent->set_child_invalidated();
252 void
253 DisplayObject::extend_invalidated_bounds(const InvalidatedRanges& ranges)
255 set_invalidated(__FILE__, __LINE__);
256 m_old_invalidated_ranges.add(ranges);
259 as_value
260 DisplayObject::blendMode(const fn_call& fn)
262 DisplayObject* ch = ensure<IsDisplayObject<> >(fn);
264 // This is AS-correct, but doesn't do anything.
265 // TODO: implement in the renderers!
266 LOG_ONCE(log_unimpl(_("blendMode")));
268 if (!fn.nargs)
270 // Getter
271 BlendMode bm = ch->getBlendMode();
273 /// If the blend mode is undefined, it doesn't return a string.
274 if (bm == BLENDMODE_UNDEFINED) return as_value();
276 std::ostringstream blendMode;
277 blendMode << bm;
278 return as_value(blendMode.str());
282 // Setter
285 const as_value& bm = fn.arg(0);
287 // Undefined argument sets blend mode to normal.
288 if (bm.is_undefined()) {
289 ch->setBlendMode(BLENDMODE_NORMAL);
290 return as_value();
293 // Numeric argument.
294 if (bm.is_number()) {
295 double mode = bm.to_number();
297 // Hardlight is the last known value. This also performs range checking
298 // for float-to-int conversion.
299 if (mode < 0 || mode > BLENDMODE_HARDLIGHT) {
301 // An invalid numeric argument becomes undefined.
302 ch->setBlendMode(BLENDMODE_UNDEFINED);
304 else {
305 /// The extra static cast is required to keep OpenBSD happy.
306 ch->setBlendMode(static_cast<BlendMode>(static_cast<int>(mode)));
308 return as_value();
311 // Other arguments use toString method.
312 const std::string& mode = bm.to_string();
314 const BlendModeMap& bmm = getBlendModeMap();
315 BlendModeMap::const_iterator it = std::find_if(bmm.begin(), bmm.end(),
316 boost::bind(blendModeMatches, _1, mode));
318 if (it != bmm.end()) {
319 ch->setBlendMode(it->first);
322 // An invalid string argument has no effect.
324 return as_value();
328 void
329 DisplayObject::set_visible(bool visible)
331 if (_visible != visible) set_invalidated(__FILE__, __LINE__);
333 // Remove focus from this DisplayObject if it changes from visible to
334 // invisible (see Selection.as).
335 if (_visible && !visible) {
336 assert(_object);
337 movie_root& mr = getRoot(*_object);
338 if (mr.getFocus() == this) {
339 mr.setFocus(0);
342 _visible = visible;
345 void
346 DisplayObject::setWidth(double newwidth)
348 const SWFRect& bounds = getBounds();
349 const double oldwidth = bounds.width();
350 assert(oldwidth >= 0);
352 const double xscale = oldwidth ? (newwidth / oldwidth) : 0;
353 const double rotation = _rotation * PI / 180.0;
355 SWFMatrix m = getMatrix(*this);
356 const double yscale = m.get_y_scale();
357 m.set_scale_rotation(xscale, yscale, rotation);
358 setMatrix(m, true);
361 as_value
362 getHeight(DisplayObject& o)
364 SWFRect bounds = o.getBounds();
365 const SWFMatrix m = getMatrix(o);
366 m.transform(bounds);
367 return twipsToPixels(bounds.height());
370 void
371 setHeight(DisplayObject& o, const as_value& val)
373 const double newheight = pixelsToTwips(val.to_number());
374 if (newheight <= 0) {
375 IF_VERBOSE_ASCODING_ERRORS(
376 log_aserror(_("Setting _height=%g of DisplayObject %s (%s)"),
377 newheight / 20, o.getTarget(), typeName(o));
380 o.setHeight(newheight);
383 void
384 DisplayObject::setHeight(double newheight)
386 const SWFRect& bounds = getBounds();
388 const double oldheight = bounds.height();
389 assert(oldheight >= 0);
391 const double yscale = oldheight ? (newheight / oldheight) : 0;
392 const double rotation = _rotation * PI / 180.0;
394 SWFMatrix m = getMatrix(*this);
395 const double xscale = m.get_x_scale();
396 m.set_scale_rotation(xscale, yscale, rotation);
397 setMatrix(m, true);
400 void
401 DisplayObject::setMatrix(const SWFMatrix& m, bool updateCache)
404 if (m == _transform.matrix) return;
406 //log_debug("setting SWFMatrix to: %s", m);
407 set_invalidated(__FILE__, __LINE__);
408 _transform.matrix = m;
410 // don't update caches if SWFMatrix wasn't updated too
411 if (updateCache) {
412 _xscale = _transform.matrix.get_x_scale() * 100.0;
413 _yscale = _transform.matrix.get_y_scale() * 100.0;
414 _rotation = _transform.matrix.get_rotation() * 180.0 / PI;
419 void
420 DisplayObject::set_event_handlers(const Events& copyfrom)
422 for (Events::const_iterator it=copyfrom.begin(), itE=copyfrom.end();
423 it != itE; ++it)
425 const event_id& ev = it->first;
426 const BufferList& bufs = it->second;
427 for (size_t i = 0, e = bufs.size(); i < e; ++i)
429 const action_buffer* buf = bufs[i];
430 assert(buf);
431 add_event_handler(ev, *buf);
436 void
437 DisplayObject::add_event_handler(const event_id& id, const action_buffer& code)
439 _event_handlers[id].push_back(&code);
441 // todo: drop the DisplayObject as a listener
442 // if it gets no valid handlers for
443 // mouse or Key events.
446 std::auto_ptr<ExecutableCode>
447 DisplayObject::get_event_handler(const event_id& id) const
449 std::auto_ptr<ExecutableCode> handler;
451 Events::const_iterator it = _event_handlers.find(id);
452 if ( it == _event_handlers.end() ) return handler;
454 #ifndef GNASH_USE_GC
455 assert(get_ref_count() > 0);
456 #endif // GNASH_USE_GC
457 DisplayObject* this_ptr = const_cast<DisplayObject*>(this);
459 handler.reset( new EventCode(this_ptr, it->second) );
460 return handler;
463 bool
464 DisplayObject::unload()
467 const bool childHandler = unloadChildren();
469 if (!_unloaded) {
470 queueEvent(event_id::UNLOAD, movie_root::PRIORITY_DOACTION);
473 // Unregister this DisplayObject as mask and/or maskee.
474 if (_maskee) _maskee->setMask(0);
475 if (_mask) _mask->setMaskee(0);
477 const bool hasEvent = hasEventHandler(event_id::UNLOAD) || childHandler;
479 if (!hasEvent) {
480 stage().removeQueuedConstructor(this);
483 _unloaded = true;
485 return hasEvent;
488 void
489 DisplayObject::queueEvent(const event_id& id, int lvl)
491 if (!_object) return;
492 std::auto_ptr<ExecutableCode> event(new QueuedEvent(this, id));
493 stage().pushAction(event, lvl);
496 bool
497 DisplayObject::hasEventHandler(const event_id& id) const
499 Events::const_iterator it = _event_handlers.find(id);
500 if (it != _event_handlers.end()) return true;
502 if (!_object) return false;
504 as_value tmp;
505 if (_object->get_member(id.functionKey(), &tmp)) {
506 return tmp.to_function();
508 return false;
512 /// Set the real and cached x scale.
514 /// Cached rotation and y scale are not updated.
515 void
516 DisplayObject::set_x_scale(double scale_percent)
518 double xscale = scale_percent / 100.0;
520 if (xscale != 0.0 && _xscale != 0.0)
522 if (scale_percent * _xscale < 0.0)
524 xscale = -std::abs(xscale);
526 else xscale = std::abs(xscale);
529 _xscale = scale_percent;
531 // As per misc-ming.all/SWFMatrix_test.{c,swf}
532 // we don't need to recompute the SWFMatrix from the
533 // caches.
535 SWFMatrix m = getMatrix(*this);
537 m.set_x_scale(xscale);
539 setMatrix(m); // we updated the cache ourselves
541 transformedByScript();
544 /// Set the real and cached rotation.
546 /// Cached scale values are not updated.
547 void
548 DisplayObject::set_rotation(double rot)
550 // Translate to the -180 .. 180 range
551 rot = std::fmod(rot, 360.0);
552 if (rot > 180.0) rot -= 360.0;
553 else if (rot < -180.0) rot += 360.0;
555 double rotation = rot * PI / 180.0;
557 if (_xscale < 0 ) rotation += PI;
559 SWFMatrix m = getMatrix(*this);
560 m.set_rotation(rotation);
562 // Update the matrix from the cached x scale to avoid accumulating
563 // errors.
564 // TODO: also update y scale? The x scale update is needed to keep
565 // TextField correct; no tests for y scale.
566 m.set_x_scale(std::abs(scaleX() / 100.0));
567 setMatrix(m); // we update the cache ourselves
569 _rotation = rot;
571 transformedByScript();
575 /// Set the real and cached y scale.
577 /// Cached rotation and x scale are not updated.
578 void
579 DisplayObject::set_y_scale(double scale_percent)
581 double yscale = scale_percent / 100.0;
583 if (yscale != 0.0 && _yscale != 0.0)
585 if (scale_percent * _yscale < 0.0) yscale = -std::abs(yscale);
586 else yscale = std::abs(yscale);
589 _yscale = scale_percent;
591 SWFMatrix m = getMatrix(*this);
592 m.set_y_scale(yscale);
593 setMatrix(m); // we updated the cache ourselves
595 transformedByScript();
599 std::string
600 DisplayObject::getTargetPath() const
602 // TODO: check what happens when this DisplayObject
603 // is a Movie loaded into another
604 // running movie.
606 typedef std::vector<std::string> Path;
607 Path path;
609 // Build parents stack
610 const DisplayObject* topLevel = 0;
611 const DisplayObject* ch = this;
613 string_table& st = getStringTable(*getObject(this));
614 for (;;)
616 const DisplayObject* parent = ch->parent();
618 // Don't push the _root name on the stack
619 if (!parent) {
620 topLevel = ch;
621 break;
624 path.push_back(ch->get_name().toString(st));
625 ch = parent;
628 assert(topLevel);
630 if (path.empty()) {
631 if (&stage().getRootMovie() == this) return "/";
632 std::stringstream ss;
633 ss << "_level" << _depth-DisplayObject::staticDepthOffset;
634 return ss.str();
637 // Build the target string from the parents stack
638 std::string target;
639 if (topLevel != &stage().getRootMovie()) {
640 std::stringstream ss;
641 ss << "_level" <<
642 topLevel->get_depth() - DisplayObject::staticDepthOffset;
643 target = ss.str();
645 for (Path::reverse_iterator it=path.rbegin(), itEnd=path.rend();
646 it != itEnd; ++it) {
647 target += "/" + *it;
649 return target;
653 std::string
654 DisplayObject::getTarget() const
657 // TODO: check what happens when this DisplayObject
658 // is a Movie loaded into another
659 // running movie.
661 typedef std::vector<std::string> Path;
662 Path path;
664 // Build parents stack
665 const DisplayObject* ch = this;
666 string_table& st = stage().getVM().getStringTable();
667 for (;;)
669 const DisplayObject* parent = ch->parent();
671 // Don't push the _root name on the stack
672 if (!parent) {
674 std::stringstream ss;
675 if (!dynamic_cast<const Movie*>(ch)) {
676 // must be an as-referenceable
677 // DisplayObject created using 'new'
678 // like, new MovieClip, new Video, new TextField...
679 //log_debug("DisplayObject %p (%s) doesn't have a parent and "
680 // "is not a Movie", ch, typeName(*ch));
681 ss << "<no parent, depth" << ch->get_depth() << ">";
682 path.push_back(ss.str());
684 else {
685 ss << "_level" <<
686 ch->get_depth() - DisplayObject::staticDepthOffset;
687 path.push_back(ss.str());
689 break;
692 path.push_back(ch->get_name().toString(st));
693 ch = parent;
696 assert (!path.empty());
698 // Build the target string from the parents stack
699 std::string target;
700 for (Path::const_reverse_iterator it=path.rbegin(), itEnd=path.rend();
701 it != itEnd; ++it) {
703 if (!target.empty()) target += ".";
704 target += *it;
707 return target;
711 void
712 DisplayObject::destroy()
714 // in case we are destroyed without being unloaded first
715 // see bug #21842
716 _unloaded = true;
718 /// we may destory a DisplayObject that's not unloaded.
719 ///(we don't have chance to unload it in current model,
720 /// see new_child_in_unload_test.c)
721 /// We don't destroy ourself twice, right ?
723 if (_object) _object->clearProperties();
725 assert(!_destroyed);
726 _destroyed = true;
729 void
730 DisplayObject::markReachableResources() const
732 markOwnResources();
733 if (_object) _object->setReachable();
734 if (_parent) _parent->setReachable();
735 if (_mask) _mask->setReachable();
736 if (_maskee) _maskee->setReachable();
739 /// Whether to use a hand cursor when the mouse is over this DisplayObject
741 /// This depends on the useHandCursor AS property, but:
742 /// 1. Only AS-referenceable objects may use a hand cursor (TODO: check
743 /// Video).
744 /// 2. Only objects with a release event may use a hand cursor.
745 /// 3. The default value (if the property is not defined) is true.
746 bool
747 DisplayObject::allowHandCursor() const
749 if (!getObject(this)) return false;
751 if (!hasEventHandler(event_id::RELEASE)) return false;
753 as_value val;
754 if (!getObject(this)->get_member(NSV::PROP_USEHANDCURSOR, &val)) {
755 return true;
757 return val.to_bool();
760 void
761 DisplayObject::setMask(DisplayObject* mask)
763 if ( _mask == mask ) return;
765 set_invalidated();
767 // Backup this before setMaskee has a chance to change it..
768 DisplayObject* prevMaskee = _maskee;
770 // If we had a previous mask unregister with it
771 if ( _mask && _mask != mask )
773 // the mask will call setMask(NULL)
774 // on any previously registered maskee
775 // so we make sure to set our _mask to
776 // NULL before getting called again
777 _mask->setMaskee(0);
780 // if we had a maskee, notify it to stop using
781 // us as a mask
782 if (prevMaskee) prevMaskee->setMask(0);
784 // TODO: should we reset any original clip depth
785 // specified by PlaceObject tag ?
786 set_clip_depth(noClipDepthValue);
787 _mask = mask;
788 _maskee = 0;
790 if (_mask) {
791 /// Register as as masked by the mask
792 _mask->setMaskee(this);
796 void
797 DisplayObject::setMaskee(DisplayObject* maskee)
799 if ( _maskee == maskee ) { return; }
801 if (_maskee) {
802 // We don't want the maskee to call setMaskee(null)
803 // on us again
804 _maskee->_mask = 0;
807 _maskee = maskee;
809 if (!maskee)
811 // TODO: should we reset any original clip depth
812 // specified by PlaceObject tag ?
813 set_clip_depth(noClipDepthValue);
818 bool
819 DisplayObject::boundsInClippingArea(Renderer& renderer) const
821 SWFRect mybounds = getBounds();
822 getWorldMatrix(*this).transform(mybounds);
824 return renderer.bounds_in_clipping_area(mybounds.getRange());
827 #ifdef USE_SWFTREE
828 DisplayObject::InfoTree::iterator
829 DisplayObject::getMovieInfo(InfoTree& tr, InfoTree::iterator it)
831 const std::string yes = _("yes");
832 const std::string no = _("no");
834 it = tr.append_child(it, StringPair(getTarget(), typeName(*this)));
836 std::ostringstream os;
837 os << get_depth();
838 tr.append_child(it, StringPair(_("Depth"), os.str()));
840 /// Don't add if the DisplayObject has no ratio value
841 if (get_ratio() >= 0)
843 os.str("");
844 os << get_ratio();
845 tr.append_child(it, StringPair(_("Ratio"), os.str()));
848 /// Don't add if it's not a real clipping depth
849 if (int cd = get_clip_depth() != noClipDepthValue)
851 os.str("");
852 if (_maskee) os << "Dynamic mask";
853 else os << cd;
855 tr.append_child(it, StringPair(_("Clipping depth"), os.str()));
858 os.str("");
859 os << getBounds().width() << "x" << getBounds().height();
860 tr.append_child(it, StringPair(_("Dimensions"), os.str()));
862 tr.append_child(it, StringPair(_("Dynamic"), isDynamic() ? yes : no));
863 tr.append_child(it, StringPair(_("Mask"), isMaskLayer() ? yes : no));
864 tr.append_child(it, StringPair(_("Destroyed"), isDestroyed() ? yes : no));
865 tr.append_child(it, StringPair(_("Unloaded"), unloaded() ? yes : no));
867 os.str("");
868 os << _blendMode;
869 tr.append_child(it, StringPair(_("Blend mode"), os.str()));
870 #ifndef NDEBUG
871 // This probably isn't interesting for non-developers
872 tr.append_child(it, StringPair(_("Invalidated"), _invalidated ? yes : no));
873 tr.append_child(it, StringPair(_("Child invalidated"),
874 _child_invalidated ? yes : no));
875 #endif
876 return it;
878 #endif
880 MovieClip*
881 DisplayObject::getAsRoot()
883 return get_root();
886 void
887 setIndexedProperty(size_t index, DisplayObject& o, const as_value& val)
889 string_table::key prop = getPropertyByIndex(index);
890 if (!prop) return;
891 doSet(prop, o, val);
894 void
895 getIndexedProperty(size_t index, DisplayObject& o, as_value& val)
897 string_table::key prop = getPropertyByIndex(index);
898 if (!prop) {
899 val.set_undefined();
900 return;
902 doGet(prop, o, val);
906 /// DisplayObject property lookup
908 /// This function is only called on the first object in the inheritance chain
909 /// after the object's own properties have been checked.
910 /// In AS2, any DisplayObject marks the end of the inheritance chain for
911 /// lookups.
913 /// Lookup order:
915 /// 1. _level0.._level9
916 /// 2. Objects on the DisplayList of a MovieClip
917 /// 3. DisplayObject magic properties (_x, _y etc).
918 /// 4. MovieClips' TextField variables (this is probably not the best
919 /// way to do it, but as it is done like this, this must be called here.
920 /// It will cause an infinite recursion otherwise.
921 bool
922 getDisplayObjectProperty(DisplayObject& obj, const ObjectURI& uri,
923 as_value& val)
926 as_object* o = getObject(&obj);
927 assert(o);
929 string_table& st = getStringTable(*o);
930 const std::string& propname = uri.toString(st);
932 // Check _level0.._level9
933 movie_root& mr = getRoot(*getObject(&obj));
934 unsigned int levelno;
935 if (isLevelTarget(getSWFVersion(*o), propname, levelno)) {
936 MovieClip* mo = mr.getLevel(levelno);
937 if (mo) {
938 val = getObject(mo);
939 return true;
941 return false;
944 MovieClip* mc = dynamic_cast<MovieClip*>(&obj);
945 if (mc) {
946 DisplayObject* ch = mc->getDisplayListObject(uri);
947 if (ch) {
948 val = getObject(ch);
949 return true;
953 const string_table::key noCaseKey = uri.noCase(st);
955 // These properties have normal case-sensitivity.
956 // They are tested to exist for TextField, MovieClip, and Button
957 // but do not belong to the inheritance chain.
958 switch (caseless(*o) ? noCaseKey : getName(uri))
960 default:
961 break;
962 case NSV::PROP_uROOT:
963 if (getSWFVersion(*o) < 5) break;
964 val = getObject(obj.getAsRoot());
965 return true;
966 case NSV::PROP_uGLOBAL:
967 // TODO: clean up this mess.
968 assert(getObject(&obj));
969 if (getSWFVersion(*o) < 6) break;
970 val = &getGlobal(*o);
971 return true;
974 // These magic properties are case insensitive in all versions!
975 if (doGet(noCaseKey, obj, val)) return true;
977 // Check MovieClip such as TextField variables.
978 // TODO: check if there's a better way to find these properties.
979 if (mc && mc->getTextFieldVariables(uri, val)) return true;
981 return false;
985 bool
986 setDisplayObjectProperty(DisplayObject& obj, const ObjectURI& uri,
987 const as_value& val)
989 // These magic properties are case insensitive in all versions!
990 string_table& st = getStringTable(*getObject(&obj));
991 return doSet(uri.noCase(st), obj, val);
994 DisplayObject::MaskRenderer::MaskRenderer(Renderer& r, const DisplayObject& o)
996 _renderer(r),
997 _mask(o.visible() && o.getMask() && !o.getMask()->unloaded() ? o.getMask()
998 : 0)
1000 if (!_mask) return;
1002 _renderer.begin_submit_mask();
1003 DisplayObject* p = _mask->parent();
1004 const Transform tr = p ?
1005 Transform(getWorldMatrix(*p), getWorldCxForm(*p)) : Transform();
1006 _mask->display(_renderer, tr);
1007 _renderer.end_submit_mask();
1010 DisplayObject::MaskRenderer::~MaskRenderer()
1012 if (_mask) _renderer.disable_mask();
1015 namespace {
1017 as_value
1018 getQuality(DisplayObject& o)
1020 movie_root& mr = getRoot(*getObject(&o));
1021 switch (mr.getQuality())
1023 case QUALITY_BEST:
1024 return as_value("BEST");
1025 case QUALITY_HIGH:
1026 return as_value("HIGH");
1027 case QUALITY_MEDIUM:
1028 return as_value("MEDIUM");
1029 case QUALITY_LOW:
1030 return as_value("LOW");
1033 return as_value();
1037 void
1038 setQuality(DisplayObject& o, const as_value& val)
1040 movie_root& mr = getRoot(*getObject(&o));
1042 if (!val.is_string()) return;
1044 const std::string& q = val.to_string();
1046 StringNoCaseEqual noCaseCompare;
1048 if (noCaseCompare(q, "BEST")) mr.setQuality(QUALITY_BEST);
1049 else if (noCaseCompare(q, "HIGH")) {
1050 mr.setQuality(QUALITY_HIGH);
1052 else if (noCaseCompare(q, "MEDIUM")) {
1053 mr.setQuality(QUALITY_MEDIUM);
1055 else if (noCaseCompare(q, "LOW")) {
1056 mr.setQuality(QUALITY_LOW);
1059 return;
1062 as_value
1063 getURL(DisplayObject& o)
1065 return as_value(o.get_root()->url());
1068 as_value
1069 getHighQuality(DisplayObject& o)
1071 movie_root& mr = getRoot(*getObject(&o));
1072 switch (mr.getQuality())
1074 case QUALITY_BEST:
1075 return as_value(2.0);
1076 case QUALITY_HIGH:
1077 return as_value(1.0);
1078 case QUALITY_MEDIUM:
1079 case QUALITY_LOW:
1080 return as_value(0.0);
1082 return as_value();
1085 void
1086 setHighQuality(DisplayObject& o, const as_value& val)
1088 movie_root& mr = getRoot(*getObject(&o));
1090 const double q = val.to_number();
1092 if (q < 0) mr.setQuality(QUALITY_HIGH);
1093 else if (q > 2) mr.setQuality(QUALITY_BEST);
1094 else {
1095 int i = static_cast<int>(q);
1096 switch(i)
1098 case 0:
1099 mr.setQuality(QUALITY_LOW);
1100 break;
1101 case 1:
1102 mr.setQuality(QUALITY_HIGH);
1103 break;
1104 case 2:
1105 mr.setQuality(QUALITY_BEST);
1106 break;
1112 void
1113 setY(DisplayObject& o, const as_value& val)
1116 const double newy = val.to_number();
1118 // NaN is skipped, Infinite isn't
1119 if (isNaN(newy))
1121 IF_VERBOSE_ASCODING_ERRORS(
1122 log_aserror(_("Attempt to set %s._y to %s "
1123 "(evaluating to number %g) refused"),
1124 o.getTarget(), val, newy);
1126 return;
1129 SWFMatrix m = getMatrix(o);
1130 // NOTE: infinite_to_zero is wrong here, see actionscript.all/setProperty.as
1131 m.set_y_translation(pixelsToTwips(infinite_to_zero(newy)));
1132 o.setMatrix(m);
1133 o.transformedByScript();
1136 as_value
1137 getY(DisplayObject& o)
1139 SWFMatrix m = getMatrix(o);
1140 return twipsToPixels(m.get_y_translation());
1143 void
1144 setX(DisplayObject& o, const as_value& val)
1147 const double newx = val.to_number();
1149 // NaN is skipped, Infinite isn't
1150 if (isNaN(newx))
1152 IF_VERBOSE_ASCODING_ERRORS(
1153 log_aserror(_("Attempt to set %s._x to %s "
1154 "(evaluating to number %g) refused"),
1155 o.getTarget(), val, newx);
1157 return;
1160 SWFMatrix m = getMatrix(o);
1161 // NOTE: infinite_to_zero is wrong here, see actionscript.all/setProperty.as
1162 m.set_x_translation(pixelsToTwips(infinite_to_zero(newx)));
1163 o.setMatrix(m);
1164 o.transformedByScript();
1167 as_value
1168 getX(DisplayObject& o)
1170 SWFMatrix m = getMatrix(o);
1171 return twipsToPixels(m.get_x_translation());
1174 void
1175 setScaleX(DisplayObject& o, const as_value& val)
1178 const double scale_percent = val.to_number();
1180 // NaN is skipped, Infinite is not, see actionscript.all/setProperty.as
1181 if (isNaN(scale_percent))
1183 IF_VERBOSE_ASCODING_ERRORS(
1184 log_aserror(_("Attempt to set %s._xscale to %s "
1185 "(evaluating to number %g) refused"),
1186 o.getTarget(), val, scale_percent);
1188 return;
1191 // input is in percent
1192 o.set_x_scale(scale_percent);
1196 as_value
1197 getScaleX(DisplayObject& o)
1199 return o.scaleX();
1202 void
1203 setScaleY(DisplayObject& o, const as_value& val)
1206 const double scale_percent = val.to_number();
1208 // NaN is skipped, Infinite is not, see actionscript.all/setProperty.as
1209 if (isNaN(scale_percent))
1211 IF_VERBOSE_ASCODING_ERRORS(
1212 log_aserror(_("Attempt to set %s._yscale to %s "
1213 "(evaluating to number %g) refused"),
1214 o.getTarget(), val, scale_percent);
1216 return;
1219 // input is in percent
1220 o.set_y_scale(scale_percent);
1224 as_value
1225 getScaleY(DisplayObject& o)
1227 return o.scaleY();
1230 as_value
1231 getVisible(DisplayObject& o)
1233 return o.visible();
1236 void
1237 setVisible(DisplayObject& o, const as_value& val)
1240 /// We cast to number and rely (mostly) on C++'s automatic
1241 /// cast to bool, as string "0" should be converted to
1242 /// its numeric equivalent, not interpreted as 'true', which
1243 /// SWF7+ does for strings.
1244 double d = val.to_number();
1246 // Infinite or NaN is skipped
1247 if (isInf(d) || isNaN(d)) {
1248 IF_VERBOSE_ASCODING_ERRORS(
1249 log_aserror(_("Attempt to set %s._visible to %s "
1250 "(evaluating to number %g) refused"),
1251 o.getTarget(), val, d);
1253 return;
1256 o.set_visible(d);
1258 o.transformedByScript();
1261 as_value
1262 getAlpha(DisplayObject& o)
1264 return as_value(getCxForm(o).aa / 2.56);
1267 void
1268 setAlpha(DisplayObject& o, const as_value& val)
1271 // The new internal alpha value is input / 100.0 * 256.
1272 // We test for finiteness later, but the multiplication
1273 // won't make any difference.
1274 const double newAlpha = val.to_number() * 2.56;
1276 // NaN is skipped, Infinite is not, see actionscript.all/setProperty.as
1277 if (isNaN(newAlpha)) {
1278 IF_VERBOSE_ASCODING_ERRORS(
1279 log_aserror(_("Attempt to set %s._alpha to %s "
1280 "(evaluating to number %g) refused"),
1281 o.getTarget(), val, newAlpha);
1283 return;
1286 SWFCxForm cx = getCxForm(o);
1288 // Overflows are *not* truncated, but set to -32768.
1289 if (newAlpha > std::numeric_limits<boost::int16_t>::max() ||
1290 newAlpha < std::numeric_limits<boost::int16_t>::min()) {
1291 cx.aa = std::numeric_limits<boost::int16_t>::min();
1293 else {
1294 cx.aa = static_cast<boost::int16_t>(newAlpha);
1297 o.setCxForm(cx);
1298 o.transformedByScript();
1302 as_value
1303 getMouseX(DisplayObject& o)
1305 // Local coord of mouse IN PIXELS.
1306 boost::int32_t x, y;
1307 getRoot(*getObject(&o)).get_mouse_state(x, y);
1309 SWFMatrix m = getWorldMatrix(o);
1310 point a(pixelsToTwips(x), pixelsToTwips(y));
1312 m.invert().transform(a);
1313 return as_value(twipsToPixels(a.x));
1316 as_value
1317 getMouseY(DisplayObject& o)
1319 // Local coord of mouse IN PIXELS.
1320 boost::int32_t x, y;
1321 getRoot(*getObject(&o)).get_mouse_state(x, y);
1323 SWFMatrix m = getWorldMatrix(o);
1324 point a(pixelsToTwips(x), pixelsToTwips(y));
1325 m.invert().transform(a);
1326 return as_value(twipsToPixels(a.y));
1329 as_value
1330 getRotation(DisplayObject& o)
1332 return o.rotation();
1336 void
1337 setRotation(DisplayObject& o, const as_value& val)
1340 // input is in degrees
1341 const double rotation_val = val.to_number();
1343 // NaN is skipped, Infinity isn't
1344 if (isNaN(rotation_val))
1346 IF_VERBOSE_ASCODING_ERRORS(
1347 log_aserror(_("Attempt to set %s._rotation to %s "
1348 "(evaluating to number %g) refused"),
1349 o.getTarget(), val, rotation_val);
1351 return;
1353 o.set_rotation(rotation_val);
1357 as_value
1358 getParent(DisplayObject& o)
1360 as_object* p = getObject(o.parent());
1361 return p ? p : as_value();
1364 as_value
1365 getTarget(DisplayObject& o)
1367 return o.getTargetPath();
1370 as_value
1371 getNameProperty(DisplayObject& o)
1373 string_table& st = getStringTable(*getObject(&o));
1374 const std::string& name = o.get_name().toString(st);
1375 if (getSWFVersion(*getObject(&o)) < 6 && name.empty()) return as_value();
1376 return as_value(name);
1379 void
1380 setName(DisplayObject& o, const as_value& val)
1382 string_table& st = getStringTable(*getObject(&o));
1383 o.set_name(st.find(val.to_string().c_str()));
1386 void
1387 setSoundBufTime(DisplayObject& /*o*/, const as_value& /*val*/)
1389 LOG_ONCE(log_unimpl("_soundbuftime setting"));
1392 as_value
1393 getSoundBufTime(DisplayObject& /*o*/)
1395 return as_value(0.0);
1398 as_value
1399 getWidth(DisplayObject& o)
1401 SWFRect bounds = o.getBounds();
1402 const SWFMatrix& m = getMatrix(o);
1403 m.transform(bounds);
1404 return twipsToPixels(bounds.width());
1407 void
1408 setWidth(DisplayObject& o, const as_value& val)
1410 const double newwidth = pixelsToTwips(val.to_number());
1411 if (newwidth <= 0) {
1412 IF_VERBOSE_ASCODING_ERRORS(
1413 log_aserror(_("Setting _width=%g of DisplayObject %s (%s)"),
1414 newwidth/20, o.getTarget(), typeName(o));
1417 o.setWidth(newwidth);
1420 as_value
1421 getFocusRect(DisplayObject& /*o*/)
1423 LOG_ONCE(log_unimpl("_focusrect"));
1424 return as_value(true);
1427 void
1428 setFocusRect(DisplayObject& /*o*/, const as_value& /*val*/)
1430 LOG_ONCE(log_unimpl("_focusrect setting"));
1433 as_value
1434 getDropTarget(DisplayObject& o)
1436 // This property only applies to MovieClips.
1437 MovieClip* mc = dynamic_cast<MovieClip*>(&o);
1438 if (!mc) return as_value();
1439 return as_value(mc->getDropTarget());
1442 as_value
1443 getCurrentFrame(DisplayObject& o)
1445 // This property only applies to MovieClips.
1446 MovieClip* mc = dynamic_cast<MovieClip*>(&o);
1447 if (!mc) return as_value();
1448 const int currframe =
1449 std::min(mc->get_loaded_frames(), mc->get_current_frame() + 1);
1450 return as_value(currframe);
1453 as_value
1454 getFramesLoaded(DisplayObject& o)
1456 // This property only applies to MovieClips.
1457 MovieClip* mc = dynamic_cast<MovieClip*>(&o);
1458 if (!mc) return as_value();
1459 return as_value(mc->get_loaded_frames());
1462 as_value
1463 getTotalFrames(DisplayObject& o)
1465 // This property only applies to MovieClips.
1466 MovieClip* mc = dynamic_cast<MovieClip*>(&o);
1467 if (!mc) return as_value();
1468 return as_value(mc->get_frame_count());
1472 string_table::key
1473 getPropertyByIndex(size_t index)
1476 // This is a magic number; defining it here makes sure that the
1477 // table is really this size.
1478 const size_t size = 22;
1480 if (index >= size) return 0;
1482 static const string_table::key props[size] = {
1483 NSV::PROP_uX,
1484 NSV::PROP_uY,
1485 NSV::PROP_uXSCALE,
1486 NSV::PROP_uYSCALE,
1487 NSV::PROP_uCURRENTFRAME,
1488 NSV::PROP_uTOTALFRAMES,
1489 NSV::PROP_uALPHA,
1490 NSV::PROP_uVISIBLE,
1491 NSV::PROP_uWIDTH,
1492 NSV::PROP_uHEIGHT,
1493 NSV::PROP_uROTATION,
1494 NSV::PROP_uTARGET,
1495 NSV::PROP_uFRAMESLOADED,
1496 NSV::PROP_uNAME,
1497 NSV::PROP_uDROPTARGET,
1498 NSV::PROP_uURL,
1499 NSV::PROP_uHIGHQUALITY,
1500 NSV::PROP_uFOCUSRECT,
1501 NSV::PROP_uSOUNDBUFTIME,
1502 NSV::PROP_uQUALITY,
1503 NSV::PROP_uXMOUSE,
1504 NSV::PROP_uYMOUSE
1506 return props[index];
1509 bool
1510 doGet(string_table::key prop, DisplayObject& o, as_value& val)
1512 const Getters& getters = displayObjectGetters();
1513 const Getters::const_iterator it = getters.find(prop);
1514 if (it == getters.end()) return false;
1516 val = (*it->second)(o);
1517 return true;
1521 /// Do the actual setProperty
1523 /// Return true if the property is a DisplayObject property, regardless of
1524 /// whether it was successfully set or not.
1526 /// @param prop The property to search for. Note that all special
1527 /// properties are lower-case, so for a caseless check
1528 /// it is sufficient for prop to be caseless.
1529 bool
1530 doSet(string_table::key prop, DisplayObject& o, const as_value& val)
1532 const Setters& setters = displayObjectSetters();
1533 const Setters::const_iterator it = setters.find(prop);
1534 if (it == setters.end()) return false;
1536 const Setter s = it->second;
1538 // Read-only.
1539 if (!s) return true;
1541 if (val.is_undefined() || val.is_null()) {
1542 IF_VERBOSE_ASCODING_ERRORS(
1543 log_aserror(_("Attempt to set property to %s, refused"),
1544 o.getTarget(), val);
1546 return true;
1549 (*s)(o, val);
1550 return true;
1553 const Getters&
1554 displayObjectGetters()
1556 static const Getters getters = boost::assign::map_list_of
1557 (NSV::PROP_uX, &getX)
1558 (NSV::PROP_uY, &getY)
1559 (NSV::PROP_uXSCALE, &getScaleX)
1560 (NSV::PROP_uYSCALE, &getScaleY)
1561 (NSV::PROP_uROTATION, &getRotation)
1562 (NSV::PROP_uHIGHQUALITY, &getHighQuality)
1563 (NSV::PROP_uQUALITY, &getQuality)
1564 (NSV::PROP_uALPHA, &getAlpha)
1565 (NSV::PROP_uWIDTH, &getWidth)
1566 (NSV::PROP_uURL, &getURL)
1567 (NSV::PROP_uHEIGHT, &getHeight)
1568 (NSV::PROP_uNAME, &getNameProperty)
1569 (NSV::PROP_uVISIBLE, &getVisible)
1570 (NSV::PROP_uSOUNDBUFTIME, &getSoundBufTime)
1571 (NSV::PROP_uFOCUSRECT, &getFocusRect)
1572 (NSV::PROP_uDROPTARGET, &getDropTarget)
1573 (NSV::PROP_uCURRENTFRAME, &getCurrentFrame)
1574 (NSV::PROP_uFRAMESLOADED, &getFramesLoaded)
1575 (NSV::PROP_uTOTALFRAMES, &getTotalFrames)
1576 (NSV::PROP_uPARENT, &getParent)
1577 (NSV::PROP_uTARGET, &getTarget)
1578 (NSV::PROP_uXMOUSE, &getMouseX)
1579 (NSV::PROP_uYMOUSE, &getMouseY);
1580 return getters;
1583 const Setters&
1584 displayObjectSetters()
1586 const Setter n = 0;
1588 static const Setters setters = boost::assign::map_list_of
1589 (NSV::PROP_uX, &setX)
1590 (NSV::PROP_uY, &setY)
1591 (NSV::PROP_uXSCALE, &setScaleX)
1592 (NSV::PROP_uYSCALE, &setScaleY)
1593 (NSV::PROP_uROTATION, &setRotation)
1594 (NSV::PROP_uHIGHQUALITY, &setHighQuality)
1595 (NSV::PROP_uQUALITY, &setQuality)
1596 (NSV::PROP_uALPHA, &setAlpha)
1597 (NSV::PROP_uWIDTH, &setWidth)
1598 (NSV::PROP_uHEIGHT, &setHeight)
1599 (NSV::PROP_uNAME, &setName)
1600 (NSV::PROP_uVISIBLE, &setVisible)
1601 (NSV::PROP_uSOUNDBUFTIME, &setSoundBufTime)
1602 (NSV::PROP_uFOCUSRECT, &setFocusRect)
1603 (NSV::PROP_uDROPTARGET, n)
1604 (NSV::PROP_uCURRENTFRAME, n)
1605 (NSV::PROP_uFRAMESLOADED, n)
1606 (NSV::PROP_uTOTALFRAMES, n)
1607 (NSV::PROP_uPARENT, n)
1608 (NSV::PROP_uURL, n)
1609 (NSV::PROP_uTARGET, n)
1610 (NSV::PROP_uXMOUSE, n)
1611 (NSV::PROP_uYMOUSE, n);
1612 return setters;
1616 const BlendModeMap&
1617 getBlendModeMap()
1619 /// BLENDMODE_UNDEFINED has no matching string in AS. It is included
1620 /// here for logging purposes.
1621 static const BlendModeMap bm = boost::assign::map_list_of
1622 (DisplayObject::BLENDMODE_UNDEFINED, "undefined")
1623 (DisplayObject::BLENDMODE_NORMAL, "normal")
1624 (DisplayObject::BLENDMODE_LAYER, "layer")
1625 (DisplayObject::BLENDMODE_MULTIPLY, "multiply")
1626 (DisplayObject::BLENDMODE_SCREEN, "screen")
1627 (DisplayObject::BLENDMODE_LIGHTEN, "lighten")
1628 (DisplayObject::BLENDMODE_DARKEN, "darken")
1629 (DisplayObject::BLENDMODE_DIFFERENCE, "difference")
1630 (DisplayObject::BLENDMODE_ADD, "add")
1631 (DisplayObject::BLENDMODE_SUBTRACT, "subtract")
1632 (DisplayObject::BLENDMODE_INVERT, "invert")
1633 (DisplayObject::BLENDMODE_ALPHA, "alpha")
1634 (DisplayObject::BLENDMODE_ERASE, "erase")
1635 (DisplayObject::BLENDMODE_OVERLAY, "overlay")
1636 (DisplayObject::BLENDMODE_HARDLIGHT, "hardlight");
1638 return bm;
1641 // Match a blend mode to its string.
1642 bool
1643 blendModeMatches(const BlendModeMap::value_type& val, const std::string& mode)
1645 /// The match must be case-sensitive.
1646 if (mode.empty()) return false;
1647 return (val.second == mode);
1652 std::ostream&
1653 operator<<(std::ostream& o, DisplayObject::BlendMode bm)
1655 const BlendModeMap& bmm = getBlendModeMap();
1656 return (o << bmm.find(bm)->second);
1660 } // namespace gnash
1662 // local variables:
1663 // mode: c++
1664 // indent-tabs-mode: t
1665 // end: