use ObjectURI more consistently
[gnash.git] / libcore / asobj / MovieClip_as.cpp
blobb98dd44c9049348ceebc8465e4b2732d21d8d50e
1 // MovieClip_as.cpp: ActionScript "MovieClip" class, for Gnash.
2 //
3 // Copyright (C) 2009, 2010 Free Software Foundation, Inc.
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 #include "MovieClip_as.h"
22 #include <boost/lexical_cast.hpp>
24 #include "MovieClip.h"
25 #include "Movie.h"
26 #include "display/BitmapData_as.h"
27 #include "NetStream_as.h"
28 #include "movie_root.h"
29 #include "GnashNumeric.h"
30 #include "as_value.h"
31 #include "log.h"
32 #include "fn_call.h"
33 #include "Global_as.h"
34 #include "smart_ptr.h" // for boost intrusive_ptr
35 #include "builtin_function.h" // need builtin_function
36 #include "NativeFunction.h"
37 #include "Bitmap.h"
38 #include "Array_as.h"
39 #include "FillStyle.h"
40 #include "namedStrings.h"
41 #include "Renderer.h"
42 #include "RunResources.h"
43 #include "ASConversions.h"
45 namespace gnash {
47 // Forward declarations
48 namespace {
50 void attachMovieClipAS2Interface(as_object& o);
52 as_value movieclip_as2_ctor(const fn_call& fn);
53 as_value movieclip_transform(const fn_call& fn);
54 as_value movieclip_scale9Grid(const fn_call& fn);
55 as_value movieclip_attachVideo(const fn_call& fn);
56 as_value movieclip_attachAudio(const fn_call& fn);
57 as_value movieclip_attachMovie(const fn_call& fn);
58 as_value movieclip_unloadMovie(const fn_call& fn);
59 as_value movieclip_loadMovie(const fn_call& fn);
60 as_value movieclip_getURL(const fn_call& fn);
61 as_value movieclip_attachBitmap(const fn_call& fn);
62 as_value movieclip_beginBitmapFill(const fn_call& fn);
63 as_value movieclip_createEmptyMovieClip(const fn_call& fn);
64 as_value movieclip_removeMovieClip(const fn_call& fn);
65 as_value movieclip_curveTo(const fn_call& fn);
66 as_value movieclip_beginFill(const fn_call& fn);
67 as_value movieclip_prevFrame(const fn_call& fn);
68 as_value movieclip_nextFrame(const fn_call& fn);
69 as_value movieclip_endFill(const fn_call& fn);
70 as_value movieclip_clear(const fn_call& fn);
71 as_value movieclip_lineStyle(const fn_call& fn);
72 as_value movieclip_lineTo(const fn_call& fn);
73 as_value movieclip_moveTo(const fn_call& fn);
74 as_value movieclip_beginGradientFill(const fn_call& fn);
75 as_value movieclip_stopDrag(const fn_call& fn);
76 as_value movieclip_startDrag(const fn_call& fn);
77 as_value movieclip_gotoAndStop(const fn_call& fn);
78 as_value movieclip_duplicateMovieClip(const fn_call& fn);
79 as_value movieclip_gotoAndPlay(const fn_call& fn);
80 as_value movieclip_stop(const fn_call& fn);
81 as_value movieclip_play(const fn_call& fn);
82 as_value movieclip_setMask(const fn_call& fn);
83 as_value movieclip_getDepth(const fn_call& fn);
84 as_value movieclip_getBytesTotal(const fn_call& fn);
85 as_value movieclip_getBytesLoaded(const fn_call& fn);
86 as_value movieclip_getBounds(const fn_call& fn);
87 as_value movieclip_hitTest(const fn_call& fn);
88 as_value movieclip_globalToLocal(const fn_call& fn);
89 as_value movieclip_localToGlobal(const fn_call& fn);
90 as_value movieclip_lockroot(const fn_call& fn);
91 as_value movieclip_swapDepths(const fn_call& fn);
92 as_value movieclip_scrollRect(const fn_call& fn);
93 as_value movieclip_getInstanceAtDepth(const fn_call& fn);
94 as_value movieclip_getNextHighestDepth(const fn_call& fn);
95 as_value movieclip_getTextSnapshot(const fn_call& fn);
96 as_value movieclip_tabIndex(const fn_call& fn);
97 as_value movieclip_opaqueBackground(const fn_call& fn);
98 as_value movieclip_filters(const fn_call& fn);
99 as_value movieclip_forceSmoothing(const fn_call& fn);
100 as_value movieclip_cacheAsBitmap(const fn_call& fn);
101 as_value movieclip_lineGradientStyle(const fn_call& fn);
102 as_value movieclip_beginMeshFill(const fn_call& fn);
103 as_value movieclip_getRect(const fn_call& fn);
104 as_value movieclip_meth(const fn_call& fn);
105 as_value movieclip_getSWFVersion(const fn_call& fn);
106 as_value movieclip_loadVariables(const fn_call& fn);
110 // extern (used by Global.cpp)
111 // proto = getDisplayObjectContainerInterface();
112 // proto = getDisplayObjectContainerInterface();
113 void
114 movieclip_class_init(as_object& where, const ObjectURI& uri)
116 Global_as& gl = getGlobal(where);
117 as_object* proto = createObject(gl);
119 as_object* cl = gl.createClass(&movieclip_as2_ctor, proto);
120 attachMovieClipAS2Interface(*proto);
122 where.init_member(uri, cl, as_object::DefaultFlags);
125 void
126 registerMovieClipNative(as_object& where)
128 VM& vm = getVM(where);
130 vm.registerNative(movieclip_attachMovie, 900, 0);
131 vm.registerNative(movieclip_swapDepths, 900, 1);
132 vm.registerNative(movieclip_localToGlobal, 900, 2);
133 vm.registerNative(movieclip_globalToLocal, 900, 3);
134 vm.registerNative(movieclip_hitTest, 900, 4);
135 vm.registerNative(movieclip_getBounds, 900, 5);
136 vm.registerNative(movieclip_getBytesTotal, 900, 6);
137 vm.registerNative(movieclip_getBytesLoaded, 900, 7);
138 vm.registerNative(movieclip_attachAudio, 900, 8);
139 vm.registerNative(movieclip_attachVideo, 900, 9);
140 vm.registerNative(movieclip_getDepth, 900, 10);
141 vm.registerNative(movieclip_setMask, 900, 11);
142 vm.registerNative(movieclip_play, 900, 12);
143 vm.registerNative(movieclip_stop, 900, 13);
144 vm.registerNative(movieclip_nextFrame, 900, 14);
145 vm.registerNative(movieclip_prevFrame, 900, 15);
146 vm.registerNative(movieclip_gotoAndPlay, 900, 16);
147 vm.registerNative(movieclip_gotoAndStop, 900, 17);
148 vm.registerNative(movieclip_duplicateMovieClip, 900, 18);
149 vm.registerNative(movieclip_removeMovieClip, 900, 19);
150 vm.registerNative(movieclip_startDrag, 900, 20);
151 vm.registerNative(movieclip_stopDrag, 900, 21);
152 vm.registerNative(movieclip_getNextHighestDepth, 900, 22);
153 vm.registerNative(movieclip_getInstanceAtDepth, 900, 23);
154 vm.registerNative(movieclip_getSWFVersion, 900, 24);
155 vm.registerNative(movieclip_attachBitmap, 900, 25);
156 vm.registerNative(movieclip_getRect, 900, 26);
158 vm.registerNative(movieclip_tabIndex, 900, 200);
160 vm.registerNative(movieclip_lockroot, 900, 300);
162 vm.registerNative(movieclip_cacheAsBitmap, 900, 401);
163 vm.registerNative(movieclip_opaqueBackground, 900, 402);
164 vm.registerNative(movieclip_scrollRect, 900, 403);
166 vm.registerNative(movieclip_filters, 900, 417);
167 vm.registerNative(movieclip_transform, 900, 418);
168 vm.registerNative(DisplayObject::blendMode, 900, 500);
169 vm.registerNative(movieclip_forceSmoothing, 900, 502);
171 vm.registerNative(movieclip_createEmptyMovieClip, 901, 0);
172 vm.registerNative(movieclip_beginFill, 901, 1);
173 vm.registerNative(movieclip_beginGradientFill, 901, 2);
174 vm.registerNative(movieclip_moveTo, 901, 3);
175 vm.registerNative(movieclip_lineTo, 901, 4);
176 vm.registerNative(movieclip_curveTo, 901, 5);
177 vm.registerNative(movieclip_lineStyle, 901, 6);
178 vm.registerNative(movieclip_endFill, 901, 7);
179 vm.registerNative(movieclip_clear, 901, 8);
180 vm.registerNative(movieclip_lineGradientStyle, 901, 9);
181 vm.registerNative(movieclip_beginMeshFill, 901, 10);
182 vm.registerNative(movieclip_beginBitmapFill, 901, 11);
183 vm.registerNative(movieclip_scale9Grid, 901, 12);
188 namespace {
190 // =======================
191 // AS2 interface
192 // =======================
194 /// Properties (and/or methods) *inherited* by MovieClip instances
195 void
196 attachMovieClipAS2Interface(as_object& o)
198 Global_as& gl = getGlobal(o);
199 VM& vm = getVM(o);
201 const int swf6Flags = as_object::DefaultFlags | PropFlags::onlySWF6Up;
202 const int swf7Flags = as_object::DefaultFlags | PropFlags::onlySWF7Up;
203 const int swf8Flags = as_object::DefaultFlags | PropFlags::onlySWF8Up;
205 o.init_member("attachMovie", vm.getNative(900, 0));
206 o.init_member("swapDepths", vm.getNative(900, 1));
207 o.init_member("localToGlobal", vm.getNative(900, 2));
208 o.init_member("globalToLocal", vm.getNative(900, 3));
209 o.init_member("hitTest", vm.getNative(900, 4));
210 o.init_member("getBounds", vm.getNative(900, 5));
211 o.init_member("getBytesTotal", vm.getNative(900, 6));
212 o.init_member("getBytesLoaded", vm.getNative(900, 7));
213 o.init_member("attachAudio", vm.getNative(900, 8), swf6Flags);
214 o.init_member("attachVideo", vm.getNative(900, 9), swf6Flags);
215 o.init_member("getDepth", vm.getNative(900, 10), swf6Flags);
216 o.init_member("setMask", vm.getNative(900, 11), swf6Flags);
217 o.init_member("play", vm.getNative(900, 12));
218 o.init_member("stop", vm.getNative(900, 13));
219 o.init_member("nextFrame", vm.getNative(900, 14));
220 o.init_member("prevFrame", vm.getNative(900, 15));
221 o.init_member("gotoAndPlay", vm.getNative(900, 16));
222 o.init_member("gotoAndStop", vm.getNative(900, 17));
223 o.init_member("duplicateMovieClip", vm.getNative(900, 18));
224 o.init_member("removeMovieClip", vm.getNative(900, 19));
225 o.init_member("startDrag", vm.getNative(900, 20));
226 o.init_member("stopDrag", vm.getNative(900, 21));
227 o.init_member("getNextHighestDepth", vm.getNative(900, 22), swf7Flags);
228 o.init_member("getInstanceAtDepth", vm.getNative(900, 23), swf7Flags);
229 o.init_member("getSWFVersion", vm.getNative(900, 24));
230 o.init_member("attachBitmap", vm.getNative(900, 25), swf8Flags);
231 o.init_member("getRect", vm.getNative(900, 26), swf8Flags);
233 o.init_member("loadMovie", gl.createFunction(movieclip_loadMovie));
234 o.init_member("loadVariables", gl.createFunction(movieclip_loadVariables));
235 o.init_member("unloadMovie", gl.createFunction( movieclip_unloadMovie));
236 o.init_member("getURL", gl.createFunction(movieclip_getURL));
237 o.init_member("meth", gl.createFunction(movieclip_meth));
239 o.init_member("enabled", true);
240 o.init_member("useHandCursor", true);
242 o.init_member("createEmptyMovieClip", vm.getNative(901, 0), swf6Flags);
243 o.init_member("beginFill", vm.getNative(901, 1), swf6Flags);
244 o.init_member("beginGradientFill", vm.getNative(901, 2), swf6Flags);
245 o.init_member("moveTo", vm.getNative(901, 3), swf6Flags);
246 o.init_member("lineTo", vm.getNative(901, 4), swf6Flags);
247 o.init_member("curveTo", vm.getNative(901, 5), swf6Flags);
248 o.init_member("lineStyle", vm.getNative(901, 6), swf6Flags);
249 o.init_member("endFill", vm.getNative(901, 7), swf6Flags);
250 o.init_member("clear", vm.getNative(901, 8), swf6Flags);
251 o.init_member("lineGradientStyle", vm.getNative(901, 9), swf8Flags);
252 o.init_member("beginMeshFill", vm.getNative(901, 10), swf8Flags);
253 o.init_member("beginBitmapFill", vm.getNative(901, 11), swf8Flags);
255 // Accessors
257 NativeFunction* getset;
259 getset = vm.getNative(900, 200);
260 o.init_property("tabIndex", *getset, *getset);
261 getset = vm.getNative(900, 300);
262 o.init_property("_lockroot", *getset, *getset);
263 getset = vm.getNative(900, 401);
264 o.init_property("cacheAsBitmap", *getset, *getset, swf8Flags);
265 getset = vm.getNative(900, 402);
266 o.init_property("opaqueBackground", *getset, *getset, swf8Flags);
267 getset = vm.getNative(900, 403);
268 o.init_property("scrollRect", *getset, *getset, swf8Flags);
269 getset = vm.getNative(900, 417);
270 o.init_property("filters", *getset, *getset, swf8Flags);
271 getset = vm.getNative(900, 418);
272 o.init_property("transform", *getset, *getset, swf8Flags);
273 getset = vm.getNative(900, 500);
274 o.init_property("blendMode", *getset, *getset, swf8Flags);
275 getset = vm.getNative(900, 502);
276 o.init_property("forceSmoothing", *getset, *getset, swf8Flags);
277 getset = vm.getNative(901, 12);
278 o.init_property("scale9Grid", *getset, *getset, swf8Flags);
280 // External functions.
281 o.init_member("createTextField", vm.getNative(104, 200));
282 o.init_member("getTextSnapshot",
283 gl.createFunction(movieclip_getTextSnapshot), swf6Flags);
287 //createEmptyMovieClip(name:String, depth:Number) : MovieClip
288 as_value
289 movieclip_createEmptyMovieClip(const fn_call& fn)
291 MovieClip* ptr = ensure<IsDisplayObject<MovieClip> >(fn);
293 if (fn.nargs != 2) {
294 if (fn.nargs < 2) {
295 IF_VERBOSE_ASCODING_ERRORS(
296 log_aserror(_("createEmptyMovieClip needs "
297 "2 args, but %d given,"
298 " returning undefined"),
299 fn.nargs);
301 return as_value();
303 IF_VERBOSE_ASCODING_ERRORS(
304 log_aserror(_("createEmptyMovieClip takes "
305 "2 args, but %d given, discarding"
306 " the excess"),
307 fn.nargs);
311 Movie* m = ptr->get_root();
312 as_object* o = getObjectWithPrototype(getGlobal(fn), NSV::CLASS_MOVIE_CLIP);
313 MovieClip* mc = new MovieClip(o, 0, m, ptr);
315 VM& vm = getVM(fn);
316 mc->set_name(getURI(vm, fn.arg(0).to_string()));
317 mc->setDynamic();
319 // Unlike other MovieClip methods, the depth argument of an empty movie clip
320 // can be any number. All numbers are converted to an int32_t, and are valid
321 // depths even when outside the usual bounds.
322 ptr->addDisplayListObject(mc, toInt(fn.arg(1), getVM(fn)));
323 return as_value(o);
327 as_value
328 movieclip_play(const fn_call& fn)
330 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
332 movieclip->setPlayState(MovieClip::PLAYSTATE_PLAY);
333 return as_value();
336 as_value
337 movieclip_stop(const fn_call& fn)
339 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
341 movieclip->setPlayState(MovieClip::PLAYSTATE_STOP);
343 return as_value();
347 //removeMovieClip() : Void
348 as_value
349 movieclip_removeMovieClip(const fn_call& fn)
351 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
352 movieclip->removeMovieClip();
353 return as_value();
357 as_value
358 movieclip_cacheAsBitmap(const fn_call& fn)
360 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
361 UNUSED(movieclip);
362 LOG_ONCE( log_unimpl(_("MovieClip.cacheAsBitmap()")) );
363 return as_value();
367 as_value
368 movieclip_filters(const fn_call& fn)
370 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
372 UNUSED(movieclip);
374 LOG_ONCE(log_unimpl(_("MovieClip.filters()")));
376 if (!fn.nargs) {
377 // Getter
378 Global_as& gl = getGlobal(fn);
379 as_object* array = gl.createArray();
380 return as_value(array);
383 // Setter
384 return as_value();
388 as_value
389 movieclip_forceSmoothing(const fn_call& fn)
391 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
392 UNUSED(movieclip);
393 LOG_ONCE(log_unimpl(_("MovieClip.forceSmoothing()")));
394 return as_value();
398 as_value
399 movieclip_opaqueBackground(const fn_call& fn)
401 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
402 UNUSED(movieclip);
403 LOG_ONCE(log_unimpl(_("MovieClip.opaqueBackground()")));
404 return as_value();
408 as_value
409 movieclip_scale9Grid(const fn_call& fn)
411 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
412 UNUSED(movieclip);
413 LOG_ONCE(log_unimpl(_("MovieClip.scale9Grid()")));
414 return as_value();
418 as_value
419 movieclip_scrollRect(const fn_call& fn)
421 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
422 UNUSED(movieclip);
423 LOG_ONCE(log_unimpl(_("MovieClip.scrollRect()")));
424 return as_value();
428 as_value
429 movieclip_tabIndex(const fn_call& fn)
431 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
432 UNUSED(movieclip);
433 LOG_ONCE(log_unimpl(_("MovieClip.tabIndex()")));
434 return as_value();
438 // attachMovie(idName:String, newName:String,
439 // depth:Number [, initObject:Object]) : MovieClip
440 as_value
441 movieclip_attachMovie(const fn_call& fn)
443 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
445 if (fn.nargs < 3 || fn.nargs > 4)
447 IF_VERBOSE_ASCODING_ERRORS(
448 log_aserror(_("attachMovie called with wrong number of arguments"
449 " expected 3 to 4, got (%d) - returning undefined"), fn.nargs);
451 return as_value();
454 // Get exported resource
455 const std::string& id_name = fn.arg(0).to_string();
457 SWF::DefinitionTag* exported_movie =
458 movieclip->get_root()->exportedCharacter(id_name);
460 if (!exported_movie)
462 IF_VERBOSE_ASCODING_ERRORS(
463 log_aserror(_("attachMovie: exported resource '%s' "
464 "is not a DisplayObject definition. Returning undefined"), id_name);
466 return as_value();
469 const std::string& newname = fn.arg(1).to_string();
471 // Movies should be attachable from -16384 to 2130690045, according to
472 // kirupa (http://www.kirupa.com/developer/actionscript/depths2.htm)
473 // Tests in misc-ming.all/DepthLimitsTest.c show that 2130690044 is the
474 // maximum valid depth.
475 const double depth = toNumber(fn.arg(2), getVM(fn));
477 // This also checks for overflow, as both numbers are expressible as
478 // boost::int32_t.
479 if (depth < DisplayObject::lowerAccessibleBound ||
480 depth > DisplayObject::upperAccessibleBound)
482 IF_VERBOSE_ASCODING_ERRORS(
483 log_aserror(_("MovieClip.attachMovie: invalid depth %d "
484 "passed; not attaching"), depth);
486 return as_value();
489 boost::int32_t depthValue = static_cast<boost::int32_t>(depth);
491 Global_as& gl = getGlobal(fn);
492 DisplayObject* newch = exported_movie->createDisplayObject(gl, movieclip);
494 VM& vm = getVM(fn);
495 newch->set_name(getURI(vm, newname));
496 newch->setDynamic();
498 boost::intrusive_ptr<as_object> initObj;
500 if (fn.nargs > 3 ) {
501 initObj = toObject(fn.arg(3), getVM(fn));
502 if (!initObj) {
503 // This is actually a valid thing to do,
504 // the documented behaviour is to just NOT
505 // initialize the properties in this
506 // case.
507 IF_VERBOSE_ASCODING_ERRORS(
508 log_aserror(_("Fourth argument of attachMovie doesn't cast to "
509 "an object (%s), we'll act as if it wasn't given"),
510 fn.arg(3));
515 // placeDisplayObject() will set depth on newch
516 if (!movieclip->attachCharacter(*newch, depthValue, initObj.get()))
518 log_error(_("Could not attach DisplayObject at depth %d"), depthValue);
519 return as_value();
522 return as_value(getObject(newch));
526 // attachAudio(id:Object) : Void
527 as_value
528 movieclip_attachAudio(const fn_call& fn)
530 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
532 if (!fn.nargs)
534 IF_VERBOSE_ASCODING_ERRORS(
535 log_aserror("MovieClip.attachAudio(): %s", _("missing arguments"));
537 return as_value();
540 NetStream_as* ns;
541 if (!isNativeType(toObject(fn.arg(0), getVM(fn)), ns))
543 std::stringstream ss; fn.dump_args(ss);
544 // TODO: find out what to do here
545 log_error("MovieClip.attachAudio(%s): first arg doesn't cast to a "
546 "NetStream", ss.str());
547 return as_value();
550 ns->setAudioController(movieclip);
552 LOG_ONCE( log_unimpl("MovieClip.attachAudio() - TESTING") );
553 return as_value();
557 // MovieClip.attachVideo
558 as_value
559 movieclip_attachVideo(const fn_call& fn)
561 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
562 UNUSED(movieclip);
564 LOG_ONCE( log_unimpl("MovieClip.attachVideo()") );
565 return as_value();
569 as_value
570 movieclip_getDepth(const fn_call& fn)
572 // Unlike TextField.getDepth this works for any DisplayObject
573 DisplayObject* d = ensure<IsDisplayObject<> >(fn);
575 const int n = d->get_depth();
577 return as_value(n);
580 //swapDepths(target:Object|target:Number)
582 // Returns void.
583 as_value
584 movieclip_swapDepths(const fn_call& fn)
587 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
589 const int this_depth = movieclip->get_depth();
591 if (fn.nargs < 1)
593 IF_VERBOSE_ASCODING_ERRORS(
594 log_aserror(_("%s.swapDepths() needs one arg"), movieclip->getTarget());
596 return as_value();
599 // Lower bound of source depth below which swapDepth has no effect
600 // (below Timeline/static zone)
601 if (this_depth < DisplayObject::lowerAccessibleBound)
603 IF_VERBOSE_ASCODING_ERRORS(
604 std::stringstream ss;
605 fn.dump_args(ss);
606 log_aserror(_("%s.swapDepths(%s): won't swap a clip below "
607 "depth %d (%d)"), movieclip->getTarget(), ss.str(),
608 DisplayObject::lowerAccessibleBound,
609 this_depth);
611 return as_value();
614 MovieClip* this_parent = dynamic_cast<MovieClip*>(movieclip->parent());
616 //CharPtr target = NULL;
617 int target_depth = 0;
619 // movieclip.swapDepth(movieclip)
620 if (MovieClip* target_movieclip = fn.arg(0).toMovieClip()) {
622 if (movieclip == target_movieclip) {
623 IF_VERBOSE_ASCODING_ERRORS(
624 log_aserror(_("%s.swapDepths(%s): invalid call, "
625 "swapping to self?"), movieclip->getTarget(),
626 target_movieclip->getTarget());
628 return as_value();
631 MovieClip* target_parent =
632 dynamic_cast<MovieClip*>(movieclip->parent());
634 if (this_parent != target_parent) {
635 IF_VERBOSE_ASCODING_ERRORS(
636 log_aserror(_("%s.swapDepths(%s): invalid call, the two "
637 "DisplayObjects don't have the same parent"),
638 movieclip->getTarget(), target_movieclip->getTarget());
640 return as_value();
643 target_depth = target_movieclip->get_depth();
645 // Check we're not swapping the our own depth so
646 // to avoid unecessary bounds invalidation and immunizing
647 // the instance from subsequent PlaceObject tags attempting
648 // to transform it.
649 if (movieclip->get_depth() == target_depth)
651 IF_VERBOSE_ASCODING_ERRORS(
652 std::stringstream ss; fn.dump_args(ss);
653 log_aserror(_("%s.swapDepths(%s): ignored, source and "
654 "target DisplayObjects have the same depth %d"),
655 movieclip->getTarget(), ss.str(), target_depth);
657 return as_value();
661 // movieclip.swapDepth(depth)
662 else {
664 const double td = toNumber(fn.arg(0), getVM(fn));
665 if (isNaN(td)) {
666 IF_VERBOSE_ASCODING_ERRORS(
667 std::stringstream ss; fn.dump_args(ss);
668 log_aserror(_("%s.swapDepths(%s): first argument invalid "
669 "(neither a movieclip nor a number)"),
670 movieclip->getTarget(), ss.str());
672 return as_value();
674 if (td > DisplayObject::upperAccessibleBound) {
675 IF_VERBOSE_ASCODING_ERRORS(
676 std::stringstream ss; fn.dump_args(ss);
677 log_aserror(_("%s.swapDepths(%s): requested depth is above "
678 "the accessible range."),
679 movieclip->getTarget(), ss.str());
681 return as_value();
684 target_depth = static_cast<int>(td);
686 // Check we're not swapping the our own depth so
687 // to avoid unecessary bounds invalidation and immunizing
688 // the instance from subsequent PlaceObjec tags attempting
689 // to transform it.
690 if (movieclip->get_depth() == target_depth) {
691 IF_VERBOSE_ASCODING_ERRORS(
692 std::stringstream ss; fn.dump_args(ss);
693 log_aserror(_("%s.swapDepths(%s): ignored, DisplayObject already "
694 "at depth %d"),
695 movieclip->getTarget(), ss.str(), target_depth);
697 return as_value();
699 // TODO : check other kind of validities ?
702 if (this_parent) {
703 this_parent->swapDepths(movieclip, target_depth);
705 else {
706 movie_root& root = getRoot(fn);
707 root.swapLevels(movieclip, target_depth);
708 return as_value();
711 return as_value();
715 // TODO: wrap the functionality in a MovieClip method
716 // and invoke it from here, this should only be a wrapper
718 //duplicateMovieClip(name:String, depth:Number, [initObject:Object]) : MovieClip
719 as_value
720 movieclip_duplicateMovieClip(const fn_call& fn)
722 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
724 if (fn.nargs < 2)
726 IF_VERBOSE_ASCODING_ERRORS(
727 log_aserror(_("MovieClip.duplicateMovieClip() needs 2 or 3 args"));
729 return as_value();
732 const std::string& newname = fn.arg(0).to_string();
734 // Depth as in attachMovie
735 const double depth = toNumber(fn.arg(1), getVM(fn));
737 // This also checks for overflow, as both numbers are expressible as
738 // boost::int32_t.
739 if (depth < DisplayObject::lowerAccessibleBound ||
740 depth > DisplayObject::upperAccessibleBound)
742 IF_VERBOSE_ASCODING_ERRORS(
743 log_aserror(_("MovieClip.duplicateMovieClip: "
744 "invalid depth %d passed; not duplicating"), depth);
746 return as_value();
749 boost::int32_t depthValue = static_cast<boost::int32_t>(depth);
751 MovieClip* ch;
753 // Copy members from initObject
754 if (fn.nargs == 3)
756 as_object* initObject = toObject(fn.arg(2), getVM(fn));
757 ch = movieclip->duplicateMovieClip(newname, depthValue, initObject);
759 else
761 ch = movieclip->duplicateMovieClip(newname, depthValue);
764 return as_value(getObject(ch));
767 as_value
768 movieclip_gotoAndPlay(const fn_call& fn)
770 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
772 if (fn.nargs < 1)
774 IF_VERBOSE_ASCODING_ERRORS(
775 log_aserror(_("movieclip_goto_and_play needs one arg"));
777 return as_value();
780 size_t frame_number;
781 if ( ! movieclip->get_frame_number(fn.arg(0), frame_number) )
783 // No dice.
784 IF_VERBOSE_ASCODING_ERRORS(
785 log_aserror(_("movieclip_goto_and_play('%s') -- invalid frame"),
786 fn.arg(0));
788 return as_value();
791 // Convert to 0-based
792 movieclip->goto_frame(frame_number);
793 movieclip->setPlayState(MovieClip::PLAYSTATE_PLAY);
794 return as_value();
797 as_value movieclip_gotoAndStop(const fn_call& fn)
799 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
801 if (fn.nargs < 1)
803 IF_VERBOSE_ASCODING_ERRORS(
804 log_aserror(_("movieclip_goto_and_stop needs one arg"));
806 return as_value();
809 size_t frame_number;
810 if ( ! movieclip->get_frame_number(fn.arg(0), frame_number) )
812 // No dice.
813 IF_VERBOSE_ASCODING_ERRORS(
814 log_aserror(_("movieclip_goto_and_stop('%s') -- invalid frame"),
815 fn.arg(0));
817 return as_value();
820 // Convert to 0-based
821 movieclip->goto_frame(frame_number);
822 movieclip->setPlayState(MovieClip::PLAYSTATE_STOP);
823 return as_value();
826 as_value movieclip_nextFrame(const fn_call& fn)
828 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
830 const size_t frame_count = movieclip->get_frame_count();
831 const size_t current_frame = movieclip->get_current_frame();
832 if (current_frame < frame_count)
834 movieclip->goto_frame(current_frame + 1);
836 movieclip->setPlayState(MovieClip::PLAYSTATE_STOP);
837 return as_value();
840 as_value
841 movieclip_prevFrame(const fn_call& fn)
843 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
845 const size_t current_frame = movieclip->get_current_frame();
846 if (current_frame > 0)
848 movieclip->goto_frame(current_frame - 1);
850 movieclip->setPlayState(MovieClip::PLAYSTATE_STOP);
851 return as_value();
854 as_value
855 movieclip_getBytesLoaded(const fn_call& fn)
857 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
859 return as_value(movieclip->get_bytes_loaded());
862 as_value
863 movieclip_getBytesTotal(const fn_call& fn)
865 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
867 return as_value(movieclip->get_bytes_total());
870 // MovieClip.loadMovie(url:String [,variables:String]).
872 // Returns 1 for "get", 2 for "post", and otherwise 0. Case-insensitive.
873 // This *always* calls MovieClip.meth.
874 as_value
875 movieclip_loadMovie(const fn_call& fn)
877 DisplayObject* dobj = ensure<IsDisplayObject<> >(fn);
879 as_value val;
880 if (fn.nargs > 1) {
881 val = callMethod(getObject(dobj), NSV::PROP_METH, fn.arg(1));
883 else val = callMethod(getObject(dobj), NSV::PROP_METH);
885 if (fn.nargs < 1) // url
887 IF_VERBOSE_ASCODING_ERRORS(
888 log_aserror(_("MovieClip.loadMovie() "
889 "expected 1 or 2 args, got %d - returning undefined"),
890 fn.nargs);
892 return as_value();
895 const std::string& urlstr = fn.arg(0).to_string();
896 if (urlstr.empty())
898 IF_VERBOSE_ASCODING_ERRORS(
899 std::stringstream ss; fn.dump_args(ss);
900 log_aserror(_("First argument of MovieClip.loadMovie(%s) "
901 "evaluates to an empty string - "
902 "returning undefined"),
903 ss.str());
905 return as_value();
908 movie_root& mr = getRoot(fn);
909 std::string target = dobj->getTarget();
911 // TODO: if GET/POST should send variables of *this* movie,
912 // no matter if the target will be replaced by another movie !!
913 const MovieClip::VariablesMethod method =
914 static_cast<MovieClip::VariablesMethod>(toInt(val, getVM(fn)));
916 std::string data;
918 // This is just an optimization if we aren't going
919 // to send the data anyway. It might be wrong, though.
920 if (method != MovieClip::METHOD_NONE) {
921 data = getURLEncodedVars(*getObject(dobj));
924 mr.loadMovie(urlstr, target, data, method);
926 return as_value();
929 // my_mc.loadVariables(url:String [, variables:String]) : Void
930 as_value
931 movieclip_loadVariables(const fn_call& fn)
933 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
935 as_object* obj = getObject(movieclip);
936 assert(obj);
938 // This always calls MovieClip.meth, even when there are no
939 // arguments.
940 as_value val;
941 if (fn.nargs > 1)
943 val = callMethod(obj, NSV::PROP_METH, fn.arg(1));
945 else val = callMethod(obj, NSV::PROP_METH);
947 if (fn.nargs < 1) // url
949 IF_VERBOSE_ASCODING_ERRORS(
950 log_aserror(_("MovieClip.loadVariables() "
951 "expected 1 or 2 args, got %d - returning undefined"),
952 fn.nargs);
954 return as_value();
957 const std::string& urlstr = fn.arg(0).to_string();
958 if (urlstr.empty())
960 IF_VERBOSE_ASCODING_ERRORS(
961 std::stringstream ss; fn.dump_args(ss);
962 log_aserror(_("First argument passed to MovieClip.loadVariables(%s) "
963 "evaluates to an empty string - "
964 "returning undefined"),
965 ss.str());
967 return as_value();
970 const MovieClip::VariablesMethod method =
971 static_cast<MovieClip::VariablesMethod>(toInt(val, getVM(fn)));
973 movieclip->loadVariables(urlstr, method);
974 log_debug("MovieClip.loadVariables(%s) - TESTING ", urlstr);
976 return as_value();
979 // my_mc.unloadMovie() : Void
980 as_value
981 movieclip_unloadMovie(const fn_call& fn)
983 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
985 movieclip->unloadMovie();
987 return as_value();
990 as_value
991 movieclip_hitTest(const fn_call& fn)
993 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
995 switch (fn.nargs)
997 case 1: // target
999 const as_value& tgt_val = fn.arg(0);
1000 DisplayObject* target = findTarget(fn.env(), tgt_val.to_string());
1001 if ( ! target )
1003 IF_VERBOSE_ASCODING_ERRORS(
1004 log_aserror(_("Can't find hitTest target %s"),
1005 tgt_val);
1007 return as_value();
1010 SWFRect thisbounds = movieclip->getBounds();
1011 const SWFMatrix thismat = getWorldMatrix(*movieclip);
1012 thismat.transform(thisbounds);
1014 SWFRect tgtbounds = target->getBounds();
1015 SWFMatrix tgtmat = getWorldMatrix(*target);
1016 tgtmat.transform(tgtbounds);
1018 return thisbounds.getRange().intersects(tgtbounds.getRange());
1020 break;
1023 case 2: // x, y
1025 boost::int32_t x = pixelsToTwips(toNumber(fn.arg(0), getVM(fn)));
1026 boost::int32_t y = pixelsToTwips(toNumber(fn.arg(1), getVM(fn)));
1028 return movieclip->pointInBounds(x, y);
1031 case 3: // x, y, shapeFlag
1033 const boost::int32_t x = pixelsToTwips(toNumber(fn.arg(0),
1034 getVM(fn)));
1035 const boost::int32_t y = pixelsToTwips(toNumber(fn.arg(1),
1036 getVM(fn)));
1037 const bool shapeFlag = toBool(fn.arg(2), getVM(fn));
1039 if (!shapeFlag) return movieclip->pointInBounds(x, y);
1040 else return movieclip->pointInHitableShape(x, y);
1043 default:
1045 IF_VERBOSE_ASCODING_ERRORS(
1046 log_aserror(_("hitTest() called with %u args"),
1047 fn.nargs);
1049 break;
1053 return as_value();
1057 //getNextHighestDepth() : Number
1058 as_value
1059 movieclip_getNextHighestDepth(const fn_call& fn)
1061 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
1063 int nextdepth = movieclip->getNextHighestDepth();
1064 return as_value(static_cast<double>(nextdepth));
1067 //getInstanceAtDepth(depth:Number) : MovieClip
1068 as_value
1069 movieclip_getInstanceAtDepth(const fn_call& fn)
1071 MovieClip* mc = ensure<IsDisplayObject<MovieClip> >(fn);
1073 if (fn.nargs < 1 || fn.arg(0).is_undefined()) {
1074 IF_VERBOSE_ASCODING_ERRORS(
1075 log_aserror("MovieClip.getInstanceAtDepth(): missing or "
1076 "undefined depth argument");
1078 return as_value();
1081 const int depth = toInt(fn.arg(0), getVM(fn));
1083 DisplayObject* ch = mc->getDisplayObjectAtDepth(depth);
1085 // we want 'undefined', not 'null'
1086 if (!ch) return as_value();
1088 return as_value(getObject(ch));
1091 /// MovieClip.getURL(url:String[, window:String[, method:String]])
1093 /// Tested manually to function as a method of any as_object. Hard to
1094 /// test automatically as it doesn't return anything and only has external
1095 /// side-effects.
1096 /// Returns void.
1097 as_value
1098 movieclip_getURL(const fn_call& fn)
1100 as_object* movieclip = ensure<ValidThis>(fn);
1102 std::string urlstr;
1103 std::string target;
1105 as_value val;
1106 if (fn.nargs > 2)
1108 val = callMethod(movieclip, NSV::PROP_METH, fn.arg(2));
1110 else val = callMethod(movieclip, NSV::PROP_METH);
1112 switch (fn.nargs)
1114 case 0:
1116 IF_VERBOSE_ASCODING_ERRORS(
1117 log_aserror(_("No arguments passed to MovieClip.getURL()"));
1119 return as_value();
1121 default:
1123 IF_VERBOSE_ASCODING_ERRORS(
1124 std::ostringstream os;
1125 fn.dump_args(os);
1126 log_aserror(_("MovieClip.getURL(%s): extra arguments "
1127 "dropped"), os.str());
1130 case 3:
1131 // This argument has already been handled.
1132 case 2:
1133 target = fn.arg(1).to_string();
1134 case 1:
1135 urlstr = fn.arg(0).to_string();
1136 break;
1140 MovieClip::VariablesMethod method =
1141 static_cast<MovieClip::VariablesMethod>(toInt(val, getVM(fn)));
1143 std::string vars;
1145 if (method != MovieClip::METHOD_NONE) {
1146 // Get encoded vars.
1147 vars = getURLEncodedVars(*movieclip);
1150 movie_root& m = getRoot(fn);
1152 m.getURL(urlstr, target, vars, method);
1154 return as_value();
1157 // getSWFVersion() : Number
1158 as_value
1159 movieclip_getSWFVersion(const fn_call& fn)
1161 DisplayObject* o = get<DisplayObject>(fn.this_ptr);
1162 if (!o) return as_value(-1);
1163 return as_value(o->getDefinitionVersion());
1166 // MovieClip.meth(<string>) : Number
1168 // Parses case-insensitive "get" and "post" into 1 and 2, 0 anything else
1170 as_value
1171 movieclip_meth(const fn_call& fn)
1174 if (!fn.nargs) return as_value(MovieClip::METHOD_NONE);
1176 as_object* o = toObject(fn.arg(0), getVM(fn));
1177 if (!o) {
1178 return as_value(MovieClip::METHOD_NONE);
1181 as_value lc = callMethod(o, NSV::PROP_TO_LOWER_CASE);
1183 std::string s = lc.to_string();
1185 if (s == "get") return as_value(MovieClip::METHOD_GET);
1186 if (s == "post") return as_value(MovieClip::METHOD_POST);
1187 return as_value(MovieClip::METHOD_NONE);
1191 // getTextSnapshot() : TextSnapshot
1192 as_value
1193 movieclip_getTextSnapshot(const fn_call& fn)
1195 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
1197 // If not found, construction fails.
1198 as_value textSnapshot(findObject(fn.env(), "TextSnapshot"));
1200 boost::intrusive_ptr<as_function> tsCtor = textSnapshot.to_function();
1202 if (!tsCtor) {
1203 IF_VERBOSE_ASCODING_ERRORS(
1204 log_aserror("MovieClip.getTextSnapshot: failed to construct "
1205 "TextSnapshot (object probably overridden)");
1207 return as_value();
1210 // Construct a flash.geom.Transform object with "this" as argument.
1211 fn_call::Args args;
1212 args += getObject(movieclip);
1214 boost::intrusive_ptr<as_object> ts =
1215 constructInstance(*tsCtor, fn.env(), args);
1217 return as_value(ts.get());
1221 // getBounds(targetCoordinateSpace:Object) : Object
1222 as_value
1223 movieclip_getBounds(const fn_call& fn)
1225 DisplayObject* movieclip = ensure<IsDisplayObject<> >(fn);
1227 SWFRect bounds = movieclip->getBounds();
1229 if ( fn.nargs > 0 )
1231 DisplayObject* target = fn.arg(0).toDisplayObject();
1232 if ( ! target )
1234 IF_VERBOSE_ASCODING_ERRORS(
1235 log_aserror(_("MovieClip.getBounds(%s): invalid call, first "
1236 "arg must be a DisplayObject"),
1237 fn.arg(0));
1239 return as_value();
1242 const SWFMatrix tgtwmat = getWorldMatrix(*target).invert();
1243 const SWFMatrix srcwmat = getWorldMatrix(*movieclip);
1245 srcwmat.transform(bounds);
1246 tgtwmat.transform(bounds);
1249 double xMin, yMin, xMax, yMax;
1251 if (!bounds.is_null()) {
1252 // Round to the twip
1253 xMin = twipsToPixels(bounds.get_x_min());
1254 yMin = twipsToPixels(bounds.get_y_min());
1255 xMax = twipsToPixels(bounds.get_x_max());
1256 yMax = twipsToPixels(bounds.get_y_max());
1258 else {
1259 const double magicMin = 6710886.35;
1260 xMin = yMin = xMax = yMax = magicMin;
1263 // This is a bare object.
1264 as_object* bounds_obj = new as_object(getGlobal(fn));
1265 bounds_obj->init_member("xMin", xMin);
1266 bounds_obj->init_member("yMin", yMin);
1267 bounds_obj->init_member("xMax", xMax);
1268 bounds_obj->init_member("yMax", yMax);
1270 return as_value(bounds_obj);
1273 as_value
1274 movieclip_globalToLocal(const fn_call& fn)
1276 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
1278 as_value ret;
1280 if ( fn.nargs < 1 )
1282 IF_VERBOSE_ASCODING_ERRORS(
1283 log_aserror(_("MovieClip.globalToLocal() takes one arg"));
1285 return ret;
1288 boost::intrusive_ptr<as_object> obj = toObject(fn.arg(0), getVM(fn));
1289 if ( ! obj )
1291 IF_VERBOSE_ASCODING_ERRORS(
1292 log_aserror(_("MovieClip.globalToLocal(%s): "
1293 "first argument doesn't cast to an object"),
1294 fn.arg(0));
1296 return ret;
1299 as_value tmp;
1300 boost::int32_t x = 0;
1301 boost::int32_t y = 0;
1303 if ( ! obj->get_member(NSV::PROP_X, &tmp) )
1305 IF_VERBOSE_ASCODING_ERRORS(
1306 log_aserror(_("MovieClip.globalToLocal(%s): "
1307 "object parameter doesn't have an 'x' member"),
1308 fn.arg(0));
1310 return ret;
1312 x = pixelsToTwips(toNumber(tmp, getVM(fn)));
1314 if ( ! obj->get_member(NSV::PROP_Y, &tmp) )
1316 IF_VERBOSE_ASCODING_ERRORS(
1317 log_aserror(_("MovieClip.globalToLocal(%s): "
1318 "object parameter doesn't have an 'y' member"),
1319 fn.arg(0));
1321 return ret;
1323 y = pixelsToTwips(toNumber(tmp, getVM(fn)));
1325 point pt(x, y);
1326 const SWFMatrix world_mat = getWorldMatrix(*movieclip).invert();
1327 world_mat.transform(pt);
1329 obj->set_member(NSV::PROP_X, twipsToPixels(pt.x));
1330 obj->set_member(NSV::PROP_Y, twipsToPixels(pt.y));
1332 return ret;
1335 as_value
1336 movieclip_localToGlobal(const fn_call& fn)
1338 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
1340 as_value ret;
1342 if ( fn.nargs < 1 )
1344 IF_VERBOSE_ASCODING_ERRORS(
1345 log_aserror(_("MovieClip.localToGlobal() takes one arg"));
1347 return ret;
1350 boost::intrusive_ptr<as_object> obj = toObject(fn.arg(0), getVM(fn));
1351 if ( ! obj )
1353 IF_VERBOSE_ASCODING_ERRORS(
1354 log_aserror(_("MovieClip.localToGlobal(%s): "
1355 "first argument doesn't cast to an object"),
1356 fn.arg(0));
1358 return ret;
1361 as_value tmp;
1362 boost::int32_t x = 0;
1363 boost::int32_t y = 0;
1365 if ( ! obj->get_member(NSV::PROP_X, &tmp) )
1367 IF_VERBOSE_ASCODING_ERRORS(
1368 log_aserror(_("MovieClip.localToGlobal(%s): "
1369 "object parameter doesn't have an 'x' member"),
1370 fn.arg(0));
1372 return ret;
1374 x = pixelsToTwips(toNumber(tmp, getVM(fn)));
1376 if ( ! obj->get_member(NSV::PROP_Y, &tmp) )
1378 IF_VERBOSE_ASCODING_ERRORS(
1379 log_aserror(_("MovieClip.localToGlobal(%s): "
1380 "object parameter doesn't have an 'y' member"),
1381 fn.arg(0));
1383 return ret;
1385 y = pixelsToTwips(toNumber(tmp, getVM(fn)));
1387 point pt(x, y);
1388 const SWFMatrix world_mat = getWorldMatrix(*movieclip);
1389 world_mat.transform(pt);
1391 obj->set_member(NSV::PROP_X, twipsToPixels(pt.x));
1392 obj->set_member(NSV::PROP_Y, twipsToPixels(pt.y));
1393 return ret;
1397 as_value
1398 movieclip_setMask(const fn_call& fn)
1400 // swfdec/test/image/mask-textfield-6.swf shows that setMask should also
1401 // work against TextFields, we have no tests for other DisplayObject
1402 // types so we generalize it for any DisplayObject.
1403 DisplayObject* maskee = ensure<IsDisplayObject<> >(fn);
1405 if ( ! fn.nargs )
1407 IF_VERBOSE_ASCODING_ERRORS(
1408 log_aserror(_("%s.setMask() : needs an argument"), maskee->getTarget());
1410 return as_value();
1413 const as_value& arg = fn.arg(0);
1414 if ( arg.is_null() || arg.is_undefined() )
1416 // disable mask
1417 maskee->setMask(0);
1419 else
1422 as_object* obj = toObject(arg, getVM(fn));
1423 DisplayObject* mask = get<DisplayObject>(obj);
1424 if (!mask)
1426 IF_VERBOSE_ASCODING_ERRORS(
1427 log_aserror(_("%s.setMask(%s) : first argument is not a DisplayObject"),
1428 maskee->getTarget(), arg);
1430 return as_value();
1433 // ch is possibly NULL, which is intended
1434 maskee->setMask(mask);
1437 //log_debug("MovieClip.setMask() TESTING");
1439 return as_value(true);
1442 as_value
1443 movieclip_endFill(const fn_call& fn)
1445 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
1446 movieclip->graphics().endFill();
1447 return as_value();
1450 as_value
1451 movieclip_lineTo(const fn_call& fn)
1453 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
1455 if (fn.nargs < 2) {
1456 IF_VERBOSE_ASCODING_ERRORS(
1457 log_aserror(_("MovieClip.lineTo() needs at least two arguments"));
1459 return as_value();
1462 double x = toNumber(fn.arg(0), getVM(fn));
1463 double y = toNumber(fn.arg(1), getVM(fn));
1465 if (!isFinite(x)) x = 0;
1466 if (!isFinite(y)) y = 0;
1468 #ifdef DEBUG_DRAWING_API
1469 log_debug("%s.lineTo(%g,%g);", movieclip->getTarget(), x, y);
1470 #endif
1471 movieclip->graphics().lineTo(pixelsToTwips(x), pixelsToTwips(y),
1472 movieclip->getDefinitionVersion());
1473 return as_value();
1476 as_value
1477 movieclip_moveTo(const fn_call& fn)
1479 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
1481 if (fn.nargs < 2) {
1482 IF_VERBOSE_ASCODING_ERRORS(
1483 log_aserror(_("MovieClip.moveTo() takes two args"));
1485 return as_value();
1488 double x = toNumber(fn.arg(0), getVM(fn));
1489 double y = toNumber(fn.arg(1), getVM(fn));
1491 if (!isFinite(x)) x = 0;
1492 if (!isFinite(y)) y = 0;
1494 movieclip->graphics().moveTo(pixelsToTwips(x), pixelsToTwips(y));
1495 return as_value();
1498 // SWF6,7: lineStyle(thickness:Number, rgb:Number, alpha:Number) : Void
1500 // SWF8+: lineStyle(thickness:Number, rgb:Number, alpha:Number,
1501 // pixelHinting:Boolean, noScale:String,
1502 // capsStyle:String, jointStyle:String,
1503 // miterLimit:Number) : Void
1504 as_value
1505 movieclip_lineStyle(const fn_call& fn)
1507 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
1509 if (!fn.nargs) {
1510 movieclip->graphics().resetLineStyle();
1511 return as_value();
1514 boost::uint8_t r = 0;
1515 boost::uint8_t g = 0;
1516 boost::uint8_t b = 0;
1517 boost::uint8_t a = 255;
1518 boost::uint16_t thickness = 0;
1519 bool scaleThicknessVertically = true;
1520 bool scaleThicknessHorizontally = true;
1521 bool pixelHinting = false;
1522 bool noClose = false;
1523 CapStyle capStyle = CAP_ROUND;
1524 JoinStyle joinStyle = JOIN_ROUND;
1525 float miterLimitFactor = 1.0f;
1527 int arguments = fn.nargs;
1529 const int swfVersion = getSWFVersion(fn);
1530 if (swfVersion < 8 && fn.nargs > 3) {
1531 IF_VERBOSE_ASCODING_ERRORS(
1532 std::ostringstream ss;
1533 fn.dump_args(ss);
1534 log_aserror(_("MovieClip.lineStyle(%s): args after the "
1535 "first three will be discarded"), ss.str());
1537 arguments = 3;
1540 switch (arguments) {
1541 default:
1542 case 8:
1543 miterLimitFactor = clamp<int>(toInt(fn.arg(7), getVM(fn)), 1, 255);
1544 case 7:
1546 std::string joinStyleStr = fn.arg(6).to_string();
1547 if (joinStyleStr == "miter") joinStyle = JOIN_MITER;
1548 else if (joinStyleStr == "round") joinStyle = JOIN_ROUND;
1549 else if (joinStyleStr == "bevel") joinStyle = JOIN_BEVEL;
1550 else {
1551 IF_VERBOSE_ASCODING_ERRORS(
1552 std::ostringstream ss;
1553 fn.dump_args(ss);
1554 log_aserror(_("MovieClip.lineStyle(%s): invalid joinStyle"
1555 "value '%s' (valid values: %s|%s|%s)"),
1556 ss.str(), joinStyleStr, "miter", "round", "bevel");
1560 case 6:
1562 const std::string capStyleStr = fn.arg(5).to_string();
1563 if (capStyleStr == "none") capStyle = CAP_NONE;
1564 else if (capStyleStr == "round") capStyle = CAP_ROUND;
1565 else if (capStyleStr == "square") capStyle = CAP_SQUARE;
1566 else {
1567 IF_VERBOSE_ASCODING_ERRORS(
1568 std::ostringstream ss;
1569 fn.dump_args(ss);
1570 log_aserror(_("MovieClip.lineStyle(%s): invalid capStyle "
1571 "value '%s' (valid values: none|round|square)"),
1572 ss.str(), capStyleStr);
1576 case 5:
1578 // Both values to be set here are true, so just set the
1579 // appropriate values to false.
1580 const std::string noScaleString = fn.arg(4).to_string();
1581 if (noScaleString == "none") {
1582 scaleThicknessVertically = false;
1583 scaleThicknessHorizontally = false;
1585 else if (noScaleString == "vertical") {
1586 scaleThicknessVertically = false;
1588 else if (noScaleString == "horizontal") {
1589 scaleThicknessHorizontally = false;
1591 else if (noScaleString != "normal") {
1592 IF_VERBOSE_ASCODING_ERRORS(
1593 std::ostringstream ss;
1594 fn.dump_args(ss);
1595 log_aserror(_("MovieClip.lineStyle(%s): invalid "
1596 "noScale value '%s' (valid values: "
1597 "%s|%s|%s|%s)"),
1598 ss.str(), noScaleString, "none",
1599 "vertical", "horizontal", "normal");
1603 case 4:
1604 pixelHinting = toBool(fn.arg(3), getVM(fn));
1605 case 3:
1607 const float alphaval = clamp<float>(toNumber(fn.arg(2), getVM(fn)),
1608 0, 100);
1609 a = boost::uint8_t(255 * (alphaval / 100));
1611 case 2:
1613 // See pollock.swf for eventual regressions.
1614 // It sets color to a random number from
1615 // 0 to 160000000 (about 10 times more then the max).
1616 boost::uint32_t rgbval = toInt(fn.arg(1), getVM(fn));
1617 r = boost::uint8_t((rgbval & 0xFF0000) >> 16);
1618 g = boost::uint8_t((rgbval & 0x00FF00) >> 8);
1619 b = boost::uint8_t((rgbval & 0x0000FF) );
1621 case 1:
1622 thickness = boost::uint16_t(pixelsToTwips(clamp<float>(
1623 toNumber(fn.arg(0), getVM(fn)), 0, 255)));
1624 break;
1627 rgba color(r, g, b, a);
1629 movieclip->graphics().lineStyle(thickness, color,
1630 scaleThicknessVertically, scaleThicknessHorizontally,
1631 pixelHinting, noClose, capStyle, capStyle, joinStyle, miterLimitFactor);
1633 return as_value();
1636 as_value
1637 movieclip_curveTo(const fn_call& fn)
1639 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
1641 if (fn.nargs < 4) {
1642 IF_VERBOSE_ASCODING_ERRORS(
1643 log_aserror(_("MovieClip.curveTo() takes four args"));
1645 return as_value();
1648 double cx = toNumber(fn.arg(0), getVM(fn));
1649 double cy = toNumber(fn.arg(1), getVM(fn));
1650 double ax = toNumber(fn.arg(2), getVM(fn));
1651 double ay = toNumber(fn.arg(3), getVM(fn));
1653 if (!isFinite(cx)) cx = 0;
1654 if (!isFinite(cy)) cy = 0;
1655 if (!isFinite(ax)) ax = 0;
1656 if (!isFinite(ay)) ay = 0;
1658 #ifdef DEBUG_DRAWING_API
1659 log_debug(_("%s.curveTo(%g,%g,%g,%g);"), movieclip->getTarget(),
1660 cx, cy, ax, ay);
1661 #endif
1662 movieclip->graphics().curveTo(pixelsToTwips(cx), pixelsToTwips(cy),
1663 pixelsToTwips(ax), pixelsToTwips(ay),
1664 movieclip->getDefinitionVersion());
1666 return as_value();
1669 as_value
1670 movieclip_clear(const fn_call& fn)
1672 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
1673 movieclip->graphics().clear();
1674 return as_value();
1677 as_value
1678 movieclip_beginFill(const fn_call& fn)
1680 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
1682 if (fn.nargs < 1) {
1683 IF_VERBOSE_ASCODING_ERRORS(
1684 log_aserror("beginFill() with no args is a no-op");
1686 return as_value();
1689 // 2^24 is the max here
1690 const boost::uint32_t rgbval =
1691 clamp<float>(toNumber(fn.arg(0), getVM(fn)), 0, 16777216);
1693 const boost::uint8_t r = (rgbval & 0xFF0000) >> 16;
1694 const boost::uint8_t g = (rgbval & 0x00FF00) >> 8;
1695 const boost::uint8_t b = rgbval & 0x0000FF;
1696 boost::uint8_t a = 255;
1698 if (fn.nargs > 1) {
1699 a = 255 * clamp<int>(toInt(fn.arg(1), getVM(fn)), 0, 100) / 100;
1702 rgba color(r, g, b, a);
1704 const FillStyle f = SolidFill(color);
1705 movieclip->graphics().beginFill(f);
1707 return as_value();
1711 /// Create a dynamic gradient fill.
1713 /// fillType, colors, alphas, ratios, matrix, [spreadMethod,
1714 /// [interpolationMethod, [focalPointRatio]]]
1716 /// Colors, alphas and ratios must be (possibly fake) arrays, and must have
1717 /// the same size. This applies even when there are more gradients than
1718 /// allowed.
1719 as_value
1720 movieclip_beginGradientFill(const fn_call& fn)
1722 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
1724 // The arguments up to and including matrix must be present.
1725 if (fn.nargs < 5) {
1726 IF_VERBOSE_ASCODING_ERRORS(
1727 std::stringstream ss; fn.dump_args(ss);
1728 log_aserror(_("%s.beginGradientFill(%s): invalid call: 5 arguments "
1729 "needed"), movieclip->getTarget(), ss.str());
1731 return as_value();
1734 const size_t maxargs = getSWFVersion(fn) >= 8 ? 8 : 5;
1736 if (fn.nargs > maxargs) {
1737 IF_VERBOSE_ASCODING_ERRORS(
1738 std::stringstream ss; fn.dump_args(ss);
1739 log_aserror(_("%s.beginGradientFill(%s): extra arguments "
1740 "invalidate call!"));
1742 return as_value();
1745 GradientFill::Type t;
1747 std::string typeStr = fn.arg(0).to_string();
1749 // An unexpected fill type results in no fill in all versions.
1750 if (typeStr == "radial") {
1751 t = GradientFill::RADIAL;
1753 else if (typeStr == "linear") {
1754 t = GradientFill::LINEAR;
1756 else {
1757 IF_VERBOSE_ASCODING_ERRORS(
1758 std::stringstream ss; fn.dump_args(ss);
1759 log_aserror(_("%s.beginGradientFill(%s): first arg must be "
1760 "'radial', 'focal', or 'linear'"),
1761 movieclip->getTarget(), ss.str());
1763 return as_value();
1766 typedef boost::intrusive_ptr<as_object> ObjPtr;
1767 ObjPtr colors = toObject(fn.arg(1), getVM(fn));
1768 ObjPtr alphas = toObject(fn.arg(2), getVM(fn));
1769 ObjPtr ratios = toObject(fn.arg(3), getVM(fn));
1770 ObjPtr matrix = toObject(fn.arg(4), getVM(fn));
1772 if (!colors || !alphas || !ratios || !matrix) {
1773 IF_VERBOSE_ASCODING_ERRORS(
1774 std::stringstream ss; fn.dump_args(ss);
1775 log_aserror(_("%s.beginGradientFill(%s): one or more of the "
1776 " args from 2nd to 5th don't cast to objects"),
1777 movieclip->getTarget(), ss.str());
1779 return as_value();
1782 size_t stops = arrayLength(*colors);
1784 // Check that the arrays are all the same size.
1785 if (stops != arrayLength(*alphas) || stops != arrayLength(*ratios)) {
1787 IF_VERBOSE_ASCODING_ERRORS(
1788 std::stringstream ss; fn.dump_args(ss);
1789 log_aserror(_("%s.beginGradientFill(%s): colors, alphas and "
1790 "ratios args don't have same length"),
1791 movieclip->getTarget(), ss.str());
1793 return as_value();
1796 // Then limit gradients. It's documented to be a maximum of 15, though
1797 // this isn't tested. The arrays must be the same size regardless of
1798 // this limit.
1799 if (stops > 15) {
1800 std::stringstream ss; fn.dump_args(ss);
1801 log_debug(_("%s.beginGradientFill(%s) : too many array elements"
1802 " for colors and ratios (%d), will trim to 8"),
1803 movieclip->getTarget(), ss.str(), stops);
1804 stops = 15;
1807 SWFMatrix mat = toSWFMatrix(*matrix);
1809 // ----------------------------
1810 // Create the gradients vector
1811 // ----------------------------
1813 VM& vm = getVM(fn);
1815 std::vector<GradientRecord> gradients;
1816 gradients.reserve(stops);
1817 for (size_t i = 0; i < stops; ++i) {
1819 const ObjectURI& key = getURI(vm, boost::lexical_cast<std::string>(i));
1821 as_value colVal = getMember(*colors, key);
1822 boost::uint32_t col = colVal.is_number() ? toInt(colVal, getVM(fn)) : 0;
1824 /// Alpha is the range 0..100.
1825 as_value alpVal = getMember(*alphas, key);
1826 const double a = alpVal.is_number() ?
1827 clamp<double>(toNumber(alpVal, getVM(fn)), 0, 100) : 0;
1828 const boost::uint8_t alp = 0xff * (a / 100);
1830 // Ratio is the range 0..255, but a ratio may never be smaller than
1831 // the previous value. The pp adjusts it to be greater than the
1832 // last value if it is smaller. But we must be careful not to exceed
1833 // 0xff.
1835 // From looking it looks like the minimum adjustment is 2. Even
1836 // steps of 1 appear to be adjusted.
1837 const int step = 2;
1838 const as_value& ratVal = getMember(*ratios, key);
1839 const boost::uint32_t minRatio =
1840 gradients.empty() ? 0 :
1841 std::min<boost::uint32_t>(gradients[i - 1].ratio + step, 0xff);
1843 boost::uint8_t rat = ratVal.is_number() ?
1844 clamp<boost::uint32_t>(toInt(ratVal, getVM(fn)), minRatio, 0xff)
1845 : minRatio;
1847 // The renderer may expect successively larger ratios; failure to
1848 // do this can lead to memory errors.
1849 if (!gradients.empty()) {
1850 assert((rat != 0xff && rat > gradients[i - 1].ratio) ||
1851 (rat >= gradients[i - 1].ratio));
1854 rgba color;
1855 color.parseRGB(col);
1856 color.m_a = alp;
1858 gradients.push_back(GradientRecord(rat, color));
1861 // Make sure we don't try to construct a GradientFill with only 1 stop!
1862 if (stops < 2) {
1863 const FillStyle f = SolidFill(gradients[0].color);
1864 movieclip->graphics().beginFill(f);
1865 return as_value();
1868 GradientFill fd(t, mat.invert(), gradients);
1870 // Set spread mode if present. Defaults to "pad", which is GradientFill's
1871 // default.
1872 if (fn.nargs > 5) {
1873 const std::string& spread = fn.arg(5).to_string();
1874 if (spread == "reflect") fd.spreadMode = GradientFill::REFLECT;
1875 else if (spread == "repeat") fd.spreadMode = GradientFill::REPEAT;
1876 else assert(fd.spreadMode == GradientFill::PAD);
1879 /// TODO: set interpolation mode and spread mode.
1881 /// Add a focus if present.
1882 if (fn.nargs > 7) {
1883 fd.setFocalPoint(toNumber(fn.arg(7), getVM(fn)));
1886 movieclip->graphics().beginFill(fd);
1888 return as_value();
1891 // startDrag([lockCenter:Boolean], [left:Number], [top:Number],
1892 // [right:Number], [bottom:Number]) : Void`
1893 as_value
1894 movieclip_startDrag(const fn_call& fn)
1896 MovieClip* movieclip = ensure<IsDisplayObject<MovieClip> >(fn);
1898 DragState st(movieclip);
1900 // mark this DisplayObject is transformed.
1901 movieclip->transformedByScript();
1903 if (fn.nargs) {
1904 st.setLockCentered(toBool(fn.arg(0), getVM(fn)));
1906 if (fn.nargs > 4) {
1907 double x0 = toNumber(fn.arg(1), getVM(fn));
1908 double y0 = toNumber(fn.arg(2), getVM(fn));
1909 double x1 = toNumber(fn.arg(3), getVM(fn));
1910 double y1 = toNumber(fn.arg(4), getVM(fn));
1912 // check for infinite values
1913 bool gotinf = false;
1914 if (!isFinite(x0)) { x0=0; gotinf=true; }
1915 if (!isFinite(y0)) { y0=0; gotinf=true; }
1916 if (!isFinite(x1)) { x1=0; gotinf=true; }
1917 if (!isFinite(y1)) { y1=0; gotinf=true; }
1919 // check for swapped values
1920 bool swapped = false;
1921 if (y1 < y0) {
1922 std::swap(y1, y0);
1923 swapped = true;
1926 if (x1 < x0) {
1927 std::swap(x1, x0);
1928 swapped = true;
1931 IF_VERBOSE_ASCODING_ERRORS(
1932 if (gotinf || swapped) {
1933 std::stringstream ss; fn.dump_args(ss);
1934 if (swapped) {
1935 log_aserror(_("min/max bbox values in "
1936 "MovieClip.startDrag(%s) swapped, fixing"),
1937 ss.str());
1939 if (gotinf) {
1940 log_aserror(_("non-finite bbox values in "
1941 "MovieClip.startDrag(%s), took as zero"),
1942 ss.str());
1947 SWFRect bounds(pixelsToTwips(x0), pixelsToTwips(y0),
1948 pixelsToTwips(x1), pixelsToTwips(y1));
1949 st.setBounds(bounds);
1953 getRoot(fn).setDragState(st);
1955 return as_value();
1958 // stopDrag() : Void
1959 as_value
1960 movieclip_stopDrag(const fn_call& fn)
1962 // Should this be a MovieClip only function? It isn't
1963 // necessary.
1964 getRoot(fn).stop_drag();
1965 return as_value();
1969 as_value
1970 movieclip_beginBitmapFill(const fn_call& fn)
1972 MovieClip* ptr = ensure<IsDisplayObject<MovieClip> >(fn);
1973 if (fn.nargs < 1) {
1974 return as_value();
1977 as_object* obj = toObject(fn.arg(0), getVM(fn));
1978 BitmapData_as* bd;
1980 if (!isNativeType(obj, bd) || bd->disposed()) {
1981 IF_VERBOSE_ASCODING_ERRORS(
1982 log_debug("MovieClip.attachBitmap: first argument should be a "
1983 "valid BitmapData", fn.arg(1));
1985 return as_value();
1988 SWFMatrix mat;
1990 if (fn.nargs > 1) {
1991 as_object* matrix = toObject(fn.arg(1), getVM(fn));
1992 if (matrix) {
1993 mat = toSWFMatrix(*matrix);
1997 BitmapFill::Type t = BitmapFill::TILED;
1998 if (fn.nargs > 2) {
1999 const bool repeat = toBool(fn.arg(2), getVM(fn));
2000 if (!repeat) t = BitmapFill::CLIPPED;
2003 BitmapFill::SmoothingPolicy p = BitmapFill::SMOOTHING_OFF;
2004 if (fn.nargs > 3 && toBool(fn.arg(3), getVM(fn))) {
2005 p = BitmapFill::SMOOTHING_ON;
2008 // This is needed to get the bitmap to the right size and have it in the
2009 // correct place. Maybe it would be better handled somewhere else, as it's
2010 // not exactly intuitive.
2011 mat.invert();
2012 mat.concatenate_scale(1 / 20., 1 / 20.);
2013 mat.tx /= 20;
2014 mat.ty /= 20;
2016 ptr->graphics().beginFill(BitmapFill(t, bd->bitmapInfo(), mat, p));
2017 bd->attach(ptr);
2019 return as_value();
2023 as_value
2024 movieclip_getRect(const fn_call& fn)
2026 MovieClip* ptr = ensure<IsDisplayObject<MovieClip> >(fn);
2027 UNUSED(ptr);
2028 LOG_ONCE( log_unimpl (__FUNCTION__) );
2029 return as_value();
2033 as_value
2034 movieclip_lineGradientStyle(const fn_call& fn)
2036 MovieClip* ptr = ensure<IsDisplayObject<MovieClip> >(fn);
2037 UNUSED(ptr);
2038 LOG_ONCE( log_unimpl (__FUNCTION__) );
2039 return as_value();
2043 as_value
2044 movieclip_attachBitmap(const fn_call& fn)
2047 MovieClip* ptr = ensure<IsDisplayObject<MovieClip> >(fn);
2049 if (fn.nargs < 2) {
2050 IF_VERBOSE_ASCODING_ERRORS(
2051 log_debug("MovieClip.attachBitmap: expected 2 args, got %d",
2052 fn.nargs);
2054 return as_value();
2057 as_object* obj = toObject(fn.arg(0), getVM(fn));
2058 BitmapData_as* bd;
2060 if (!isNativeType(obj, bd) || bd->disposed()) {
2061 IF_VERBOSE_ASCODING_ERRORS(
2062 log_debug("MovieClip.attachBitmap: first argument should be a "
2063 "valid BitmapData", fn.arg(1));
2065 return as_value();
2068 int depth = toInt(fn.arg(1), getVM(fn));
2070 DisplayObject* bm = new Bitmap(getRoot(fn), 0, bd, ptr);
2071 ptr->attachCharacter(*bm, depth, 0);
2073 return as_value();
2077 as_value
2078 movieclip_as2_ctor(const fn_call& /*fn*/)
2080 return as_value();
2084 as_value
2085 movieclip_transform(const fn_call& fn)
2087 MovieClip* ptr = ensure<IsDisplayObject<MovieClip> >(fn);
2089 // If not found, construction fails.
2090 as_value transform(findObject(fn.env(), "flash.geom.Transform"));
2092 boost::intrusive_ptr<as_function> transCtor = transform.to_function();
2094 if (!transCtor) {
2095 log_error("Failed to construct flash.geom.Transform!");
2096 return as_value();
2099 // Construct a flash.geom.Transform object with "this" as argument.
2100 fn_call::Args args;
2101 args += getObject(ptr);
2103 boost::intrusive_ptr<as_object> newTrans =
2104 constructInstance(*transCtor, fn.env(), args);
2106 return as_value(newTrans.get());
2109 as_value
2110 movieclip_beginMeshFill(const fn_call& /*fn*/)
2113 LOG_ONCE(log_unimpl("MovieClip.beginMeshFill"));
2114 return as_value();
2118 as_value
2119 movieclip_lockroot(const fn_call& fn)
2121 MovieClip* ptr = ensure<IsDisplayObject<MovieClip> >(fn);
2123 if (!fn.nargs) {
2124 return as_value(ptr->getLockRoot());
2127 ptr->setLockRoot(toBool(fn.arg(0), getVM(fn)));
2128 return as_value();
2131 } // anonymous namespace
2132 } // gnash namespace
2134 // local Variables:
2135 // mode: C++
2136 // indent-tabs-mode: t
2137 // End: