Fix taking the lower case of element id's for timing attributes
[laugh.git] / src / laugh-timing.c
blob6a6a3ade19a60e4d1572b81c24a6374b72ebcd7e
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 *orig = tstr;
207 gchar *lower;
208 const gchar *p = tstr;
209 gchar *idref = NULL;
211 for ( ; *p; ++p, ++orig) {
212 if (*p != ' ')
213 break;
216 timing->type = LaughTimingUnknown;
217 if (!p || !*p)
218 return 0;
220 lower = g_ascii_strdown (p, -1);
221 p = lower;
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, ')');
227 if (p) {
228 idref = g_strndup (orig + 3, p - lower - 3);
229 p++;
231 if (*p) {
232 const char *q = strchr (p, '(');
233 if (q)
234 p = q;
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;
243 if (!idref) {
244 int last_esc = 0;
245 for ( ; *p; p++) {
246 if (*p == '\\') {
247 last_esc = last_esc ? 0 : 1;
248 } else if (*p == '.' && !last_esc) {
249 break;
252 if (!*p)
253 idref = g_strdup (orig + (idref_start - lower));
254 else
255 idref = g_strndup (orig + (idref_start - lower), p - idref_start);
257 ++p;
258 if (idref) {
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);
277 } else {
278 g_printerr ("get_timings no match %s", lower);
282 g_free (lower);
283 return 1;
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);
297 g_free (timing);
300 LaughRoleTiming *laugh_timing_role_new (LaughNode *node)
302 LaughRoleTiming *segment = g_new0 (LaughRoleTiming, 1);
304 segment->role.type = LaughRoleTypeTiming;
305 segment->node = node;
307 return segment;
310 void laugh_timing_role_delete (LaughRoleTiming *segment)
312 LaughRoleTiming *parent = segment->parent;
314 if (segment->begin)
315 laugh_timing_delete (segment->begin);
316 if (segment->dur)
317 laugh_timing_delete (segment->dur);
318 if (segment->end)
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));
325 if (parent) {
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);
331 if (!thread)
332 parent->sub_segments = g_slist_remove (parent->sub_segments,
333 s->data);
334 else
335 s->data = thread;
336 break;
340 g_free (segment);
343 LaughTiming *laugh_timing_new_from_string (const gchar *value)
345 LaughTiming *t = laugh_timing_new ();
346 if (laugh_timing_set_value (t, value))
347 return t;
348 laugh_timing_delete (t);
349 return NULL;
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));
357 } else {
358 GSList *last = g_slist_last (s->sub_segments);
359 last->data = g_slist_append ((GSList *) last->data, child);
362 child->parent = s;
365 gboolean laugh_timing_setting_set_attribute (LaughRoleTiming *segment,
366 LaughNodeAttributeId att, const gchar *val)
368 LaughTiming **timing;
370 switch (att) {
371 case LaughAttrIdBegin:
372 timing = &segment->begin;
373 break;
374 case LaughAttrIdDur:
375 timing = &segment->dur;
376 break;
377 case LaughAttrIdEnd:
378 timing = &segment->end;
379 break;
380 default:
381 return FALSE;
383 if (*timing) {
384 if (val) {
385 laugh_timing_set_value (*timing, val);
386 } else {
387 laugh_timing_delete (*timing);
388 *timing = NULL;
390 } else if (val) {
391 *timing = laugh_timing_new_from_string (val);
394 return TRUE;
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);
403 break;
404 /*TODO disconnect other types
405 g_signal_handler_disconnect (timing->handler_id);*/
406 default:
407 break;
409 timing->handler_id = 0;
413 static gboolean _laugh_timing_timeout (gpointer data);
415 static void _laugh_timing_setting_stop_freeze (LaughRoleTiming *segment)
417 GSList *s;
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) {
425 GSList *sub;
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);
430 break;
435 segment->active = FALSE;
438 static void _laugh_timing_setting_stopped (LaughRoleTiming *segment)
440 GSList *s;
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));
450 if (parent) {
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);
454 if (l) {
455 found = TRUE;
456 if (l->next) {
457 start_next = (LaughRoleTiming*) l->next->data;
458 last_one = FALSE;
459 } else {
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")))
465 freeze = TRUE;
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)
473 break;
476 if (freeze) {
477 _laugh_timing_setting_stop_freeze (segment);
478 laugh_node_freeze (segment->node);
479 } else {
480 laugh_timing_setting_stop (segment);
483 if (start_next)
484 laugh_timing_setting_start (start_next);
485 else if (last_one &&
486 (!parent->dur ||
487 (parent->dur->type == LaughTimingTime && !parent->dur->handler_id)))
488 _laugh_timing_setting_stopped (parent);
489 else if (freeze)
490 g_printf ("freeze %s\n", laugh_tag_from_id (segment->node->id));
492 } else {
493 laugh_timing_setting_stop (segment);
497 static void _laugh_timing_setting_started (LaughRoleTiming *segment)
499 GSList *s;
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);
509 else
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;
542 int offset = 0;
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;
554 switch (tt) {
555 case LaughTimingTime:
556 if (offset > 0)
557 segment->begin->handler_id = g_timeout_add (offset,
558 _laugh_timing_timeout, segment);
559 else
560 _laugh_timing_setting_started (segment);
561 break;
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));
569 if (!elm)
570 break;
571 role = (LaughRoleTiming *) laugh_node_role_get (elm, LaughRoleTypeTiming);
572 if (!role)
573 break;
574 if (elm->state >= LaughStateBegun) {
575 glong diff = (clutter_get_timestamp ()-role->start_time_stamp) / 1000;
576 if (diff < offset)
577 segment->begin->handler_id = g_timeout_add (offset - diff,
578 _laugh_timing_timeout, segment);
579 else
580 _laugh_timing_setting_started (segment);
581 } else {
582 g_printf ("TODO: laugh_timing_setting_start StartSync\n");
585 break;
586 /*TODO other types*/
587 default:
588 break;
591 return TRUE;
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)
617 return segment;
618 if (segment->sub_segments) {
619 GSList *s;
620 for (s = segment->sub_segments; s; s = s->next) {
621 GSList *sub;
622 for (sub = (GSList *) s->data; sub; sub = sub->next) {
623 LaughRoleTiming *seg = _laugh_timing_settting_find (
624 (LaughRoleTiming *) sub->data, node);
625 if (seg)
626 return seg;
630 return NULL;
633 static void _laugh_timing_settting_sub_segments_force_stop (LaughRoleTiming *segment)
635 GSList *s;
636 for (s = segment->sub_segments; s; s = s->next) {
637 GSList *sub;
638 for (sub = (GSList *) s->data; sub; sub = sub->next) {
639 LaughRoleTiming *seg = (LaughRoleTiming *) sub->data;
640 if (seg->active) {
641 laugh_timing_setting_stop (seg);
642 break;
648 static void _laugh_timing_settting_recursive_start (LaughRoleTiming *segment)
650 if (!segment) {
651 g_printerr ("jump target starting underflow error\n");
652 return;
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");
667 return;
670 segment = _laugh_timing_settting_find (segment, target);
671 if (!segment) {
672 g_printerr ("jump target not in tree\n");
673 return;
676 for (seg = segment->parent; seg; seg = seg->parent)
677 if (seg->active) {
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);
683 break;
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);
694 return n;
697 int laugh_timing_set_value (LaughTiming *timing, const gchar *value)
699 return _get_timing (value, timing);
702 /*#define TIMING_TEST*/
704 #ifdef 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) \
710 int val = 0; \
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) {
716 switch (tt) {
717 case LaughTimingUnknown:
718 return "unknown";
719 case LaughTimingTime:
720 return "timer";
721 case LaughTimingIndefinite:
722 return "infinite";
723 case LaughTimingMedia:
724 return "media";
725 case LaughTimingActivated:
726 return "activated";
727 case LaughTimingInbounds:
728 return "inbounds";
729 case LaughTimingOutbounds:
730 return "outbounds";
731 case LaughTimingEndSync:
732 return "endSync";
733 case LaughTimingStartSync:
734 return "startSync";
735 default:
736 break;
738 return "error";
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); \
749 int main () {
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");
772 return 0;
775 #endif