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>
28 #include "laugh-timing.h"
30 #define LAUGH_TIMING_CONTAINER_GET_PRIVATE(obj) \
31 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), LAUGH_TYPE_TIMING_CONTAINER, LaughTimingContainerPrivate))
34 static gpointer laugh_timing_container_parent_class
= ((void *)0);
36 static void laugh_timing_container_finalize (GObject
*object
)
38 G_OBJECT_CLASS (laugh_timing_container_parent_class
)->finalize (object
);
41 static void laugh_timing_container_dispose (GObject
*object
)
43 LaughTimingContainer
*self
= LAUGH_TIMING_CONTAINER(object
);
44 LaughTimingSegment
*segment
= self
->timing_segment
;
46 G_OBJECT_CLASS (laugh_timing_container_parent_class
)->dispose (object
);
48 laugh_timing_segment_delete (segment
);
52 _laugh_timing_container_init (LaughNode
*node
, LaughInitializer
*initializer
)
54 LaughTimingContainer
*self
= (LaughTimingContainer
*) node
;
56 LaughTimingSegment
*parent_segment
= initializer
->parent_segment
;
59 g_hash_table_foreach (node
->attributes
, laugh_attributes_set
, node
);
61 laugh_timing_segment_add(initializer
->parent_segment
, self
->timing_segment
);
63 initializer
->parent_segment
= self
->timing_segment
;
65 for (child
= node
->first_child
; child
; child
= child
->next_sibling
)
66 laugh_node_init (child
, initializer
);
68 initializer
->parent_segment
= parent_segment
;
71 static void _laugh_timing_container_set_attribute (LaughNode
*node
,
72 LaughNodeAttributeId att
, const gchar
*value
, gpointer
*undo
)
74 const gchar
*val
= value
;
75 LaughTimingContainer
*self
= (LaughTimingContainer
*)node
;
77 laugh_node_base_set_attribute (node
, att
, val
, undo
);
78 /*g_printf ("_laugh_timing_container_set_attribute %s=%s\n", laugh_attribute_from_id (att), val);*/
81 val
= *(const gchar
**)undo
;
83 laugh_timing_setting_set_attribute (self
->timing_segment
, att
, val
);
86 static void laugh_timing_container_class_init (LaughTimingContainerClass
*klass
)
88 GObjectClass
*gobject_class
= G_OBJECT_CLASS (klass
);
89 LaughNodeClass
*node_class
= (LaughNodeClass
*) klass
;
91 laugh_timing_container_parent_class
= g_type_class_peek_parent (klass
);
93 gobject_class
->finalize
= laugh_timing_container_finalize
;
94 gobject_class
->dispose
= laugh_timing_container_dispose
;
95 node_class
->init
= _laugh_timing_container_init
;
96 node_class
->set_attribute
= _laugh_timing_container_set_attribute
;
98 /*g_type_class_add_private (gobject_class, sizeof (LaughTimingContainerPrivate));*/
102 void laugh_timing_container_instance_init (GTypeInstance
*instance
, gpointer g_class
)
104 LaughTimingContainer
*self
= (LaughTimingContainer
*)instance
;
106 /*self->priv = LAUGH_TIMING_CONTAINER_GET_PRIVATE (self);*/
108 self
->timing_segment
= g_new0 (LaughTimingSegment
, 1);
109 self
->timing_segment
->node
= (LaughNode
*) self
;
112 GType
laugh_timing_container_get_type (void)
114 static GType type
= 0;
116 static const GTypeInfo info
= {
117 sizeof (LaughTimingContainerClass
),
118 NULL
, /* base_init */
119 NULL
, /* base_finalize */
120 (GClassInitFunc
) laugh_timing_container_class_init
, /* class_init */
121 NULL
, /* class_finalize */
122 NULL
, /* class_data */
123 sizeof (LaughTimingContainer
),
125 laugh_timing_container_instance_init
/* instance_init */
127 type
= g_type_register_static (LAUGH_TYPE_NODE
,
128 "LaughTimingContainerType",
134 static int _timing_parse_time (const gchar
*tstr
, int *value
) {
137 const gchar
*nstart
= NULL
;
138 const gchar
*p
= tstr
;
150 } else if (*p
== '-') {
155 } else if (*p
>= '0' && *p
<= '9') {
158 } else if (*p
== '.') {
164 } else if (*p
== ' ') {
173 num
= g_strndup (nstart
, p
- tstr
);
174 *value
= sign
* (int) (1000 * g_strtod (nstart
, NULL
));
180 } else if (*p
== 'h') {
183 } else if (*p
!= ' ')
192 static int _get_timing (const gchar
*tstr
, LaughTiming
*timing
) {
194 const gchar
*p
= tstr
;
202 timing
->type
= LaughTimingUnknown
;
206 lower
= g_ascii_strdown (p
, -1);
209 if (_timing_parse_time (lower
, &timing
->offset
)) {
210 timing
->type
= LaughTimingTime
;
211 } else if (!strncmp (lower
, "id(", 3)) {
212 p
= (const gchar
*) strchr (lower
+ 3, ')');
214 idref
= g_strndup (lower
+ 3, p
- lower
- 3);
218 const char *q
= strchr (p
, '(');
222 } else if (!strncmp (lower
, "indefinite", 10)) {
223 timing
->type
= LaughTimingIndefinite
;
224 } else if (!strncmp (lower
, "media", 5)) {
225 timing
->type
= LaughTimingMedia
;
227 if (LaughTimingUnknown
== timing
->type
) {
228 const gchar
*idref_start
= p
;
233 last_esc
= last_esc
? 0 : 1;
234 } else if (*p
== '.' && !last_esc
) {
239 idref
= g_strdup (idref_start
);
241 idref
= g_strndup (idref_start
, p
- idref_start
);
245 timing
->element_id
= idref
;
246 if (_timing_parse_time (p
, &timing
->offset
)) {
247 timing
->type
= LaughTimingStartSync
;
248 } else if (*p
&& !strncmp (p
, "end", 3)) {
249 timing
->type
= LaughTimingEndSync
;
250 _timing_parse_time (p
+3, &timing
->offset
);
251 } else if (*p
&& !strncmp (p
, "begin", 5)) {
252 timing
->type
= LaughTimingStartSync
;
253 _timing_parse_time (p
+5, &timing
->offset
);
254 } else if (*p
&& !strncmp (p
, "activateevent", 13)) {
255 timing
->type
= LaughTimingActivated
;
256 _timing_parse_time (p
+13, &timing
->offset
);
257 } else if (*p
&& !strncmp (p
, "inboundsevent", 13)) {
258 timing
->type
= LaughTimingInbounds
;
259 _timing_parse_time (p
+13, &timing
->offset
);
260 } else if (*p
&& !strncmp (p
, "outofboundsevent", 16)) {
261 timing
->type
= LaughTimingOutbounds
;
262 _timing_parse_time (p
+16, &timing
->offset
);
264 g_printerr ("get_timings no match %s", lower
);
272 LaughTiming
*laugh_timing_new ()
274 return g_new0 (LaughTiming
, 1);
277 void laugh_timing_delete (LaughTiming
*timing
)
279 if (timing
->element_id
)
280 g_free (timing
->element_id
);
284 void laugh_timing_segment_delete (LaughTimingSegment
*segment
)
286 LaughTimingSegment
*parent
= segment
->parent
;
289 laugh_timing_delete (segment
->begin
);
291 laugh_timing_delete (segment
->dur
);
293 laugh_timing_delete (segment
->end
);
295 if (segment
->sub_segments
)
296 g_printerr ("TimingSegment %s leaking sub segments\n",
297 laugh_tag_from_id (segment
->node
->id
));
300 GSList
*s
= parent
->sub_segments
;
301 for ( ;s
; s
= s
->next
) {
302 GSList
*thread
= (GSList
*) s
->data
;
303 if (g_slist_find (thread
, segment
)) {
304 thread
= g_slist_remove (thread
, segment
);
306 parent
->sub_segments
= g_slist_remove (parent
->sub_segments
,
317 LaughTiming
*laugh_timing_new_from_string (const gchar
*value
)
319 LaughTiming
*t
= laugh_timing_new ();
320 if (laugh_timing_set_value (t
, value
))
322 laugh_timing_delete (t
);
326 void laugh_timing_segment_add (LaughTimingSegment
*s
, LaughTimingSegment
*child
)
328 if (!s
->sub_segments
|| LaughTagIdPar
== s
->node
->id
) {
329 s
->sub_segments
= g_slist_append (s
->sub_segments
,
330 g_slist_append (NULL
, child
));
332 GSList
*last
= g_slist_last (s
->sub_segments
);
333 last
->data
= g_slist_append ((GSList
*) last
->data
, child
);
339 gboolean
laugh_timing_setting_set_attribute (LaughTimingSegment
*segment
,
340 LaughNodeAttributeId att
, const gchar
*val
)
342 LaughTiming
**timing
;
345 case LaughAttrIdBegin
:
346 timing
= &segment
->begin
;
349 timing
= &segment
->dur
;
352 timing
= &segment
->end
;
359 laugh_timing_set_value (*timing
, val
);
361 laugh_timing_delete (*timing
);
365 *timing
= laugh_timing_new_from_string (val
);
371 void _laugh_timing_stop (LaughTiming
*timing
)
373 if (timing
&& timing
->handler_id
) {
374 switch (timing
->type
) {
375 case LaughTimingTime
:
376 g_source_remove (timing
->handler_id
);
378 /*TODO disconnect other types
379 g_signal_handler_disconnect (timing->handler_id);*/
383 timing
->handler_id
= 0;
387 static gboolean
_laugh_timing_timeout (gpointer data
);
389 static void _laugh_timing_setting_stop_freeze (LaughTimingSegment
*segment
)
393 _laugh_timing_stop (segment
->begin
);
394 _laugh_timing_stop (segment
->dur
);
395 _laugh_timing_stop (segment
->end
);
397 /*TODO determine freeze */
398 for (s
= segment
->sub_segments
; s
; s
= s
->next
) {
400 for (sub
= (GSList
*) s
->data
; sub
; sub
= sub
->next
) {
401 LaughTimingSegment
*seg
= (LaughTimingSegment
*) sub
->data
;
402 if (seg
->active
|| LaughStateFreezed
== seg
->node
->state
) {
403 laugh_timing_setting_stop (seg
);
409 segment
->active
= FALSE
;
412 static void _laugh_timing_setting_stopped (LaughTimingSegment
*segment
)
415 LaughTimingSegment
*parent
= segment
->parent
;
416 gboolean last_one
= TRUE
;
417 LaughTimingSegment
*start_next
= NULL
;
418 gboolean freeze
= FALSE
;
419 gboolean found
= FALSE
;
421 g_printf ("laugh_timing_setting_stopped %s\n",
422 laugh_tag_from_id (segment
->node
->id
));
425 for (s
= parent
->sub_segments
; s
; s
= s
->next
) {
426 GSList
*sub
= (GSList
*) s
->data
;
427 GSList
*l
= found
? NULL
: g_slist_find (sub
, segment
);
431 start_next
= (LaughTimingSegment
*) l
->next
->data
;
434 const gchar
*fill
= laugh_node_get_attribute (
435 segment
->node
, LaughAttrIdFill
);
436 if ((!fill
&& !segment
->dur
&& !segment
->end
) ||
437 (fill
&& !strcmp (fill
, "freeze")) ||
438 (fill
&& !strcmp (fill
, "hold")))
441 } else if (last_one
) {
442 LaughTimingSegment
*seg
=
443 (LaughTimingSegment
*) g_slist_last (sub
)->data
;
444 last_one
= seg
->node
->state
>= LaughStateStopped
;
446 if (found
&& !last_one
)
451 _laugh_timing_setting_stop_freeze (segment
);
452 laugh_node_freeze (segment
->node
);
454 laugh_timing_setting_stop (segment
);
458 laugh_timing_setting_start (start_next
);
461 (parent
->dur
->type
== LaughTimingTime
&& !parent
->dur
->handler_id
)))
462 _laugh_timing_setting_stopped (parent
);
464 g_printf ("freeze %s\n", laugh_tag_from_id (segment
->node
->id
));
467 laugh_timing_setting_stop (segment
);
471 static void _laugh_timing_setting_started (LaughTimingSegment
*segment
)
475 g_printf ("laugh_timing_setting_started %s\n",
476 laugh_tag_from_id (segment
->node
->id
));
477 laugh_node_start (segment
->node
);
479 if (segment
->dur
&& LaughTimingTime
== segment
->dur
->type
) {
480 if (segment
->dur
->offset
> 0)
481 segment
->dur
->handler_id
= g_timeout_add (segment
->dur
->offset
,
482 _laugh_timing_timeout
, segment
);
484 _laugh_timing_setting_stopped (segment
);
487 if (segment
->active
) {
488 if (segment
->sub_segments
) {
489 for (s
= segment
->sub_segments
; s
&& segment
->active
; s
= s
->next
)
490 laugh_timing_setting_start (
491 (LaughTimingSegment
*)((GSList
*) s
->data
)->data
);
492 } else if (!segment
->dur
&& !segment
->end
) {
493 _laugh_timing_setting_stopped (segment
);
498 static gboolean
_laugh_timing_timeout (gpointer data
)
500 LaughTimingSegment
*segment
= (LaughTimingSegment
*) data
;
502 if (segment
->begin
&& segment
->begin
->handler_id
) {
503 laugh_timing_notify (segment
, segment
->begin
->handler_id
);
504 segment
->begin
->handler_id
= 0;
505 } else if (segment
->dur
&& segment
->dur
->handler_id
) {
506 laugh_timing_notify (segment
, segment
->dur
->handler_id
);
507 segment
->dur
->handler_id
= 0;
510 return FALSE
; /*TODO handle repeat*/
513 gboolean
laugh_timing_setting_start (LaughTimingSegment
*segment
)
515 LaughTimingType tt
= LaughTimingTime
;
518 g_printf ("laugh_timing_setting_start %s\n",
519 laugh_tag_from_id (segment
->node
->id
));
520 segment
->active
= TRUE
;
522 if (segment
->begin
) {
523 tt
= segment
->begin
->type
;
524 offset
= segment
->begin
->offset
;
528 case LaughTimingTime
:
530 segment
->begin
->handler_id
= g_timeout_add (offset
,
531 _laugh_timing_timeout
, segment
);
533 _laugh_timing_setting_started (segment
);
543 void laugh_timing_notify (LaughTimingSegment
*segment
, gulong handler_id
)
545 if (segment
->begin
&& segment
->begin
->handler_id
== handler_id
) {
546 _laugh_timing_setting_started (segment
);
547 } else if (segment
->dur
&& segment
->dur
->handler_id
== handler_id
) {
548 _laugh_timing_setting_stopped (segment
);
552 void laugh_timing_setting_stop (LaughTimingSegment
*segment
)
554 g_printf ("laugh_timing_setting_stop %s\n",
555 laugh_tag_from_id (segment
->node
->id
));
557 _laugh_timing_setting_stop_freeze (segment
);
559 laugh_node_stop (segment
->node
);
562 static LaughTimingSegment
*
563 _laugh_timing_settting_find (LaughTimingSegment
*segment
, const LaughNode
*node
)
565 if (segment
->node
== node
)
567 if (segment
->sub_segments
) {
569 for (s
= segment
->sub_segments
; s
; s
= s
->next
) {
571 for (sub
= (GSList
*) s
->data
; sub
; sub
= sub
->next
) {
572 LaughTimingSegment
*seg
= _laugh_timing_settting_find (
573 (LaughTimingSegment
*) sub
->data
, node
);
582 static void _laugh_timing_settting_sub_segments_force_stop (LaughTimingSegment
*segment
)
585 for (s
= segment
->sub_segments
; s
; s
= s
->next
) {
587 for (sub
= (GSList
*) s
->data
; sub
; sub
= sub
->next
) {
588 LaughTimingSegment
*seg
= (LaughTimingSegment
*) sub
->data
;
590 laugh_timing_setting_stop (seg
);
597 static void _laugh_timing_settting_recursive_start (LaughTimingSegment
*segment
)
600 g_printerr ("jump target starting underflow error\n");
603 if (!segment
->active
) {
604 _laugh_timing_settting_recursive_start (segment
->parent
);
605 laugh_timing_setting_start (segment
);
609 void laugh_timing_setting_jump (LaughNode
*target
)
611 LaughTimingSegment
*segment
= target
->document
->timing
;
612 LaughTimingSegment
*seg
;
614 if (LaughStateBegun
== target
->state
) {
615 g_printerr ("jump target already active\n");
619 segment
= _laugh_timing_settting_find (segment
, target
);
621 g_printerr ("jump target not in tree\n");
625 for (seg
= segment
->parent
; seg
; seg
= seg
->parent
)
627 g_printf ("laugh_timing_setting_jump force stop %s\n",
628 laugh_tag_from_id (seg
->node
->id
));
629 if (LaughTagIdPar
!= seg
->node
->id
)
630 _laugh_timing_settting_sub_segments_force_stop (seg
);
631 _laugh_timing_settting_recursive_start (segment
);
636 LaughNode
*laugh_timing_container_new (LaughDocument
*doc
, LaughNodeTagId id
,
637 GHashTable
*attributes
)
639 LaughNode
*n
= (LaughNode
*)g_object_new(LAUGH_TYPE_TIMING_CONTAINER
, NULL
);
641 laugh_node_base_construct (doc
, n
, id
, attributes
);
646 int laugh_timing_set_value (LaughTiming
*timing
, const gchar
*value
)
648 return _get_timing (value
, timing
);
651 /*#define TIMING_TEST*/
655 /* gcc laugh-timing.c -o timing-test `pkg-config --cflags --libs glib-2.0` -DTIMING_TEST*/
657 #define TEST_PARSE_TIME(s) \
660 int b = _timing_parse_time(s, &val); \
661 g_printf( "_timing_parse_time(%s) => %d: %d\n", (s), b, val); \
664 const char *timing_string (LaughTimingType tt
) {
666 case LaughTimingUnknown
:
668 case LaughTimingTime
:
670 case LaughTimingIndefinite
:
672 case LaughTimingMedia
:
674 case LaughTimingActivated
:
676 case LaughTimingInbounds
:
678 case LaughTimingOutbounds
:
680 case LaughTimingEndSync
:
682 case LaughTimingStartSync
:
690 #define TEST_GET_TIMING(s) \
692 LaughTiming *t = laugh_timing_new_from_string(s); \
693 g_printf( "_get_timing %s => target %s event %s offset%d\n", \
694 (s), t->element_id, timing_string(t->type), t->offset); \
695 laugh_timing_delete (t); \
699 TEST_PARSE_TIME("12");
700 TEST_PARSE_TIME("12s");
701 TEST_PARSE_TIME("12m");
702 TEST_PARSE_TIME("12h");
703 TEST_PARSE_TIME("12.4");
704 TEST_PARSE_TIME("12.4s");
705 TEST_PARSE_TIME("12.4m");
706 TEST_PARSE_TIME("12.4h");
707 TEST_PARSE_TIME(".4");
708 TEST_PARSE_TIME(".4s");
709 TEST_PARSE_TIME("+.4");
710 TEST_PARSE_TIME("+.4s");
711 TEST_PARSE_TIME("-.4");
712 TEST_PARSE_TIME("-.4s");
713 TEST_GET_TIMING("4");
714 TEST_GET_TIMING("myimage.activateEvent");
715 TEST_GET_TIMING("myimage.activateEvent+2");
716 TEST_GET_TIMING("myimage.endSync");
717 TEST_GET_TIMING("myimage.endSync-2m");
718 TEST_GET_TIMING("indefinite");