strings.h include no longer needed
[swfdec.git] / swfdec / swfdec_movie.c
blobadbe5a7432f1ec36070ad0a40964f09d30c9573b
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_graphic.h"
38 #include "swfdec_image.h"
39 #include "swfdec_loader_internal.h"
40 #include "swfdec_player_internal.h"
41 #include "swfdec_sprite.h"
42 #include "swfdec_sprite_movie.h"
43 #include "swfdec_renderer_internal.h"
44 #include "swfdec_resource.h"
45 #include "swfdec_system.h"
46 #include "swfdec_text_field_movie.h"
47 #include "swfdec_utils.h"
48 #include "swfdec_video_movie.h"
50 /*** MOVIE ***/
52 enum {
53 PROP_0,
54 PROP_DEPTH,
55 PROP_GRAPHIC,
56 PROP_NAME,
57 PROP_PARENT,
58 PROP_RESOURCE
61 enum {
62 MATRIX_CHANGED,
63 LAST_SIGNAL
66 static guint signals[LAST_SIGNAL] = { 0, };
68 G_DEFINE_ABSTRACT_TYPE (SwfdecMovie, swfdec_movie, SWFDEC_TYPE_AS_OBJECT)
70 static void
71 swfdec_movie_init (SwfdecMovie * movie)
73 movie->blend_mode = 1;
75 movie->xscale = 100;
76 movie->yscale = 100;
77 cairo_matrix_init_identity (&movie->original_transform);
78 cairo_matrix_init_identity (&movie->matrix);
79 cairo_matrix_init_identity (&movie->inverse_matrix);
81 swfdec_color_transform_init_identity (&movie->color_transform);
83 movie->visible = TRUE;
84 movie->cache_state = SWFDEC_MOVIE_INVALID_EXTENTS;
85 movie->invalidate_last = TRUE;
86 movie->invalidate_next = TRUE;
88 swfdec_rect_init_empty (&movie->extents);
91 /**
92 * swfdec_movie_invalidate:
93 * @movie: a #SwfdecMovie
94 * @parent_to_global: This is the matrix from the parent to the global matrix.
95 * It is only used for caching reasons
96 * @new_contents: %TRUE if this is the invalidation of the new contents, %FALSE
97 * if the old contents are invalidated.
99 * Performs an instant invalidation on @movie. You most likely don't want to
100 * call this function directly, but use swfdec_movie_invalidate_last() or
101 * swfdec_movie_invalidate_next() instead.
103 void
104 swfdec_movie_invalidate (SwfdecMovie *movie, const cairo_matrix_t *parent_to_global,
105 gboolean new_contents)
107 SwfdecMovieClass *klass;
108 cairo_matrix_t matrix;
110 if (new_contents) {
111 movie->invalidate_next = FALSE;
112 } else {
113 SwfdecPlayer *player;
114 if (movie->invalidate_last)
115 return;
116 movie->invalidate_last = TRUE;
117 player = SWFDEC_PLAYER (swfdec_gc_object_get_context (movie));
118 player->priv->invalid_pending = g_slist_prepend (player->priv->invalid_pending, movie);
120 g_assert (movie->cache_state <= SWFDEC_MOVIE_INVALID_CHILDREN);
121 SWFDEC_LOG ("invalidating %s %s at %s", G_OBJECT_TYPE_NAME (movie),
122 movie->name, new_contents ? "end" : "start");
123 cairo_matrix_multiply (&matrix, &movie->matrix, parent_to_global);
124 klass = SWFDEC_MOVIE_GET_CLASS (movie);
125 klass->invalidate (movie, &matrix, new_contents);
129 * swfdec_movie_invalidate_last:
130 * @movie: a #SwfdecMovie
132 * Ensures the movie's contents are invalidated. This function must be called
133 * before changing the movie or the output will have artifacts.
135 void
136 swfdec_movie_invalidate_last (SwfdecMovie *movie)
138 cairo_matrix_t matrix;
140 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
142 if (movie->invalidate_last)
143 return;
145 if (movie->parent)
146 swfdec_movie_local_to_global_matrix (movie->parent, &matrix);
147 else
148 cairo_matrix_init_identity (&matrix);
149 swfdec_movie_invalidate (movie, &matrix, FALSE);
150 g_assert (movie->invalidate_last);
154 * swfdec_movie_invalidate_next:
155 * @movie: a #SwfdecMovie
157 * Ensures the movie will be invalidated after script execution is done. So
158 * after calling this function you can modify position and contents of the
159 * @movie in any way.
161 void
162 swfdec_movie_invalidate_next (SwfdecMovie *movie)
164 SwfdecPlayer *player;
166 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
168 swfdec_movie_invalidate_last (movie);
169 movie->invalidate_next = TRUE;
170 player = SWFDEC_PLAYER (swfdec_gc_object_get_context (movie));
171 if (movie == SWFDEC_MOVIE (player->priv->focus))
172 swfdec_player_invalidate_focusrect (player);
176 * swfdec_movie_queue_update:
177 * @movie: a #SwfdecMovie
178 * @state: how much needs to be updated
180 * Queues an update of all cached values inside @movie and invalidates it.
182 void
183 swfdec_movie_queue_update (SwfdecMovie *movie, SwfdecMovieCacheState state)
185 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
187 while (movie && movie->cache_state < state) {
188 movie->cache_state = state;
189 movie = movie->parent;
190 state = SWFDEC_MOVIE_INVALID_CHILDREN;
194 static void
195 swfdec_movie_update_extents (SwfdecMovie *movie)
197 SwfdecMovieClass *klass;
198 GList *walk;
199 SwfdecRect *rect = &movie->original_extents;
200 SwfdecRect *extents = &movie->extents;
202 *rect = movie->draw_extents;
203 if (movie->image) {
204 SwfdecRect image_extents = { 0, 0,
205 movie->image->width * SWFDEC_TWIPS_SCALE_FACTOR,
206 movie->image->height * SWFDEC_TWIPS_SCALE_FACTOR };
207 swfdec_rect_union (rect, rect, &image_extents);
209 for (walk = movie->list; walk; walk = walk->next) {
210 swfdec_rect_union (rect, rect, &SWFDEC_MOVIE (walk->data)->extents);
212 klass = SWFDEC_MOVIE_GET_CLASS (movie);
213 if (klass->update_extents)
214 klass->update_extents (movie, rect);
215 if (swfdec_rect_is_empty (rect)) {
216 *extents = *rect;
217 return;
219 swfdec_rect_transform (extents, rect, &movie->matrix);
220 if (movie->parent && movie->parent->cache_state < SWFDEC_MOVIE_INVALID_EXTENTS) {
221 /* no need to invalidate here */
222 movie->parent->cache_state = SWFDEC_MOVIE_INVALID_EXTENTS;
226 void
227 swfdec_movie_begin_update_matrix (SwfdecMovie *movie)
229 swfdec_movie_invalidate_next (movie);
232 void
233 swfdec_movie_end_update_matrix (SwfdecMovie *movie)
235 double d, e;
237 swfdec_movie_queue_update (movie, SWFDEC_MOVIE_INVALID_EXTENTS);
239 /* we operate on x0 and y0 when setting movie._x and movie._y */
240 if (movie->modified) {
241 movie->matrix.xx = movie->original_transform.xx;
242 movie->matrix.yx = movie->original_transform.yx;
243 movie->matrix.xy = movie->original_transform.xy;
244 movie->matrix.yy = movie->original_transform.yy;
245 } else {
246 movie->matrix = movie->original_transform;
249 d = movie->xscale / swfdec_matrix_get_xscale (&movie->original_transform);
250 e = movie->yscale / swfdec_matrix_get_yscale (&movie->original_transform);
251 cairo_matrix_scale (&movie->matrix, d, e);
252 if (isfinite (movie->rotation)) {
253 d = movie->rotation - swfdec_matrix_get_rotation (&movie->original_transform);
254 cairo_matrix_rotate (&movie->matrix, d * G_PI / 180);
256 swfdec_matrix_ensure_invertible (&movie->matrix, &movie->inverse_matrix);
258 g_signal_emit (movie, signals[MATRIX_CHANGED], 0);
261 static void
262 swfdec_movie_do_update (SwfdecMovie *movie)
264 GList *walk;
266 for (walk = movie->list; walk; walk = walk->next) {
267 SwfdecMovie *child = walk->data;
269 if (child->cache_state != SWFDEC_MOVIE_UP_TO_DATE)
270 swfdec_movie_do_update (child);
273 switch (movie->cache_state) {
274 case SWFDEC_MOVIE_INVALID_EXTENTS:
275 swfdec_movie_update_extents (movie);
276 /* fall through */
277 case SWFDEC_MOVIE_INVALID_CHILDREN:
278 break;
279 case SWFDEC_MOVIE_UP_TO_DATE:
280 default:
281 g_assert_not_reached ();
283 movie->cache_state = SWFDEC_MOVIE_UP_TO_DATE;
287 * swfdec_movie_update:
288 * @movie: a #SwfdecMovie
290 * Brings the cached values of @movie up-to-date if they are not. This includes
291 * transformation matrices and extents. It needs to be called before accessing
292 * the relevant values.
294 void
295 swfdec_movie_update (SwfdecMovie *movie)
297 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
299 if (movie->cache_state == SWFDEC_MOVIE_UP_TO_DATE)
300 return;
302 if (movie->parent && movie->parent->cache_state != SWFDEC_MOVIE_UP_TO_DATE) {
303 swfdec_movie_update (movie->parent);
304 } else {
305 swfdec_movie_do_update (movie);
309 SwfdecMovie *
310 swfdec_movie_find (SwfdecMovie *movie, int depth)
312 GList *walk;
314 g_return_val_if_fail (SWFDEC_IS_MOVIE (movie), NULL);
316 for (walk = movie->list; walk; walk = walk->next) {
317 SwfdecMovie *cur= walk->data;
319 if (cur->depth < depth)
320 continue;
321 if (cur->depth == depth)
322 return cur;
323 break;
325 return NULL;
328 static void
329 swfdec_movie_unset_actor (SwfdecPlayer *player, SwfdecActor *actor)
331 SwfdecPlayerPrivate *priv = player->priv;
333 if (priv->mouse_below == actor)
334 priv->mouse_below = NULL;
335 if (priv->mouse_grab == actor)
336 priv->mouse_grab = NULL;
337 if (priv->mouse_drag == actor)
338 priv->mouse_drag = NULL;
340 if (priv->focus_previous == actor)
341 priv->focus_previous = NULL;
342 if (priv->focus == actor) {
343 priv->focus = NULL;
344 swfdec_player_invalidate_focusrect (player);
348 static gboolean
349 swfdec_movie_do_remove (SwfdecMovie *movie, gboolean destroy)
351 SwfdecPlayer *player;
353 SWFDEC_LOG ("removing %s %s", G_OBJECT_TYPE_NAME (movie), movie->name);
355 player = SWFDEC_PLAYER (swfdec_gc_object_get_context (movie));
356 while (movie->list) {
357 GList *walk = movie->list;
358 while (walk && SWFDEC_MOVIE (walk->data)->state >= SWFDEC_MOVIE_STATE_REMOVED)
359 walk = walk->next;
360 if (walk == NULL)
361 break;
362 destroy &= swfdec_movie_do_remove (walk->data, destroy);
364 /* FIXME: all of this here or in destroy callback? */
365 swfdec_movie_invalidate_last (movie);
366 movie->state = SWFDEC_MOVIE_STATE_REMOVED;
368 if (SWFDEC_IS_ACTOR (movie)) {
369 SwfdecActor *actor = SWFDEC_ACTOR (movie);
370 swfdec_movie_unset_actor (player, actor);
371 if ((actor->events &&
372 swfdec_event_list_has_conditions (actor->events, SWFDEC_AS_OBJECT (movie), SWFDEC_EVENT_UNLOAD, 0)) ||
373 swfdec_as_object_has_variable (SWFDEC_AS_OBJECT (movie), SWFDEC_AS_STR_onUnload)) {
374 swfdec_actor_queue_script (actor, SWFDEC_EVENT_UNLOAD);
375 destroy = FALSE;
378 if (destroy)
379 swfdec_movie_destroy (movie);
380 return destroy;
384 * swfdec_movie_remove:
385 * @movie: #SwfdecMovie to remove
387 * Removes this movie from its parent. In contrast to swfdec_movie_destroy (),
388 * it might still be possible to reference it from Actionscript, if the movie
389 * queues onUnload event handlers.
391 void
392 swfdec_movie_remove (SwfdecMovie *movie)
394 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
396 if (movie->state > SWFDEC_MOVIE_STATE_RUNNING)
397 return;
398 if (swfdec_movie_do_remove (movie, TRUE))
399 return;
401 swfdec_movie_set_depth (movie, -32769 - movie->depth); /* don't ask me why... */
405 * swfdec_movie_destroy:
406 * @movie: #SwfdecMovie to destroy
408 * Removes this movie from its parent. After this it will no longer be present,
409 * neither visually nor via ActionScript. This function will not cause an
410 * unload event. Compare with swfdec_movie_remove ().
412 void
413 swfdec_movie_destroy (SwfdecMovie *movie)
415 SwfdecMovieClass *klass = SWFDEC_MOVIE_GET_CLASS (movie);
416 SwfdecPlayer *player = SWFDEC_PLAYER (swfdec_gc_object_get_context (movie));
418 g_assert (movie->state < SWFDEC_MOVIE_STATE_DESTROYED);
419 SWFDEC_LOG ("destroying movie %s", movie->name);
420 while (movie->list) {
421 swfdec_movie_destroy (movie->list->data);
423 if (movie->parent) {
424 movie->parent->list = g_list_remove (movie->parent->list, movie);
425 } else {
426 player->priv->roots = g_list_remove (player->priv->roots, movie);
428 /* unset masks */
429 if (movie->masked_by)
430 movie->masked_by->mask_of = NULL;
431 if (movie->mask_of)
432 movie->mask_of->masked_by = NULL;
433 movie->masked_by = NULL;
434 movie->mask_of = NULL;
435 /* FIXME: figure out how to handle destruction pre-init/construct.
436 * This is just a stop-gap measure to avoid dead movies in those queues */
437 if (SWFDEC_IS_ACTOR (movie))
438 swfdec_player_remove_all_actions (player, SWFDEC_ACTOR (movie));
439 if (klass->finish_movie)
440 klass->finish_movie (movie);
441 player->priv->actors = g_list_remove (player->priv->actors, movie);
442 if (movie->invalidate_last)
443 player->priv->invalid_pending = g_slist_remove (player->priv->invalid_pending, movie);
444 movie->state = SWFDEC_MOVIE_STATE_DESTROYED;
445 /* unset prototype here, so we don't work in AS anymore */
446 SWFDEC_AS_OBJECT (movie)->prototype = NULL;
447 g_object_unref (movie);
451 * swfdec_movie_resolve:
452 * @movie: movie to resolve
454 * Resolves a movie clip to its real version. Since movie clips can be
455 * explicitly destroyed, they have problems with references to them. In the
456 * case of destruction, these references will remain as "dangling pointers".
457 * However, if a movie with the same name is later created again, the reference
458 * will point to that movie. This function does this resolving.
460 * Returns: The movie clip @movie resolves to or %NULL if none.
462 SwfdecMovie *
463 swfdec_movie_resolve (SwfdecMovie *movie)
465 SwfdecMovie *parent;
467 g_return_val_if_fail (SWFDEC_IS_MOVIE (movie), NULL);
469 if (movie->state != SWFDEC_MOVIE_STATE_DESTROYED)
470 return movie;
471 if (movie->parent == NULL) {
472 SWFDEC_FIXME ("figure out how to resolve root movies");
473 return NULL;
475 parent = swfdec_movie_resolve (movie->parent);
476 if (parent == NULL)
477 return NULL;
478 /* FIXME: include unnamed ones? */
479 return swfdec_movie_get_by_name (parent, movie->original_name, FALSE);
482 guint
483 swfdec_movie_get_version (SwfdecMovie *movie)
485 return movie->resource->version;
488 void
489 swfdec_movie_local_to_global (SwfdecMovie *movie, double *x, double *y)
491 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
492 g_return_if_fail (x != NULL);
493 g_return_if_fail (y != NULL);
495 do {
496 cairo_matrix_transform_point (&movie->matrix, x, y);
497 } while ((movie = movie->parent));
500 void
501 swfdec_movie_rect_local_to_global (SwfdecMovie *movie, SwfdecRect *rect)
503 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
504 g_return_if_fail (rect != NULL);
506 swfdec_movie_local_to_global (movie, &rect->x0, &rect->y0);
507 swfdec_movie_local_to_global (movie, &rect->x1, &rect->y1);
508 if (rect->x0 > rect->x1) {
509 double tmp = rect->x1;
510 rect->x1 = rect->x0;
511 rect->x0 = tmp;
513 if (rect->y0 > rect->y1) {
514 double tmp = rect->y1;
515 rect->y1 = rect->y0;
516 rect->y0 = tmp;
520 void
521 swfdec_movie_global_to_local_matrix (SwfdecMovie *movie, cairo_matrix_t *matrix)
523 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
524 g_return_if_fail (matrix != NULL);
526 cairo_matrix_init_identity (matrix);
527 while (movie) {
528 cairo_matrix_multiply (matrix, &movie->inverse_matrix, matrix);
529 movie = movie->parent;
533 void
534 swfdec_movie_local_to_global_matrix (SwfdecMovie *movie, cairo_matrix_t *matrix)
536 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
537 g_return_if_fail (matrix != NULL);
539 cairo_matrix_init_identity (matrix);
540 while (movie) {
541 cairo_matrix_multiply (matrix, matrix, &movie->matrix);
542 movie = movie->parent;
546 void
547 swfdec_movie_global_to_local (SwfdecMovie *movie, double *x, double *y)
549 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
550 g_return_if_fail (x != NULL);
551 g_return_if_fail (y != NULL);
553 if (movie->parent) {
554 swfdec_movie_global_to_local (movie->parent, x, y);
556 cairo_matrix_transform_point (&movie->inverse_matrix, x, y);
559 void
560 swfdec_movie_rect_global_to_local (SwfdecMovie *movie, SwfdecRect *rect)
562 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
563 g_return_if_fail (rect != NULL);
565 swfdec_movie_global_to_local (movie, &rect->x0, &rect->y0);
566 swfdec_movie_global_to_local (movie, &rect->x1, &rect->y1);
567 if (rect->x0 > rect->x1) {
568 double tmp = rect->x1;
569 rect->x1 = rect->x0;
570 rect->x0 = tmp;
572 if (rect->y0 > rect->y1) {
573 double tmp = rect->y1;
574 rect->y1 = rect->y0;
575 rect->y0 = tmp;
580 * swfdec_movie_get_mouse:
581 * @movie: a #SwfdecMovie
582 * @x: pointer to hold result of X coordinate
583 * @y: pointer to hold result of y coordinate
585 * Gets the mouse coordinates in the coordinate space of @movie.
587 void
588 swfdec_movie_get_mouse (SwfdecMovie *movie, double *x, double *y)
590 SwfdecPlayer *player;
592 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
593 g_return_if_fail (x != NULL);
594 g_return_if_fail (y != NULL);
596 player = SWFDEC_PLAYER (swfdec_gc_object_get_context (movie));
597 *x = player->priv->mouse_x;
598 *y = player->priv->mouse_y;
599 swfdec_player_stage_to_global (player, x, y);
600 swfdec_movie_global_to_local (movie, x, y);
604 * swfdec_movie_get_movie_at:
605 * @movie: a #SwfdecMovie
606 * @x: x coordinate in parent's coordinate space
607 * @y: y coordinate in the parent's coordinate space
608 * @events: %TRUE to only prefer movies that receive events
610 * Gets the child at the given coordinates. The coordinates are in the
611 * coordinate system of @movie's parent (or the global coordinate system for
612 * root movies). The @events parameter determines if movies that don't receive
613 * events should be respected.
615 * Returns: the child of @movie at the given coordinates or %NULL if none
617 SwfdecMovie *
618 swfdec_movie_get_movie_at (SwfdecMovie *movie, double x, double y, gboolean events)
620 SwfdecMovie *ret;
621 SwfdecMovieClass *klass;
623 g_return_val_if_fail (SWFDEC_IS_MOVIE (movie), NULL);
625 SWFDEC_LOG ("%s %p getting mouse at: %g %g", G_OBJECT_TYPE_NAME (movie), movie, x, y);
626 if (movie->cache_state >= SWFDEC_MOVIE_INVALID_EXTENTS)
627 swfdec_movie_update (movie);
628 if (!swfdec_rect_contains (&movie->extents, x, y)) {
629 return NULL;
631 cairo_matrix_transform_point (&movie->inverse_matrix, &x, &y);
633 klass = SWFDEC_MOVIE_GET_CLASS (movie);
634 g_return_val_if_fail (klass->contains, NULL);
635 ret = klass->contains (movie, x, y, events);
637 return ret;
640 static SwfdecMovie *
641 swfdec_movie_do_contains (SwfdecMovie *movie, double x, double y, gboolean events)
643 GList *walk;
644 GSList *walk2;
645 SwfdecMovie *ret, *got;
647 ret = NULL;
648 for (walk = movie->list; walk; walk = walk->next) {
649 SwfdecMovie *child = walk->data;
651 if (!child->visible) {
652 SWFDEC_LOG ("%s %s (depth %d) is invisible, ignoring", G_OBJECT_TYPE_NAME (movie), movie->name, movie->depth);
653 continue;
655 got = swfdec_movie_get_movie_at (child, x, y, events);
656 if (got != NULL) {
657 if (events) {
658 /* set the return value to the topmost movie */
659 if (SWFDEC_IS_ACTOR (got) && swfdec_actor_get_mouse_events (SWFDEC_ACTOR (got))) {
660 ret = got;
661 } else if (ret == NULL) {
662 ret = movie;
664 } else {
665 /* if thie is not a clipped movie, we've found something */
666 if (child->clip_depth == 0)
667 return movie;
669 } else {
670 if (child->clip_depth) {
671 /* skip obscured movies */
672 SwfdecMovie *tmp = walk->next ? walk->next->data : NULL;
673 while (tmp && tmp->depth <= child->clip_depth) {
674 walk = walk->next;
675 tmp = walk->next ? walk->next->data : NULL;
680 if (ret)
681 return ret;
683 for (walk2 = movie->draws; walk2; walk2 = walk2->next) {
684 SwfdecDraw *draw = walk2->data;
686 if (swfdec_draw_contains (draw, x, y))
687 return movie;
690 return NULL;
693 static gboolean
694 swfdec_movie_needs_group (SwfdecMovie *movie)
696 return (movie->blend_mode > 1);
699 static cairo_operator_t
700 swfdec_movie_get_operator_for_blend_mode (guint blend_mode)
702 switch (blend_mode) {
703 case SWFDEC_BLEND_MODE_NORMAL:
704 SWFDEC_ERROR ("shouldn't need to get operator without blend mode?!");
705 case SWFDEC_BLEND_MODE_LAYER:
706 return CAIRO_OPERATOR_OVER;
707 case SWFDEC_BLEND_MODE_ADD:
708 return CAIRO_OPERATOR_ADD;
709 case SWFDEC_BLEND_MODE_ALPHA:
710 return CAIRO_OPERATOR_DEST_IN;
711 case SWFDEC_BLEND_MODE_ERASE:
712 return CAIRO_OPERATOR_DEST_OUT;
713 case SWFDEC_BLEND_MODE_MULTIPLY:
714 case SWFDEC_BLEND_MODE_SCREEN:
715 case SWFDEC_BLEND_MODE_LIGHTEN:
716 case SWFDEC_BLEND_MODE_DARKEN:
717 case SWFDEC_BLEND_MODE_DIFFERENCE:
718 case SWFDEC_BLEND_MODE_SUBTRACT:
719 case SWFDEC_BLEND_MODE_INVERT:
720 case SWFDEC_BLEND_MODE_OVERLAY:
721 case SWFDEC_BLEND_MODE_HARDLIGHT:
722 SWFDEC_FIXME ("blend mode %u unimplemented in cairo", blend_mode);
723 return CAIRO_OPERATOR_OVER;
724 default:
725 SWFDEC_WARNING ("invalid blend mode %u", blend_mode);
726 return CAIRO_OPERATOR_OVER;
731 * swfdec_movie_mask:
732 * @movie: The movie to act as the mask
733 * @cr: a cairo context which should be used for masking. The cairo context's
734 * matrix is assumed to be in the coordinate system of the movie's parent.
735 * @matrix: matrix to apply before rendering
737 * Creates a pattern suitable for masking. To do rendering using the returned
738 * mask, you want to use code like this:
739 * <informalexample><programlisting>
740 * mask = swfdec_movie_mask (cr, movie, matrix);
741 * cairo_push_group (cr);
742 * // do rendering here
743 * cairo_pop_group_to_source (cr);
744 * cairo_mask (cr, mask);
745 * cairo_pattern_destroy (mask);
746 * </programlisting></informalexample>
748 * Returns: A new cairo_patten_t to be used as the mask.
750 static cairo_pattern_t *
751 swfdec_movie_mask (cairo_t *cr, SwfdecMovie *movie,
752 const cairo_matrix_t *matrix)
754 SwfdecColorTransform black;
756 swfdec_color_transform_init_mask (&black);
757 cairo_push_group_with_content (cr, CAIRO_CONTENT_ALPHA);
758 cairo_transform (cr, matrix);
760 swfdec_movie_render (movie, cr, &black);
761 return cairo_pop_group (cr);
764 void
765 swfdec_movie_render (SwfdecMovie *movie, cairo_t *cr,
766 const SwfdecColorTransform *color_transform)
768 SwfdecMovieClass *klass;
769 SwfdecColorTransform trans;
770 gboolean group;
772 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
773 g_return_if_fail (cr != NULL);
774 if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) {
775 g_warning ("%s", cairo_status_to_string (cairo_status (cr)));
777 g_return_if_fail (color_transform != NULL);
779 if (movie->mask_of != NULL && !swfdec_color_transform_is_mask (color_transform)) {
780 SWFDEC_LOG ("not rendering %s %p, movie is a mask",
781 G_OBJECT_TYPE_NAME (movie), movie->name);
782 return;
785 if (movie->masked_by != NULL) {
786 cairo_push_group (cr);
788 group = swfdec_movie_needs_group (movie);
789 if (group) {
790 SWFDEC_DEBUG ("pushing group for blend mode %u", movie->blend_mode);
791 cairo_push_group (cr);
793 cairo_save (cr);
795 SWFDEC_LOG ("transforming movie, transform: %g %g %g %g %g %g",
796 movie->matrix.xx, movie->matrix.yy,
797 movie->matrix.xy, movie->matrix.yx,
798 movie->matrix.x0, movie->matrix.y0);
799 cairo_transform (cr, &movie->matrix);
800 swfdec_color_transform_chain (&trans, &movie->color_transform, color_transform);
802 klass = SWFDEC_MOVIE_GET_CLASS (movie);
803 g_return_if_fail (klass->render);
804 klass->render (movie, cr, &trans);
805 #if 0
806 /* code to draw a red rectangle around the area occupied by this movie clip */
808 double x = 1.0, y = 0.0;
809 cairo_transform (cr, &movie->inverse_transform);
810 cairo_user_to_device_distance (cr, &x, &y);
811 cairo_set_source_rgb (cr, 1.0, 0.0, 0.0);
812 cairo_set_line_width (cr, 1 / sqrt (x * x + y * y));
813 cairo_rectangle (cr, object->extents.x0 + 10, object->extents.y0 + 10,
814 object->extents.x1 - object->extents.x0 - 20,
815 object->extents.y1 - object->extents.y0 - 20);
816 cairo_stroke (cr);
818 #endif
819 if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) {
820 g_warning ("error rendering with cairo: %s", cairo_status_to_string (cairo_status (cr)));
822 cairo_restore (cr);
823 if (group) {
824 cairo_pattern_t *pattern;
826 pattern = cairo_pop_group (cr);
827 cairo_set_source (cr, pattern);
828 cairo_set_operator (cr, swfdec_movie_get_operator_for_blend_mode (movie->blend_mode));
829 cairo_paint (cr);
830 cairo_pattern_destroy (pattern);
832 if (movie->masked_by) {
833 cairo_pattern_t *mask;
834 cairo_matrix_t mat;
835 if (movie->parent)
836 swfdec_movie_global_to_local_matrix (movie->parent, &mat);
837 else
838 cairo_matrix_init_identity (&mat);
839 if (movie->masked_by->parent) {
840 cairo_matrix_t mat2;
841 swfdec_movie_local_to_global_matrix (movie->masked_by->parent, &mat2);
842 cairo_matrix_multiply (&mat, &mat, &mat2);
844 mask = swfdec_movie_mask (cr, movie->masked_by, &mat);
845 cairo_pop_group_to_source (cr);
846 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
847 cairo_mask (cr, mask);
848 cairo_pattern_destroy (mask);
852 static void
853 swfdec_movie_get_property (GObject *object, guint param_id, GValue *value,
854 GParamSpec * pspec)
856 SwfdecMovie *movie = SWFDEC_MOVIE (object);
858 switch (param_id) {
859 case PROP_DEPTH:
860 g_value_set_int (value, movie->depth);
861 break;
862 case PROP_PARENT:
863 g_value_set_object (value, movie->parent);
864 break;
865 case PROP_RESOURCE:
866 g_value_set_object (value, movie->resource);
867 break;
868 default:
869 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
870 break;
874 static void
875 swfdec_movie_set_property (GObject *object, guint param_id, const GValue *value,
876 GParamSpec * pspec)
878 SwfdecMovie *movie = SWFDEC_MOVIE (object);
879 SwfdecAsContext *cx = swfdec_gc_object_get_context (movie);
881 /* The context must be set before all movie-related properties */
882 g_assert (cx);
884 switch (param_id) {
885 case PROP_DEPTH:
886 /* parent must be set after depth */
887 g_assert (movie->parent == NULL);
888 movie->depth = g_value_get_int (value);
889 break;
890 case PROP_GRAPHIC:
891 movie->graphic = g_value_get_object (value);
892 if (movie->graphic)
893 g_object_ref (movie->graphic);
894 break;
895 case PROP_NAME:
896 movie->name = g_value_get_string (value);
897 if (movie->name) {
898 movie->name = swfdec_as_context_get_string (cx, movie->name);
899 movie->original_name = movie->name;
900 } else {
901 movie->original_name = SWFDEC_AS_STR_EMPTY;
902 if (SWFDEC_IS_SPRITE_MOVIE (movie) || SWFDEC_IS_BUTTON_MOVIE (movie)) {
903 movie->name = swfdec_as_context_give_string (cx,
904 g_strdup_printf ("instance%u", ++SWFDEC_PLAYER (cx)->priv->unnamed_count));
905 } else {
906 movie->name = SWFDEC_AS_STR_EMPTY;
909 break;
910 case PROP_PARENT:
911 movie->parent = g_value_get_object (value);
912 /* parent holds a reference */
913 g_object_ref (movie);
914 if (movie->parent) {
915 movie->parent->list = g_list_insert_sorted (movie->parent->list, movie, swfdec_movie_compare_depths);
916 SWFDEC_DEBUG ("inserting %s %p into %s %p", G_OBJECT_TYPE_NAME (movie), movie,
917 G_OBJECT_TYPE_NAME (movie->parent), movie->parent);
918 /* invalidate the parent, so it gets visible */
919 swfdec_movie_queue_update (movie->parent, SWFDEC_MOVIE_INVALID_CHILDREN);
920 } else {
921 SwfdecAsValue val;
922 SwfdecPlayerPrivate *priv = SWFDEC_PLAYER (cx)->priv;
923 priv->roots = g_list_insert_sorted (priv->roots, movie, swfdec_movie_compare_depths);
924 SWFDEC_AS_VALUE_SET_STRING (&val, swfdec_as_context_get_string (cx, priv->system->version));
925 swfdec_as_object_set_variable (SWFDEC_AS_OBJECT (movie), SWFDEC_AS_STR__version, &val);
927 break;
928 case PROP_RESOURCE:
929 movie->resource = g_value_get_object (value);
930 /* NB: the resource assumes it can access the player via the movie */
931 if (movie->resource->movie == NULL) {
932 g_assert (SWFDEC_IS_SPRITE_MOVIE (movie));
933 movie->resource->movie = SWFDEC_SPRITE_MOVIE (movie);
935 break;
936 default:
937 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
938 break;
942 static void
943 swfdec_movie_dispose (GObject *object)
945 SwfdecMovie * movie = SWFDEC_MOVIE (object);
946 GSList *iter;
948 g_assert (movie->list == NULL);
950 SWFDEC_LOG ("disposing movie %s (depth %d)", movie->name, movie->depth);
951 if (movie->graphic) {
952 g_object_unref (movie->graphic);
953 movie->graphic = NULL;
955 for (iter = movie->variable_listeners; iter != NULL; iter = iter->next) {
956 g_free (iter->data);
958 g_slist_free (movie->variable_listeners);
959 movie->variable_listeners = NULL;
961 if (movie->image) {
962 g_object_unref (movie->image);
963 movie->image = NULL;
965 g_slist_foreach (movie->draws, (GFunc) g_object_unref, NULL);
966 g_slist_free (movie->draws);
967 movie->draws = NULL;
969 G_OBJECT_CLASS (swfdec_movie_parent_class)->dispose (G_OBJECT (movie));
972 static void
973 swfdec_movie_mark (SwfdecGcObject *object)
975 SwfdecMovie *movie = SWFDEC_MOVIE (object);
976 GList *walk;
977 GSList *iter;
979 if (movie->parent)
980 swfdec_gc_object_mark (movie->parent);
981 swfdec_as_string_mark (movie->original_name);
982 swfdec_as_string_mark (movie->name);
983 for (walk = movie->list; walk; walk = walk->next) {
984 swfdec_gc_object_mark (walk->data);
986 for (iter = movie->variable_listeners; iter != NULL; iter = iter->next) {
987 SwfdecMovieVariableListener *listener = iter->data;
988 swfdec_gc_object_mark (listener->object);
989 swfdec_as_string_mark (listener->name);
991 swfdec_gc_object_mark (movie->resource);
993 SWFDEC_GC_OBJECT_CLASS (swfdec_movie_parent_class)->mark (object);
997 * swfdec_movie_is_scriptable:
998 * @movie: a movie
1000 * Checks if the movie may be accessed by scripts. If not, the movie is not
1001 * accessible by Actionscript and functions that would return the movie should
1002 * instead return its parent.
1004 * Returns: %TRUE if scripts may access this movie, %FALSE if the parent
1005 * should be used.
1007 gboolean
1008 swfdec_movie_is_scriptable (SwfdecMovie *movie)
1010 return (SWFDEC_IS_ACTOR (movie) || SWFDEC_IS_VIDEO_MOVIE (movie)) &&
1011 (swfdec_movie_get_version (movie) > 5 || !SWFDEC_IS_TEXT_FIELD_MOVIE (movie));
1014 /* FIXME: This function can definitely be implemented easier */
1015 SwfdecMovie *
1016 swfdec_movie_get_by_name (SwfdecMovie *movie, const char *name, gboolean unnamed)
1018 GList *walk;
1019 int i;
1020 guint version = swfdec_gc_object_get_context (movie)->version;
1021 SwfdecPlayer *player = SWFDEC_PLAYER (swfdec_gc_object_get_context (movie));
1023 i = swfdec_player_get_level (player, name, version);
1024 if (i >= 0)
1025 return SWFDEC_MOVIE (swfdec_player_get_movie_at_level (player, i));
1027 for (walk = movie->list; walk; walk = walk->next) {
1028 SwfdecMovie *cur = walk->data;
1029 if (cur->original_name == SWFDEC_AS_STR_EMPTY && !unnamed)
1030 continue;
1031 if (swfdec_strcmp (version, cur->name, name) == 0) {
1032 if (swfdec_movie_is_scriptable (cur))
1033 return cur;
1034 else
1035 return movie;
1038 return NULL;
1041 SwfdecMovie *
1042 swfdec_movie_get_root (SwfdecMovie *movie)
1044 SwfdecMovie *real_root;
1046 g_return_val_if_fail (SWFDEC_IS_MOVIE (movie), NULL);
1048 real_root = movie;
1049 while (real_root->parent)
1050 real_root = real_root->parent;
1052 while (movie->parent && !(movie->lockroot &&
1053 (swfdec_movie_get_version (movie) != 6 ||
1054 swfdec_movie_get_version (real_root) != 6))) {
1055 movie = movie->parent;
1058 return movie;
1061 static gboolean
1062 swfdec_movie_get_variable (SwfdecAsObject *object, SwfdecAsObject *orig,
1063 const char *variable, SwfdecAsValue *val, guint *flags)
1065 SwfdecMovie *movie, *ret;
1066 guint prop_id;
1068 movie = SWFDEC_MOVIE (object);
1069 movie = swfdec_movie_resolve (movie);
1070 if (movie == NULL)
1071 return FALSE;
1072 object = SWFDEC_AS_OBJECT (movie);
1074 if (SWFDEC_AS_OBJECT_CLASS (swfdec_movie_parent_class)->get (object, orig, variable, val, flags))
1075 return TRUE;
1077 /* FIXME: check that this is correct */
1078 if (swfdec_gc_object_get_context (object)->version > 5 && variable == SWFDEC_AS_STR__global) {
1079 SWFDEC_AS_VALUE_SET_OBJECT (val, SWFDEC_AS_OBJECT (movie->resource->sandbox));
1080 *flags = 0;
1081 return TRUE;
1084 ret = swfdec_movie_get_by_name (movie, variable, FALSE);
1085 if (ret) {
1086 SWFDEC_AS_VALUE_SET_OBJECT (val, SWFDEC_AS_OBJECT (ret));
1087 *flags = 0;
1088 return TRUE;
1091 prop_id = swfdec_movie_property_lookup (variable);
1092 if (prop_id != G_MAXUINT) {
1093 swfdec_movie_property_get (movie, prop_id, val);
1094 *flags = 0;
1095 return TRUE;
1098 return FALSE;
1101 void
1102 swfdec_movie_add_variable_listener (SwfdecMovie *movie, SwfdecAsObject *object,
1103 const char *name, const SwfdecMovieVariableListenerFunction function)
1105 SwfdecMovieVariableListener *listener;
1106 GSList *iter;
1108 for (iter = movie->variable_listeners; iter != NULL; iter = iter->next) {
1109 listener = iter->data;
1111 if (listener->object == object && listener->name == name &&
1112 listener->function == function)
1113 break;
1115 if (iter != NULL)
1116 return;
1118 listener = g_new0 (SwfdecMovieVariableListener, 1);
1119 listener->object = object;
1120 listener->name = name;
1121 listener->function = function;
1123 movie->variable_listeners = g_slist_prepend (movie->variable_listeners,
1124 listener);
1127 void
1128 swfdec_movie_remove_variable_listener (SwfdecMovie *movie,
1129 SwfdecAsObject *object, const char *name,
1130 const SwfdecMovieVariableListenerFunction function)
1132 GSList *iter;
1134 for (iter = movie->variable_listeners; iter != NULL; iter = iter->next) {
1135 SwfdecMovieVariableListener *listener = iter->data;
1137 if (listener->object == object && listener->name == name &&
1138 listener->function == function)
1139 break;
1141 if (iter == NULL)
1142 return;
1144 g_free (iter->data);
1145 movie->variable_listeners =
1146 g_slist_remove (movie->variable_listeners, iter->data);
1149 static void
1150 swfdec_movie_call_variable_listeners (SwfdecMovie *movie, const char *name,
1151 const SwfdecAsValue *val)
1153 GSList *iter;
1155 for (iter = movie->variable_listeners; iter != NULL; iter = iter->next) {
1156 SwfdecMovieVariableListener *listener = iter->data;
1158 if (listener->name != name &&
1159 (swfdec_gc_object_get_context (movie)->version >= 7 ||
1160 !swfdec_str_case_equal (listener->name, name)))
1161 continue;
1163 listener->function (listener->object, name, val);
1167 static void
1168 swfdec_movie_set_variable (SwfdecAsObject *object, const char *variable,
1169 const SwfdecAsValue *val, guint flags)
1171 SwfdecMovie *movie = SWFDEC_MOVIE (object);
1172 guint prop_id;
1174 movie = swfdec_movie_resolve (movie);
1175 if (movie == NULL)
1176 return;
1177 object = SWFDEC_AS_OBJECT (movie);
1179 prop_id = swfdec_movie_property_lookup (variable);
1180 if (prop_id != G_MAXUINT) {
1181 swfdec_movie_property_set (movie, prop_id, val);
1182 return;
1185 swfdec_movie_call_variable_listeners (movie, variable, val);
1187 SWFDEC_AS_OBJECT_CLASS (swfdec_movie_parent_class)->set (object, variable, val, flags);
1190 static gboolean
1191 swfdec_movie_foreach_variable (SwfdecAsObject *object, SwfdecAsVariableForeach func, gpointer data)
1193 SwfdecMovie *movie = SWFDEC_MOVIE (object);
1194 SwfdecAsValue val;
1195 GList *walk;
1196 gboolean ret;
1198 ret = SWFDEC_AS_OBJECT_CLASS (swfdec_movie_parent_class)->foreach (object, func, data);
1200 for (walk = movie->list; walk && ret; walk = walk->next) {
1201 SwfdecMovie *cur = walk->data;
1202 if (cur->original_name == SWFDEC_AS_STR_EMPTY)
1203 continue;
1204 SWFDEC_AS_VALUE_SET_OBJECT (&val, walk->data);
1205 ret &= func (object, cur->name, &val, 0, data);
1208 return ret;
1211 static char *
1212 swfdec_movie_get_debug (SwfdecAsObject *object)
1214 SwfdecMovie *movie = SWFDEC_MOVIE (object);
1216 return swfdec_movie_get_path (movie, TRUE);
1219 typedef struct {
1220 SwfdecMovie * movie;
1221 int depth;
1222 } ClipEntry;
1224 static void
1225 swfdec_movie_do_render (SwfdecMovie *movie, cairo_t *cr,
1226 const SwfdecColorTransform *ctrans)
1228 static const cairo_matrix_t ident = { 1, 0, 0, 1, 0, 0};
1229 GList *g;
1230 GSList *walk;
1231 GSList *clips = NULL;
1232 ClipEntry *clip = NULL;
1234 if (movie->draws || movie->image) {
1235 SwfdecRect inval;
1237 cairo_clip_extents (cr, &inval.x0, &inval.y0, &inval.x1, &inval.y1);
1239 /* exeute the movie's drawing commands */
1240 for (walk = movie->draws; walk; walk = walk->next) {
1241 SwfdecDraw *draw = walk->data;
1243 if (!swfdec_rect_intersect (NULL, &draw->extents, &inval))
1244 continue;
1246 swfdec_draw_paint (draw, cr, ctrans);
1249 /* if the movie loaded an image, draw it here now */
1250 /* FIXME: add check to only draw if inside clip extents */
1251 if (movie->image) {
1252 SwfdecRenderer *renderer = swfdec_renderer_get (cr);
1253 cairo_surface_t *surface;
1254 cairo_pattern_t *pattern;
1255 double alpha = 1.0;
1257 if (swfdec_color_transform_is_mask (ctrans)) {
1258 surface = NULL;
1259 } else if (swfdec_color_transform_is_alpha (ctrans)) {
1260 surface = swfdec_image_create_surface (movie->image, renderer);
1261 alpha = ctrans->aa / 256.0;
1262 } else {
1263 surface = swfdec_image_create_surface_transformed (movie->image,
1264 renderer, ctrans);
1266 if (surface) {
1267 static const cairo_matrix_t matrix = { 1.0 / SWFDEC_TWIPS_SCALE_FACTOR, 0, 0, 1.0 / SWFDEC_TWIPS_SCALE_FACTOR, 0, 0 };
1268 pattern = cairo_pattern_create_for_surface (surface);
1269 SWFDEC_LOG ("rendering loaded image");
1270 cairo_pattern_set_matrix (pattern, &matrix);
1271 } else {
1272 pattern = cairo_pattern_create_rgb (1.0, 0.0, 0.0);
1274 cairo_set_source (cr, pattern);
1275 cairo_paint_with_alpha (cr, alpha);
1276 cairo_pattern_destroy (pattern);
1277 cairo_surface_destroy (surface);
1281 /* draw the children movies */
1282 for (g = movie->list; g; g = g_list_next (g)) {
1283 SwfdecMovie *child = g->data;
1285 while (clip && clip->depth < child->depth) {
1286 cairo_pattern_t *mask;
1287 SWFDEC_INFO ("unsetting clip depth %d for depth %d", clip->depth, child->depth);
1288 mask = swfdec_movie_mask (cr, clip->movie, &ident);
1289 cairo_pop_group_to_source (cr);
1290 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1291 cairo_mask (cr, mask);
1292 cairo_pattern_destroy (mask);
1293 g_slice_free (ClipEntry, clip);
1294 clips = g_slist_delete_link (clips, clips);
1295 clip = clips ? clips->data : NULL;
1298 if (child->clip_depth) {
1299 clip = g_slice_new (ClipEntry);
1300 clips = g_slist_prepend (clips, clip);
1301 clip->movie = child;
1302 clip->depth = child->clip_depth;
1303 SWFDEC_INFO ("clipping up to depth %d by using %s with depth %d", child->clip_depth,
1304 child->name, child->depth);
1305 cairo_push_group (cr);
1306 continue;
1309 SWFDEC_LOG ("rendering %p with depth %d", child, child->depth);
1310 if (child->visible)
1311 swfdec_movie_render (child, cr, ctrans);
1313 while (clip) {
1314 cairo_pattern_t *mask;
1315 SWFDEC_INFO ("unsetting clip depth %d", clip->depth);
1316 mask = swfdec_movie_mask (cr, clip->movie, &ident);
1317 cairo_pop_group_to_source (cr);
1318 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1319 cairo_mask (cr, mask);
1320 cairo_pattern_destroy (mask);
1321 g_slice_free (ClipEntry, clip);
1322 clips = g_slist_delete_link (clips, clips);
1323 clip = clips ? clips->data : NULL;
1325 g_assert (clips == NULL);
1328 static void
1329 swfdec_movie_do_invalidate (SwfdecMovie *movie, const cairo_matrix_t *matrix, gboolean last)
1331 GList *walk;
1332 SwfdecRect rect;
1334 if (movie->image) {
1335 rect.x0 = rect.y0 = 0;
1336 rect.x1 = movie->image->width * SWFDEC_TWIPS_SCALE_FACTOR;
1337 rect.y1 = movie->image->height * SWFDEC_TWIPS_SCALE_FACTOR;
1338 } else {
1339 swfdec_rect_init_empty (&rect);
1341 swfdec_rect_union (&rect, &rect, &movie->draw_extents);
1342 swfdec_rect_transform (&rect, &rect, matrix);
1343 swfdec_player_invalidate (SWFDEC_PLAYER (swfdec_gc_object_get_context (movie)), &rect);
1345 for (walk = movie->list; walk; walk = walk->next) {
1346 swfdec_movie_invalidate (walk->data, matrix, last);
1350 static GObject *
1351 swfdec_movie_constructor (GType type, guint n_construct_properties,
1352 GObjectConstructParam *construct_properties)
1354 GObject *object;
1355 SwfdecPlayerPrivate *priv;
1357 object = G_OBJECT_CLASS (swfdec_movie_parent_class)->constructor (type,
1358 n_construct_properties, construct_properties);
1360 priv = SWFDEC_PLAYER (swfdec_gc_object_get_context (object))->priv;
1361 /* the movie is created invalid */
1362 priv->invalid_pending = g_slist_prepend (priv->invalid_pending, object);
1364 return object;
1367 static void
1368 swfdec_movie_class_init (SwfdecMovieClass * movie_class)
1370 GObjectClass *object_class = G_OBJECT_CLASS (movie_class);
1371 SwfdecGcObjectClass *gc_class = SWFDEC_GC_OBJECT_CLASS (movie_class);
1372 SwfdecAsObjectClass *asobject_class = SWFDEC_AS_OBJECT_CLASS (movie_class);
1374 object_class->constructor = swfdec_movie_constructor;
1375 object_class->dispose = swfdec_movie_dispose;
1376 object_class->get_property = swfdec_movie_get_property;
1377 object_class->set_property = swfdec_movie_set_property;
1379 gc_class->mark = swfdec_movie_mark;
1381 asobject_class->get = swfdec_movie_get_variable;
1382 asobject_class->set = swfdec_movie_set_variable;
1383 asobject_class->foreach = swfdec_movie_foreach_variable;
1384 asobject_class->debug = swfdec_movie_get_debug;
1386 signals[MATRIX_CHANGED] = g_signal_new ("matrix-changed", G_TYPE_FROM_CLASS (movie_class),
1387 G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID,
1388 G_TYPE_NONE, 0);
1390 g_object_class_install_property (object_class, PROP_DEPTH,
1391 g_param_spec_int ("depth", "depth", "z order inside the parent",
1392 G_MININT, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1393 g_object_class_install_property (object_class, PROP_GRAPHIC,
1394 g_param_spec_object ("graphic", "graphic", "graphic represented by this movie",
1395 SWFDEC_TYPE_GRAPHIC, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1396 g_object_class_install_property (object_class, PROP_NAME,
1397 g_param_spec_string ("name", "name", "the name given to this movie (can be empty)",
1398 NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1399 g_object_class_install_property (object_class, PROP_PARENT,
1400 g_param_spec_object ("parent", "parent", "parent movie containing this movie",
1401 SWFDEC_TYPE_MOVIE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1402 g_object_class_install_property (object_class, PROP_RESOURCE,
1403 g_param_spec_object ("resource", "resource", "the resource that spawned this movie",
1404 SWFDEC_TYPE_RESOURCE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1406 movie_class->render = swfdec_movie_do_render;
1407 movie_class->invalidate = swfdec_movie_do_invalidate;
1408 movie_class->contains = swfdec_movie_do_contains;
1409 movie_class->property_get = swfdec_movie_property_do_get;
1410 movie_class->property_set = swfdec_movie_property_do_set;
1413 void
1414 swfdec_movie_initialize (SwfdecMovie *movie)
1416 SwfdecMovieClass *klass;
1418 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
1420 klass = SWFDEC_MOVIE_GET_CLASS (movie);
1421 if (klass->init_movie)
1422 klass->init_movie (movie);
1425 void
1426 swfdec_movie_set_depth (SwfdecMovie *movie, int depth)
1428 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
1430 if (movie->depth == depth)
1431 return;
1433 swfdec_movie_invalidate_last (movie);
1434 movie->depth = depth;
1435 if (movie->parent) {
1436 movie->parent->list = g_list_sort (movie->parent->list, swfdec_movie_compare_depths);
1437 } else {
1438 SwfdecPlayerPrivate *player = SWFDEC_PLAYER (swfdec_gc_object_get_context (movie))->priv;
1439 player->roots = g_list_sort (player->roots, swfdec_movie_compare_depths);
1441 g_object_notify (G_OBJECT (movie), "depth");
1445 * swfdec_movie_new:
1446 * @player: a #SwfdecPlayer
1447 * @depth: depth of movie
1448 * @parent: the parent movie or %NULL to make this a root movie
1449 * @resource: the resource that is responsible for this movie
1450 * @graphic: the graphic that is displayed by this movie or %NULL to create an
1451 * empty movieclip
1452 * @name: a garbage-collected string to be used as the name for this movie or
1453 * %NULL for a default one.
1455 * Creates a new movie #SwfdecMovie for the given properties. No movie may exist
1456 * at the given @depth. The actual type of
1457 * this movie depends on the @graphic parameter. The movie will be initialized
1458 * with default properties. No script execution will be scheduled. After all
1459 * properties are set, the new-movie signal will be emitted if @player is a
1460 * debugger.
1462 * Returns: a new #SwfdecMovie
1464 SwfdecMovie *
1465 swfdec_movie_new (SwfdecPlayer *player, int depth, SwfdecMovie *parent, SwfdecResource *resource,
1466 SwfdecGraphic *graphic, const char *name)
1468 SwfdecMovie *movie;
1469 GType type;
1471 g_return_val_if_fail (SWFDEC_IS_PLAYER (player), NULL);
1472 g_return_val_if_fail (parent == NULL || SWFDEC_IS_MOVIE (parent), NULL);
1473 g_return_val_if_fail (SWFDEC_IS_RESOURCE (resource), NULL);
1474 g_return_val_if_fail (graphic == NULL || SWFDEC_IS_GRAPHIC (graphic), NULL);
1476 /* create the right movie */
1477 if (graphic == NULL) {
1478 type = SWFDEC_TYPE_SPRITE_MOVIE;
1479 } else {
1480 SwfdecGraphicClass *klass = SWFDEC_GRAPHIC_GET_CLASS (graphic);
1481 g_return_val_if_fail (g_type_is_a (klass->movie_type, SWFDEC_TYPE_MOVIE), NULL);
1482 type = klass->movie_type;
1484 movie = g_object_new (type, "context", player, "depth", depth,
1485 "parent", parent, "name", name, "resource", resource,
1486 "graphic", graphic, NULL);
1488 return movie;
1491 /* FIXME: since this is only used in PlaceObject, wouldn't it be easier to just have
1492 * swfdec_movie_update_static_properties (movie); that's notified when any of these change
1493 * and let PlaceObject modify the movie directly?
1495 void
1496 swfdec_movie_set_static_properties (SwfdecMovie *movie, const cairo_matrix_t *transform,
1497 const SwfdecColorTransform *ctrans, int ratio, int clip_depth, guint blend_mode,
1498 SwfdecEventList *events)
1500 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
1501 g_return_if_fail (clip_depth >= -16384 || clip_depth <= 0);
1502 g_return_if_fail (ratio >= -1);
1504 if (movie->modified) {
1505 SWFDEC_LOG ("%s has already been modified by scripts, ignoring updates", movie->name);
1506 return;
1508 if (transform) {
1509 swfdec_movie_begin_update_matrix (movie);
1510 movie->original_transform = *transform;
1511 movie->matrix.x0 = movie->original_transform.x0;
1512 movie->matrix.y0 = movie->original_transform.y0;
1513 movie->xscale = swfdec_matrix_get_xscale (&movie->original_transform);
1514 movie->yscale = swfdec_matrix_get_yscale (&movie->original_transform);
1515 movie->rotation = swfdec_matrix_get_rotation (&movie->original_transform);
1516 swfdec_movie_end_update_matrix (movie);
1518 if (ctrans) {
1519 swfdec_movie_invalidate_last (movie);
1520 movie->color_transform = *ctrans;
1522 if (ratio >= 0 && (guint) ratio != movie->original_ratio) {
1523 SwfdecMovieClass *klass;
1524 movie->original_ratio = ratio;
1525 klass = SWFDEC_MOVIE_GET_CLASS (movie);
1526 if (klass->set_ratio)
1527 klass->set_ratio (movie);
1529 if (clip_depth && clip_depth != movie->clip_depth) {
1530 movie->clip_depth = clip_depth;
1531 /* FIXME: is this correct? */
1532 swfdec_movie_invalidate_last (movie->parent ? movie->parent : movie);
1534 if (blend_mode != movie->blend_mode) {
1535 movie->blend_mode = blend_mode;
1536 swfdec_movie_invalidate_last (movie);
1538 if (events) {
1539 if (SWFDEC_IS_SPRITE_MOVIE (movie)) {
1540 SwfdecActor *actor = SWFDEC_ACTOR (movie);
1541 if (actor->events)
1542 swfdec_event_list_free (actor->events);
1543 actor->events = swfdec_event_list_copy (events);
1544 } else {
1545 SWFDEC_WARNING ("trying to set events on a %s, not allowed", G_OBJECT_TYPE_NAME (movie));
1551 * swfdec_movie_duplicate:
1552 * @movie: #SwfdecMovie to copy
1553 * @name: garbage-collected name for the new copy
1554 * @depth: depth to put this movie in
1556 * Creates a duplicate of @movie. The duplicate will not be initialized or
1557 * queued up for any events. You have to do this manually. In particular calling
1558 * swfdec_movie_initialize() on the returned movie must be done.
1559 * This function must be called from within a script.
1561 * Returns: a newly created movie or %NULL on error
1563 SwfdecMovie *
1564 swfdec_movie_duplicate (SwfdecMovie *movie, const char *name, int depth)
1566 SwfdecMovie *parent, *copy;
1567 SwfdecSandbox *sandbox;
1568 GSList *walk;
1570 g_return_val_if_fail (SWFDEC_IS_MOVIE (movie), NULL);
1571 g_return_val_if_fail (name != NULL, NULL);
1573 parent = movie->parent;
1574 if (movie->parent == NULL) {
1575 SWFDEC_FIXME ("don't know how to duplicate root movies");
1576 return NULL;
1578 copy = swfdec_movie_find (movie->parent, depth);
1579 if (copy) {
1580 SWFDEC_LOG ("depth %d already occupied while duplicating, removing old movie", depth);
1581 swfdec_movie_remove (copy);
1583 copy = swfdec_movie_new (SWFDEC_PLAYER (swfdec_gc_object_get_context (movie)), depth,
1584 parent, movie->resource, movie->graphic, name);
1585 /* copy properties */
1586 swfdec_movie_set_static_properties (copy, &movie->original_transform,
1587 &movie->color_transform, movie->original_ratio, movie->clip_depth,
1588 movie->blend_mode, SWFDEC_IS_ACTOR (movie) ? SWFDEC_ACTOR (movie)->events : NULL);
1589 /* Copy drawing state.
1590 * We can keep refs to all finalized draw objects, but need to create copies
1591 * of the still active ones as their path can still change */
1592 copy->draws = g_slist_copy (movie->draws);
1593 g_slist_foreach (copy->draws, (GFunc) g_object_ref, NULL);
1594 copy->draw_extents = movie->draw_extents;
1595 for (walk = copy->draws; walk; walk = walk->next) {
1596 if (walk->data == movie->draw_line) {
1597 copy->draw_line = swfdec_draw_copy (walk->data);
1598 g_object_unref (walk->data);
1599 walk->data = copy->draw_line;
1600 } else if (walk->data == movie->draw_fill) {
1601 copy->draw_fill = swfdec_draw_copy (walk->data);
1602 g_object_unref (walk->data);
1603 walk->data = copy->draw_fill;
1606 copy->draw_x = movie->draw_x;
1607 copy->draw_y = movie->draw_y;
1608 g_assert (copy->cache_state >= SWFDEC_MOVIE_INVALID_EXTENTS);
1610 sandbox = SWFDEC_SANDBOX (swfdec_gc_object_get_context (movie)->global);
1611 swfdec_sandbox_unuse (sandbox);
1612 if (SWFDEC_IS_SPRITE_MOVIE (copy)) {
1613 SwfdecActor *actor = SWFDEC_ACTOR (copy);
1614 swfdec_actor_queue_script (actor, SWFDEC_EVENT_INITIALIZE);
1615 swfdec_actor_queue_script (actor, SWFDEC_EVENT_LOAD);
1616 swfdec_actor_execute (actor, SWFDEC_EVENT_CONSTRUCT, 0);
1618 swfdec_movie_initialize (copy);
1619 swfdec_sandbox_use (sandbox);
1620 return copy;
1623 char *
1624 swfdec_movie_get_path (SwfdecMovie *movie, gboolean dot)
1626 GString *s;
1628 g_return_val_if_fail (SWFDEC_IS_MOVIE (movie), NULL);
1630 s = g_string_new ("");
1631 do {
1632 if (movie->parent) {
1633 g_string_prepend (s, movie->name);
1634 g_string_prepend_c (s, (dot ? '.' : '/'));
1635 } else {
1636 char *ret;
1637 if (dot) {
1638 ret = g_strdup_printf ("_level%u%s", movie->depth + 16384, s->str);
1639 g_string_free (s, TRUE);
1640 } else {
1641 if (s->str[0] != '/')
1642 g_string_prepend_c (s, '/');
1643 ret = g_string_free (s, FALSE);
1645 return ret;
1647 movie = movie->parent;
1648 } while (TRUE);
1650 g_assert_not_reached ();
1652 return NULL;
1656 swfdec_movie_compare_depths (gconstpointer a, gconstpointer b)
1658 if (SWFDEC_MOVIE (a)->depth < SWFDEC_MOVIE (b)->depth)
1659 return -1;
1660 if (SWFDEC_MOVIE (a)->depth > SWFDEC_MOVIE (b)->depth)
1661 return 1;
1662 return 0;
1666 * swfdec_depth_classify:
1667 * @depth: the depth to classify
1669 * Classifies a depth. This classification is mostly used when deciding if
1670 * certain operations are valid in ActionScript.
1672 * Returns: the classification of the depth.
1674 SwfdecDepthClass
1675 swfdec_depth_classify (int depth)
1677 if (depth < -16384)
1678 return SWFDEC_DEPTH_CLASS_EMPTY;
1679 if (depth < 0)
1680 return SWFDEC_DEPTH_CLASS_TIMELINE;
1681 if (depth < 1048576)
1682 return SWFDEC_DEPTH_CLASS_DYNAMIC;
1683 if (depth < 2130690046)
1684 return SWFDEC_DEPTH_CLASS_RESERVED;
1685 return SWFDEC_DEPTH_CLASS_EMPTY;
1689 * swfdec_movie_get_own_resource:
1690 * @movie: movie to query
1692 * Queries the movie for his own resource. A movie only has its own resource if
1693 * it contains data loaded with the loadMovie() function, or if it is the root
1694 * movie.
1696 * Returns: The own resource of @movie or %NULL
1698 SwfdecResource *
1699 swfdec_movie_get_own_resource (SwfdecMovie *movie)
1701 g_return_val_if_fail (SWFDEC_IS_MOVIE (movie), NULL);
1703 if (!SWFDEC_IS_SPRITE_MOVIE (movie))
1704 return NULL;
1706 if (movie->resource->movie != SWFDEC_SPRITE_MOVIE (movie))
1707 return NULL;
1709 return movie->resource;
1712 void
1713 swfdec_movie_property_set (SwfdecMovie *movie, guint id, const SwfdecAsValue *val)
1715 SwfdecMovieClass *klass;
1717 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
1718 g_return_if_fail (val != NULL);
1720 klass = SWFDEC_MOVIE_GET_CLASS (movie);
1721 klass->property_set (movie, id, val);
1724 void
1725 swfdec_movie_property_get (SwfdecMovie *movie, guint id, SwfdecAsValue *val)
1727 SwfdecMovieClass *klass;
1729 g_return_if_fail (SWFDEC_IS_MOVIE (movie));
1730 g_return_if_fail (val != NULL);
1732 klass = SWFDEC_MOVIE_GET_CLASS (movie);
1733 klass->property_get (movie, id, val);