Update with current status
[gnash.git] / libcore / DisplayObject.cpp
blob3b43731c0d898f29685f92e846f8fb304ea7ec26
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.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 #ifdef HAVE_CONFIG_H
22 #include "gnashconfig.h" // USE_SWFTREE
23 #endif
25 #include "DisplayObject.h"
27 #include <utility>
28 #include <functional>
29 #include <boost/logic/tribool.hpp>
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
50 #undef set_invalidated
52 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 void(*Setter)(DisplayObject&, const as_value&);
64 typedef std::pair<Getter, Setter> GetterSetter;
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);
70 // NOTE: comparison will be case-insensitive
71 const GetterSetter& getGetterSetterByURI(const ObjectURI& uri,
72 string_table& st);
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);
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;
86 DisplayObject::DisplayObject(movie_root& mr, as_object* object,
87 DisplayObject* parent)
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)
114 assert(m_old_invalidated_ranges.isNull());
116 // This informs the core that the object is a DisplayObject.
117 if (_object) _object->setDisplayObject(this);
120 void
121 DisplayObject::getLoadedMovie(Movie* extern_movie)
123 LOG_ONCE(log_unimpl(_("loadMovie against a %s DisplayObject"),
124 typeName(*this))
127 // TODO: look at the MovieClip implementation, but most importantly
128 // test all the event handlers copies etc..
130 UNUSED(extern_movie);
133 ObjectURI
134 DisplayObject::getNextUnnamedInstanceName()
136 assert(_object);
137 movie_root& mr = stage();
139 std::ostringstream ss;
140 ss << "instance" << mr.nextUnnamedInstance();
142 VM& vm = mr.getVM();
143 return getURI(vm, ss.str(), true);
148 DisplayObject::getWorldVolume() const
150 int volume = _volume;
151 if (_parent) {
152 volume = int(volume*_parent->getVolume()/100.0);
155 return volume;
159 as_object*
160 DisplayObject::pathElement(const ObjectURI& uri)
162 as_object* obj = getObject(this);
163 if (!obj) return nullptr;
165 string_table::key key = getName(uri);
167 string_table& st = stage().getVM().getStringTable();
169 // TODO: put ".." and "." in namedStrings
170 if (key == st.find("..")) return getObject(parent());
171 if (key == st.find(".")) return obj;
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;
179 return nullptr;
182 void
183 DisplayObject::set_invalidated()
185 set_invalidated("unknown", -1);
188 void
189 DisplayObject::set_invalidated(const char* debug_file, int debug_line)
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();
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.
203 if ( ! _invalidated )
205 _invalidated = true;
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
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...
220 m_old_invalidated_ranges.setNull();
221 add_invalidated_bounds(m_old_invalidated_ranges, true);
225 void
226 DisplayObject::add_invalidated_bounds(InvalidatedRanges& ranges, bool force)
228 ranges.add(m_old_invalidated_ranges);
229 if (visible() && (_invalidated||force))
231 SWFRect bounds;
232 bounds.expand_to_transformed_rect(getWorldMatrix(*this), getBounds());
233 ranges.add(bounds.getRange());
237 void
238 DisplayObject::set_child_invalidated()
240 if (!_child_invalidated) {
241 _child_invalidated=true;
242 if (_parent) _parent->set_child_invalidated();
246 void
247 DisplayObject::extend_invalidated_bounds(const InvalidatedRanges& ranges)
249 set_invalidated(__FILE__, __LINE__);
250 m_old_invalidated_ranges.add(ranges);
253 as_value
254 DisplayObject::blendMode(const fn_call& fn)
256 DisplayObject* ch = ensure<IsDisplayObject<> >(fn);
258 // This is AS-correct, but doesn't do anything.
259 // TODO: implement in the renderers!
260 LOG_ONCE(log_unimpl(_("blendMode")));
262 if (!fn.nargs)
264 // Getter
265 BlendMode bm = ch->getBlendMode();
267 /// If the blend mode is undefined, it doesn't return a string.
268 if (bm == BLENDMODE_UNDEFINED) return as_value();
270 std::ostringstream blendMode;
271 blendMode << bm;
272 return as_value(blendMode.str());
276 // Setter
279 const as_value& bm = fn.arg(0);
281 // Undefined argument sets blend mode to normal.
282 if (bm.is_undefined()) {
283 ch->setBlendMode(BLENDMODE_NORMAL);
284 return as_value();
287 // Numeric argument.
288 if (bm.is_number()) {
289 double mode = toNumber(bm, getVM(fn));
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) {
295 // An invalid numeric argument becomes undefined.
296 ch->setBlendMode(BLENDMODE_UNDEFINED);
298 else {
299 /// The extra static cast is required to keep OpenBSD happy.
300 ch->setBlendMode(static_cast<BlendMode>(static_cast<int>(mode)));
302 return as_value();
305 // Other arguments use toString method.
306 const std::string& mode = bm.to_string();
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));
312 if (it != bmm.end()) {
313 ch->setBlendMode(it->first);
316 // An invalid string argument has no effect.
318 return as_value();
322 void
323 DisplayObject::set_visible(bool visible)
325 if (_visible != visible) set_invalidated(__FILE__, __LINE__);
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);
336 _visible = visible;
339 void
340 DisplayObject::setWidth(double newwidth)
342 const SWFRect& bounds = getBounds();
343 const double oldwidth = bounds.width();
344 assert(oldwidth >= 0);
346 const double xscale = oldwidth ? (newwidth / oldwidth) : 0;
347 const double rotation = _rotation * PI / 180.0;
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);
355 as_value
356 getHeight(DisplayObject& o)
358 SWFRect bounds = o.getBounds();
359 const SWFMatrix& m = getMatrix(o);
360 m.transform(bounds);
361 return twipsToPixels(bounds.height());
364 void
365 setHeight(DisplayObject& o, const as_value& val)
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));
374 o.setHeight(newheight);
377 void
378 DisplayObject::setHeight(double newheight)
380 const SWFRect& bounds = getBounds();
382 const double oldheight = bounds.height();
383 assert(oldheight >= 0);
385 const double yscale = oldheight ? (newheight / oldheight) : 0;
386 const double rotation = _rotation * PI / 180.0;
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);
394 void
395 DisplayObject::setMatrix(const SWFMatrix& m, bool updateCache)
398 if (m == _transform.matrix) return;
400 set_invalidated(__FILE__, __LINE__);
401 _transform.matrix = m;
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;
412 void
413 DisplayObject::set_event_handlers(const Events& copyfrom)
415 for (const auto& event : copyfrom)
417 const event_id& ev = event.first;
418 const BufferList& bufs = event.second;
419 for (const action_buffer* buf : bufs)
421 assert(buf);
422 add_event_handler(ev, *buf);
427 void
428 DisplayObject::add_event_handler(const event_id& id, const action_buffer& code)
430 _event_handlers[id].push_back(&code);
433 std::unique_ptr<ExecutableCode>
434 DisplayObject::get_event_handler(const event_id& id) const
436 std::unique_ptr<ExecutableCode> handler;
438 Events::const_iterator it = _event_handlers.find(id);
439 if ( it == _event_handlers.end() ) return handler;
441 DisplayObject* this_ptr = const_cast<DisplayObject*>(this);
443 handler.reset( new EventCode(this_ptr, it->second) );
444 return handler;
447 bool
448 DisplayObject::unload()
450 const bool unloadHandler = unloadChildren();
452 // Unregister this DisplayObject as mask and/or maskee.
453 if (_maskee) _maskee->setMask(nullptr);
454 if (_mask) _mask->setMaskee(nullptr);
456 _unloaded = true;
458 return unloadHandler;
461 bool
462 DisplayObject::hasEventHandler(const event_id& id) const
464 Events::const_iterator it = _event_handlers.find(id);
465 if (it != _event_handlers.end()) return true;
467 if (!_object) return false;
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;
475 return false;
479 /// Set the real and cached x scale.
481 /// Cached rotation and y scale are not updated.
482 void
483 DisplayObject::set_x_scale(double scale_percent)
485 double xscale = scale_percent / 100.0;
487 if (xscale != 0.0 && _xscale != 0.0)
489 if (scale_percent * _xscale < 0.0)
491 xscale = -std::abs(xscale);
493 else xscale = std::abs(xscale);
496 _xscale = scale_percent;
498 // As per misc-ming.all/SWFMatrix_test.{c,swf}
499 // we don't need to recompute the SWFMatrix from the
500 // caches.
502 SWFMatrix m = getMatrix(*this);
504 m.set_x_scale(xscale);
506 setMatrix(m); // we updated the cache ourselves
508 transformedByScript();
511 /// Set the real and cached rotation.
513 /// Cached scale values are not updated.
514 void
515 DisplayObject::set_rotation(double rot)
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;
522 double rotation = rot * PI / 180.0;
524 if (_xscale < 0) rotation += PI;
526 SWFMatrix m = getMatrix(*this);
527 m.set_rotation(rotation);
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
536 _rotation = rot;
538 transformedByScript();
542 /// Set the real and cached y scale.
544 /// Cached rotation and x scale are not updated.
545 void
546 DisplayObject::set_y_scale(double scale_percent)
548 double yscale = scale_percent / 100.0;
550 if (yscale != 0.0 && _yscale != 0.0)
552 if (scale_percent * _yscale < 0.0) yscale = -std::abs(yscale);
553 else yscale = std::abs(yscale);
556 _yscale = scale_percent;
558 SWFMatrix m = getMatrix(*this);
559 m.set_y_scale(yscale);
560 setMatrix(m); // we updated the cache ourselves
562 transformedByScript();
566 std::string
567 DisplayObject::getTargetPath() const
569 // TODO: check what happens when this DisplayObject
570 // is a Movie loaded into another
571 // running movie.
573 typedef std::vector<std::string> Path;
574 Path path;
576 // Build parents stack
577 const DisplayObject* topLevel = nullptr;
578 const DisplayObject* ch = this;
580 string_table& st = getStringTable(*getObject(this));
581 for (;;)
583 const DisplayObject* parent = ch->parent();
585 // Don't push the _root name on the stack
586 if (!parent) {
587 topLevel = ch;
588 break;
591 path.push_back(ch->get_name().toString(st));
592 ch = parent;
595 assert(topLevel);
597 if (path.empty()) {
598 if (&stage().getRootMovie() == this) return "/";
599 std::stringstream ss;
600 ss << "_level" << _depth-DisplayObject::staticDepthOffset;
601 return ss.str();
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();
612 for (Path::reverse_iterator it=path.rbegin(), itEnd=path.rend();
613 it != itEnd; ++it) {
614 target += "/" + *it;
616 return target;
620 std::string
621 DisplayObject::getTarget() const
624 // TODO: check what happens when this DisplayObject
625 // is a Movie loaded into another
626 // running movie.
628 typedef std::vector<std::string> Path;
629 Path path;
631 // Build parents stack
632 const DisplayObject* ch = this;
633 string_table& st = stage().getVM().getStringTable();
634 for (;;) {
636 const DisplayObject* parent = ch->parent();
638 // Don't push the _root name on the stack
639 if (!parent) {
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());
649 else {
650 ss << "_level" <<
651 ch->get_depth() - DisplayObject::staticDepthOffset;
652 path.push_back(ss.str());
654 break;
657 path.push_back(ch->get_name().toString(st));
658 ch = parent;
661 assert (!path.empty());
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) {
668 if (!target.empty()) target += ".";
669 target += *it;
672 return target;
676 void
677 DisplayObject::destroy()
679 // in case we are destroyed without being unloaded first
680 // see bug #21842
681 _unloaded = true;
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 ?
688 if (_object) _object->clearProperties();
690 assert(!_destroyed);
691 _destroyed = true;
694 void
695 DisplayObject::markReachableResources() const
697 markOwnResources();
698 if (_object) _object->setReachable();
699 if (_parent) _parent->setReachable();
700 if (_mask) _mask->setReachable();
701 if (_maskee) _maskee->setReachable();
704 /// Whether to use a hand cursor when the mouse is over this DisplayObject
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
713 DisplayObject::allowHandCursor() const
715 as_object* obj = getObject(this);
716 if (!obj) return false;
718 as_value val;
719 if (!obj->get_member(NSV::PROP_USEHANDCURSOR, &val)) {
720 return true;
722 return toBool(val, getVM(*obj));
725 void
726 DisplayObject::setMask(DisplayObject* mask)
728 if ( _mask == mask ) return;
730 set_invalidated();
732 // Backup this before setMaskee has a chance to change it..
733 DisplayObject* prevMaskee = _maskee;
735 // If we had a previous mask unregister with it
736 if ( _mask && _mask != mask )
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);
745 // if we had a maskee, notify it to stop using
746 // us as a mask
747 if (prevMaskee) prevMaskee->setMask(nullptr);
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;
755 if (_mask) {
756 /// Register as as masked by the mask
757 _mask->setMaskee(this);
761 void
762 DisplayObject::setMaskee(DisplayObject* maskee)
764 if ( _maskee == maskee ) { return; }
766 if (_maskee) {
767 // We don't want the maskee to call setMaskee(null)
768 // on us again
769 _maskee->_mask = nullptr;
772 _maskee = maskee;
774 if (!maskee)
776 // TODO: should we reset any original clip depth
777 // specified by PlaceObject tag ?
778 set_clip_depth(noClipDepthValue);
783 bool
784 DisplayObject::boundsInClippingArea(Renderer& renderer) const
786 SWFRect mybounds = getBounds();
787 getWorldMatrix(*this).transform(mybounds);
789 return renderer.bounds_in_clipping_area(mybounds.getRange());
792 #ifdef USE_SWFTREE
793 DisplayObject::InfoTree::iterator
794 DisplayObject::getMovieInfo(InfoTree& tr, InfoTree::iterator it)
796 const std::string yes = _("yes");
797 const std::string no = _("no");
799 it = tr.append_child(it, std::make_pair(getTarget(), typeName(*this)));
801 std::ostringstream os;
802 os << get_depth();
803 tr.append_child(it, std::make_pair(_("Depth"), os.str()));
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()));
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;
819 tr.append_child(it, std::make_pair(_("Clipping depth"), os.str()));
822 os.str("");
823 os << getBounds().width() << "x" << getBounds().height();
824 tr.append_child(it, std::make_pair(_("Dimensions"), os.str()));
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));
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;
844 #endif
846 MovieClip*
847 DisplayObject::getAsRoot()
849 return get_root();
852 void
853 setIndexedProperty(size_t index, DisplayObject& o, const as_value& val)
855 const Setter s = getGetterSetterByIndex(index).second;
856 if (!s) return; // read-only (warn?)
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);
863 return;
866 (*s)(o, val);
869 void
870 getIndexedProperty(size_t index, DisplayObject& o, as_value& val)
872 const Getter s = getGetterSetterByIndex(index).first;
873 if (!s) {
874 val.set_undefined();
875 return;
877 val = (*s)(o);
881 /// DisplayObject property lookup
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.
888 /// Lookup order:
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
897 getDisplayObjectProperty(DisplayObject& obj, const ObjectURI& uri,
898 as_value& val)
901 as_object* o = getObject(&obj);
902 assert(o);
904 string_table& st = getStringTable(*o);
905 const std::string& propname = uri.toString(st);
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;
916 return false;
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;
928 const string_table::key noCaseKey = uri.noCase(st);
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))
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;
949 // These magic properties are case insensitive in all versions!
950 if (doGet(uri, obj, val)) return true;
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;
956 return false;
960 bool
961 setDisplayObjectProperty(DisplayObject& obj, const ObjectURI& uri,
962 const as_value& val)
964 // These magic properties are case insensitive in all versions!
965 return doSet(uri, obj, val);
968 DisplayObject::MaskRenderer::MaskRenderer(Renderer& r, const DisplayObject& o)
970 _renderer(r),
971 _mask(o.visible() && o.getMask() && !o.getMask()->unloaded() ? o.getMask()
972 : nullptr)
974 if (!_mask) return;
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();
984 DisplayObject::MaskRenderer::~MaskRenderer()
986 if (_mask) _renderer.disable_mask();
989 namespace {
991 as_value
992 getQuality(DisplayObject& o)
994 movie_root& mr = getRoot(*getObject(&o));
995 switch (mr.getQuality())
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");
1007 return as_value();
1011 void
1012 setQuality(DisplayObject& o, const as_value& val)
1014 movie_root& mr = getRoot(*getObject(&o));
1016 if (!val.is_string()) return;
1018 const std::string& q = val.to_string();
1020 StringNoCaseEqual noCaseCompare;
1022 if (noCaseCompare(q, "BEST")) mr.setQuality(QUALITY_BEST);
1023 else if (noCaseCompare(q, "HIGH")) {
1024 mr.setQuality(QUALITY_HIGH);
1026 else if (noCaseCompare(q, "MEDIUM")) {
1027 mr.setQuality(QUALITY_MEDIUM);
1029 else if (noCaseCompare(q, "LOW")) {
1030 mr.setQuality(QUALITY_LOW);
1033 return;
1036 as_value
1037 getURL(DisplayObject& o)
1039 return as_value(o.get_root()->url());
1042 as_value
1043 getHighQuality(DisplayObject& o)
1045 movie_root& mr = getRoot(*getObject(&o));
1046 switch (mr.getQuality())
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);
1056 return as_value();
1059 void
1060 setHighQuality(DisplayObject& o, const as_value& val)
1062 movie_root& mr = getRoot(*getObject(&o));
1064 const double q = toNumber(val, getVM(*getObject(&o)));
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)
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;
1086 void
1087 setY(DisplayObject& o, const as_value& val)
1089 const double newy = toNumber(val, getVM(*getObject(&o)));
1091 // NaN is skipped, Infinite isn't
1092 if (isNaN(newy))
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);
1099 return;
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();
1109 as_value
1110 getY(DisplayObject& o)
1112 const SWFMatrix& m = getMatrix(o);
1113 return twipsToPixels(m.get_y_translation());
1116 void
1117 setX(DisplayObject& o, const as_value& val)
1120 const double newx = toNumber(val, getVM(*getObject(&o)));
1122 // NaN is skipped, Infinite isn't
1123 if (isNaN(newx))
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);
1130 return;
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();
1140 as_value
1141 getX(DisplayObject& o)
1143 const SWFMatrix& m = getMatrix(o);
1144 return twipsToPixels(m.get_x_translation());
1147 void
1148 setScaleX(DisplayObject& o, const as_value& val)
1150 const double scale_percent = toNumber(val, getVM(*getObject(&o)));
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);
1159 return;
1162 // input is in percent
1163 o.set_x_scale(scale_percent);
1167 as_value
1168 getScaleX(DisplayObject& o)
1170 return o.scaleX();
1173 void
1174 setScaleY(DisplayObject& o, const as_value& val)
1176 const double scale_percent = toNumber(val, getVM(*getObject(&o)));
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);
1185 return;
1188 // input is in percent
1189 o.set_y_scale(scale_percent);
1193 as_value
1194 getScaleY(DisplayObject& o)
1196 return o.scaleY();
1199 as_value
1200 getVisible(DisplayObject& o)
1202 return o.visible();
1205 void
1206 setVisible(DisplayObject& o, const as_value& val)
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)));
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);
1221 return;
1224 o.set_visible(d);
1226 o.transformedByScript();
1229 as_value
1230 getAlpha(DisplayObject& o)
1232 return as_value(getCxForm(o).aa / 2.56);
1235 void
1236 setAlpha(DisplayObject& o, const as_value& val)
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;
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);
1250 return;
1253 SWFCxForm cx = getCxForm(o);
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();
1260 else {
1261 cx.aa = static_cast<std::int16_t>(newAlpha);
1264 o.setCxForm(cx);
1265 o.transformedByScript();
1269 as_value
1270 getMouseX(DisplayObject& o)
1272 // Local coord of mouse IN PIXELS.
1273 std::int32_t x, y;
1274 boost::tie(x, y) = getRoot(*getObject(&o)).mousePosition();
1276 SWFMatrix m = getWorldMatrix(o);
1277 point a(pixelsToTwips(x), pixelsToTwips(y));
1279 m.invert().transform(a);
1280 return as_value(twipsToPixels(a.x));
1283 as_value
1284 getMouseY(DisplayObject& o)
1286 // Local coord of mouse IN PIXELS.
1287 std::int32_t x, y;
1288 boost::tie(x, y) = getRoot(*getObject(&o)).mousePosition();
1290 SWFMatrix m = getWorldMatrix(o);
1291 point a(pixelsToTwips(x), pixelsToTwips(y));
1292 m.invert().transform(a);
1293 return as_value(twipsToPixels(a.y));
1296 as_value
1297 getRotation(DisplayObject& o)
1299 return o.rotation();
1303 void
1304 setRotation(DisplayObject& o, const as_value& val)
1306 // input is in degrees
1307 const double rotation_val = toNumber(val, getVM(*getObject(&o)));
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);
1316 return;
1318 o.set_rotation(rotation_val);
1322 as_value
1323 getParent(DisplayObject& o)
1325 as_object* p = getObject(o.parent());
1326 return p ? p : as_value();
1329 as_value
1330 getTarget(DisplayObject& o)
1332 return o.getTargetPath();
1335 as_value
1336 getNameProperty(DisplayObject& o)
1338 string_table& st = getStringTable(*getObject(&o));
1339 const std::string& name = o.get_name().toString(st);
1340 return as_value(name);
1343 void
1344 setName(DisplayObject& o, const as_value& val)
1346 o.set_name(getURI(getVM(*getObject(&o)), val.to_string()));
1349 void
1350 setSoundBufTime(DisplayObject& /*o*/, const as_value& /*val*/)
1352 LOG_ONCE(log_unimpl(_("_soundbuftime setting")));
1355 as_value
1356 getSoundBufTime(DisplayObject& /*o*/)
1358 return as_value(0.0);
1361 as_value
1362 getWidth(DisplayObject& o)
1364 SWFRect bounds = o.getBounds();
1365 const SWFMatrix& m = getMatrix(o);
1366 m.transform(bounds);
1367 return twipsToPixels(bounds.width());
1370 void
1371 setWidth(DisplayObject& o, const as_value& val)
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));
1380 o.setWidth(newwidth);
1383 as_value
1384 getFocusRect(DisplayObject& o)
1386 LOG_ONCE(log_unimpl(_("_focusrect")));
1388 const boost::tribool fr = o.focusRect();
1389 if (boost::indeterminate(fr)) {
1390 as_value null;
1391 null.set_null();
1392 return null;
1394 const bool ret = static_cast<bool>(fr);
1395 if (getSWFVersion(*getObject(&o)) == 5) {
1396 return as_value(static_cast<double>(ret));
1398 return as_value(ret);
1401 void
1402 setFocusRect(DisplayObject& o, const as_value& val)
1404 LOG_ONCE(log_unimpl(_("_focusrect")));
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;
1413 o.focusRect(toBool(val, vm));
1416 as_value
1417 getDropTarget(DisplayObject& o)
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());
1425 as_value
1426 getCurrentFrame(DisplayObject& o)
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);
1436 as_value
1437 getFramesLoaded(DisplayObject& o)
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());
1445 as_value
1446 getTotalFrames(DisplayObject& o)
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());
1455 /// @param uri The property to search for. Note that all special
1456 /// properties are lower-case.
1458 /// NOTE that all properties have getters so you can recognize a
1459 /// 'not-found' condition by checking .first = 0
1460 const GetterSetter&
1461 getGetterSetterByURI(const ObjectURI& uri, string_table& st)
1463 typedef std::map<ObjectURI, GetterSetter, ObjectURI::CaseLessThan>
1464 GetterSetters;
1466 static const GetterSetters gs =
1467 getURIMap<GetterSetters>(ObjectURI::CaseLessThan(st, true));
1469 const GetterSetters::const_iterator it = gs.find(uri);
1471 if (it == gs.end()) {
1472 static const GetterSetter none(nullptr, nullptr);
1473 return none;
1476 return it->second;
1480 const GetterSetter&
1481 getGetterSetterByIndex(size_t index)
1483 const Setter n = nullptr;
1485 static const GetterSetter props[] = {
1486 GetterSetter(&getX, &setX),
1487 GetterSetter(&getY, &setY),
1488 GetterSetter(&getScaleX, &setScaleX),
1489 GetterSetter(&getScaleY, &setScaleY),
1491 GetterSetter(&getCurrentFrame, n),
1492 GetterSetter(&getTotalFrames, n),
1493 GetterSetter(&getAlpha, &setAlpha),
1494 GetterSetter(&getVisible, &setVisible),
1496 GetterSetter(&getWidth, &setWidth),
1497 GetterSetter(&getHeight, &setHeight),
1498 GetterSetter(&getRotation, &setRotation),
1499 GetterSetter(&getTarget, n),
1501 GetterSetter(&getFramesLoaded, n),
1502 GetterSetter(&getNameProperty, &setName),
1503 GetterSetter(&getDropTarget, n),
1504 GetterSetter(&getURL, n),
1506 GetterSetter(&getHighQuality, &setHighQuality),
1507 GetterSetter(&getFocusRect, &setFocusRect),
1508 GetterSetter(&getSoundBufTime, &setSoundBufTime),
1509 GetterSetter(&getQuality, &setQuality),
1511 GetterSetter(&getMouseX, n),
1512 GetterSetter(&getMouseY, n)
1516 if (index >= arraySize(props)) {
1517 const Getter ng = nullptr;
1518 static const GetterSetter none(ng, n);
1519 return none;
1522 return props[index];
1526 bool
1527 doGet(const ObjectURI& uri, DisplayObject& o, as_value& val)
1529 string_table& st = getStringTable(*getObject(&o));
1530 const Getter s = getGetterSetterByURI(uri, st).first;
1531 if (!s) return false;
1533 val = (*s)(o);
1534 return true;
1538 /// Do the actual setProperty
1540 /// Return true if the property is a DisplayObject property, regardless of
1541 /// whether it was successfully set or not.
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
1547 doSet(const ObjectURI& uri, DisplayObject& o, const as_value& val)
1549 string_table& st = getStringTable(*getObject(&o));
1551 const GetterSetter& gs = getGetterSetterByURI(uri, st);
1553 // not found (all props have getters)
1554 if (!gs.first) return false;
1556 const Setter& s = gs.second;
1558 // read-only (TODO: aserror ?)
1559 if (!s) return true;
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);
1567 return true;
1570 (*s)(o, val);
1571 return true;
1575 const BlendModeMap&
1576 getBlendModeMap()
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"}
1598 return bm;
1602 // Match a blend mode to its string.
1603 bool
1604 blendModeMatches(const BlendModeMap::value_type& val, const std::string& mode)
1606 /// The match must be case-sensitive.
1607 if (mode.empty()) return false;
1608 return (val.second == mode);
1611 /// Return a const map of property URI to function.
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
1617 getURIMap(const typename Map::key_compare& cmp)
1619 const Setter n = nullptr;
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;
1664 } // anonymous namespace
1666 std::ostream&
1667 operator<<(std::ostream& o, DisplayObject::BlendMode bm)
1669 const BlendModeMap& bmm = getBlendModeMap();
1670 return (o << bmm.find(bm)->second);
1673 } // namespace gnash
1675 // local variables:
1676 // mode: c++
1677 // indent-tabs-mode: t
1678 // end: