add blend mode tests
[swfdec.git] / swfdec / swfdec_text_field_movie.c
blob9f0f0b01443a29ab1937960b50be6cf731c2b2e5
1 /* Swfdec
2 * Copyright (C) 2006-2008 Benjamin Otte <otte@gnome.org>
3 * 2007 Pekka Lampila <pekka.lampila@iki.fi>
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301 USA
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
25 #include <string.h>
26 #include <math.h>
27 #include <pango/pangocairo.h>
29 #include "swfdec_text_field_movie.h"
30 #include "swfdec_as_context.h"
31 #include "swfdec_as_internal.h"
32 #include "swfdec_as_interpret.h"
33 #include "swfdec_as_strings.h"
34 #include "swfdec_debug.h"
35 #include "swfdec_internal.h"
36 #include "swfdec_player_internal.h"
37 #include "swfdec_renderer_internal.h"
38 #include "swfdec_resource.h"
39 #include "swfdec_sandbox.h"
40 #include "swfdec_text_format.h"
41 #include "swfdec_xml.h"
43 G_DEFINE_TYPE (SwfdecTextFieldMovie, swfdec_text_field_movie, SWFDEC_TYPE_ACTOR)
45 #define BORDER_TOP 2
46 #define BORDER_LEFT 2
47 #define BORDER_RIGHT 2
48 #define BORDER_BOTTOM 2
50 /*** VFUNCS ***/
52 static void
53 swfdec_text_field_movie_compute_layout_area (SwfdecTextFieldMovie *text)
55 int tmpx, tmpy;
57 tmpx = round ((BORDER_LEFT + BORDER_RIGHT) * SWFDEC_TWIPS_SCALE_FACTOR * text->to_layout.xx);
58 tmpy = round ((BORDER_TOP + BORDER_BOTTOM) * SWFDEC_TWIPS_SCALE_FACTOR * text->to_layout.yy);
59 text->layout_area = text->stage_area;
60 if (tmpx >= text->layout_area.width ||
61 tmpy >= text->layout_area.height) {
62 return;
65 text->layout_area.x += tmpx / 2;
66 text->layout_area.y += tmpy / 2;
67 text->layout_area.width -= tmpx;
68 text->layout_area.height -= tmpy;
71 /* NB: This signal can happen without a locked player */
72 static void
73 swfdec_text_field_movie_update_area (SwfdecTextFieldMovie *text)
75 SwfdecMovie *movie = SWFDEC_MOVIE (text);
76 cairo_matrix_t *matrix, translate;
77 double x, y;
79 if (swfdec_player_is_locked (SWFDEC_PLAYER (swfdec_gc_object_get_context (text))))
80 swfdec_movie_invalidate_next (movie);
82 /* check if we indeed want to render */
83 matrix = &text->to_layout;
84 swfdec_movie_local_to_global_matrix (movie, matrix);
85 cairo_matrix_multiply (matrix, matrix,
86 &SWFDEC_PLAYER (swfdec_gc_object_get_context (movie))->priv->global_to_stage);
87 if (matrix->xy != 0.0 || matrix->yx != 0.0 ||
88 matrix->xx <= 0.0 || matrix->yy <= 0.0) {
89 swfdec_rectangle_init_empty (&text->stage_area);
90 swfdec_rectangle_init_empty (&text->layout_area);
91 return;
94 x = text->extents.x0;
95 y = text->extents.y0;
96 cairo_matrix_transform_point (matrix, &x, &y);
97 cairo_matrix_init_translate (&translate, round (x) - x, round (y) - y);
98 cairo_matrix_multiply (matrix, matrix, &translate);
99 text->from_layout = *matrix;
100 if (cairo_matrix_invert (&text->from_layout)) {
101 SWFDEC_ERROR ("cannot invert to-layout matrix");
102 swfdec_rectangle_init_empty (&text->stage_area);
103 swfdec_rectangle_init_empty (&text->layout_area);
104 return;
107 x = text->extents.x0;
108 y = text->extents.y0;
109 cairo_matrix_transform_point (matrix, &x, &y);
110 text->stage_area.x = x;
111 text->stage_area.y = y;
112 x = text->extents.x1;
113 y = text->extents.y1;
114 cairo_matrix_transform_point (matrix, &x, &y);
115 /* FIXME: floor, ceil or round? */
116 text->stage_area.width = round (x) - text->stage_area.x;
117 text->stage_area.height = round (y) - text->stage_area.y;
119 swfdec_text_field_movie_compute_layout_area (text);
121 swfdec_text_layout_set_scale (text->layout, matrix->yy * SWFDEC_TWIPS_SCALE_FACTOR);
122 swfdec_text_layout_set_wrap_width (text->layout, text->layout_area.width);
125 void
126 swfdec_text_field_movie_autosize (SwfdecTextFieldMovie *text)
128 SwfdecMovie *movie = SWFDEC_MOVIE (text);
129 double x0, z0, x1, z1; /* y0 and y1 are taken by math.h */
131 if (text->auto_size == SWFDEC_AUTO_SIZE_NONE)
132 return;
134 swfdec_text_field_movie_update_layout (text);
135 x1 = text->layout_width;
136 z1 = text->layout_height;
137 cairo_matrix_transform_distance (&text->from_layout, &x1, &z1);
138 x1 += (BORDER_LEFT + BORDER_RIGHT) * SWFDEC_TWIPS_SCALE_FACTOR;
139 z1 += (BORDER_TOP + BORDER_BOTTOM) * SWFDEC_TWIPS_SCALE_FACTOR;
140 cairo_matrix_transform_distance (&movie->inverse_matrix, &x1, &z1);
141 x1 -= text->extents.x1 - text->extents.x0;
142 z1 -= text->extents.y1 - text->extents.y0;
144 /* when word wrapping is enabled, don't resize width */
145 if (swfdec_text_layout_get_word_wrap (text->layout))
146 x1 = 0;
148 if (x1 == 0 && z1 == 0)
149 return;
151 x1 = ceil (x1);
152 z1 = ceil (z1);
154 switch (text->auto_size) {
155 case SWFDEC_AUTO_SIZE_LEFT:
156 x0 = 0;
157 break;
158 case SWFDEC_AUTO_SIZE_RIGHT:
159 x0 = -x1;
160 x1 = 0;
161 break;
162 case SWFDEC_AUTO_SIZE_CENTER:
163 x1 = x1 / 2;
164 x0 = -x1;
165 break;
166 case SWFDEC_AUTO_SIZE_NONE:
167 default:
168 x0 = 0;
169 g_assert_not_reached ();
171 z0 = 0;
173 swfdec_movie_invalidate_next (movie);
174 swfdec_movie_queue_update (movie, SWFDEC_MOVIE_INVALID_EXTENTS);
176 text->extents.x0 += x0;
177 text->extents.y0 += z0;
178 text->extents.x1 += x1;
179 text->extents.y1 += z1;
181 swfdec_text_field_movie_update_area (text);
184 static void
185 swfdec_text_field_movie_update_extents (SwfdecMovie *movie,
186 SwfdecRect *extents)
188 SwfdecTextFieldMovie *text = SWFDEC_TEXT_FIELD_MOVIE (movie);
190 swfdec_rect_union (extents, extents, &text->extents);
193 static void
194 swfdec_text_field_movie_invalidate (SwfdecMovie *movie, const cairo_matrix_t *matrix, gboolean last)
196 SwfdecTextFieldMovie *text = SWFDEC_TEXT_FIELD_MOVIE (movie);
197 SwfdecRect rect;
199 rect.x0 = text->stage_area.x;
200 rect.y0 = text->stage_area.y;
201 rect.x1 = text->stage_area.x + text->stage_area.width;
202 rect.y1 = text->stage_area.y + text->stage_area.height;
204 if (text->border) {
205 rect.x1++;
206 rect.y1++;
209 swfdec_rect_transform (&rect, &rect,
210 &SWFDEC_PLAYER (swfdec_gc_object_get_context (text))->priv->stage_to_global);
211 swfdec_player_invalidate (SWFDEC_PLAYER (swfdec_gc_object_get_context (movie)),
212 movie, &rect);
215 static gboolean
216 swfdec_text_field_movie_has_focus (SwfdecTextFieldMovie *text)
218 SwfdecPlayer *player = SWFDEC_PLAYER (swfdec_gc_object_get_context (text));
220 return swfdec_player_has_focus (player, SWFDEC_ACTOR (text));
223 static void
224 swfdec_text_field_movie_render (SwfdecMovie *movie, cairo_t *cr,
225 const SwfdecColorTransform *ctrans)
227 SwfdecTextFieldMovie *text = SWFDEC_TEXT_FIELD_MOVIE (movie);
228 SwfdecRectangle *area;
229 SwfdecColor color;
230 SwfdecRect inval;
232 /* textfields don't mask */
233 if (swfdec_color_transform_is_mask (ctrans) ||
234 swfdec_rectangle_is_empty (&text->stage_area))
235 return;
237 swfdec_renderer_reset_matrix (cr);
239 if (text->background) {
240 cairo_rectangle (cr, text->stage_area.x, text->stage_area.y,
241 text->stage_area.width, text->stage_area.height);
242 color = swfdec_color_apply_transform (text->background_color, ctrans);
243 swfdec_color_set_source (cr, SWFDEC_COLOR_OPAQUE (color));
244 cairo_fill (cr);
246 if (text->border) {
247 cairo_rectangle (cr, text->stage_area.x + 0.5, text->stage_area.y + 0.5,
248 text->stage_area.width, text->stage_area.height);
249 color = swfdec_color_apply_transform (text->border_color, ctrans);
250 swfdec_color_set_source (cr, SWFDEC_COLOR_OPAQUE (color));
251 cairo_set_line_width (cr, 1.0);
252 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
253 cairo_stroke (cr);
256 if (swfdec_text_field_movie_has_focus (text) &&
257 (text->editable ||
258 (text->selectable && swfdec_text_buffer_has_selection (text->text)))) {
259 if (text->background) {
260 color = swfdec_color_apply_transform (text->background_color, ctrans);
261 color = SWFDEC_COLOR_OPAQUE (color);
262 } else {
263 color = SWFDEC_COLOR_WHITE;
265 } else {
266 color = 0;
269 area = &text->layout_area;
270 /* Don't draw if out of clip */
271 cairo_clip_extents (cr, &inval.x0, &inval.y0, &inval.x1, &inval.y1);
272 if (inval.x1 <= area->x || inval.x0 >= area->x + area->width ||
273 inval.y1 <= area->y || inval.y0 >= area->y + area->height)
274 return;
275 /* render the layout */
276 cairo_rectangle (cr, area->x, area->y, area->width, area->height);
277 cairo_clip (cr);
278 /* FIXME: This -1 is spacing? */
279 cairo_translate (cr, (double) area->x - text->hscroll, area->y - 1);
280 swfdec_text_layout_render (text->layout, cr, ctrans,
281 text->scroll, area->height, color);
284 static void
285 swfdec_text_field_movie_dispose (GObject *object)
287 SwfdecTextFieldMovie *text;
289 text = SWFDEC_TEXT_FIELD_MOVIE (object);
291 if (text->style_sheet) {
292 if (SWFDEC_IS_STYLE_SHEET (text->style_sheet->relay)) {
293 g_signal_handlers_disconnect_matched (text->style_sheet->relay,
294 G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, text);
296 text->style_sheet = NULL;
299 if (text->text) {
300 g_signal_handlers_disconnect_matched (text->text, G_SIGNAL_MATCH_DATA,
301 0, 0, NULL, NULL, text);
302 g_object_unref (text->text);
303 text->text = NULL;
305 if (text->layout) {
306 g_object_unref (text->layout);
307 text->layout = NULL;
310 G_OBJECT_CLASS (swfdec_text_field_movie_parent_class)->dispose (object);
313 static void
314 swfdec_text_field_movie_mark (SwfdecGcObject *object)
316 SwfdecTextFieldMovie *text;
318 text = SWFDEC_TEXT_FIELD_MOVIE (object);
320 if (text->variable != NULL)
321 swfdec_as_string_mark (text->variable);
323 swfdec_text_buffer_mark (text->text);
324 if (text->style_sheet != NULL)
325 swfdec_as_object_mark (text->style_sheet);
326 if (text->style_sheet_input != NULL)
327 swfdec_as_string_mark (text->style_sheet_input);
328 if (text->restrict_ != NULL)
329 swfdec_as_string_mark (text->restrict_);
331 SWFDEC_GC_OBJECT_CLASS (swfdec_text_field_movie_parent_class)->mark (object);
334 void
335 swfdec_text_field_movie_emit_onScroller (SwfdecTextFieldMovie *text)
337 g_return_if_fail (SWFDEC_IS_TEXT_FIELD_MOVIE (text));
339 if (!text->onScroller_emitted && swfdec_movie_get_version (SWFDEC_MOVIE (text)) > 6) {
340 swfdec_player_add_action (SWFDEC_PLAYER (swfdec_gc_object_get_context (text)),
341 SWFDEC_ACTOR (text), SWFDEC_EVENT_SCROLL, 0, SWFDEC_PLAYER_ACTION_QUEUE_NORMAL);
343 text->onScroller_emitted = TRUE;
346 void
347 swfdec_text_field_movie_update_layout (SwfdecTextFieldMovie *text)
349 guint scroll_max, lines_visible, rows, height, max;
350 gboolean scroll_changed = FALSE;
352 g_return_if_fail (SWFDEC_IS_TEXT_FIELD_MOVIE (text));
354 text->layout_width = swfdec_text_layout_get_width (text->layout);
355 text->layout_height = swfdec_text_layout_get_height (text->layout);
357 height = text->layout_area.height;
358 rows = swfdec_text_layout_get_n_rows (text->layout);
359 scroll_max = rows - swfdec_text_layout_get_visible_rows_end (text->layout, height);
360 if (scroll_max != text->scroll_max) {
361 text->scroll_max = scroll_max;
362 scroll_changed = TRUE;
364 if (scroll_max < text->scroll) {
365 text->scroll = scroll_max;
366 scroll_changed = TRUE;
368 lines_visible = swfdec_text_layout_get_visible_rows (text->layout,
369 text->scroll, height);
370 if (lines_visible != text->lines_visible) {
371 text->lines_visible = lines_visible;
372 scroll_changed = TRUE;
375 max = swfdec_text_field_movie_get_hscroll_max (text);
376 if (text->hscroll > max) {
377 text->hscroll = max;
378 scroll_changed = TRUE;
381 if (scroll_changed)
382 swfdec_text_field_movie_emit_onScroller (text);
385 static void
386 swfdec_text_field_movie_init_movie (SwfdecMovie *movie)
388 SwfdecTextFieldMovie *text = SWFDEC_TEXT_FIELD_MOVIE (movie);
389 SwfdecTextField *text_field = SWFDEC_TEXT_FIELD (movie->graphic);
390 SwfdecAsContext *cx;
391 SwfdecAsValue val;
392 SwfdecMovie *parent;
393 SwfdecAsObject *array;
394 gboolean needs_unuse;
396 needs_unuse = swfdec_sandbox_try_use (movie->resource->sandbox);
398 cx = swfdec_gc_object_get_context (movie);
399 if (movie->resource->version > 5) {
400 SwfdecAsObject *o = swfdec_as_relay_get_as_object (SWFDEC_AS_RELAY (movie));
402 swfdec_text_field_movie_init_properties (cx);
404 swfdec_as_object_set_constructor_by_name (o,
405 SWFDEC_AS_STR_TextField, NULL);
407 /* create _listeners array containing self */
408 array = swfdec_as_array_new (cx);
409 SWFDEC_AS_VALUE_SET_MOVIE (&val, movie);
410 swfdec_as_array_push (array, &val);
411 SWFDEC_AS_VALUE_SET_OBJECT (&val, array);
412 swfdec_as_object_set_variable_and_flags (o, SWFDEC_AS_STR__listeners,
413 &val, SWFDEC_AS_VARIABLE_HIDDEN | SWFDEC_AS_VARIABLE_PERMANENT);
416 text->border_color = SWFDEC_COLOR_COMBINE (0, 0, 0, 0);
417 text->background_color = SWFDEC_COLOR_COMBINE (255, 255, 255, 0);
419 if (text_field) {
420 text->extents = SWFDEC_GRAPHIC (text_field)->extents;
422 text->html = text_field->html;
423 text->editable = text_field->editable;
424 swfdec_text_layout_set_password (text->layout, text_field->password);
425 text->max_chars = text_field->max_chars;
426 text->selectable = text_field->selectable;
427 text->embed_fonts = text_field->embed_fonts;
428 swfdec_text_layout_set_word_wrap (text->layout, text_field->word_wrap);
429 text->multiline = text_field->multiline;
430 text->auto_size = text_field->auto_size;
431 text->border = text_field->border;
433 text->text->default_attributes.color = text_field->color;
434 text->text->default_attributes.align = text_field->align;
435 if (text_field->font != NULL) {
436 text->text->default_attributes.font =
437 swfdec_as_context_get_string (cx, text_field->font);
439 text->text->default_attributes.size = text_field->size / 20;
440 text->text->default_attributes.left_margin = text_field->left_margin / 20;
441 text->text->default_attributes.right_margin = text_field->right_margin / 20;
442 text->text->default_attributes.indent = text_field->indent / 20;
443 text->text->default_attributes.leading = text_field->leading / 20;
446 // text
447 if (text_field && text_field->input != NULL) {
448 swfdec_text_field_movie_set_text (text,
449 swfdec_as_context_get_string (cx, text_field->input),
450 text->html);
451 } else {
452 swfdec_text_field_movie_set_text (text, SWFDEC_AS_STR_EMPTY,
453 text->html);
456 // variable
457 if (text_field && text_field->variable != NULL) {
458 swfdec_text_field_movie_set_listen_variable (text,
459 swfdec_as_context_get_string (cx, text_field->variable));
462 if (needs_unuse)
463 swfdec_sandbox_unuse (movie->resource->sandbox);
465 parent = movie;
466 while (parent) {
467 g_signal_connect_swapped (parent, "matrix-changed",
468 G_CALLBACK (swfdec_text_field_movie_update_area), movie);
469 parent = parent->parent;
471 /* don't emit onScroller here, plz */
472 text->onScroller_emitted = TRUE;
473 swfdec_text_field_movie_update_layout (text);
474 if (swfdec_movie_get_version (movie) > 6)
475 text->onScroller_emitted = FALSE;
476 swfdec_text_field_movie_update_area (text);
479 static void
480 swfdec_text_field_movie_finish_movie (SwfdecMovie *movie)
482 SwfdecTextFieldMovie *text = SWFDEC_TEXT_FIELD_MOVIE (movie);
483 SwfdecMovie *parent;
485 parent = SWFDEC_MOVIE (text);
486 while (parent) {
487 g_signal_handlers_disconnect_matched (parent,
488 G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, text);
489 parent = parent->parent;
492 swfdec_text_field_movie_set_listen_variable (text, NULL);
495 static void
496 swfdec_text_field_movie_iterate (SwfdecActor *actor)
498 SwfdecTextFieldMovie *text = SWFDEC_TEXT_FIELD_MOVIE (actor);
500 while (text->changed) {
501 swfdec_player_add_action (SWFDEC_PLAYER (swfdec_gc_object_get_context (text)),
502 SWFDEC_ACTOR (text), SWFDEC_EVENT_CHANGED, 0, SWFDEC_PLAYER_ACTION_QUEUE_NORMAL);
503 text->changed--;
506 if (text->onScroller_emitted && swfdec_movie_get_version (SWFDEC_MOVIE (text)) <= 6) {
507 swfdec_player_add_action (SWFDEC_PLAYER (swfdec_gc_object_get_context (text)),
508 SWFDEC_ACTOR (text), SWFDEC_EVENT_SCROLL, 0, SWFDEC_PLAYER_ACTION_QUEUE_NORMAL);
510 text->onScroller_emitted = FALSE;
513 static SwfdecMovie *
514 swfdec_text_field_movie_contains (SwfdecMovie *movie, double x, double y,
515 gboolean events)
517 if (events) {
518 /* check for movies in a higher layer that react to events */
519 SwfdecMovie *ret;
520 ret = SWFDEC_MOVIE_CLASS (swfdec_text_field_movie_parent_class)->contains (
521 movie, x, y, TRUE);
522 if (ret && SWFDEC_IS_ACTOR (ret) && swfdec_actor_get_mouse_events (SWFDEC_ACTOR (ret)))
523 return ret;
526 return movie;
529 static void
530 swfdec_text_field_movie_parse_listen_variable (SwfdecTextFieldMovie *text,
531 const char *variable, SwfdecAsObject **object, const char **name)
533 SwfdecAsContext *cx;
534 SwfdecAsObject *parent;
535 const char *p1, *p2;
537 g_return_if_fail (SWFDEC_IS_TEXT_FIELD_MOVIE (text));
538 g_return_if_fail (variable != NULL);
539 g_return_if_fail (object != NULL);
540 g_return_if_fail (name != NULL);
542 *object = NULL;
543 *name = NULL;
545 if (SWFDEC_MOVIE (text)->parent == NULL)
546 return;
548 cx = swfdec_gc_object_get_context (text);
549 parent = swfdec_as_relay_get_as_object (SWFDEC_AS_RELAY (SWFDEC_MOVIE (text)->parent));
551 p1 = strrchr (variable, '.');
552 p2 = strrchr (variable, ':');
553 if (p1 == NULL && p2 == NULL) {
554 *object = parent;
555 *name = variable;
556 } else {
557 if (p1 == NULL || (p2 != NULL && p2 > p1))
558 p1 = p2;
559 if (strlen (p1) == 1)
560 return;
561 *object = swfdec_action_lookup_object (cx, parent, variable, p1);
562 if (*object == NULL)
563 return;
564 *name = swfdec_as_context_get_string (cx, p1 + 1);
568 static void
569 swfdec_text_field_movie_asfunction (SwfdecTextFieldMovie *text,
570 const char *url)
572 char **parts;
573 SwfdecAsObject *object;
574 const char *name;
575 SwfdecAsContext *cx;
577 g_return_if_fail (g_ascii_strncasecmp (url, "asfunction:",
578 strlen ("asfunction:")) == 0);
580 cx = swfdec_gc_object_get_context (text);
582 parts = g_strsplit (url + strlen ("asfunction:"), ",", 2);
583 if (parts[0] == NULL) {
584 SWFDEC_ERROR ("asfunction link without function name clicked");
585 g_strfreev (parts);
586 return;
589 swfdec_text_field_movie_parse_listen_variable (text,
590 swfdec_as_context_get_string (cx, parts[0]), &object, &name);
592 if (object == NULL || name == NULL) {
593 SWFDEC_ERROR ("Function in asfunction link not found: %s", parts[0]);
594 g_strfreev (parts);
595 return;
598 swfdec_sandbox_use (SWFDEC_MOVIE (text)->resource->sandbox);
599 if (parts[1] != NULL) {
600 SwfdecAsValue val;
601 SWFDEC_AS_VALUE_SET_STRING (&val,
602 swfdec_as_context_get_string (cx, parts[1]));
603 swfdec_as_object_call (object, name, 1, &val, NULL);
604 } else {
605 swfdec_as_object_call (object, name, 0, NULL, NULL);
607 swfdec_sandbox_unuse (SWFDEC_MOVIE (text)->resource->sandbox);
609 g_strfreev (parts);
612 static void
613 swfdec_text_field_movie_letter_clicked (SwfdecTextFieldMovie *text,
614 guint index_)
616 const SwfdecTextAttributes *attr;
618 g_return_if_fail (SWFDEC_IS_TEXT_FIELD_MOVIE (text));
619 g_return_if_fail (index_ <= swfdec_text_buffer_get_length (text->text));
621 attr = swfdec_text_buffer_get_attributes (text->text, index_);
623 if (attr->url != SWFDEC_AS_STR_EMPTY) {
624 if (g_ascii_strncasecmp (attr->url, "asfunction:",
625 strlen ("asfunction:")) == 0) {
626 swfdec_text_field_movie_asfunction (text, attr->url);
627 } else {
628 swfdec_player_launch (SWFDEC_PLAYER (swfdec_gc_object_get_context (text)),
629 attr->url, attr->target, NULL);
634 static void
635 swfdec_text_field_movie_xy_to_index (SwfdecTextFieldMovie *text, double x,
636 double y, gsize *index_, gboolean *hit)
638 int trailing;
640 cairo_matrix_transform_point (&text->to_layout, &x, &y);
641 swfdec_text_layout_query_position (text->layout, text->scroll,
642 x - text->layout_area.x, y - text->layout_area.y, index_, hit, &trailing);
644 if (index_)
645 *index_ += trailing;
648 static SwfdecMouseCursor
649 swfdec_text_field_movie_mouse_cursor (SwfdecActor *actor)
651 SwfdecTextFieldMovie *text = SWFDEC_TEXT_FIELD_MOVIE (actor);
652 double x, y;
653 gsize index_;
654 const SwfdecTextAttributes *attr;
655 gboolean hit;
657 swfdec_movie_get_mouse (SWFDEC_MOVIE (actor), &x, &y);
659 swfdec_text_field_movie_xy_to_index (text, x, y, &index_, &hit);
660 if (hit) {
661 attr = swfdec_text_buffer_get_attributes (text->text, index_);
662 } else {
663 attr = NULL;
666 if (attr != NULL && attr->url != SWFDEC_AS_STR_EMPTY) {
667 return SWFDEC_MOUSE_CURSOR_CLICK;
668 } else if (text->selectable) {
669 return SWFDEC_MOUSE_CURSOR_TEXT;
670 } else{
671 return SWFDEC_MOUSE_CURSOR_NORMAL;
675 static gboolean
676 swfdec_text_field_movie_mouse_events (SwfdecActor *actor)
678 SwfdecTextFieldMovie *text = SWFDEC_TEXT_FIELD_MOVIE (actor);
680 return text->selectable;
683 static void
684 swfdec_text_field_movie_mouse_press (SwfdecActor *actor, guint button)
686 SwfdecTextFieldMovie *text = SWFDEC_TEXT_FIELD_MOVIE (actor);
687 double x, y;
688 gsize index_;
689 gboolean hit;
691 if (!text->selectable)
692 return;
694 if (button != 0) {
695 SWFDEC_FIXME ("implement popup menus, scrollwheel and middle mouse paste");
696 return;
699 swfdec_movie_get_mouse (SWFDEC_MOVIE (actor), &x, &y);
701 swfdec_text_field_movie_xy_to_index (text, x, y, &index_, &hit);
703 if (hit) {
704 text->character_pressed = index_;
705 } else {
706 text->character_pressed = -1;
709 swfdec_player_grab_focus (SWFDEC_PLAYER (swfdec_gc_object_get_context (text)), actor);
711 text->mouse_pressed = TRUE;
712 swfdec_text_buffer_set_cursor (text->text, index_, index_);
715 static void
716 swfdec_text_field_movie_mouse_move (SwfdecActor *actor, double x, double y)
718 SwfdecTextFieldMovie *text = SWFDEC_TEXT_FIELD_MOVIE (actor);
719 gsize index_;
720 gsize start, end;
722 if (!text->selectable)
723 return;
725 if (!text->mouse_pressed)
726 return;
728 swfdec_text_field_movie_xy_to_index (text, x, y, &index_, NULL);
730 swfdec_text_buffer_get_selection (text->text, &start, &end);
731 swfdec_text_buffer_set_cursor (text->text,
732 swfdec_text_buffer_get_cursor (text->text) == start ? end : start, index_);
735 static void
736 swfdec_text_field_movie_mouse_release (SwfdecActor *actor, guint button)
738 SwfdecTextFieldMovie *text = SWFDEC_TEXT_FIELD_MOVIE (actor);
739 double x, y;
740 gsize index_;
741 gboolean hit;
743 if (!text->selectable)
744 return;
746 if (button != 0) {
747 SWFDEC_FIXME ("implement popup menus, scrollwheel and middle mouse paste");
748 return;
751 swfdec_movie_get_mouse (SWFDEC_MOVIE (text), &x, &y);
753 text->mouse_pressed = FALSE;
755 swfdec_text_field_movie_xy_to_index (text, x, y, &index_, &hit);
757 if (hit && text->character_pressed == index_) {
758 swfdec_text_field_movie_letter_clicked (text, text->character_pressed);
759 text->character_pressed = -1;
763 static void
764 swfdec_text_field_movie_focus_in (SwfdecActor *actor)
766 SwfdecTextFieldMovie *text = SWFDEC_TEXT_FIELD_MOVIE (actor);
768 swfdec_text_buffer_set_cursor (text->text, 0,
769 swfdec_text_buffer_get_length (text->text));
770 if (text->editable || text->selectable)
771 swfdec_movie_invalidate_last (SWFDEC_MOVIE (actor));
774 static void
775 swfdec_text_field_movie_focus_out (SwfdecActor *actor)
777 SwfdecTextFieldMovie *text = SWFDEC_TEXT_FIELD_MOVIE (actor);
779 if (text->editable || text->selectable)
780 swfdec_movie_invalidate_last (SWFDEC_MOVIE (actor));
783 static void
784 swfdec_text_field_movie_key_press (SwfdecActor *actor, guint keycode, guint character)
786 SwfdecTextFieldMovie *text = SWFDEC_TEXT_FIELD_MOVIE (actor);
787 char insert[7];
788 guint len;
789 gsize start, end;
790 const char *string;
791 #define BACKWARD(text, _index) ((_index) == 0 ? 0 : (gsize) (g_utf8_prev_char ((text) + (_index)) - (text)))
792 #define FORWARD(text, _index) ((text)[_index] == 0 ? (_index) : (gsize) (g_utf8_next_char ((text) + (_index)) - (text)))
794 if (!text->editable)
795 return;
797 string = swfdec_text_buffer_get_text (text->text);
798 swfdec_text_buffer_get_selection (text->text, &start, &end);
800 switch (keycode) {
801 case SWFDEC_KEY_LEFT:
802 if (swfdec_text_buffer_has_selection (text->text)) {
803 swfdec_text_buffer_set_cursor (text->text, start, start);
804 } else {
805 start = BACKWARD (string, start);
806 swfdec_text_buffer_set_cursor (text->text, start, start);
808 return;
809 case SWFDEC_KEY_RIGHT:
810 if (swfdec_text_buffer_has_selection (text->text)) {
811 swfdec_text_buffer_set_cursor (text->text, end, end);
812 } else {
813 start = FORWARD (string, start);
814 swfdec_text_buffer_set_cursor (text->text, start, start);
816 return;
817 case SWFDEC_KEY_BACKSPACE:
818 if (!swfdec_text_buffer_has_selection (text->text)) {
819 start = BACKWARD (string, start);
821 if (start != end) {
822 swfdec_text_buffer_delete_text (text->text, start, end - start);
824 return;
825 case SWFDEC_KEY_DELETE:
826 if (!swfdec_text_buffer_has_selection (text->text)) {
827 end = FORWARD (string, end);
829 if (start != end) {
830 swfdec_text_buffer_delete_text (text->text, start, end - start);
832 return;
833 default:
834 break;
837 if (character == 0)
838 return;
839 len = g_unichar_to_utf8 (character, insert);
840 if (len) {
841 insert[len] = 0;
842 if (start != end)
843 swfdec_text_buffer_delete_text (text->text, start, end - start);
844 swfdec_text_buffer_insert_text (text->text, start, insert);
848 static void
849 swfdec_text_field_movie_key_release (SwfdecActor *actor, guint keycode, guint character)
851 //SwfdecTextFieldMovie *text = SWFDEC_TEXT_FIELD_MOVIE (actor);
854 static SwfdecAsValue
855 swfdec_text_field_movie_property_get (SwfdecMovie *movie, guint prop_id)
857 SwfdecTextFieldMovie *text = SWFDEC_TEXT_FIELD_MOVIE (movie);
858 SwfdecAsContext *cx = swfdec_gc_object_get_context (text);
859 double d;
861 switch (prop_id) {
862 case SWFDEC_MOVIE_PROPERTY_X:
863 swfdec_text_field_movie_autosize (text);
864 swfdec_movie_update (movie);
865 d = SWFDEC_TWIPS_TO_DOUBLE (movie->matrix.x0 + text->extents.x0);
866 return swfdec_as_value_from_number (cx, d);
867 case SWFDEC_MOVIE_PROPERTY_Y:
868 swfdec_text_field_movie_autosize (text);
869 swfdec_movie_update (movie);
870 d = SWFDEC_TWIPS_TO_DOUBLE (movie->matrix.y0 + text->extents.y0);
871 return swfdec_as_value_from_number (cx, d);
872 case SWFDEC_MOVIE_PROPERTY_WIDTH:
873 case SWFDEC_MOVIE_PROPERTY_HEIGHT:
874 swfdec_text_field_movie_autosize (text);
875 break;
876 default:
877 break;
880 return SWFDEC_MOVIE_CLASS (swfdec_text_field_movie_parent_class)->property_get (movie, prop_id);
883 static void
884 swfdec_text_field_movie_property_set (SwfdecMovie *movie, guint prop_id,
885 SwfdecAsValue val)
887 SwfdecTextFieldMovie *text = SWFDEC_TEXT_FIELD_MOVIE (movie);
888 SwfdecAsContext *cx = swfdec_gc_object_get_context (movie);
889 SwfdecTwips twips;
891 switch (prop_id) {
892 case SWFDEC_MOVIE_PROPERTY_X:
893 if (!swfdec_as_value_to_twips (swfdec_gc_object_get_context (movie), &val, FALSE, &twips))
894 return;
895 movie->modified = TRUE;
896 twips -= text->extents.x0;
897 if (twips != movie->matrix.x0) {
898 swfdec_movie_begin_update_matrix (movie);
899 movie->matrix.x0 = twips;
900 swfdec_movie_end_update_matrix (movie);
902 return;
903 case SWFDEC_MOVIE_PROPERTY_Y:
904 if (!swfdec_as_value_to_twips (swfdec_gc_object_get_context (movie), &val, FALSE, &twips))
905 return;
906 movie->modified = TRUE;
907 twips -= text->extents.y0;
908 if (twips != movie->matrix.y0) {
909 swfdec_movie_begin_update_matrix (movie);
910 movie->matrix.y0 = twips;
911 swfdec_movie_end_update_matrix (movie);
913 return;
914 case SWFDEC_MOVIE_PROPERTY_WIDTH:
915 if (swfdec_as_value_to_twips (cx, &val, TRUE, &twips)) {
916 movie->modified = TRUE;
917 if (text->extents.x1 != text->extents.x0 + twips) {
918 swfdec_movie_invalidate_next (movie);
919 swfdec_movie_queue_update (movie, SWFDEC_MOVIE_INVALID_EXTENTS);
920 text->extents.x1 = text->extents.x0 + twips;
921 swfdec_text_field_movie_update_area (text);
924 return;
925 case SWFDEC_MOVIE_PROPERTY_HEIGHT:
926 movie->modified = TRUE;
927 if (swfdec_as_value_to_twips (cx, &val, TRUE, &twips)) {
928 movie->modified = TRUE;
929 if (text->extents.y1 != text->extents.y0 + twips) {
930 swfdec_movie_invalidate_next (movie);
931 swfdec_movie_queue_update (movie, SWFDEC_MOVIE_INVALID_EXTENTS);
932 text->extents.y1 = text->extents.y0 + twips;
933 swfdec_text_field_movie_update_area (text);
936 return;
937 default:
938 break;
941 SWFDEC_MOVIE_CLASS (swfdec_text_field_movie_parent_class)->property_set (movie, prop_id, val);
944 static void
945 swfdec_text_field_movie_class_init (SwfdecTextFieldMovieClass * g_class)
947 GObjectClass *object_class = G_OBJECT_CLASS (g_class);
948 SwfdecGcObjectClass *gc_class = SWFDEC_GC_OBJECT_CLASS (g_class);
949 SwfdecMovieClass *movie_class = SWFDEC_MOVIE_CLASS (g_class);
950 SwfdecActorClass *actor_class = SWFDEC_ACTOR_CLASS (g_class);
952 object_class->dispose = swfdec_text_field_movie_dispose;
954 gc_class->mark = swfdec_text_field_movie_mark;
956 movie_class->init_movie = swfdec_text_field_movie_init_movie;
957 movie_class->finish_movie = swfdec_text_field_movie_finish_movie;
958 movie_class->update_extents = swfdec_text_field_movie_update_extents;
959 movie_class->render = swfdec_text_field_movie_render;
960 movie_class->invalidate = swfdec_text_field_movie_invalidate;
961 movie_class->contains = swfdec_text_field_movie_contains;
962 movie_class->property_get = swfdec_text_field_movie_property_get;
963 movie_class->property_set = swfdec_text_field_movie_property_set;
965 actor_class->mouse_cursor = swfdec_text_field_movie_mouse_cursor;
966 actor_class->mouse_events = swfdec_text_field_movie_mouse_events;
967 actor_class->mouse_press = swfdec_text_field_movie_mouse_press;
968 actor_class->mouse_release = swfdec_text_field_movie_mouse_release;
969 actor_class->mouse_move = swfdec_text_field_movie_mouse_move;
970 actor_class->focus_in = swfdec_text_field_movie_focus_in;
971 actor_class->focus_out = swfdec_text_field_movie_focus_out;
972 actor_class->key_press = swfdec_text_field_movie_key_press;
973 actor_class->key_release = swfdec_text_field_movie_key_release;
975 actor_class->iterate_start = swfdec_text_field_movie_iterate;
978 static void
979 swfdec_text_field_movie_text_changed (SwfdecTextBuffer *buffer,
980 SwfdecTextFieldMovie *text)
982 swfdec_movie_invalidate_last (SWFDEC_MOVIE (text));
983 text->changed++;
986 static void
987 swfdec_text_field_movie_cursor_changed (SwfdecTextBuffer *buffer,
988 gulong start, gulong end, SwfdecTextFieldMovie *text)
990 swfdec_movie_invalidate_last (SWFDEC_MOVIE (text));
991 /* FIXME: update selection */
994 static void
995 swfdec_text_field_movie_init (SwfdecTextFieldMovie *text)
997 text->text = swfdec_text_buffer_new ();
998 g_signal_connect (text->text, "cursor-changed",
999 G_CALLBACK (swfdec_text_field_movie_cursor_changed), text);
1000 g_signal_connect (text->text, "text-changed",
1001 G_CALLBACK (swfdec_text_field_movie_text_changed), text);
1003 text->layout = swfdec_text_layout_new (text->text);
1005 text->mouse_wheel_enabled = TRUE;
1006 text->character_pressed = -1;
1009 void
1010 swfdec_text_field_movie_set_listen_variable_text (SwfdecTextFieldMovie *text,
1011 const char *value)
1013 SwfdecAsObject *object;
1014 const char *name;
1016 g_return_if_fail (SWFDEC_IS_TEXT_FIELD_MOVIE (text));
1017 g_return_if_fail (text->variable != NULL);
1018 g_return_if_fail (value != NULL);
1020 swfdec_text_field_movie_parse_listen_variable (text, text->variable,
1021 &object, &name);
1022 if (object != NULL) {
1023 SwfdecAsValue val;
1024 SWFDEC_AS_VALUE_SET_STRING (&val, value);
1025 swfdec_as_object_set_variable (object, name, &val);
1029 static void
1030 swfdec_text_field_movie_variable_listener_callback (gpointer data,
1031 const char *name, const SwfdecAsValue *val)
1033 SwfdecTextFieldMovie *text = SWFDEC_TEXT_FIELD_MOVIE (data);
1035 swfdec_text_field_movie_set_text (text,
1036 swfdec_as_value_to_string (swfdec_gc_object_get_context (text), *val), text->html);
1039 void
1040 swfdec_text_field_movie_set_listen_variable (SwfdecTextFieldMovie *text,
1041 const char *value)
1043 SwfdecAsObject *object;
1044 const char *name;
1046 if (text->variable != NULL) {
1047 swfdec_text_field_movie_parse_listen_variable (text, text->variable,
1048 &object, &name);
1049 if (object != NULL && object->movie) {
1050 swfdec_movie_remove_variable_listener (SWFDEC_MOVIE (object->relay),
1051 text, name, swfdec_text_field_movie_variable_listener_callback);
1055 text->variable = value;
1057 if (value != NULL) {
1058 SwfdecTextField *text_field =
1059 SWFDEC_TEXT_FIELD (SWFDEC_MOVIE (text)->graphic);
1060 SwfdecAsValue val;
1062 swfdec_text_field_movie_parse_listen_variable (text, value, &object,
1063 &name);
1064 if (object != NULL && swfdec_as_object_get_variable (object, name, &val)) {
1065 swfdec_text_field_movie_set_text (text,
1066 swfdec_as_value_to_string (swfdec_gc_object_get_context (text), val),
1067 text->html);
1068 } else if (text_field != NULL && text_field->input != NULL) {
1069 // Set to the original value from the tag, not current value
1070 const char *initial = swfdec_as_context_get_string (
1071 swfdec_gc_object_get_context (text), text_field->input);
1072 swfdec_text_field_movie_set_listen_variable_text (text, initial);
1073 // FIXME: html value correct here?
1074 swfdec_text_field_movie_set_text (text, initial, text_field->html);
1076 if (object != NULL && object->movie) {
1077 swfdec_movie_add_variable_listener (SWFDEC_MOVIE (object->relay),
1078 text, name, swfdec_text_field_movie_variable_listener_callback);
1083 const char *
1084 swfdec_text_field_movie_get_text (SwfdecTextFieldMovie *text)
1086 char *ret, *p;
1087 const char *org;
1088 gsize filled, len;
1090 org = swfdec_text_buffer_get_text (text->text);
1091 len = swfdec_text_buffer_get_length (text->text);
1093 ret = g_new (char, len + 1);
1094 /* remove all \r */
1095 filled = 0;
1096 while ((p = strchr (org, '\r'))) {
1097 memcpy (ret + filled, org, p - org);
1098 filled += p - org;
1099 org = p + 1;
1100 len--;
1102 g_assert (len >= filled);
1103 memcpy (ret + filled, org, len - filled);
1104 ret[len] = 0;
1106 /* change all \n to \r */
1107 p = ret;
1108 while ((p = strchr (p, '\n')) != NULL) {
1109 *p = '\r';
1112 return swfdec_as_context_give_string (swfdec_gc_object_get_context (text), ret);
1115 void
1116 swfdec_text_field_movie_set_text (SwfdecTextFieldMovie *text, const char *str,
1117 gboolean html)
1119 gsize length;
1121 g_return_if_fail (SWFDEC_IS_TEXT_FIELD_MOVIE (text));
1122 g_return_if_fail (str != NULL);
1124 /* Flash 7 resets to default style here */
1125 if (html && swfdec_gc_object_get_context (text)->version < 8)
1126 swfdec_text_buffer_reset_default_attributes (text->text);
1128 length = swfdec_text_buffer_get_length (text->text);
1129 if (length)
1130 swfdec_text_buffer_delete_text (text->text, 0, length);
1132 if (swfdec_gc_object_get_context (text)->version >= 7 &&
1133 text->style_sheet != NULL)
1135 text->style_sheet_input = str;
1136 swfdec_text_field_movie_html_parse (text, str);
1138 else
1140 text->style_sheet_input = NULL;
1141 if (html) {
1142 swfdec_text_field_movie_html_parse (text, str);
1143 } else {
1144 char *s, *p;
1145 s = p = g_strdup (str);
1146 while ((p = strchr (p, '\r')))
1147 *p = '\n';
1148 swfdec_text_buffer_insert_text (text->text, 0, s);
1149 g_free (s);
1154 guint
1155 swfdec_text_field_movie_get_hscroll_max (SwfdecTextFieldMovie *text)
1157 g_return_val_if_fail (SWFDEC_IS_TEXT_FIELD_MOVIE (text), 0);
1159 if ((guint) text->layout_area.width >= text->layout_width ||
1160 swfdec_text_layout_get_word_wrap (text->layout))
1161 return 0;
1162 else
1163 return text->layout_width - text->layout_area.width;