Implement linking to other documents and change document initialization
[laugh.git] / src / laugh-timing.c
blob69e870f3a126318f6b78d6d9ca6956556a54c928
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>
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);
51 static void
52 _laugh_timing_container_init (LaughNode *node, LaughInitializer *initializer)
54 LaughTimingContainer *self = (LaughTimingContainer *) node;
55 LaughNode *child;
56 LaughTimingSegment *parent_segment = initializer->parent_segment;
58 if (node->attributes)
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);*/
80 if (!value && undo)
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));*/
101 static
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;
115 if (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),
124 0, /* n_preallocs */
125 laugh_timing_container_instance_init /* instance_init */
127 type = g_type_register_static (LAUGH_TYPE_NODE,
128 "LaughTimingContainerType",
129 &info, 0);
131 return type;
134 static int _timing_parse_time (const gchar *tstr, int *value) {
135 int sign = 1;
136 int fp_seen = 0;
137 const gchar *nstart = NULL;
138 const gchar *p = tstr;
139 gchar *num;
141 if (!tstr)
142 return 0;
144 for ( ; *p; p++) {
145 if (*p == '+') {
146 if (nstart)
147 break;
148 else
149 sign = 1;
150 } else if (*p == '-') {
151 if (nstart)
152 break;
153 else
154 sign = -1;
155 } else if (*p >= '0' && *p <= '9') {
156 if (!nstart)
157 nstart = p;
158 } else if (*p == '.') {
159 if (fp_seen)
160 break;
161 fp_seen = 1;
162 if (!nstart)
163 nstart = p;
164 } else if (*p == ' ') {
165 if (nstart)
166 break;
167 } else
168 break;
170 if (!nstart)
171 return 0;
173 num = g_strndup (nstart, p - tstr);
174 *value = sign * (int) (1000 * g_strtod (nstart, NULL));
176 for ( ; *p; p++ ) {
177 if (*p == 'm') {
178 *value *= 60;
179 break;
180 } else if (*p == 'h') {
181 *value *= 3600;
182 break;
183 } else if (*p != ' ')
184 break;
187 g_free (num);
189 return 1;
192 static int _get_timing (const gchar *tstr, LaughTiming *timing) {
193 gchar *lower;
194 const gchar *p = tstr;
195 gchar *idref = NULL;
197 for ( ; *p; ++p) {
198 if (*p != ' ')
199 break;
202 timing->type = LaughTimingUnknown;
203 if (!p || !*p)
204 return 0;
206 lower = g_ascii_strdown (p, -1);
207 p = lower;
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, ')');
213 if (p) {
214 idref = g_strndup (lower + 3, p - lower - 3);
215 p++;
217 if (*p) {
218 const char *q = strchr (p, '(');
219 if (q)
220 p = q;
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;
229 if (!idref) {
230 int last_esc = 0;
231 for ( ; *p; p++) {
232 if (*p == '\\') {
233 last_esc = last_esc ? 0 : 1;
234 } else if (*p == '.' && !last_esc) {
235 break;
238 if (!*p)
239 idref = g_strdup (idref_start);
240 else
241 idref = g_strndup (idref_start, p - idref_start);
243 ++p;
244 if (idref) {
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);
263 } else {
264 g_printerr ("get_timings no match %s", lower);
268 g_free (lower);
269 return 1;
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);
281 g_free (timing);
284 void laugh_timing_segment_delete (LaughTimingSegment *segment)
286 LaughTimingSegment *parent = segment->parent;
288 if (segment->begin)
289 laugh_timing_delete (segment->begin);
290 if (segment->dur)
291 laugh_timing_delete (segment->dur);
292 if (segment->end)
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));
299 if (parent) {
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);
305 if (!thread)
306 parent->sub_segments = g_slist_remove (parent->sub_segments,
307 s->data);
308 else
309 s->data = thread;
310 break;
314 g_free (segment);
317 LaughTiming *laugh_timing_new_from_string (const gchar *value)
319 LaughTiming *t = laugh_timing_new ();
320 if (laugh_timing_set_value (t, value))
321 return t;
322 laugh_timing_delete (t);
323 return NULL;
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));
331 } else {
332 GSList *last = g_slist_last (s->sub_segments);
333 last->data = g_slist_append ((GSList *) last->data, child);
336 child->parent = s;
339 gboolean laugh_timing_setting_set_attribute (LaughTimingSegment *segment,
340 LaughNodeAttributeId att, const gchar *val)
342 LaughTiming **timing;
344 switch (att) {
345 case LaughAttrIdBegin:
346 timing = &segment->begin;
347 break;
348 case LaughAttrIdDur:
349 timing = &segment->dur;
350 break;
351 case LaughAttrIdEnd:
352 timing = &segment->end;
353 break;
354 default:
355 return FALSE;
357 if (*timing) {
358 if (val) {
359 laugh_timing_set_value (*timing, val);
360 } else {
361 laugh_timing_delete (*timing);
362 *timing = NULL;
364 } else if (val) {
365 *timing = laugh_timing_new_from_string (val);
368 return TRUE;
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);
377 break;
378 /*TODO disconnect other types
379 g_signal_handler_disconnect (timing->handler_id);*/
380 default:
381 break;
383 timing->handler_id = 0;
387 static gboolean _laugh_timing_timeout (gpointer data);
389 static void _laugh_timing_setting_stop_freeze (LaughTimingSegment *segment)
391 GSList *s;
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) {
399 GSList *sub;
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);
404 break;
409 segment->active = FALSE;
412 static void _laugh_timing_setting_stopped (LaughTimingSegment *segment)
414 GSList *s;
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));
424 if (parent) {
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);
428 if (l) {
429 found = TRUE;
430 if (l->next) {
431 start_next = (LaughTimingSegment*) l->next->data;
432 last_one = FALSE;
433 } else {
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")))
439 freeze = TRUE;
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)
447 break;
450 if (freeze) {
451 _laugh_timing_setting_stop_freeze (segment);
452 laugh_node_freeze (segment->node);
453 } else {
454 laugh_timing_setting_stop (segment);
457 if (start_next)
458 laugh_timing_setting_start (start_next);
459 else if (last_one &&
460 (!parent->dur ||
461 (parent->dur->type == LaughTimingTime && !parent->dur->handler_id)))
462 _laugh_timing_setting_stopped (parent);
463 else if (freeze)
464 g_printf ("freeze %s\n", laugh_tag_from_id (segment->node->id));
466 } else {
467 laugh_timing_setting_stop (segment);
471 static void _laugh_timing_setting_started (LaughTimingSegment *segment)
473 GSList *s;
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);
483 else
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;
516 int offset = 0;
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;
527 switch (tt) {
528 case LaughTimingTime:
529 if (offset > 0)
530 segment->begin->handler_id = g_timeout_add (offset,
531 _laugh_timing_timeout, segment);
532 else
533 _laugh_timing_setting_started (segment);
534 break;
535 /*TODO other types*/
536 default:
537 break;
540 return TRUE;
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)
566 return segment;
567 if (segment->sub_segments) {
568 GSList *s;
569 for (s = segment->sub_segments; s; s = s->next) {
570 GSList *sub;
571 for (sub = (GSList *) s->data; sub; sub = sub->next) {
572 LaughTimingSegment *seg = _laugh_timing_settting_find (
573 (LaughTimingSegment *) sub->data, node);
574 if (seg)
575 return seg;
579 return NULL;
582 static void _laugh_timing_settting_sub_segments_force_stop (LaughTimingSegment *segment)
584 GSList *s;
585 for (s = segment->sub_segments; s; s = s->next) {
586 GSList *sub;
587 for (sub = (GSList *) s->data; sub; sub = sub->next) {
588 LaughTimingSegment *seg = (LaughTimingSegment *) sub->data;
589 if (seg->active) {
590 laugh_timing_setting_stop (seg);
591 break;
597 static void _laugh_timing_settting_recursive_start (LaughTimingSegment *segment)
599 if (!segment) {
600 g_printerr ("jump target starting underflow error\n");
601 return;
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");
616 return;
619 segment = _laugh_timing_settting_find (segment, target);
620 if (!segment) {
621 g_printerr ("jump target not in tree\n");
622 return;
625 for (seg = segment->parent; seg; seg = seg->parent)
626 if (seg->active) {
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);
632 break;
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);
643 return n;
646 int laugh_timing_set_value (LaughTiming *timing, const gchar *value)
648 return _get_timing (value, timing);
651 /*#define TIMING_TEST*/
653 #ifdef 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) \
659 int val = 0; \
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) {
665 switch (tt) {
666 case LaughTimingUnknown:
667 return "unknown";
668 case LaughTimingTime:
669 return "timer";
670 case LaughTimingIndefinite:
671 return "infinite";
672 case LaughTimingMedia:
673 return "media";
674 case LaughTimingActivated:
675 return "activated";
676 case LaughTimingInbounds:
677 return "inbounds";
678 case LaughTimingOutbounds:
679 return "outbounds";
680 case LaughTimingEndSync:
681 return "endSync";
682 case LaughTimingStartSync:
683 return "startSync";
684 default:
685 break;
687 return "error";
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); \
698 int main () {
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");
719 return 0;
722 #endif