fix build for --disable-gtk-doc
[swfdec.git] / swfdec / swfdec_movie.c
blob735cf7815577391b0211b9365b4da173fb47b261
1 /* Swfdec
2 * Copyright (C) 2006-2008 Benjamin Otte <otte@gnome.org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301 USA
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
24 #include <math.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <errno.h>
29 #include "swfdec_movie.h"
30 #include "swfdec_as_context.h"
31 #include "swfdec_as_internal.h"
32 #include "swfdec_as_strings.h"
33 #include "swfdec_button_movie.h"
34 #include "swfdec_debug.h"
35 #include "swfdec_draw.h"
36 #include "swfdec_event.h"
37 #include "swfdec_filter.h"
38 #include "swfdec_graphic.h"
39 #include "swfdec_image.h"
40 #include "swfdec_loader_internal.h"
41 #include "swfdec_player_internal.h"
42 #include "swfdec_sprite.h"
43 #include "swfdec_sprite_movie.h"
44 #include "swfdec_renderer_internal.h"
45 #include "swfdec_resource.h"
46 #include "swfdec_sandbox.h"
47 #include "swfdec_system.h"
48 #include "swfdec_text_field_movie.h"
49 #include "swfdec_utils.h"
50 #include "swfdec_video_movie.h"
52 /*** MOVIE ***/
54 enum {
55 PROP_0,
56 PROP_DEPTH,
57 PROP_GRAPHIC,
58 PROP_NAME,
59 PROP_PARENT,
60 PROP_RESOURCE
63 enum {
64 MATRIX_CHANGED,
65 LAST_SIGNAL
68 static guint signals[LAST_SIGNAL] = { 0, };
70 G_DEFINE_ABSTRACT_TYPE (SwfdecMovie, swfdec_movie, SWFDEC_TYPE_AS_RELAY)
72 static void
73 swfdec_movie_init (SwfdecMovie * movie)
75 movie->blend_mode = SWFDEC_BLEND_MODE_NORMAL;
77 movie->xscale = 100;
78 movie->yscale = 100;
79 cairo_matrix_init_identity (&movie->original_transform);
80 cairo_matrix_init_identity (&movie->matrix);
81 cairo_matrix_init_identity (&movie->inverse_matrix);
83 swfdec_color_transform_init_identity (&movie->color_transform);
85 movie->visible = TRUE;
86 movie->cache_state = SWFDEC_MOVIE_INVALID_EXTENTS;
87 movie->invalidate_last = TRUE;
88 movie->invalidate_next = TRUE;
90 swfdec_rect_init_empty (&movie->extents);
93 /**
94 * swfdec_movie_invalidate:
95 * @movie: a #SwfdecMovie
96 * @parent_to_global: This is the matrix from the parent to the global matrix.
97 * It is only used for caching reasons
98 * @new_contents: %TRUE if this is the invalidation of the new contents, %FALSE
99 * if the old contents are invalidated.
101 * Performs an instant invalidation on @movie. You most likely don't want to
102 * call this function directly, but use swfdec_movie_invalidate_last() or
103 * swfdec_movie_invalidate_next() instead.
105 void
106 swfdec_movie_invalidate (SwfdecMovie *movie, const cairo_matrix_t *parent_to_global,
107 gboolean new_contents)
109 SwfdecMovieClass *klass;
110 cairo_matrix_t matrix;
112 if (new_contents) {
113 movie->invalidate_next = FALSE;
114 } else {
115 SwfdecPlayer *player;
116 if (movie->invalidate_last)
117 return;
118 movie->invalidate_last = TRUE;
119 player = SWFDEC_PLAYER (swfdec_gc_object_get_context (movie));
120 player->priv->invalid_pending = g_slist_prepend (player->priv->invalid_pending, movie);
122 g_assert (movie->cache_state <= SWFDEC_MOVIE_INVALID_CHILDREN);
123 SWFDEC_LOG ("invalidating %s %s at %s", G_OBJECT_TYPE_NAME (movie),
124 movie->name, new_contents ? "end" : "start");
125 cairo_matrix_multiply (&matrix, &movie->matrix, parent_to_global);
126 klass = SWFDEC_MOVIE_GET_CLASS (movie);
127 klass->invalidate (movie, &matrix, new_contents);
131 * swfdec_movie_invalidate_last:
132 * @movie: a #SwfdecMovie
134 * Ensures the movie's contents are invalidated. This function must be called
135 * before changing the movie or the output will have artifacts.
137 void
138 swfdec_movie_invalidate_last (SwfdecMovie *movie)
140 cairo_matrix_t matrix;
142 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
144 if (movie->invalidate_last)
145 return;
147 if (movie->parent)
148 swfdec_movie_local_to_global_matrix (movie->parent, &matrix);
149 else
150 cairo_matrix_init_identity (&matrix);
151 swfdec_movie_invalidate (movie, &matrix, FALSE);
152 g_assert (movie->invalidate_last);
156 * swfdec_movie_invalidate_next:
157 * @movie: a #SwfdecMovie
159 * Ensures the movie will be invalidated after script execution is done. So
160 * after calling this function you can modify position and contents of the
161 * @movie in any way.
163 void
164 swfdec_movie_invalidate_next (SwfdecMovie *movie)
166 SwfdecPlayer *player;
168 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
170 swfdec_movie_invalidate_last (movie);
171 movie->invalidate_next = TRUE;
172 player = SWFDEC_PLAYER (swfdec_gc_object_get_context (movie));
173 if (movie == SWFDEC_MOVIE (player->priv->focus))
174 swfdec_player_invalidate_focusrect (player);
178 * swfdec_movie_queue_update:
179 * @movie: a #SwfdecMovie
180 * @state: how much needs to be updated
182 * Queues an update of all cached values inside @movie and invalidates it.
184 void
185 swfdec_movie_queue_update (SwfdecMovie *movie, SwfdecMovieCacheState state)
187 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
189 while (movie && movie->cache_state < state) {
190 movie->cache_state = state;
191 movie = movie->parent;
192 state = SWFDEC_MOVIE_INVALID_CHILDREN;
196 static void
197 swfdec_movie_update_extents (SwfdecMovie *movie)
199 SwfdecMovieClass *klass;
200 GList *walk;
201 SwfdecRect *rect = &movie->original_extents;
202 SwfdecRect *extents = &movie->extents;
204 *rect = movie->draw_extents;
205 if (movie->image) {
206 SwfdecRect image_extents = { 0, 0,
207 movie->image->width * SWFDEC_TWIPS_SCALE_FACTOR,
208 movie->image->height * SWFDEC_TWIPS_SCALE_FACTOR };
209 swfdec_rect_union (rect, rect, &image_extents);
211 for (walk = movie->list; walk; walk = walk->next) {
212 swfdec_rect_union (rect, rect, &SWFDEC_MOVIE (walk->data)->extents);
214 klass = SWFDEC_MOVIE_GET_CLASS (movie);
215 if (klass->update_extents)
216 klass->update_extents (movie, rect);
217 if (swfdec_rect_is_empty (rect)) {
218 *extents = *rect;
219 return;
221 swfdec_rect_transform (extents, rect, &movie->matrix);
222 if (movie->parent && movie->parent->cache_state < SWFDEC_MOVIE_INVALID_EXTENTS) {
223 /* no need to invalidate here */
224 movie->parent->cache_state = SWFDEC_MOVIE_INVALID_EXTENTS;
228 void
229 swfdec_movie_begin_update_matrix (SwfdecMovie *movie)
231 swfdec_movie_invalidate_next (movie);
234 void
235 swfdec_movie_end_update_matrix (SwfdecMovie *movie)
237 double d, e;
239 swfdec_movie_queue_update (movie, SWFDEC_MOVIE_INVALID_EXTENTS);
241 /* we operate on x0 and y0 when setting movie._x and movie._y */
242 if (movie->modified) {
243 movie->matrix.xx = movie->original_transform.xx;
244 movie->matrix.yx = movie->original_transform.yx;
245 movie->matrix.xy = movie->original_transform.xy;
246 movie->matrix.yy = movie->original_transform.yy;
247 } else {
248 movie->matrix = movie->original_transform;
251 d = movie->xscale / swfdec_matrix_get_xscale (&movie->original_transform);
252 e = movie->yscale / swfdec_matrix_get_yscale (&movie->original_transform);
253 cairo_matrix_scale (&movie->matrix, d, e);
254 if (isfinite (movie->rotation)) {
255 d = movie->rotation - swfdec_matrix_get_rotation (&movie->original_transform);
256 cairo_matrix_rotate (&movie->matrix, d * G_PI / 180);
258 swfdec_matrix_ensure_invertible (&movie->matrix, &movie->inverse_matrix);
260 g_signal_emit (movie, signals[MATRIX_CHANGED], 0);
263 static void
264 swfdec_movie_do_update (SwfdecMovie *movie)
266 GList *walk;
268 for (walk = movie->list; walk; walk = walk->next) {
269 SwfdecMovie *child = walk->data;
271 if (child->cache_state != SWFDEC_MOVIE_UP_TO_DATE)
272 swfdec_movie_do_update (child);
275 switch (movie->cache_state) {
276 case SWFDEC_MOVIE_INVALID_EXTENTS:
277 swfdec_movie_update_extents (movie);
278 /* fall through */
279 case SWFDEC_MOVIE_INVALID_CHILDREN:
280 break;
281 case SWFDEC_MOVIE_UP_TO_DATE:
282 default:
283 g_assert_not_reached ();
285 movie->cache_state = SWFDEC_MOVIE_UP_TO_DATE;
289 * swfdec_movie_update:
290 * @movie: a #SwfdecMovie
292 * Brings the cached values of @movie up-to-date if they are not. This includes
293 * transformation matrices and extents. It needs to be called before accessing
294 * the relevant values.
296 void
297 swfdec_movie_update (SwfdecMovie *movie)
299 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
301 if (movie->cache_state == SWFDEC_MOVIE_UP_TO_DATE)
302 return;
304 if (movie->parent && movie->parent->cache_state != SWFDEC_MOVIE_UP_TO_DATE) {
305 swfdec_movie_update (movie->parent);
306 } else {
307 swfdec_movie_do_update (movie);
311 SwfdecMovie *
312 swfdec_movie_find (SwfdecMovie *movie, int depth)
314 GList *walk;
316 g_return_val_if_fail (SWFDEC_IS_MOVIE (movie), NULL);
318 for (walk = movie->list; walk; walk = walk->next) {
319 SwfdecMovie *cur= walk->data;
321 if (cur->depth < depth)
322 continue;
323 if (cur->depth == depth)
324 return cur;
325 break;
327 return NULL;
330 static void
331 swfdec_movie_unset_actor (SwfdecPlayer *player, SwfdecActor *actor)
333 SwfdecPlayerPrivate *priv = player->priv;
335 if (priv->mouse_below == actor)
336 priv->mouse_below = NULL;
337 if (priv->mouse_grab == actor)
338 priv->mouse_grab = NULL;
339 if (priv->mouse_drag == actor)
340 priv->mouse_drag = NULL;
342 if (priv->focus_previous == actor)
343 priv->focus_previous = NULL;
344 if (priv->focus == actor) {
345 priv->focus = NULL;
346 swfdec_player_invalidate_focusrect (player);
350 static gboolean
351 swfdec_movie_do_remove (SwfdecMovie *movie, gboolean destroy)
353 SwfdecPlayer *player;
355 SWFDEC_LOG ("removing %s %s", G_OBJECT_TYPE_NAME (movie), movie->name);
357 player = SWFDEC_PLAYER (swfdec_gc_object_get_context (movie));
358 while (movie->list) {
359 GList *walk = movie->list;
360 while (walk && SWFDEC_MOVIE (walk->data)->state >= SWFDEC_MOVIE_STATE_REMOVED)
361 walk = walk->next;
362 if (walk == NULL)
363 break;
364 destroy &= swfdec_movie_do_remove (walk->data, destroy);
366 /* FIXME: all of this here or in destroy callback? */
367 swfdec_movie_invalidate_last (movie);
368 movie->state = SWFDEC_MOVIE_STATE_REMOVED;
370 if (SWFDEC_IS_ACTOR (movie)) {
371 SwfdecActor *actor = SWFDEC_ACTOR (movie);
372 swfdec_movie_unset_actor (player, actor);
373 if ((actor->events &&
374 swfdec_event_list_has_conditions (actor->events, SWFDEC_EVENT_UNLOAD, 0)) ||
375 swfdec_as_object_has_variable (swfdec_as_relay_get_as_object (SWFDEC_AS_RELAY (movie)), SWFDEC_AS_STR_onUnload)) {
376 swfdec_actor_queue_script (actor, SWFDEC_EVENT_UNLOAD);
377 destroy = FALSE;
380 if (destroy)
381 swfdec_movie_destroy (movie);
382 return destroy;
386 * swfdec_movie_remove:
387 * @movie: #SwfdecMovie to remove
389 * Removes this movie from its parent. In contrast to swfdec_movie_destroy (),
390 * it might still be possible to reference it from Actionscript, if the movie
391 * queues onUnload event handlers.
393 void
394 swfdec_movie_remove (SwfdecMovie *movie)
396 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
398 if (movie->state > SWFDEC_MOVIE_STATE_RUNNING)
399 return;
400 if (swfdec_movie_do_remove (movie, TRUE))
401 return;
403 swfdec_movie_set_depth (movie, -32769 - movie->depth); /* don't ask me why... */
407 * swfdec_movie_destroy:
408 * @movie: #SwfdecMovie to destroy
410 * Removes this movie from its parent. After this it will no longer be present,
411 * neither visually nor via ActionScript. This function will not cause an
412 * unload event. Compare with swfdec_movie_remove ().
414 void
415 swfdec_movie_destroy (SwfdecMovie *movie)
417 SwfdecMovieClass *klass = SWFDEC_MOVIE_GET_CLASS (movie);
418 SwfdecPlayer *player = SWFDEC_PLAYER (swfdec_gc_object_get_context (movie));
420 g_assert (movie->state < SWFDEC_MOVIE_STATE_DESTROYED);
421 SWFDEC_LOG ("destroying movie %s", movie->name);
422 while (movie->list) {
423 swfdec_movie_destroy (movie->list->data);
425 if (movie->parent) {
426 movie->parent->list = g_list_remove (movie->parent->list, movie);
427 } else {
428 player->priv->roots = g_list_remove (player->priv->roots, movie);
430 /* unset masks */
431 if (movie->masked_by)
432 movie->masked_by->mask_of = NULL;
433 if (movie->mask_of)
434 movie->mask_of->masked_by = NULL;
435 movie->masked_by = NULL;
436 movie->mask_of = NULL;
437 /* FIXME: figure out how to handle destruction pre-init/construct.
438 * This is just a stop-gap measure to avoid dead movies in those queues */
439 if (SWFDEC_IS_ACTOR (movie))
440 swfdec_player_remove_all_actions (player, SWFDEC_ACTOR (movie));
441 if (klass->finish_movie)
442 klass->finish_movie (movie);
443 player->priv->actors = g_list_remove (player->priv->actors, movie);
444 if (movie->invalidate_last)
445 player->priv->invalid_pending = g_slist_remove (player->priv->invalid_pending, movie);
446 movie->state = SWFDEC_MOVIE_STATE_DESTROYED;
447 /* remove as value, so we can't be scripted anymore */
448 if (movie->as_value) {
449 movie->as_value->movie = NULL;
450 movie->as_value = NULL;
452 g_object_unref (movie);
455 guint
456 swfdec_movie_get_version (SwfdecMovie *movie)
458 return movie->resource->version;
461 void
462 swfdec_movie_local_to_global (SwfdecMovie *movie, double *x, double *y)
464 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
465 g_return_if_fail (x != NULL);
466 g_return_if_fail (y != NULL);
468 do {
469 cairo_matrix_transform_point (&movie->matrix, x, y);
470 } while ((movie = movie->parent));
473 void
474 swfdec_movie_rect_local_to_global (SwfdecMovie *movie, SwfdecRect *rect)
476 cairo_matrix_t matrix;
478 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
479 g_return_if_fail (rect != NULL);
481 swfdec_movie_local_to_global_matrix (movie, &matrix);
482 swfdec_rect_transform (rect, rect, &matrix);
485 void
486 swfdec_movie_global_to_local_matrix (SwfdecMovie *movie, cairo_matrix_t *matrix)
488 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
489 g_return_if_fail (matrix != NULL);
491 cairo_matrix_init_identity (matrix);
492 while (movie) {
493 cairo_matrix_multiply (matrix, &movie->inverse_matrix, matrix);
494 movie = movie->parent;
498 void
499 swfdec_movie_local_to_global_matrix (SwfdecMovie *movie, cairo_matrix_t *matrix)
501 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
502 g_return_if_fail (matrix != NULL);
504 cairo_matrix_init_identity (matrix);
505 while (movie) {
506 cairo_matrix_multiply (matrix, matrix, &movie->matrix);
507 movie = movie->parent;
511 void
512 swfdec_movie_global_to_local (SwfdecMovie *movie, double *x, double *y)
514 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
515 g_return_if_fail (x != NULL);
516 g_return_if_fail (y != NULL);
518 if (movie->parent) {
519 swfdec_movie_global_to_local (movie->parent, x, y);
521 cairo_matrix_transform_point (&movie->inverse_matrix, x, y);
524 void
525 swfdec_movie_rect_global_to_local (SwfdecMovie *movie, SwfdecRect *rect)
527 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
528 g_return_if_fail (rect != NULL);
530 swfdec_movie_global_to_local (movie, &rect->x0, &rect->y0);
531 swfdec_movie_global_to_local (movie, &rect->x1, &rect->y1);
532 if (rect->x0 > rect->x1) {
533 double tmp = rect->x1;
534 rect->x1 = rect->x0;
535 rect->x0 = tmp;
537 if (rect->y0 > rect->y1) {
538 double tmp = rect->y1;
539 rect->y1 = rect->y0;
540 rect->y0 = tmp;
545 * swfdec_movie_get_mouse:
546 * @movie: a #SwfdecMovie
547 * @x: pointer to hold result of X coordinate
548 * @y: pointer to hold result of y coordinate
550 * Gets the mouse coordinates in the coordinate space of @movie.
552 void
553 swfdec_movie_get_mouse (SwfdecMovie *movie, double *x, double *y)
555 SwfdecPlayer *player;
557 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
558 g_return_if_fail (x != NULL);
559 g_return_if_fail (y != NULL);
561 player = SWFDEC_PLAYER (swfdec_gc_object_get_context (movie));
562 *x = player->priv->mouse_x;
563 *y = player->priv->mouse_y;
564 swfdec_player_stage_to_global (player, x, y);
565 swfdec_movie_global_to_local (movie, x, y);
569 * swfdec_movie_get_movie_at:
570 * @movie: a #SwfdecMovie
571 * @x: x coordinate in parent's coordinate space
572 * @y: y coordinate in the parent's coordinate space
573 * @events: %TRUE to only prefer movies that receive events
575 * Gets the child at the given coordinates. The coordinates are in the
576 * coordinate system of @movie's parent (or the global coordinate system for
577 * root movies). The @events parameter determines if movies that don't receive
578 * events should be respected.
580 * Returns: the child of @movie at the given coordinates or %NULL if none
582 SwfdecMovie *
583 swfdec_movie_get_movie_at (SwfdecMovie *movie, double x, double y, gboolean events)
585 SwfdecMovie *ret;
586 SwfdecMovieClass *klass;
588 g_return_val_if_fail (SWFDEC_IS_MOVIE (movie), NULL);
590 SWFDEC_LOG ("%s %p getting mouse at: %g %g", G_OBJECT_TYPE_NAME (movie), movie, x, y);
591 if (movie->cache_state >= SWFDEC_MOVIE_INVALID_EXTENTS)
592 swfdec_movie_update (movie);
593 if (!swfdec_rect_contains (&movie->extents, x, y)) {
594 return NULL;
596 cairo_matrix_transform_point (&movie->inverse_matrix, &x, &y);
598 klass = SWFDEC_MOVIE_GET_CLASS (movie);
599 g_return_val_if_fail (klass->contains, NULL);
600 ret = klass->contains (movie, x, y, events);
602 return ret;
605 static SwfdecMovie *
606 swfdec_movie_do_contains (SwfdecMovie *movie, double x, double y, gboolean events)
608 GList *walk;
609 GSList *walk2;
610 SwfdecMovie *ret, *got;
612 ret = NULL;
613 for (walk = movie->list; walk; walk = walk->next) {
614 SwfdecMovie *child = walk->data;
616 if (!child->visible) {
617 SWFDEC_LOG ("%s %s (depth %d) is invisible, ignoring", G_OBJECT_TYPE_NAME (movie), movie->name, movie->depth);
618 continue;
620 got = swfdec_movie_get_movie_at (child, x, y, events);
621 if (got != NULL) {
622 if (events) {
623 /* set the return value to the topmost movie */
624 if (SWFDEC_IS_ACTOR (got) && swfdec_actor_get_mouse_events (SWFDEC_ACTOR (got))) {
625 ret = got;
626 } else if (ret == NULL) {
627 ret = movie;
629 } else {
630 /* if thie is not a clipped movie, we've found something */
631 if (child->clip_depth == 0)
632 return movie;
634 } else {
635 if (child->clip_depth) {
636 /* skip obscured movies */
637 SwfdecMovie *tmp = walk->next ? walk->next->data : NULL;
638 while (tmp && tmp->depth <= child->clip_depth) {
639 walk = walk->next;
640 tmp = walk->next ? walk->next->data : NULL;
645 if (ret)
646 return ret;
648 for (walk2 = movie->draws; walk2; walk2 = walk2->next) {
649 SwfdecDraw *draw = walk2->data;
651 if (swfdec_draw_contains (draw, x, y))
652 return movie;
655 return NULL;
658 /* NB: order is important */
659 typedef enum {
660 SWFDEC_GROUP_NONE = 0,
661 SWFDEC_GROUP_NORMAL,
662 SWFDEC_GROUP_CACHED,
663 SWFDEC_GROUP_FILTERS
664 } SwfdecGroup;
666 static SwfdecGroup
667 swfdec_movie_needs_group (SwfdecMovie *movie)
669 /* yes, masked movies don't get filters applied */
670 if (movie->filters && movie->masked_by == NULL)
671 return SWFDEC_GROUP_FILTERS;
672 if (movie->cache_as_bitmap)
673 return SWFDEC_GROUP_CACHED;
674 if (movie->blend_mode > 1)
675 return SWFDEC_GROUP_NORMAL;
676 return SWFDEC_GROUP_NONE;
679 static cairo_operator_t
680 swfdec_movie_get_operator_for_blend_mode (guint blend_mode)
682 switch (blend_mode) {
683 case SWFDEC_BLEND_MODE_NONE:
684 case SWFDEC_BLEND_MODE_NORMAL:
685 case SWFDEC_BLEND_MODE_LAYER:
686 return CAIRO_OPERATOR_OVER;
687 case SWFDEC_BLEND_MODE_ADD:
688 return CAIRO_OPERATOR_ADD;
689 case SWFDEC_BLEND_MODE_ALPHA:
690 return CAIRO_OPERATOR_DEST_IN;
691 case SWFDEC_BLEND_MODE_ERASE:
692 return CAIRO_OPERATOR_DEST_OUT;
693 case SWFDEC_BLEND_MODE_MULTIPLY:
694 case SWFDEC_BLEND_MODE_SCREEN:
695 case SWFDEC_BLEND_MODE_LIGHTEN:
696 case SWFDEC_BLEND_MODE_DARKEN:
697 case SWFDEC_BLEND_MODE_DIFFERENCE:
698 case SWFDEC_BLEND_MODE_SUBTRACT:
699 case SWFDEC_BLEND_MODE_INVERT:
700 case SWFDEC_BLEND_MODE_OVERLAY:
701 case SWFDEC_BLEND_MODE_HARDLIGHT:
702 SWFDEC_FIXME ("blend mode %u unimplemented in cairo", blend_mode);
703 return CAIRO_OPERATOR_OVER;
704 default:
705 SWFDEC_WARNING ("invalid blend mode %u", blend_mode);
706 return CAIRO_OPERATOR_OVER;
710 static cairo_pattern_t *
711 swfdec_movie_apply_filters (SwfdecMovie *movie, cairo_pattern_t *pattern)
713 SwfdecRectangle area;
714 SwfdecPlayer *player;
715 SwfdecRect rect;
716 GSList *walk;
717 double xscale, yscale;
719 if (movie->filters == NULL)
720 return pattern;
722 player = SWFDEC_PLAYER (swfdec_gc_object_get_context (movie));
723 rect = movie->original_extents;
724 swfdec_movie_rect_local_to_global (movie, &rect);
725 swfdec_rect_transform (&rect, &rect,
726 &player->priv->global_to_stage);
727 swfdec_rectangle_init_rect (&area, &rect);
728 /* FIXME: hack to make textfield borders work - looks like Adobe does this, too */
729 area.width++;
730 area.height++;
731 xscale = player->priv->global_to_stage.xx * SWFDEC_TWIPS_SCALE_FACTOR;
732 yscale = player->priv->global_to_stage.yy * SWFDEC_TWIPS_SCALE_FACTOR;
733 for (walk = movie->filters; walk; walk = walk->next) {
734 pattern = swfdec_filter_apply (walk->data, pattern, xscale, yscale, &area);
735 swfdec_filter_get_rectangle (walk->data, &area, xscale, yscale, &area);
737 return pattern;
741 * swfdec_movie_mask:
742 * @movie: The movie to act as the mask
743 * @cr: a cairo context which should be used for masking. The cairo context's
744 * matrix is assumed to be in the coordinate system of the movie's parent.
745 * @matrix: matrix to apply before rendering
747 * Creates a pattern suitable for masking. To do rendering using the returned
748 * mask, you want to use code like this:
749 * <informalexample><programlisting>
750 * mask = swfdec_movie_mask (cr, movie, matrix);
751 * cairo_push_group (cr);
752 * // do rendering here
753 * cairo_pop_group_to_source (cr);
754 * cairo_mask (cr, mask);
755 * cairo_pattern_destroy (mask);
756 * </programlisting></informalexample>
758 * Returns: A new cairo_patten_t to be used as the mask.
760 static cairo_pattern_t *
761 swfdec_movie_mask (cairo_t *cr, SwfdecMovie *movie,
762 const cairo_matrix_t *matrix)
764 SwfdecColorTransform black;
766 swfdec_color_transform_init_mask (&black);
767 cairo_push_group_with_content (cr, CAIRO_CONTENT_ALPHA);
768 cairo_transform (cr, matrix);
770 swfdec_movie_render (movie, cr, &black);
771 return cairo_pop_group (cr);
774 void
775 swfdec_movie_render (SwfdecMovie *movie, cairo_t *cr,
776 const SwfdecColorTransform *color_transform)
778 SwfdecMovieClass *klass;
779 SwfdecColorTransform trans;
780 SwfdecGroup group;
781 gboolean needs_mask;
783 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
784 g_return_if_fail (cr != NULL);
785 if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) {
786 g_warning ("%s", cairo_status_to_string (cairo_status (cr)));
788 g_return_if_fail (color_transform != NULL);
790 if (movie->mask_of != NULL && !swfdec_color_transform_is_mask (color_transform)) {
791 SWFDEC_LOG ("not rendering %s %p, movie is a mask",
792 G_OBJECT_TYPE_NAME (movie), movie->name);
793 return;
796 group = swfdec_movie_needs_group (movie);
797 if (group == SWFDEC_GROUP_NORMAL) {
798 SWFDEC_DEBUG ("pushing group for blend mode %u", movie->blend_mode);
799 cairo_push_group (cr);
800 } else if (group != SWFDEC_GROUP_NONE) {
801 SWFDEC_FIXME ("implement cache-as-bitmap here");
802 cairo_push_group (cr);
804 /* yes, movie with filters, don't get masked */
805 needs_mask = movie->masked_by != NULL && movie->filters == NULL;
806 if (needs_mask) {
807 cairo_push_group (cr);
810 /* do extra save/restore so the render vfunc can mess with cr */
811 cairo_save (cr);
813 SWFDEC_LOG ("transforming movie, transform: %g %g %g %g %g %g",
814 movie->matrix.xx, movie->matrix.yy,
815 movie->matrix.xy, movie->matrix.yx,
816 movie->matrix.x0, movie->matrix.y0);
817 cairo_transform (cr, &movie->matrix);
818 swfdec_color_transform_chain (&trans, &movie->color_transform, color_transform);
820 klass = SWFDEC_MOVIE_GET_CLASS (movie);
821 g_return_if_fail (klass->render);
822 klass->render (movie, cr, &trans);
823 #if 0
824 /* code to draw a red rectangle around the area occupied by this movie clip */
826 cairo_save (cr);
827 cairo_transform (cr, &movie->inverse_matrix);
828 cairo_rectangle (cr, movie->extents.x0, movie->extents.y0,
829 movie->extents.x1 - movie->extents.x0,
830 movie->extents.y1 - movie->extents.y0);
831 swfdec_renderer_reset_matrix (cr);
832 cairo_set_source_rgb (cr, 0.0, 0.0, 1.0);
833 cairo_set_line_width (cr, 2.0);
834 cairo_stroke (cr);
835 cairo_restore (cr);
837 #endif
838 if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) {
839 g_warning ("error rendering with cairo: %s", cairo_status_to_string (cairo_status (cr)));
841 cairo_restore (cr);
843 if (needs_mask) {
844 cairo_pattern_t *mask;
845 cairo_matrix_t mat;
846 if (movie->parent)
847 swfdec_movie_global_to_local_matrix (movie->parent, &mat);
848 else
849 cairo_matrix_init_identity (&mat);
850 if (movie->masked_by->parent) {
851 cairo_matrix_t mat2;
852 swfdec_movie_local_to_global_matrix (movie->masked_by->parent, &mat2);
853 cairo_matrix_multiply (&mat, &mat, &mat2);
855 mask = swfdec_movie_mask (cr, movie->masked_by, &mat);
856 cairo_pop_group_to_source (cr);
857 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
858 cairo_mask (cr, mask);
859 cairo_pattern_destroy (mask);
861 if (group == SWFDEC_GROUP_FILTERS) {
862 cairo_pattern_t *pattern;
864 pattern = cairo_pop_group (cr);
865 cairo_save (cr);
866 swfdec_renderer_reset_matrix (cr);
868 cairo_matrix_t mat;
869 cairo_get_matrix (cr, &mat);
870 cairo_matrix_invert (&mat);
871 cairo_pattern_set_matrix (pattern, &mat);
873 pattern = swfdec_movie_apply_filters (movie, pattern);
874 cairo_set_source (cr, pattern);
875 cairo_set_operator (cr, swfdec_movie_get_operator_for_blend_mode (movie->blend_mode));
876 cairo_paint (cr);
877 cairo_pattern_destroy (pattern);
878 cairo_restore (cr);
879 } else if (group != SWFDEC_GROUP_NONE) {
880 cairo_pop_group_to_source (cr);
881 cairo_set_operator (cr, swfdec_movie_get_operator_for_blend_mode (movie->blend_mode));
882 cairo_paint (cr);
886 static void
887 swfdec_movie_get_property (GObject *object, guint param_id, GValue *value,
888 GParamSpec * pspec)
890 SwfdecMovie *movie = SWFDEC_MOVIE (object);
892 switch (param_id) {
893 case PROP_DEPTH:
894 g_value_set_int (value, movie->depth);
895 break;
896 case PROP_PARENT:
897 g_value_set_object (value, movie->parent);
898 break;
899 case PROP_RESOURCE:
900 g_value_set_object (value, movie->resource);
901 break;
902 default:
903 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
904 break;
908 static void
909 swfdec_movie_set_property (GObject *object, guint param_id, const GValue *value,
910 GParamSpec * pspec)
912 SwfdecMovie *movie = SWFDEC_MOVIE (object);
913 SwfdecAsContext *cx = swfdec_gc_object_get_context (movie);
915 /* The context must be set before all movie-related properties */
916 g_assert (cx);
918 switch (param_id) {
919 case PROP_DEPTH:
920 /* parent must be set after depth */
921 g_assert (movie->parent == NULL);
922 movie->depth = g_value_get_int (value);
923 break;
924 case PROP_GRAPHIC:
925 movie->graphic = g_value_get_object (value);
926 if (movie->graphic)
927 g_object_ref (movie->graphic);
928 break;
929 case PROP_NAME:
930 movie->name = g_value_get_string (value);
931 if (movie->name) {
932 movie->name = swfdec_as_context_get_string (cx, movie->name);
933 } else {
934 movie->name = SWFDEC_AS_STR_EMPTY;
936 break;
937 case PROP_PARENT:
938 movie->parent = g_value_get_object (value);
939 /* parent holds a reference */
940 g_object_ref (movie);
941 if (movie->parent) {
942 movie->parent->list = g_list_insert_sorted (movie->parent->list, movie, swfdec_movie_compare_depths);
943 SWFDEC_DEBUG ("inserting %s %p into %s %p", G_OBJECT_TYPE_NAME (movie), movie,
944 G_OBJECT_TYPE_NAME (movie->parent), movie->parent);
945 /* invalidate the parent, so it gets visible */
946 swfdec_movie_queue_update (movie->parent, SWFDEC_MOVIE_INVALID_CHILDREN);
947 } else {
948 SwfdecPlayerPrivate *priv = SWFDEC_PLAYER (cx)->priv;
949 priv->roots = g_list_insert_sorted (priv->roots, movie, swfdec_movie_compare_depths);
951 break;
952 case PROP_RESOURCE:
953 movie->resource = g_value_get_object (value);
954 break;
955 default:
956 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
957 break;
961 static void
962 swfdec_movie_dispose (GObject *object)
964 SwfdecMovie * movie = SWFDEC_MOVIE (object);
965 GSList *iter;
967 g_assert (movie->list == NULL);
969 SWFDEC_LOG ("disposing movie %s (depth %d)", movie->name, movie->depth);
970 if (movie->graphic) {
971 g_object_unref (movie->graphic);
972 movie->graphic = NULL;
974 for (iter = movie->variable_listeners; iter != NULL; iter = iter->next) {
975 g_free (iter->data);
977 g_slist_free (movie->variable_listeners);
978 movie->variable_listeners = NULL;
980 if (movie->image) {
981 g_object_unref (movie->image);
982 movie->image = NULL;
984 g_slist_foreach (movie->draws, (GFunc) g_object_unref, NULL);
985 g_slist_free (movie->draws);
986 movie->draws = NULL;
988 G_OBJECT_CLASS (swfdec_movie_parent_class)->dispose (G_OBJECT (movie));
991 static void
992 swfdec_movie_mark (SwfdecGcObject *object)
994 SwfdecMovie *movie = SWFDEC_MOVIE (object);
995 GSList *iter;
997 if (movie->parent)
998 swfdec_gc_object_mark (movie->parent);
999 if (movie->as_value)
1000 swfdec_as_movie_value_mark (movie->as_value);
1001 swfdec_as_string_mark (movie->name);
1002 g_list_foreach (movie->list, (GFunc) swfdec_gc_object_mark, NULL);
1003 g_slist_foreach (movie->filters, (GFunc) swfdec_gc_object_mark, NULL);
1005 for (iter = movie->variable_listeners; iter != NULL; iter = iter->next) {
1006 SwfdecMovieVariableListener *listener = iter->data;
1007 swfdec_as_string_mark (listener->name);
1009 swfdec_gc_object_mark (movie->resource);
1011 SWFDEC_GC_OBJECT_CLASS (swfdec_movie_parent_class)->mark (object);
1015 * swfdec_movie_is_scriptable:
1016 * @movie: a movie
1018 * Checks if the movie may be accessed by scripts. If not, the movie is not
1019 * accessible by Actionscript and functions that would return the movie should
1020 * instead return its parent.
1022 * Returns: %TRUE if scripts may access this movie, %FALSE if the parent
1023 * should be used.
1025 gboolean
1026 swfdec_movie_is_scriptable (SwfdecMovie *movie)
1028 /* FIXME: It would be much easier if we'd just check that there's no as_value
1029 * for non-scriptable movies */
1030 return (SWFDEC_IS_ACTOR (movie) || SWFDEC_IS_VIDEO_MOVIE (movie)) &&
1031 (swfdec_movie_get_version (movie) > 5 || !SWFDEC_IS_TEXT_FIELD_MOVIE (movie));
1034 /* FIXME: This function can definitely be implemented easier */
1035 SwfdecMovie *
1036 swfdec_movie_get_by_name (SwfdecMovie *movie, const char *name, gboolean unnamed)
1038 GList *walk;
1039 int i;
1040 guint version = swfdec_gc_object_get_context (movie)->version;
1041 SwfdecPlayer *player = SWFDEC_PLAYER (swfdec_gc_object_get_context (movie));
1043 if (name[0] == '\0')
1044 return NULL;
1046 i = swfdec_player_get_level (player, name, version);
1047 if (i >= 0)
1048 return SWFDEC_MOVIE (swfdec_player_get_movie_at_level (player, i));
1050 for (walk = movie->list; walk; walk = walk->next) {
1051 SwfdecMovie *cur = walk->data;
1052 if (swfdec_strcmp (version, cur->name, name) == 0) {
1053 if (swfdec_movie_is_scriptable (cur))
1054 return cur;
1055 else
1056 return movie;
1058 if (unnamed && cur->name == SWFDEC_AS_STR_EMPTY && cur->as_value) {
1059 if (swfdec_strcmp (version, cur->as_value->names[cur->as_value->n_names - 1], name) == 0) {
1060 /* unnamed movies are always scriptable */
1061 return cur;
1065 return NULL;
1068 SwfdecMovie *
1069 swfdec_movie_get_root (SwfdecMovie *movie)
1071 SwfdecMovie *real_root;
1073 g_return_val_if_fail (SWFDEC_IS_MOVIE (movie), NULL);
1075 real_root = movie;
1076 while (real_root->parent)
1077 real_root = real_root->parent;
1079 while (movie->parent && !(movie->lockroot &&
1080 (swfdec_movie_get_version (movie) != 6 ||
1081 swfdec_movie_get_version (real_root) != 6))) {
1082 movie = movie->parent;
1085 return movie;
1088 void
1089 swfdec_movie_add_variable_listener (SwfdecMovie *movie, gpointer data,
1090 const char *name, const SwfdecMovieVariableListenerFunction function)
1092 SwfdecMovieVariableListener *listener;
1093 GSList *iter;
1095 for (iter = movie->variable_listeners; iter != NULL; iter = iter->next) {
1096 listener = iter->data;
1098 if (listener->data == data && listener->name == name &&
1099 listener->function == function)
1100 return;
1103 listener = g_new0 (SwfdecMovieVariableListener, 1);
1104 listener->data = data;
1105 listener->name = name;
1106 listener->function = function;
1108 movie->variable_listeners = g_slist_prepend (movie->variable_listeners,
1109 listener);
1112 void
1113 swfdec_movie_remove_variable_listener (SwfdecMovie *movie,
1114 gpointer data, const char *name,
1115 const SwfdecMovieVariableListenerFunction function)
1117 GSList *iter;
1119 for (iter = movie->variable_listeners; iter != NULL; iter = iter->next) {
1120 SwfdecMovieVariableListener *listener = iter->data;
1122 if (listener->data == data && listener->name == name &&
1123 listener->function == function)
1124 break;
1126 if (iter == NULL)
1127 return;
1129 g_free (iter->data);
1130 movie->variable_listeners =
1131 g_slist_remove (movie->variable_listeners, iter->data);
1134 void
1135 swfdec_movie_call_variable_listeners (SwfdecMovie *movie, const char *name,
1136 const SwfdecAsValue *val)
1138 GSList *iter;
1140 for (iter = movie->variable_listeners; iter != NULL; iter = iter->next) {
1141 SwfdecMovieVariableListener *listener = iter->data;
1143 if (listener->name != name &&
1144 (swfdec_gc_object_get_context (movie)->version >= 7 ||
1145 !swfdec_str_case_equal (listener->name, name)))
1146 continue;
1148 listener->function (listener->data, name, val);
1152 typedef struct {
1153 SwfdecMovie * movie;
1154 int depth;
1155 } ClipEntry;
1157 static void
1158 swfdec_movie_do_render (SwfdecMovie *movie, cairo_t *cr,
1159 const SwfdecColorTransform *ctrans)
1161 static const cairo_matrix_t ident = { 1, 0, 0, 1, 0, 0};
1162 GList *g;
1163 GSList *walk;
1164 GSList *clips = NULL;
1165 ClipEntry *clip = NULL;
1167 if (movie->draws || movie->image) {
1168 SwfdecRect inval;
1170 cairo_clip_extents (cr, &inval.x0, &inval.y0, &inval.x1, &inval.y1);
1172 /* exeute the movie's drawing commands */
1173 for (walk = movie->draws; walk; walk = walk->next) {
1174 SwfdecDraw *draw = walk->data;
1176 if (!swfdec_rect_intersect (NULL, &draw->extents, &inval))
1177 continue;
1179 swfdec_draw_paint (draw, cr, ctrans);
1182 /* if the movie loaded an image, draw it here now */
1183 /* FIXME: add check to only draw if inside clip extents */
1184 if (movie->image) {
1185 SwfdecRenderer *renderer = swfdec_renderer_get (cr);
1186 cairo_surface_t *surface;
1187 cairo_pattern_t *pattern;
1188 double alpha = 1.0;
1190 if (swfdec_color_transform_is_mask (ctrans)) {
1191 surface = NULL;
1192 } else if (swfdec_color_transform_is_alpha (ctrans)) {
1193 surface = swfdec_image_create_surface (movie->image, renderer);
1194 alpha = ctrans->aa / 256.0;
1195 } else {
1196 surface = swfdec_image_create_surface_transformed (movie->image,
1197 renderer, ctrans);
1199 if (surface) {
1200 static const cairo_matrix_t matrix = { 1.0 / SWFDEC_TWIPS_SCALE_FACTOR, 0, 0, 1.0 / SWFDEC_TWIPS_SCALE_FACTOR, 0, 0 };
1201 pattern = cairo_pattern_create_for_surface (surface);
1202 SWFDEC_LOG ("rendering loaded image");
1203 cairo_pattern_set_matrix (pattern, &matrix);
1204 } else {
1205 pattern = cairo_pattern_create_rgb (1.0, 0.0, 0.0);
1207 cairo_set_source (cr, pattern);
1208 cairo_paint_with_alpha (cr, alpha);
1209 cairo_pattern_destroy (pattern);
1210 cairo_surface_destroy (surface);
1214 /* draw the children movies */
1215 for (g = movie->list; g; g = g_list_next (g)) {
1216 SwfdecMovie *child = g->data;
1218 while (clip && clip->depth < child->depth) {
1219 cairo_pattern_t *mask;
1220 SWFDEC_INFO ("unsetting clip depth %d for depth %d", clip->depth, child->depth);
1221 mask = swfdec_movie_mask (cr, clip->movie, &ident);
1222 cairo_pop_group_to_source (cr);
1223 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1224 cairo_mask (cr, mask);
1225 cairo_pattern_destroy (mask);
1226 g_slice_free (ClipEntry, clip);
1227 clips = g_slist_delete_link (clips, clips);
1228 clip = clips ? clips->data : NULL;
1231 if (child->clip_depth) {
1232 clip = g_slice_new (ClipEntry);
1233 clips = g_slist_prepend (clips, clip);
1234 clip->movie = child;
1235 clip->depth = child->clip_depth;
1236 SWFDEC_INFO ("clipping up to depth %d by using %s with depth %d", child->clip_depth,
1237 child->name, child->depth);
1238 cairo_push_group (cr);
1239 continue;
1242 SWFDEC_LOG ("rendering %p with depth %d", child, child->depth);
1243 if (child->visible)
1244 swfdec_movie_render (child, cr, ctrans);
1246 while (clip) {
1247 cairo_pattern_t *mask;
1248 SWFDEC_INFO ("unsetting clip depth %d", clip->depth);
1249 mask = swfdec_movie_mask (cr, clip->movie, &ident);
1250 cairo_pop_group_to_source (cr);
1251 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1252 cairo_mask (cr, mask);
1253 cairo_pattern_destroy (mask);
1254 g_slice_free (ClipEntry, clip);
1255 clips = g_slist_delete_link (clips, clips);
1256 clip = clips ? clips->data : NULL;
1258 g_assert (clips == NULL);
1261 static void
1262 swfdec_movie_do_invalidate (SwfdecMovie *movie, const cairo_matrix_t *matrix, gboolean last)
1264 GList *walk;
1265 SwfdecRect rect;
1267 if (movie->image) {
1268 rect.x0 = rect.y0 = 0;
1269 rect.x1 = movie->image->width * SWFDEC_TWIPS_SCALE_FACTOR;
1270 rect.y1 = movie->image->height * SWFDEC_TWIPS_SCALE_FACTOR;
1271 } else {
1272 swfdec_rect_init_empty (&rect);
1274 swfdec_rect_union (&rect, &rect, &movie->draw_extents);
1275 swfdec_rect_transform (&rect, &rect, matrix);
1276 swfdec_player_invalidate (SWFDEC_PLAYER (swfdec_gc_object_get_context (movie)),
1277 movie, &rect);
1279 for (walk = movie->list; walk; walk = walk->next) {
1280 swfdec_movie_invalidate (walk->data, matrix, last);
1284 static GObject *
1285 swfdec_movie_constructor (GType type, guint n_construct_properties,
1286 GObjectConstructParam *construct_properties)
1288 SwfdecPlayerPrivate *priv;
1289 SwfdecAsContext *cx;
1290 SwfdecAsObject *o;
1291 SwfdecMovie *movie;
1292 GObject *object;
1293 const char *name;
1295 object = G_OBJECT_CLASS (swfdec_movie_parent_class)->constructor (type,
1296 n_construct_properties, construct_properties);
1297 movie = SWFDEC_MOVIE (object);
1299 cx = swfdec_gc_object_get_context (movie);
1300 priv = SWFDEC_PLAYER (cx)->priv;
1301 /* the movie is created invalid */
1302 priv->invalid_pending = g_slist_prepend (priv->invalid_pending, object);
1304 if (movie->name == SWFDEC_AS_STR_EMPTY &&
1305 (swfdec_movie_is_scriptable (movie) || SWFDEC_IS_ACTOR (movie))) {
1306 name = swfdec_as_context_give_string (cx,
1307 g_strdup_printf ("instance%u", ++priv->unnamed_count));
1308 } else {
1309 name = movie->name;
1311 if (name != SWFDEC_AS_STR_EMPTY)
1312 movie->as_value = swfdec_as_movie_value_new (movie, name);
1314 /* make the resource ours if it doesn't belong to anyone yet */
1315 if (SWFDEC_AS_VALUE_IS_UNDEFINED (movie->resource->movie)) {
1316 g_assert (SWFDEC_IS_SPRITE_MOVIE (movie));
1317 SWFDEC_AS_VALUE_SET_MOVIE (&movie->resource->movie, movie);
1320 /* create AsObject */
1321 o = swfdec_as_object_new_empty (cx);
1322 o->movie = TRUE;
1323 swfdec_as_object_set_relay (o, SWFDEC_AS_RELAY (movie));
1325 /* set $version variable */
1326 if (movie->parent == NULL) {
1327 SwfdecAsValue val;
1328 SWFDEC_AS_VALUE_SET_STRING (&val, swfdec_as_context_get_string (cx, priv->system->version));
1329 swfdec_as_object_set_variable (o, SWFDEC_AS_STR__version, &val);
1331 return object;
1334 static void
1335 swfdec_movie_class_init (SwfdecMovieClass * movie_class)
1337 GObjectClass *object_class = G_OBJECT_CLASS (movie_class);
1338 SwfdecGcObjectClass *gc_class = SWFDEC_GC_OBJECT_CLASS (movie_class);
1340 object_class->constructor = swfdec_movie_constructor;
1341 object_class->dispose = swfdec_movie_dispose;
1342 object_class->get_property = swfdec_movie_get_property;
1343 object_class->set_property = swfdec_movie_set_property;
1345 gc_class->mark = swfdec_movie_mark;
1347 signals[MATRIX_CHANGED] = g_signal_new ("matrix-changed", G_TYPE_FROM_CLASS (movie_class),
1348 G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID,
1349 G_TYPE_NONE, 0);
1351 g_object_class_install_property (object_class, PROP_DEPTH,
1352 g_param_spec_int ("depth", "depth", "z order inside the parent",
1353 G_MININT, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1354 g_object_class_install_property (object_class, PROP_GRAPHIC,
1355 g_param_spec_object ("graphic", "graphic", "graphic represented by this movie",
1356 SWFDEC_TYPE_GRAPHIC, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1357 g_object_class_install_property (object_class, PROP_NAME,
1358 g_param_spec_string ("name", "name", "the name given to this movie (can be empty)",
1359 NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1360 g_object_class_install_property (object_class, PROP_PARENT,
1361 g_param_spec_object ("parent", "parent", "parent movie containing this movie",
1362 SWFDEC_TYPE_MOVIE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1363 g_object_class_install_property (object_class, PROP_RESOURCE,
1364 g_param_spec_object ("resource", "resource", "the resource that spawned this movie",
1365 SWFDEC_TYPE_RESOURCE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1367 movie_class->render = swfdec_movie_do_render;
1368 movie_class->invalidate = swfdec_movie_do_invalidate;
1369 movie_class->contains = swfdec_movie_do_contains;
1370 movie_class->property_get = swfdec_movie_property_do_get;
1371 movie_class->property_set = swfdec_movie_property_do_set;
1374 void
1375 swfdec_movie_initialize (SwfdecMovie *movie)
1377 SwfdecMovieClass *klass;
1379 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
1381 klass = SWFDEC_MOVIE_GET_CLASS (movie);
1382 if (klass->init_movie)
1383 klass->init_movie (movie);
1386 void
1387 swfdec_movie_set_depth (SwfdecMovie *movie, int depth)
1389 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
1391 if (movie->depth == depth)
1392 return;
1394 swfdec_movie_invalidate_last (movie);
1395 movie->depth = depth;
1396 if (movie->parent) {
1397 movie->parent->list = g_list_sort (movie->parent->list, swfdec_movie_compare_depths);
1398 } else {
1399 SwfdecPlayerPrivate *player = SWFDEC_PLAYER (swfdec_gc_object_get_context (movie))->priv;
1400 player->roots = g_list_sort (player->roots, swfdec_movie_compare_depths);
1402 g_object_notify (G_OBJECT (movie), "depth");
1406 * swfdec_movie_new:
1407 * @player: a #SwfdecPlayer
1408 * @depth: depth of movie
1409 * @parent: the parent movie or %NULL to make this a root movie
1410 * @resource: the resource that is responsible for this movie
1411 * @graphic: the graphic that is displayed by this movie or %NULL to create an
1412 * empty movieclip
1413 * @name: a garbage-collected string to be used as the name for this movie or
1414 * %NULL for a default one.
1416 * Creates a new movie #SwfdecMovie for the given properties. No movie may exist
1417 * at the given @depth. The actual type of
1418 * this movie depends on the @graphic parameter. The movie will be initialized
1419 * with default properties. No script execution will be scheduled. After all
1420 * properties are set, the new-movie signal will be emitted if @player is a
1421 * debugger.
1423 * Returns: a new #SwfdecMovie
1425 SwfdecMovie *
1426 swfdec_movie_new (SwfdecPlayer *player, int depth, SwfdecMovie *parent, SwfdecResource *resource,
1427 SwfdecGraphic *graphic, const char *name)
1429 SwfdecMovie *movie;
1430 GType type;
1432 g_return_val_if_fail (SWFDEC_IS_PLAYER (player), NULL);
1433 g_return_val_if_fail (parent == NULL || SWFDEC_IS_MOVIE (parent), NULL);
1434 g_return_val_if_fail (SWFDEC_IS_RESOURCE (resource), NULL);
1435 g_return_val_if_fail (graphic == NULL || SWFDEC_IS_GRAPHIC (graphic), NULL);
1437 /* create the right movie */
1438 if (graphic == NULL) {
1439 type = SWFDEC_TYPE_SPRITE_MOVIE;
1440 } else {
1441 SwfdecGraphicClass *klass = SWFDEC_GRAPHIC_GET_CLASS (graphic);
1442 g_return_val_if_fail (g_type_is_a (klass->movie_type, SWFDEC_TYPE_MOVIE), NULL);
1443 type = klass->movie_type;
1445 movie = g_object_new (type, "context", player, "depth", depth,
1446 "parent", parent, "name", name, "resource", resource,
1447 "graphic", graphic, NULL);
1449 return movie;
1452 /* FIXME: since this is only used in PlaceObject, wouldn't it be easier to just have
1453 * swfdec_movie_update_static_properties (movie); that's notified when any of these change
1454 * and let PlaceObject modify the movie directly?
1456 void
1457 swfdec_movie_set_static_properties (SwfdecMovie *movie, const cairo_matrix_t *transform,
1458 const SwfdecColorTransform *ctrans, int ratio, int clip_depth, guint blend_mode,
1459 SwfdecEventList *events)
1461 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
1462 g_return_if_fail (clip_depth >= -16384 || clip_depth <= 0);
1463 g_return_if_fail (ratio >= -1);
1465 if (movie->modified) {
1466 SWFDEC_LOG ("%s has already been modified by scripts, ignoring updates", movie->name);
1467 return;
1469 if (transform) {
1470 swfdec_movie_begin_update_matrix (movie);
1471 movie->original_transform = *transform;
1472 movie->matrix.x0 = movie->original_transform.x0;
1473 movie->matrix.y0 = movie->original_transform.y0;
1474 movie->xscale = swfdec_matrix_get_xscale (&movie->original_transform);
1475 movie->yscale = swfdec_matrix_get_yscale (&movie->original_transform);
1476 movie->rotation = swfdec_matrix_get_rotation (&movie->original_transform);
1477 swfdec_movie_end_update_matrix (movie);
1479 if (ctrans) {
1480 swfdec_movie_invalidate_last (movie);
1481 movie->color_transform = *ctrans;
1483 if (ratio >= 0 && (guint) ratio != movie->original_ratio) {
1484 SwfdecMovieClass *klass;
1485 movie->original_ratio = ratio;
1486 klass = SWFDEC_MOVIE_GET_CLASS (movie);
1487 if (klass->set_ratio)
1488 klass->set_ratio (movie);
1490 if (clip_depth && clip_depth != movie->clip_depth) {
1491 movie->clip_depth = clip_depth;
1492 /* FIXME: is this correct? */
1493 swfdec_movie_invalidate_last (movie->parent ? movie->parent : movie);
1495 if (blend_mode != movie->blend_mode) {
1496 movie->blend_mode = blend_mode;
1497 swfdec_movie_invalidate_last (movie);
1499 if (events) {
1500 if (SWFDEC_IS_SPRITE_MOVIE (movie)) {
1501 SwfdecActor *actor = SWFDEC_ACTOR (movie);
1502 if (actor->events)
1503 swfdec_event_list_free (actor->events);
1504 actor->events = swfdec_event_list_copy (events);
1505 } else {
1506 SWFDEC_WARNING ("trying to set events on a %s, not allowed", G_OBJECT_TYPE_NAME (movie));
1512 * swfdec_movie_duplicate:
1513 * @movie: #SwfdecMovie to copy
1514 * @name: garbage-collected name for the new copy
1515 * @depth: depth to put this movie in
1517 * Creates a duplicate of @movie. The duplicate will not be initialized or
1518 * queued up for any events. You have to do this manually. In particular calling
1519 * swfdec_movie_initialize() on the returned movie must be done.
1520 * This function must be called from within a script.
1522 * Returns: a newly created movie or %NULL on error
1524 SwfdecMovie *
1525 swfdec_movie_duplicate (SwfdecMovie *movie, const char *name, int depth)
1527 SwfdecMovie *parent, *copy;
1528 SwfdecSandbox *sandbox;
1529 GSList *walk;
1531 g_return_val_if_fail (SWFDEC_IS_MOVIE (movie), NULL);
1532 g_return_val_if_fail (name != NULL, NULL);
1534 parent = movie->parent;
1535 if (movie->parent == NULL) {
1536 SWFDEC_FIXME ("don't know how to duplicate root movies");
1537 return NULL;
1539 copy = swfdec_movie_find (movie->parent, depth);
1540 if (copy) {
1541 SWFDEC_LOG ("depth %d already occupied while duplicating, removing old movie", depth);
1542 swfdec_movie_remove (copy);
1544 copy = swfdec_movie_new (SWFDEC_PLAYER (swfdec_gc_object_get_context (movie)), depth,
1545 parent, movie->resource, movie->graphic, name);
1546 /* copy properties */
1547 swfdec_movie_set_static_properties (copy, &movie->original_transform,
1548 &movie->color_transform, movie->original_ratio, movie->clip_depth,
1549 movie->blend_mode, SWFDEC_IS_ACTOR (movie) ? SWFDEC_ACTOR (movie)->events : NULL);
1550 /* Copy drawing state.
1551 * We can keep refs to all finalized draw objects, but need to create copies
1552 * of the still active ones as their path can still change */
1553 copy->draws = g_slist_copy (movie->draws);
1554 g_slist_foreach (copy->draws, (GFunc) g_object_ref, NULL);
1555 copy->draw_extents = movie->draw_extents;
1556 for (walk = copy->draws; walk; walk = walk->next) {
1557 if (walk->data == movie->draw_line) {
1558 copy->draw_line = swfdec_draw_copy (walk->data);
1559 g_object_unref (walk->data);
1560 walk->data = copy->draw_line;
1561 } else if (walk->data == movie->draw_fill) {
1562 copy->draw_fill = swfdec_draw_copy (walk->data);
1563 g_object_unref (walk->data);
1564 walk->data = copy->draw_fill;
1567 copy->draw_x = movie->draw_x;
1568 copy->draw_y = movie->draw_y;
1569 g_assert (copy->cache_state >= SWFDEC_MOVIE_INVALID_EXTENTS);
1571 sandbox = swfdec_sandbox_get (SWFDEC_PLAYER (swfdec_gc_object_get_context (movie)));
1572 swfdec_sandbox_unuse (sandbox);
1573 if (SWFDEC_IS_SPRITE_MOVIE (copy)) {
1574 SwfdecActor *actor = SWFDEC_ACTOR (copy);
1575 swfdec_actor_queue_script (actor, SWFDEC_EVENT_INITIALIZE);
1576 swfdec_actor_queue_script (actor, SWFDEC_EVENT_LOAD);
1577 swfdec_actor_execute (actor, SWFDEC_EVENT_CONSTRUCT, 0);
1579 swfdec_movie_initialize (copy);
1580 swfdec_sandbox_use (sandbox);
1581 return copy;
1584 char *
1585 swfdec_movie_get_path (SwfdecMovie *movie, gboolean dot)
1587 GString *s;
1589 g_return_val_if_fail (SWFDEC_IS_MOVIE (movie), NULL);
1591 s = g_string_new ("");
1592 do {
1593 if (movie->parent) {
1594 if (movie->name != SWFDEC_AS_STR_EMPTY) {
1595 g_string_prepend (s, movie->name);
1596 } else if (movie->as_value) {
1597 g_string_prepend (s, movie->as_value->names[movie->as_value->n_names - 1]);
1598 } else {
1599 g_assert_not_reached ();
1601 g_string_prepend_c (s, (dot ? '.' : '/'));
1602 } else {
1603 char *ret;
1604 if (dot) {
1605 ret = g_strdup_printf ("_level%u%s", movie->depth + 16384, s->str);
1606 g_string_free (s, TRUE);
1607 } else {
1608 if (s->str[0] != '/')
1609 g_string_prepend_c (s, '/');
1610 ret = g_string_free (s, FALSE);
1612 return ret;
1614 movie = movie->parent;
1615 } while (TRUE);
1617 g_assert_not_reached ();
1619 return NULL;
1623 swfdec_movie_compare_depths (gconstpointer a, gconstpointer b)
1625 if (SWFDEC_MOVIE (a)->depth < SWFDEC_MOVIE (b)->depth)
1626 return -1;
1627 if (SWFDEC_MOVIE (a)->depth > SWFDEC_MOVIE (b)->depth)
1628 return 1;
1629 return 0;
1633 * swfdec_depth_classify:
1634 * @depth: the depth to classify
1636 * Classifies a depth. This classification is mostly used when deciding if
1637 * certain operations are valid in ActionScript.
1639 * Returns: the classification of the depth.
1641 SwfdecDepthClass
1642 swfdec_depth_classify (int depth)
1644 if (depth < -16384)
1645 return SWFDEC_DEPTH_CLASS_EMPTY;
1646 if (depth < 0)
1647 return SWFDEC_DEPTH_CLASS_TIMELINE;
1648 if (depth < 1048576)
1649 return SWFDEC_DEPTH_CLASS_DYNAMIC;
1650 if (depth < 2130690046)
1651 return SWFDEC_DEPTH_CLASS_RESERVED;
1652 return SWFDEC_DEPTH_CLASS_EMPTY;
1656 * swfdec_movie_get_own_resource:
1657 * @movie: movie to query
1659 * Queries the movie for his own resource. A movie only has its own resource if
1660 * it contains data loaded with the loadMovie() function, or if it is the root
1661 * movie.
1663 * Returns: The own resource of @movie or %NULL
1665 SwfdecResource *
1666 swfdec_movie_get_own_resource (SwfdecMovie *movie)
1668 g_return_val_if_fail (SWFDEC_IS_MOVIE (movie), NULL);
1670 if (!SWFDEC_IS_SPRITE_MOVIE (movie))
1671 return NULL;
1673 if (SWFDEC_AS_VALUE_GET_MOVIE (movie->resource->movie) != movie)
1674 return NULL;
1676 return movie->resource;
1679 void
1680 swfdec_movie_property_set (SwfdecMovie *movie, guint id, SwfdecAsValue val)
1682 SwfdecMovieClass *klass;
1684 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
1686 klass = SWFDEC_MOVIE_GET_CLASS (movie);
1687 klass->property_set (movie, id, val);
1690 SwfdecAsValue
1691 swfdec_movie_property_get (SwfdecMovie *movie, guint id)
1693 SwfdecMovieClass *klass;
1695 g_return_val_if_fail (SWFDEC_IS_MOVIE (movie), SWFDEC_AS_VALUE_UNDEFINED);
1697 klass = SWFDEC_MOVIE_GET_CLASS (movie);
1698 return klass->property_get (movie, id);