Resetting handler_id is done in signal function
[laugh.git] / src / laugh-timing.c
blob2445abd55c0b890cc81e18711223e4b1b4a5e8aa
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 = TimingUnknown;
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 = TimingTime;
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 = TimingIndefinite;
224 } else if (!strncmp (lower, "media", 5)) {
225 timing->type = TimingMedia;
227 if (TimingUnknown == 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 = TimingStartSync;
248 } else if (*p && !strncmp (p, "end", 3)) {
249 timing->type = TimingEndSync;
250 _timing_parse_time (p+3, &timing->offset);
251 } else if (*p && !strncmp (p, "begin", 5)) {
252 timing->type = TimingStartSync;
253 _timing_parse_time (p+5, &timing->offset);
254 } else if (*p && !strncmp (p, "activateevent", 13)) {
255 timing->type = TimingActivated;
256 _timing_parse_time (p+13, &timing->offset);
257 } else if (*p && !strncmp (p, "inboundsevent", 13)) {
258 timing->type = TimingInbounds;
259 _timing_parse_time (p+13, &timing->offset);
260 } else if (*p && !strncmp (p, "outofboundsevent", 16)) {
261 timing->type = TimingOutbounds;
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 TimingTime:
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_stopped (LaughTimingSegment *segment)
391 GSList *s;
392 LaughTimingSegment *parent = segment->parent;
393 gboolean last_one = TRUE;
395 g_printf ("laugh_timing_setting_stopped %s\n",
396 laugh_tag_from_id (segment->node->id));
397 laugh_timing_setting_stop (segment);
399 if (parent) {
400 for (s = parent->sub_segments; s; s = s->next) {
401 GSList *l = g_slist_find ((GSList *) s->data, segment);
402 if (l) {
403 if (l->next) {
404 laugh_timing_setting_start ((LaughTimingSegment*)l->next->data);
405 last_one = FALSE;
406 } else if (last_one) {
407 LaughTimingSegment *seg =
408 (LaughTimingSegment *) g_slist_last (l)->data;
409 last_one &= seg->node->state >= LaughStateStopped;
411 break;
414 if (last_one && (!parent->dur || !parent->dur->handler_id))
415 _laugh_timing_setting_stopped (parent);
419 static void _laugh_timing_setting_started (LaughTimingSegment *segment)
421 GSList *s;
423 g_printf ("laugh_timing_setting_started %s\n",
424 laugh_tag_from_id (segment->node->id));
425 laugh_node_start (segment->node);
427 for (s = segment->sub_segments; s; s = s->next)
428 laugh_timing_setting_start (
429 (LaughTimingSegment *)((GSList *) s->data)->data);
431 if (segment->dur) {
432 if (segment->dur->offset > 0)
433 segment->dur->handler_id = g_timeout_add (segment->dur->offset,
434 _laugh_timing_timeout, segment);
435 else
436 _laugh_timing_setting_stopped (segment);
440 static gboolean _laugh_timing_timeout (gpointer data)
442 LaughTimingSegment *segment = (LaughTimingSegment *) data;
444 if (segment->begin && segment->begin->handler_id) {
445 segment->begin->handler_id = 0;
446 _laugh_timing_setting_started (segment);
447 } else if (segment->dur && segment->dur->handler_id) {
448 segment->dur->handler_id = 0;
449 _laugh_timing_setting_stopped (segment);
452 return FALSE; /*TODO handle repeat*/
455 gboolean laugh_timing_setting_start (LaughTimingSegment *segment)
457 LaughTimingType tt = TimingTime;
458 int offset = 0;
460 g_printf ("laugh_timing_setting_start %s\n",
461 laugh_tag_from_id (segment->node->id));
462 segment->active = TRUE;
464 if (segment->begin) {
465 tt = segment->begin->type;
466 offset = segment->begin->offset;
469 switch (tt) {
470 case TimingTime:
471 if (offset > 0)
472 segment->begin->handler_id = g_timeout_add (offset,
473 _laugh_timing_timeout, segment);
474 else
475 _laugh_timing_setting_started (segment);
476 break;
477 /*TODO other types*/
478 default:
479 break;
482 return TRUE;
485 void laugh_timing_setting_stop (LaughTimingSegment *segment)
487 GSList *s;
489 g_printf ("laugh_timing_setting_stop %s\n",
490 laugh_tag_from_id (segment->node->id));
491 _laugh_timing_stop (segment->begin);
492 _laugh_timing_stop (segment->dur);
493 _laugh_timing_stop (segment->end);
495 /*TODO determine freeze */
496 for (s = segment->sub_segments; s; s = s->next) {
497 GSList *sub;
498 for (sub = (GSList *) s->data; sub; sub = sub->next) {
499 LaughTimingSegment *seg = (LaughTimingSegment *) sub->data;
500 if (seg->active) {
501 laugh_timing_setting_stop (seg);
502 break;
507 segment->active = FALSE;
509 laugh_node_stop (segment->node);
512 LaughNode *laugh_timing_container_new (LaughDocument *doc, LaughNodeTagId id,
513 GHashTable *attributes)
515 LaughNode *n = (LaughNode *)g_object_new(LAUGH_TYPE_TIMING_CONTAINER, NULL);
517 laugh_node_base_construct (doc, n, id, attributes);
519 return n;
522 int laugh_timing_set_value (LaughTiming *timing, const gchar *value)
524 return _get_timing (value, timing);
527 /*#define TIMING_TEST*/
529 #ifdef TIMING_TEST
531 /* gcc laugh-timing.c -o timing-test `pkg-config --cflags --libs glib-2.0` -DTIMING_TEST*/
533 #define TEST_PARSE_TIME(s) \
535 int val = 0; \
536 int b = _timing_parse_time(s, &val); \
537 g_printf( "_timing_parse_time(%s) => %d: %d\n", (s), b, val); \
540 const char *timing_string (LaughTimingType tt) {
541 switch (tt) {
542 case TimingUnknown:
543 return "unknown";
544 case TimingTime:
545 return "timer";
546 case TimingIndefinite:
547 return "infinite";
548 case TimingMedia:
549 return "media";
550 case TimingActivated:
551 return "activated";
552 case TimingInbounds:
553 return "inbounds";
554 case TimingOutbounds:
555 return "outbounds";
556 case TimingEndSync:
557 return "endSync";
558 case TimingStartSync:
559 return "startSync";
560 default:
561 break;
563 return "error";
566 #define TEST_GET_TIMING(s) \
568 LaughTiming *t = laugh_timing_new_from_string(s); \
569 g_printf( "_get_timing %s => target %s event %s offset%d\n", \
570 (s), t->element_id, timing_string(t->type), t->offset); \
571 laugh_timing_delete (t); \
574 int main () {
575 TEST_PARSE_TIME("12");
576 TEST_PARSE_TIME("12s");
577 TEST_PARSE_TIME("12m");
578 TEST_PARSE_TIME("12h");
579 TEST_PARSE_TIME("12.4");
580 TEST_PARSE_TIME("12.4s");
581 TEST_PARSE_TIME("12.4m");
582 TEST_PARSE_TIME("12.4h");
583 TEST_PARSE_TIME(".4");
584 TEST_PARSE_TIME(".4s");
585 TEST_PARSE_TIME("+.4");
586 TEST_PARSE_TIME("+.4s");
587 TEST_PARSE_TIME("-.4");
588 TEST_PARSE_TIME("-.4s");
589 TEST_GET_TIMING("4");
590 TEST_GET_TIMING("myimage.activateEvent");
591 TEST_GET_TIMING("myimage.activateEvent+2");
592 TEST_GET_TIMING("myimage.endSync");
593 TEST_GET_TIMING("myimage.endSync-2m");
594 TEST_GET_TIMING("indefinite");
595 return 0;
598 #endif