Partially implement start synced on an element begin time
[laugh.git] / src / laugh-timing.c
blob74092bf0e8a9a66861dec207bbbce14ce2ef6268
1 /*
2 * Laugh.
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.
24 #include <string.h>
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);
52 static void
53 _laugh_timing_container_init (LaughNode *node, LaughInitializer *initializer)
55 LaughTimingContainer *self = (LaughTimingContainer *) node;
56 LaughNode *child;
57 LaughRoleTiming *parent_segment = initializer->parent_segment;
59 if (node->attributes)
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);*/
81 if (!value && undo)
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;
91 switch (type) {
92 case LaughRoleTypeTiming:
93 return (LaughRole *) self->timing_role;
94 default:
95 return NULL;
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));*/
115 static
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;
128 if (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),
137 0, /* n_preallocs */
138 laugh_timing_container_instance_init /* instance_init */
140 type = g_type_register_static (LAUGH_TYPE_NODE,
141 "LaughTimingContainerType",
142 &info, 0);
144 return type;
147 static int _timing_parse_time (const gchar *tstr, int *value) {
148 int sign = 1;
149 int fp_seen = 0;
150 const gchar *nstart = NULL;
151 const gchar *p = tstr;
152 gchar *num;
154 if (!tstr)
155 return 0;
157 for ( ; *p; p++) {
158 if (*p == '+') {
159 if (nstart)
160 break;
161 else
162 sign = 1;
163 } else if (*p == '-') {
164 if (nstart)
165 break;
166 else
167 sign = -1;
168 } else if (*p >= '0' && *p <= '9') {
169 if (!nstart)
170 nstart = p;
171 } else if (*p == '.') {
172 if (fp_seen)
173 break;
174 fp_seen = 1;
175 if (!nstart)
176 nstart = p;
177 } else if (*p == ' ') {
178 if (nstart)
179 break;
180 } else
181 break;
183 if (!nstart)
184 return 0;
186 num = g_strndup (nstart, p - tstr);
187 *value = sign * (int) (1000 * g_strtod (nstart, NULL));
189 for ( ; *p; p++ ) {
190 if (*p == 'm') {
191 *value *= 60;
192 break;
193 } else if (*p == 'h') {
194 *value *= 3600;
195 break;
196 } else if (*p != ' ')
197 break;
200 g_free (num);
202 return 1;
205 static int _get_timing (const gchar *tstr, LaughTiming *timing) {
206 gchar *lower;
207 const gchar *p = tstr;
208 gchar *idref = NULL;
210 for ( ; *p; ++p) {
211 if (*p != ' ')
212 break;
215 timing->type = LaughTimingUnknown;
216 if (!p || !*p)
217 return 0;
219 lower = g_ascii_strdown (p, -1);
220 p = lower;
222 if (_timing_parse_time (lower, &timing->offset)) {
223 timing->type = LaughTimingTime;
224 } else if (!strncmp (lower, "id(", 3)) {
225 p = (const gchar *) strchr (lower + 3, ')');
226 if (p) {
227 idref = g_strndup (lower + 3, p - lower - 3);
228 p++;
230 if (*p) {
231 const char *q = strchr (p, '(');
232 if (q)
233 p = q;
235 } else if (!strncmp (lower, "indefinite", 10)) {
236 timing->type = LaughTimingIndefinite;
237 } else if (!strncmp (lower, "media", 5)) {
238 timing->type = LaughTimingMedia;
240 if (LaughTimingUnknown == timing->type) {
241 const gchar *idref_start = p;
242 if (!idref) {
243 int last_esc = 0;
244 for ( ; *p; p++) {
245 if (*p == '\\') {
246 last_esc = last_esc ? 0 : 1;
247 } else if (*p == '.' && !last_esc) {
248 break;
251 if (!*p)
252 idref = g_strdup (idref_start);
253 else
254 idref = g_strndup (idref_start, p - idref_start);
256 ++p;
257 if (idref) {
258 timing->element_id = idref;
259 if (_timing_parse_time (p, &timing->offset)) {
260 timing->type = LaughTimingStartSync;
261 } else if (*p && !strncmp (p, "end", 3)) {
262 timing->type = LaughTimingEndSync;
263 _timing_parse_time (p+3, &timing->offset);
264 } else if (*p && !strncmp (p, "begin", 5)) {
265 timing->type = LaughTimingStartSync;
266 _timing_parse_time (p+5, &timing->offset);
267 } else if (*p && !strncmp (p, "activateevent", 13)) {
268 timing->type = LaughTimingActivated;
269 _timing_parse_time (p+13, &timing->offset);
270 } else if (*p && !strncmp (p, "inboundsevent", 13)) {
271 timing->type = LaughTimingInbounds;
272 _timing_parse_time (p+13, &timing->offset);
273 } else if (*p && !strncmp (p, "outofboundsevent", 16)) {
274 timing->type = LaughTimingOutbounds;
275 _timing_parse_time (p+16, &timing->offset);
276 } else {
277 g_printerr ("get_timings no match %s", lower);
281 g_free (lower);
282 return 1;
285 LaughTiming *laugh_timing_new ()
287 return g_new0 (LaughTiming, 1);
290 void laugh_timing_delete (LaughTiming *timing)
292 if (timing->element_id)
293 g_free (timing->element_id);
294 if (LaughTimingTime == timing->type && timing->handler_id)
295 g_source_remove (timing->handler_id);
296 g_free (timing);
299 LaughRoleTiming *laugh_timing_role_new (LaughNode *node)
301 LaughRoleTiming *segment = g_new0 (LaughRoleTiming, 1);
303 segment->role.type = LaughRoleTypeTiming;
304 segment->node = node;
306 return segment;
309 void laugh_timing_role_delete (LaughRoleTiming *segment)
311 LaughRoleTiming *parent = segment->parent;
313 if (segment->begin)
314 laugh_timing_delete (segment->begin);
315 if (segment->dur)
316 laugh_timing_delete (segment->dur);
317 if (segment->end)
318 laugh_timing_delete (segment->end);
320 if (segment->sub_segments)
321 g_printerr ("TimingSegment %s leaking sub segments\n",
322 laugh_tag_from_id (segment->node->id));
324 if (parent) {
325 GSList *s = parent->sub_segments;
326 for ( ;s; s = s->next) {
327 GSList *thread = (GSList *) s->data;
328 if (g_slist_find (thread, segment)) {
329 thread = g_slist_remove (thread, segment);
330 if (!thread)
331 parent->sub_segments = g_slist_remove (parent->sub_segments,
332 s->data);
333 else
334 s->data = thread;
335 break;
339 g_free (segment);
342 LaughTiming *laugh_timing_new_from_string (const gchar *value)
344 LaughTiming *t = laugh_timing_new ();
345 if (laugh_timing_set_value (t, value))
346 return t;
347 laugh_timing_delete (t);
348 return NULL;
351 void laugh_timing_role_add (LaughRoleTiming *s, LaughRoleTiming *child)
353 if (!s->sub_segments || LaughTagIdPar == s->node->id) {
354 s->sub_segments = g_slist_append (s->sub_segments,
355 g_slist_append (NULL, child));
356 } else {
357 GSList *last = g_slist_last (s->sub_segments);
358 last->data = g_slist_append ((GSList *) last->data, child);
361 child->parent = s;
364 gboolean laugh_timing_setting_set_attribute (LaughRoleTiming *segment,
365 LaughNodeAttributeId att, const gchar *val)
367 LaughTiming **timing;
369 switch (att) {
370 case LaughAttrIdBegin:
371 timing = &segment->begin;
372 break;
373 case LaughAttrIdDur:
374 timing = &segment->dur;
375 break;
376 case LaughAttrIdEnd:
377 timing = &segment->end;
378 break;
379 default:
380 return FALSE;
382 if (*timing) {
383 if (val) {
384 laugh_timing_set_value (*timing, val);
385 } else {
386 laugh_timing_delete (*timing);
387 *timing = NULL;
389 } else if (val) {
390 *timing = laugh_timing_new_from_string (val);
393 return TRUE;
396 void _laugh_timing_stop (LaughTiming *timing)
398 if (timing && timing->handler_id) {
399 switch (timing->type) {
400 case LaughTimingTime:
401 g_source_remove (timing->handler_id);
402 break;
403 /*TODO disconnect other types
404 g_signal_handler_disconnect (timing->handler_id);*/
405 default:
406 break;
408 timing->handler_id = 0;
412 static gboolean _laugh_timing_timeout (gpointer data);
414 static void _laugh_timing_setting_stop_freeze (LaughRoleTiming *segment)
416 GSList *s;
418 _laugh_timing_stop (segment->begin);
419 _laugh_timing_stop (segment->dur);
420 _laugh_timing_stop (segment->end);
422 /*TODO determine freeze */
423 for (s = segment->sub_segments; s; s = s->next) {
424 GSList *sub;
425 for (sub = (GSList *) s->data; sub; sub = sub->next) {
426 LaughRoleTiming *seg = (LaughRoleTiming *) sub->data;
427 if (seg->active || LaughStateFreezed == seg->node->state) {
428 laugh_timing_setting_stop (seg);
429 break;
434 segment->active = FALSE;
437 static void _laugh_timing_setting_stopped (LaughRoleTiming *segment)
439 GSList *s;
440 LaughRoleTiming *parent = segment->parent;
441 gboolean last_one = TRUE;
442 LaughRoleTiming *start_next = NULL;
443 gboolean freeze = FALSE;
444 gboolean found = FALSE;
446 g_printf ("laugh_timing_setting_stopped %s\n",
447 laugh_tag_from_id (segment->node->id));
449 if (parent) {
450 for (s = parent->sub_segments; s; s = s->next) {
451 GSList *sub = (GSList *) s->data;
452 GSList *l = found ? NULL : g_slist_find (sub, segment);
453 if (l) {
454 found = TRUE;
455 if (l->next) {
456 start_next = (LaughRoleTiming*) l->next->data;
457 last_one = FALSE;
458 } else {
459 const gchar *fill = laugh_node_get_attribute (
460 segment->node, LaughAttrIdFill);
461 if ((!fill && !segment->dur && !segment->end) ||
462 (fill && !strcmp (fill, "freeze")) ||
463 (fill && !strcmp (fill, "hold")))
464 freeze = TRUE;
466 } else if (last_one) {
467 LaughRoleTiming *seg =
468 (LaughRoleTiming *) g_slist_last (sub)->data;
469 last_one = seg->node->state >= LaughStateStopped;
471 if (found && !last_one)
472 break;
475 if (freeze) {
476 _laugh_timing_setting_stop_freeze (segment);
477 laugh_node_freeze (segment->node);
478 } else {
479 laugh_timing_setting_stop (segment);
482 if (start_next)
483 laugh_timing_setting_start (start_next);
484 else if (last_one &&
485 (!parent->dur ||
486 (parent->dur->type == LaughTimingTime && !parent->dur->handler_id)))
487 _laugh_timing_setting_stopped (parent);
488 else if (freeze)
489 g_printf ("freeze %s\n", laugh_tag_from_id (segment->node->id));
491 } else {
492 laugh_timing_setting_stop (segment);
496 static void _laugh_timing_setting_started (LaughRoleTiming *segment)
498 GSList *s;
500 g_printf ("laugh_timing_setting_started %s\n",
501 laugh_tag_from_id (segment->node->id));
502 laugh_node_start (segment->node);
504 if (segment->dur && LaughTimingTime == segment->dur->type) {
505 if (segment->dur->offset > 0)
506 segment->dur->handler_id = g_timeout_add (segment->dur->offset,
507 _laugh_timing_timeout, segment);
508 else
509 _laugh_timing_setting_stopped (segment);
512 if (segment->active) {
513 if (segment->sub_segments) {
514 for (s = segment->sub_segments; s && segment->active; s = s->next)
515 laugh_timing_setting_start (
516 (LaughRoleTiming *)((GSList *) s->data)->data);
517 } else if (!segment->dur && !segment->end) {
518 _laugh_timing_setting_stopped (segment);
523 static gboolean _laugh_timing_timeout (gpointer data)
525 LaughRoleTiming *segment = (LaughRoleTiming *) data;
527 if (segment->begin && segment->begin->handler_id) {
528 laugh_timing_notify (segment, segment->begin->handler_id);
529 segment->begin->handler_id = 0;
530 } else if (segment->dur && segment->dur->handler_id) {
531 laugh_timing_notify (segment, segment->dur->handler_id);
532 segment->dur->handler_id = 0;
535 return FALSE; /*TODO handle repeat*/
538 gboolean laugh_timing_setting_start (LaughRoleTiming *segment)
540 LaughTimingType tt = LaughTimingTime;
541 int offset = 0;
543 g_printf ("laugh_timing_setting_start %s\n",
544 laugh_tag_from_id (segment->node->id));
545 segment->active = TRUE;
546 segment->start_time_stamp = clutter_get_timestamp ();
548 if (segment->begin) {
549 tt = segment->begin->type;
550 offset = segment->begin->offset;
553 switch (tt) {
554 case LaughTimingTime:
555 if (offset > 0)
556 segment->begin->handler_id = g_timeout_add (offset,
557 _laugh_timing_timeout, segment);
558 else
559 _laugh_timing_setting_started (segment);
560 break;
561 case LaughTimingStartSync:
562 if (segment->begin->element_id) {
563 LaughRoleTiming *role;
564 LaughNode *elm = laugh_document_get_element_by_id (
565 segment->node->document,
566 laugh_document_id_from_string (
567 segment->node->document, segment->begin->element_id));
568 if (!elm)
569 break;
570 role = (LaughRoleTiming *) laugh_node_role_get (elm, LaughRoleTypeTiming);
571 if (!role)
572 break;
573 if (elm->state >= LaughStateBegun) {
574 glong diff = (clutter_get_timestamp ()-role->start_time_stamp) / 1000;
575 if (diff < offset)
576 segment->begin->handler_id = g_timeout_add (offset - diff,
577 _laugh_timing_timeout, segment);
578 else
579 _laugh_timing_setting_started (segment);
580 } else {
581 g_printf ("TODO: laugh_timing_setting_start StartSync\n");
584 /*TODO other types*/
585 default:
586 break;
589 return TRUE;
592 void laugh_timing_notify (LaughRoleTiming *segment, gulong handler_id)
594 if (segment->begin && segment->begin->handler_id == handler_id) {
595 _laugh_timing_setting_started (segment);
596 } else if (segment->dur && segment->dur->handler_id == handler_id) {
597 _laugh_timing_setting_stopped (segment);
601 void laugh_timing_setting_stop (LaughRoleTiming *segment)
603 g_printf ("laugh_timing_setting_stop %s\n",
604 laugh_tag_from_id (segment->node->id));
606 _laugh_timing_setting_stop_freeze (segment);
608 laugh_node_stop (segment->node);
611 static LaughRoleTiming *
612 _laugh_timing_settting_find (LaughRoleTiming *segment, const LaughNode *node)
614 if (segment->node == node)
615 return segment;
616 if (segment->sub_segments) {
617 GSList *s;
618 for (s = segment->sub_segments; s; s = s->next) {
619 GSList *sub;
620 for (sub = (GSList *) s->data; sub; sub = sub->next) {
621 LaughRoleTiming *seg = _laugh_timing_settting_find (
622 (LaughRoleTiming *) sub->data, node);
623 if (seg)
624 return seg;
628 return NULL;
631 static void _laugh_timing_settting_sub_segments_force_stop (LaughRoleTiming *segment)
633 GSList *s;
634 for (s = segment->sub_segments; s; s = s->next) {
635 GSList *sub;
636 for (sub = (GSList *) s->data; sub; sub = sub->next) {
637 LaughRoleTiming *seg = (LaughRoleTiming *) sub->data;
638 if (seg->active) {
639 laugh_timing_setting_stop (seg);
640 break;
646 static void _laugh_timing_settting_recursive_start (LaughRoleTiming *segment)
648 if (!segment) {
649 g_printerr ("jump target starting underflow error\n");
650 return;
652 if (!segment->active) {
653 _laugh_timing_settting_recursive_start (segment->parent);
654 laugh_timing_setting_start (segment);
658 void laugh_timing_setting_jump (LaughNode *target)
660 LaughRoleTiming *segment = target->document->timing;
661 LaughRoleTiming *seg;
663 if (LaughStateBegun == target->state) {
664 g_printerr ("jump target already active\n");
665 return;
668 segment = _laugh_timing_settting_find (segment, target);
669 if (!segment) {
670 g_printerr ("jump target not in tree\n");
671 return;
674 for (seg = segment->parent; seg; seg = seg->parent)
675 if (seg->active) {
676 g_printf ("laugh_timing_setting_jump force stop %s\n",
677 laugh_tag_from_id (seg->node->id));
678 if (LaughTagIdPar != seg->node->id)
679 _laugh_timing_settting_sub_segments_force_stop (seg);
680 _laugh_timing_settting_recursive_start (segment);
681 break;
685 LaughNode *laugh_timing_container_new (LaughDocument *doc, LaughNodeTagId id,
686 GHashTable *attributes)
688 LaughNode *n = (LaughNode *)g_object_new(LAUGH_TYPE_TIMING_CONTAINER, NULL);
690 laugh_node_base_construct (doc, n, id, attributes);
692 return n;
695 int laugh_timing_set_value (LaughTiming *timing, const gchar *value)
697 return _get_timing (value, timing);
700 /*#define TIMING_TEST*/
702 #ifdef TIMING_TEST
704 /* gcc laugh-timing.c -o timing-test `pkg-config --cflags --libs glib-2.0` -DTIMING_TEST*/
706 #define TEST_PARSE_TIME(s) \
708 int val = 0; \
709 int b = _timing_parse_time(s, &val); \
710 g_printf( "_timing_parse_time(%s) => %d: %d\n", (s), b, val); \
713 const char *timing_string (LaughTimingType tt) {
714 switch (tt) {
715 case LaughTimingUnknown:
716 return "unknown";
717 case LaughTimingTime:
718 return "timer";
719 case LaughTimingIndefinite:
720 return "infinite";
721 case LaughTimingMedia:
722 return "media";
723 case LaughTimingActivated:
724 return "activated";
725 case LaughTimingInbounds:
726 return "inbounds";
727 case LaughTimingOutbounds:
728 return "outbounds";
729 case LaughTimingEndSync:
730 return "endSync";
731 case LaughTimingStartSync:
732 return "startSync";
733 default:
734 break;
736 return "error";
739 #define TEST_GET_TIMING(s) \
741 LaughTiming *t = laugh_timing_new_from_string(s); \
742 g_printf( "_get_timing %s => target %s event %s offset%d\n", \
743 (s), t->element_id, timing_string(t->type), t->offset); \
744 laugh_timing_delete (t); \
747 int main () {
748 TEST_PARSE_TIME("12");
749 TEST_PARSE_TIME("12s");
750 TEST_PARSE_TIME("12m");
751 TEST_PARSE_TIME("12h");
752 TEST_PARSE_TIME("12.4");
753 TEST_PARSE_TIME("12.4s");
754 TEST_PARSE_TIME("12.4m");
755 TEST_PARSE_TIME("12.4h");
756 TEST_PARSE_TIME(".4");
757 TEST_PARSE_TIME(".4s");
758 TEST_PARSE_TIME("+.4");
759 TEST_PARSE_TIME("+.4s");
760 TEST_PARSE_TIME("-.4");
761 TEST_PARSE_TIME("-.4s");
762 TEST_GET_TIMING("4");
763 TEST_GET_TIMING("myimage.activateEvent");
764 TEST_GET_TIMING("myimage.activateEvent+2");
765 TEST_GET_TIMING("myimage.endSync");
766 TEST_GET_TIMING("myimage.endSync-2m");
767 TEST_GET_TIMING("indefinite");
768 return 0;
771 #endif