4 * An glib SMIL library.
6 * Authored By Koos Vriezen <koos.vriezen@gmail.com>
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the
20 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 * Boston, MA 02111-1307, USA.
25 #include <glib/gmessages.h>
26 #include <glib/gprintf.h>
27 #include <clutter/clutter-main.h>
29 #include "laugh-timing.h"
31 #define LAUGH_TIMING_CONTAINER_GET_PRIVATE(obj) \
32 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), LAUGH_TYPE_TIMING_CONTAINER, LaughTimingContainerPrivate))
35 static gpointer laugh_timing_container_parent_class
= ((void *)0);
37 static void laugh_timing_container_finalize (GObject
*object
)
39 G_OBJECT_CLASS (laugh_timing_container_parent_class
)->finalize (object
);
42 static void laugh_timing_container_dispose (GObject
*object
)
44 LaughTimingContainer
*self
= LAUGH_TIMING_CONTAINER(object
);
45 LaughRoleTiming
*segment
= self
->timing_role
;
47 G_OBJECT_CLASS (laugh_timing_container_parent_class
)->dispose (object
);
49 laugh_timing_role_delete (segment
);
53 _laugh_timing_container_init (LaughNode
*node
, LaughInitializer
*initializer
)
55 LaughTimingContainer
*self
= (LaughTimingContainer
*) node
;
57 LaughRoleTiming
*parent_segment
= initializer
->parent_segment
;
60 g_hash_table_foreach (node
->attributes
, laugh_attributes_set
, node
);
62 laugh_timing_role_add(initializer
->parent_segment
, self
->timing_role
);
64 initializer
->parent_segment
= self
->timing_role
;
66 for (child
= node
->first_child
; child
; child
= child
->next_sibling
)
67 laugh_node_init (child
, initializer
);
69 initializer
->parent_segment
= parent_segment
;
72 static void _laugh_timing_container_set_attribute (LaughNode
*node
,
73 LaughNodeAttributeId att
, const gchar
*value
, gpointer
*undo
)
75 const gchar
*val
= value
;
76 LaughTimingContainer
*self
= (LaughTimingContainer
*)node
;
78 laugh_node_base_set_attribute (node
, att
, val
, undo
);
79 /*g_printf ("_laugh_timing_container_set_attribute %s=%s\n", laugh_attribute_from_id (att), val);*/
82 val
= *(const gchar
**)undo
;
84 laugh_timing_setting_set_attribute (self
->timing_role
, att
, val
);
87 static LaughRole
*_laugh_timing_container_role (LaughNode
*node
, LaughRoleType type
)
89 LaughTimingContainer
*self
= (LaughTimingContainer
*) node
;
92 case LaughRoleTypeTiming
:
93 return (LaughRole
*) self
->timing_role
;
99 static void laugh_timing_container_class_init (LaughTimingContainerClass
*klass
)
101 GObjectClass
*gobject_class
= G_OBJECT_CLASS (klass
);
102 LaughNodeClass
*node_class
= (LaughNodeClass
*) klass
;
104 laugh_timing_container_parent_class
= g_type_class_peek_parent (klass
);
106 gobject_class
->finalize
= laugh_timing_container_finalize
;
107 gobject_class
->dispose
= laugh_timing_container_dispose
;
108 node_class
->init
= _laugh_timing_container_init
;
109 node_class
->set_attribute
= _laugh_timing_container_set_attribute
;
110 node_class
->role
= _laugh_timing_container_role
;
112 /*g_type_class_add_private (gobject_class, sizeof (LaughTimingContainerPrivate));*/
116 void laugh_timing_container_instance_init (GTypeInstance
*instance
, gpointer g_class
)
118 LaughTimingContainer
*self
= (LaughTimingContainer
*)instance
;
120 /*self->priv = LAUGH_TIMING_CONTAINER_GET_PRIVATE (self);*/
122 self
->timing_role
= laugh_timing_role_new ((LaughNode
*) self
);
125 GType
laugh_timing_container_get_type (void)
127 static GType type
= 0;
129 static const GTypeInfo info
= {
130 sizeof (LaughTimingContainerClass
),
131 NULL
, /* base_init */
132 NULL
, /* base_finalize */
133 (GClassInitFunc
) laugh_timing_container_class_init
, /* class_init */
134 NULL
, /* class_finalize */
135 NULL
, /* class_data */
136 sizeof (LaughTimingContainer
),
138 laugh_timing_container_instance_init
/* instance_init */
140 type
= g_type_register_static (LAUGH_TYPE_NODE
,
141 "LaughTimingContainerType",
147 static int _timing_parse_time (const gchar
*tstr
, int *value
) {
150 const gchar
*nstart
= NULL
;
151 const gchar
*p
= tstr
;
163 } else if (*p
== '-') {
168 } else if (*p
>= '0' && *p
<= '9') {
171 } else if (*p
== '.') {
177 } else if (*p
== ' ') {
186 num
= g_strndup (nstart
, p
- tstr
);
187 *value
= sign
* (int) (1000 * g_strtod (nstart
, NULL
));
193 } else if (*p
== 'h') {
196 } else if (*p
!= ' ')
205 static int _get_timing (const gchar
*tstr
, LaughTiming
*timing
) {
208 const gchar
*p
= tstr
;
211 for ( ; *p
; ++p
, ++orig
) {
216 timing
->type
= LaughTimingUnknown
;
220 lower
= g_ascii_strdown (p
, -1);
223 if (_timing_parse_time (lower
, &timing
->offset
)) {
224 timing
->type
= LaughTimingTime
;
225 } else if (!strncmp (lower
, "id(", 3)) {
226 p
= (const gchar
*) strchr (lower
+ 3, ')');
228 idref
= g_strndup (orig
+ 3, p
- lower
- 3);
232 const char *q
= strchr (p
, '(');
236 } else if (!strncmp (lower
, "indefinite", 10)) {
237 timing
->type
= LaughTimingIndefinite
;
238 } else if (!strncmp (lower
, "media", 5)) {
239 timing
->type
= LaughTimingMedia
;
241 if (LaughTimingUnknown
== timing
->type
) {
242 const gchar
*idref_start
= p
;
247 last_esc
= last_esc
? 0 : 1;
248 } else if (*p
== '.' && !last_esc
) {
253 idref
= g_strdup (orig
+ (idref_start
- lower
));
255 idref
= g_strndup (orig
+ (idref_start
- lower
), p
- idref_start
);
259 timing
->element_id
= idref
;
260 if (_timing_parse_time (p
, &timing
->offset
)) {
261 timing
->type
= LaughTimingStartSync
;
262 } else if (*p
&& !strncmp (p
, "end", 3)) {
263 timing
->type
= LaughTimingEndSync
;
264 _timing_parse_time (p
+3, &timing
->offset
);
265 } else if (*p
&& !strncmp (p
, "begin", 5)) {
266 timing
->type
= LaughTimingStartSync
;
267 _timing_parse_time (p
+5, &timing
->offset
);
268 } else if (*p
&& !strncmp (p
, "activateevent", 13)) {
269 timing
->type
= LaughTimingActivated
;
270 _timing_parse_time (p
+13, &timing
->offset
);
271 } else if (*p
&& !strncmp (p
, "inboundsevent", 13)) {
272 timing
->type
= LaughTimingInbounds
;
273 _timing_parse_time (p
+13, &timing
->offset
);
274 } else if (*p
&& !strncmp (p
, "outofboundsevent", 16)) {
275 timing
->type
= LaughTimingOutbounds
;
276 _timing_parse_time (p
+16, &timing
->offset
);
278 g_printerr ("get_timings no match %s", lower
);
286 LaughTiming
*laugh_timing_new ()
288 return g_new0 (LaughTiming
, 1);
291 void laugh_timing_delete (LaughTiming
*timing
)
293 if (timing
->element_id
)
294 g_free (timing
->element_id
);
295 if (LaughTimingTime
== timing
->type
&& timing
->handler_id
)
296 g_source_remove (timing
->handler_id
);
300 LaughRoleTiming
*laugh_timing_role_new (LaughNode
*node
)
302 LaughRoleTiming
*segment
= g_new0 (LaughRoleTiming
, 1);
304 segment
->role
.type
= LaughRoleTypeTiming
;
305 segment
->node
= node
;
310 void laugh_timing_role_delete (LaughRoleTiming
*segment
)
312 LaughRoleTiming
*parent
= segment
->parent
;
315 laugh_timing_delete (segment
->begin
);
317 laugh_timing_delete (segment
->dur
);
319 laugh_timing_delete (segment
->end
);
321 if (segment
->sub_segments
)
322 g_printerr ("TimingSegment %s leaking sub segments\n",
323 laugh_tag_from_id (segment
->node
->id
));
326 GSList
*s
= parent
->sub_segments
;
327 for ( ;s
; s
= s
->next
) {
328 GSList
*thread
= (GSList
*) s
->data
;
329 if (g_slist_find (thread
, segment
)) {
330 thread
= g_slist_remove (thread
, segment
);
332 parent
->sub_segments
= g_slist_remove (parent
->sub_segments
,
343 LaughTiming
*laugh_timing_new_from_string (const gchar
*value
)
345 LaughTiming
*t
= laugh_timing_new ();
346 if (laugh_timing_set_value (t
, value
))
348 laugh_timing_delete (t
);
352 void laugh_timing_role_add (LaughRoleTiming
*s
, LaughRoleTiming
*child
)
354 if (!s
->sub_segments
|| LaughTagIdPar
== s
->node
->id
) {
355 s
->sub_segments
= g_slist_append (s
->sub_segments
,
356 g_slist_append (NULL
, child
));
358 GSList
*last
= g_slist_last (s
->sub_segments
);
359 last
->data
= g_slist_append ((GSList
*) last
->data
, child
);
365 gboolean
laugh_timing_setting_set_attribute (LaughRoleTiming
*segment
,
366 LaughNodeAttributeId att
, const gchar
*val
)
368 LaughTiming
**timing
;
371 case LaughAttrIdBegin
:
372 timing
= &segment
->begin
;
375 timing
= &segment
->dur
;
378 timing
= &segment
->end
;
385 laugh_timing_set_value (*timing
, val
);
387 laugh_timing_delete (*timing
);
391 *timing
= laugh_timing_new_from_string (val
);
397 void _laugh_timing_stop (LaughTiming
*timing
)
399 if (timing
&& timing
->handler_id
) {
400 switch (timing
->type
) {
401 case LaughTimingTime
:
402 g_source_remove (timing
->handler_id
);
404 /*TODO disconnect other types
405 g_signal_handler_disconnect (timing->handler_id);*/
409 timing
->handler_id
= 0;
413 static gboolean
_laugh_timing_timeout (gpointer data
);
415 static void _laugh_timing_setting_stop_freeze (LaughRoleTiming
*segment
)
419 _laugh_timing_stop (segment
->begin
);
420 _laugh_timing_stop (segment
->dur
);
421 _laugh_timing_stop (segment
->end
);
423 /*TODO determine freeze */
424 for (s
= segment
->sub_segments
; s
; s
= s
->next
) {
426 for (sub
= (GSList
*) s
->data
; sub
; sub
= sub
->next
) {
427 LaughRoleTiming
*seg
= (LaughRoleTiming
*) sub
->data
;
428 if (seg
->active
|| LaughStateFreezed
== seg
->node
->state
) {
429 laugh_timing_setting_stop (seg
);
435 segment
->active
= FALSE
;
438 static void _laugh_timing_setting_stopped (LaughRoleTiming
*segment
)
441 LaughRoleTiming
*parent
= segment
->parent
;
442 gboolean last_one
= TRUE
;
443 LaughRoleTiming
*start_next
= NULL
;
444 gboolean freeze
= FALSE
;
445 gboolean found
= FALSE
;
447 g_printf ("laugh_timing_setting_stopped %s\n",
448 laugh_tag_from_id (segment
->node
->id
));
451 for (s
= parent
->sub_segments
; s
; s
= s
->next
) {
452 GSList
*sub
= (GSList
*) s
->data
;
453 GSList
*l
= found
? NULL
: g_slist_find (sub
, segment
);
457 start_next
= (LaughRoleTiming
*) l
->next
->data
;
460 const gchar
*fill
= laugh_node_get_attribute (
461 segment
->node
, LaughAttrIdFill
);
462 if ((!fill
&& !segment
->dur
&& !segment
->end
) ||
463 (fill
&& !strcmp (fill
, "freeze")) ||
464 (fill
&& !strcmp (fill
, "hold")))
467 } else if (last_one
) {
468 LaughRoleTiming
*seg
=
469 (LaughRoleTiming
*) g_slist_last (sub
)->data
;
470 last_one
= seg
->node
->state
>= LaughStateStopped
;
472 if (found
&& !last_one
)
477 _laugh_timing_setting_stop_freeze (segment
);
478 laugh_node_freeze (segment
->node
);
480 laugh_timing_setting_stop (segment
);
484 laugh_timing_setting_start (start_next
);
487 (parent
->dur
->type
== LaughTimingTime
&& !parent
->dur
->handler_id
)))
488 _laugh_timing_setting_stopped (parent
);
490 g_printf ("freeze %s\n", laugh_tag_from_id (segment
->node
->id
));
493 laugh_timing_setting_stop (segment
);
497 static void _laugh_timing_setting_started (LaughRoleTiming
*segment
)
501 g_printf ("laugh_timing_setting_started %s\n",
502 laugh_tag_from_id (segment
->node
->id
));
503 laugh_node_start (segment
->node
);
505 if (segment
->dur
&& LaughTimingTime
== segment
->dur
->type
) {
506 if (segment
->dur
->offset
> 0)
507 segment
->dur
->handler_id
= g_timeout_add (segment
->dur
->offset
,
508 _laugh_timing_timeout
, segment
);
510 _laugh_timing_setting_stopped (segment
);
513 if (segment
->active
) {
514 if (segment
->sub_segments
) {
515 for (s
= segment
->sub_segments
; s
&& segment
->active
; s
= s
->next
)
516 laugh_timing_setting_start (
517 (LaughRoleTiming
*)((GSList
*) s
->data
)->data
);
518 } else if (!segment
->dur
&& !segment
->end
) {
519 _laugh_timing_setting_stopped (segment
);
524 static gboolean
_laugh_timing_timeout (gpointer data
)
526 LaughRoleTiming
*segment
= (LaughRoleTiming
*) data
;
528 if (segment
->begin
&& segment
->begin
->handler_id
) {
529 laugh_timing_notify (segment
, segment
->begin
->handler_id
);
530 segment
->begin
->handler_id
= 0;
531 } else if (segment
->dur
&& segment
->dur
->handler_id
) {
532 laugh_timing_notify (segment
, segment
->dur
->handler_id
);
533 segment
->dur
->handler_id
= 0;
536 return FALSE
; /*TODO handle repeat*/
539 gboolean
laugh_timing_setting_start (LaughRoleTiming
*segment
)
541 LaughTimingType tt
= LaughTimingTime
;
544 g_printf ("laugh_timing_setting_start %s\n",
545 laugh_tag_from_id (segment
->node
->id
));
546 segment
->active
= TRUE
;
547 segment
->start_time_stamp
= clutter_get_timestamp ();
549 if (segment
->begin
) {
550 tt
= segment
->begin
->type
;
551 offset
= segment
->begin
->offset
;
555 case LaughTimingTime
:
557 segment
->begin
->handler_id
= g_timeout_add (offset
,
558 _laugh_timing_timeout
, segment
);
560 _laugh_timing_setting_started (segment
);
562 case LaughTimingStartSync
:
563 if (segment
->begin
->element_id
) {
564 LaughRoleTiming
*role
;
565 LaughNode
*elm
= laugh_document_get_element_by_id (
566 segment
->node
->document
,
567 laugh_document_id_from_string (
568 segment
->node
->document
, segment
->begin
->element_id
));
571 role
= (LaughRoleTiming
*) laugh_node_role_get (elm
, LaughRoleTypeTiming
);
574 if (elm
->state
>= LaughStateBegun
) {
575 glong diff
= (clutter_get_timestamp ()-role
->start_time_stamp
) / 1000;
577 segment
->begin
->handler_id
= g_timeout_add (offset
- diff
,
578 _laugh_timing_timeout
, segment
);
580 _laugh_timing_setting_started (segment
);
582 g_printf ("TODO: laugh_timing_setting_start StartSync\n");
594 void laugh_timing_notify (LaughRoleTiming
*segment
, gulong handler_id
)
596 if (segment
->begin
&& segment
->begin
->handler_id
== handler_id
) {
597 _laugh_timing_setting_started (segment
);
598 } else if (segment
->dur
&& segment
->dur
->handler_id
== handler_id
) {
599 _laugh_timing_setting_stopped (segment
);
603 void laugh_timing_setting_stop (LaughRoleTiming
*segment
)
605 g_printf ("laugh_timing_setting_stop %s\n",
606 laugh_tag_from_id (segment
->node
->id
));
608 _laugh_timing_setting_stop_freeze (segment
);
610 laugh_node_stop (segment
->node
);
613 static LaughRoleTiming
*
614 _laugh_timing_settting_find (LaughRoleTiming
*segment
, const LaughNode
*node
)
616 if (segment
->node
== node
)
618 if (segment
->sub_segments
) {
620 for (s
= segment
->sub_segments
; s
; s
= s
->next
) {
622 for (sub
= (GSList
*) s
->data
; sub
; sub
= sub
->next
) {
623 LaughRoleTiming
*seg
= _laugh_timing_settting_find (
624 (LaughRoleTiming
*) sub
->data
, node
);
633 static void _laugh_timing_settting_sub_segments_force_stop (LaughRoleTiming
*segment
)
636 for (s
= segment
->sub_segments
; s
; s
= s
->next
) {
638 for (sub
= (GSList
*) s
->data
; sub
; sub
= sub
->next
) {
639 LaughRoleTiming
*seg
= (LaughRoleTiming
*) sub
->data
;
641 laugh_timing_setting_stop (seg
);
648 static void _laugh_timing_settting_recursive_start (LaughRoleTiming
*segment
)
651 g_printerr ("jump target starting underflow error\n");
654 if (!segment
->active
) {
655 _laugh_timing_settting_recursive_start (segment
->parent
);
656 laugh_timing_setting_start (segment
);
660 void laugh_timing_setting_jump (LaughNode
*target
)
662 LaughRoleTiming
*segment
= target
->document
->timing
;
663 LaughRoleTiming
*seg
;
665 if (LaughStateBegun
== target
->state
) {
666 g_printerr ("jump target already active\n");
670 segment
= _laugh_timing_settting_find (segment
, target
);
672 g_printerr ("jump target not in tree\n");
676 for (seg
= segment
->parent
; seg
; seg
= seg
->parent
)
678 g_printf ("laugh_timing_setting_jump force stop %s\n",
679 laugh_tag_from_id (seg
->node
->id
));
680 if (LaughTagIdPar
!= seg
->node
->id
)
681 _laugh_timing_settting_sub_segments_force_stop (seg
);
682 _laugh_timing_settting_recursive_start (segment
);
687 LaughNode
*laugh_timing_container_new (LaughDocument
*doc
, LaughNodeTagId id
,
688 GHashTable
*attributes
)
690 LaughNode
*n
= (LaughNode
*)g_object_new(LAUGH_TYPE_TIMING_CONTAINER
, NULL
);
692 laugh_node_base_construct (doc
, n
, id
, attributes
);
697 int laugh_timing_set_value (LaughTiming
*timing
, const gchar
*value
)
699 return _get_timing (value
, timing
);
702 /*#define TIMING_TEST*/
706 /* gcc laugh-timing.c -o timing-test `pkg-config --cflags --libs glib-2.0` -DTIMING_TEST*/
708 #define TEST_PARSE_TIME(s) \
711 int b = _timing_parse_time(s, &val); \
712 g_printf( "_timing_parse_time(%s) => %d: %d\n", (s), b, val); \
715 const char *timing_string (LaughTimingType tt
) {
717 case LaughTimingUnknown
:
719 case LaughTimingTime
:
721 case LaughTimingIndefinite
:
723 case LaughTimingMedia
:
725 case LaughTimingActivated
:
727 case LaughTimingInbounds
:
729 case LaughTimingOutbounds
:
731 case LaughTimingEndSync
:
733 case LaughTimingStartSync
:
741 #define TEST_GET_TIMING(s) \
743 LaughTiming *t = laugh_timing_new_from_string(s); \
744 g_printf( "_get_timing %s => target %s event %s offset%d\n", \
745 (s), t->element_id, timing_string(t->type), t->offset); \
746 laugh_timing_delete (t); \
750 TEST_PARSE_TIME("12");
751 TEST_PARSE_TIME("12s");
752 TEST_PARSE_TIME("12m");
753 TEST_PARSE_TIME("12h");
754 TEST_PARSE_TIME("12.4");
755 TEST_PARSE_TIME("12.4s");
756 TEST_PARSE_TIME("12.4m");
757 TEST_PARSE_TIME("12.4h");
758 TEST_PARSE_TIME(".4");
759 TEST_PARSE_TIME(".4s");
760 TEST_PARSE_TIME("+.4");
761 TEST_PARSE_TIME("+.4s");
762 TEST_PARSE_TIME("-.4");
763 TEST_PARSE_TIME("-.4s");
764 TEST_GET_TIMING("4");
765 TEST_GET_TIMING("myimage.activateEvent");
766 TEST_GET_TIMING("myimage.activateEvent+2");
767 TEST_GET_TIMING("myimage.endSync");
768 TEST_GET_TIMING("myimage.endSync-2m");
769 TEST_GET_TIMING("MyImage.endSync");
770 TEST_GET_TIMING("id(MyImage)(endSync)");
771 TEST_GET_TIMING("indefinite");