1 /*
2 * theme-animation: A theme used for building animations by XML files
3 *
4 * Copyright 2012-2020 Stephan Haller <nomad@froevel.de>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 *
21 *
22 */
23
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27
28 #include <libxfdashboard/theme-animation.h>
29
30 #include <glib/gi18n-lib.h>
31 #include <glib.h>
32 #include <gio/gio.h>
33 #include <errno.h>
34
35 #include <libxfdashboard/transition-group.h>
36 #include <libxfdashboard/stylable.h>
37 #include <libxfdashboard/application.h>
38 #include <libxfdashboard/enums.h>
39 #include <libxfdashboard/utils.h>
40 #include <libxfdashboard/compat.h>
41 #include <libxfdashboard/debug.h>
42
43
44 /* Define this class in GObject system */
45 struct _XfdashboardThemeAnimationPrivate
46 {
47 /* Instance related */
48 GSList *specs;
49 };
50
51 G_DEFINE_TYPE_WITH_PRIVATE(XfdashboardThemeAnimation,
52 xfdashboard_theme_animation,
53 G_TYPE_OBJECT)
54
55
56 /* IMPLEMENTATION: Private variables and methods */
57 #define ENABLE_ANIMATIONS_XFCONF_PROP "/enable-animations"
58 #define DEFAULT_ENABLE_ANIMATIONS TRUE
59
60 enum
61 {
62 TAG_DOCUMENT,
63 TAG_ANIMATIONS,
64 TAG_TRIGGER,
65 TAG_TIMELINE,
66 TAG_APPLY,
67 TAG_PROPERTY
68 };
69
70 enum
71 {
72 XFDASHBOARD_THEME_ANIMATION_APPLY_TO_ORIGIN_SENDER=0,
73 XFDASHBOARD_THEME_ANIMATION_APPLY_TO_ORIGIN_STAGE=1
74 };
75
76 typedef struct _XfdashboardThemeAnimationSpec XfdashboardThemeAnimationSpec;
77 struct _XfdashboardThemeAnimationSpec
78 {
79 gint refCount;
80 gchar *id;
81 XfdashboardCssSelector *senderSelector;
82 gchar *signal;
83 GSList *targets;
84 };
85
86 typedef struct _XfdashboardThemeAnimationTargets XfdashboardThemeAnimationTargets;
87 struct _XfdashboardThemeAnimationTargets
88 {
89 gint refCount;
90 XfdashboardCssSelector *targetSelector;
91 guint origin;
92 ClutterTimeline *timeline;
93 GSList *properties;
94 };
95
96 typedef struct _XfdashboardThemeAnimationTargetsProperty XfdashboardThemeAnimationTargetsProperty;
97 struct _XfdashboardThemeAnimationTargetsProperty
98 {
99 gint refCount;
100 gchar *name;
101 GValue from;
102 GValue to;
103 };
104
105 typedef struct _XfdashboardThemeAnimationParserData XfdashboardThemeAnimationParserData;
106 struct _XfdashboardThemeAnimationParserData
107 {
108 XfdashboardThemeAnimation *self;
109
110 GSList *specs;
111
112 XfdashboardThemeAnimationSpec *currentSpec;
113 ClutterTimeline *currentTimeline;
114 XfdashboardThemeAnimationTargets *currentTargets;
115
116 gint lastLine;
117 gint lastPosition;
118 gint currentLine;
119 gint currentPostition;
120 };
121
122
123 /* Create, destroy, ref and unref animation targets data */
_xfdashboard_theme_animation_targets_property_new(const gchar * inPropertyName,const gchar * inPropertyFrom,const gchar * inPropertyTo)124 static XfdashboardThemeAnimationTargetsProperty* _xfdashboard_theme_animation_targets_property_new(const gchar *inPropertyName,
125 const gchar *inPropertyFrom,
126 const gchar *inPropertyTo)
127 {
128 XfdashboardThemeAnimationTargetsProperty *data;
129
130 g_return_val_if_fail(inPropertyName && *inPropertyName, NULL);
131 g_return_val_if_fail(!inPropertyFrom || *inPropertyFrom, NULL);
132 g_return_val_if_fail(!inPropertyTo || *inPropertyTo, NULL);
133
134 /* Create animation targets property data */
135 data=g_new0(XfdashboardThemeAnimationTargetsProperty, 1);
136 if(!data)
137 {
138 g_critical("Cannot allocate memory for animation targets property '%s'", inPropertyName);
139 return(NULL);
140 }
141
142 data->refCount=1;
143 data->name=g_strdup(inPropertyName);
144
145 if(inPropertyFrom)
146 {
147 g_value_init(&data->from, G_TYPE_STRING);
148 g_value_set_string(&data->from, inPropertyFrom);
149 }
150
151 if(inPropertyTo)
152 {
153 g_value_init(&data->to, G_TYPE_STRING);
154 g_value_set_string(&data->to, inPropertyTo);
155 }
156
157 return(data);
158 }
159
_xfdashboard_theme_animation_targets_property_free(XfdashboardThemeAnimationTargetsProperty * inData)160 static void _xfdashboard_theme_animation_targets_property_free(XfdashboardThemeAnimationTargetsProperty *inData)
161 {
162 g_return_if_fail(inData);
163
164 /* Free property data */
165 if(inData->name) g_free(inData->name);
166 g_value_unset(&inData->from);
167 g_value_unset(&inData->to);
168 g_free(inData);
169 }
170
_xfdashboard_theme_animation_targets_property_ref(XfdashboardThemeAnimationTargetsProperty * inData)171 static XfdashboardThemeAnimationTargetsProperty* _xfdashboard_theme_animation_targets_property_ref(XfdashboardThemeAnimationTargetsProperty *inData)
172 {
173 g_return_val_if_fail(inData, NULL);
174
175 inData->refCount++;
176 return(inData);
177 }
178
_xfdashboard_theme_animation_targets_property_unref(XfdashboardThemeAnimationTargetsProperty * inData)179 static void _xfdashboard_theme_animation_targets_property_unref(XfdashboardThemeAnimationTargetsProperty *inData)
180 {
181 g_return_if_fail(inData);
182
183 if(inData->refCount==1) _xfdashboard_theme_animation_targets_property_free(inData);
184 else inData->refCount--;
185 }
186
187 /* Create, destroy, ref and unref animation targets data */
_xfdashboard_theme_animation_targets_new(XfdashboardCssSelector * inTargetSelector,gint inOrigin,ClutterTimeline * inTimelineSource)188 static XfdashboardThemeAnimationTargets* _xfdashboard_theme_animation_targets_new(XfdashboardCssSelector *inTargetSelector,
189 gint inOrigin,
190 ClutterTimeline *inTimelineSource)
191 {
192 XfdashboardThemeAnimationTargets *data;
193
194 g_return_val_if_fail(!inTargetSelector || XFDASHBOARD_IS_CSS_SELECTOR(inTargetSelector), NULL);
195 g_return_val_if_fail(inOrigin>=XFDASHBOARD_THEME_ANIMATION_APPLY_TO_ORIGIN_SENDER && inOrigin<=XFDASHBOARD_THEME_ANIMATION_APPLY_TO_ORIGIN_STAGE, NULL);
196 g_return_val_if_fail(CLUTTER_IS_TIMELINE(inTimelineSource), NULL);
197
198 /* Create animation targets data */
199 data=g_new0(XfdashboardThemeAnimationTargets, 1);
200 if(!data)
201 {
202 gchar *selector;
203
204 selector=xfdashboard_css_selector_to_string(inTargetSelector);
205 g_critical("Cannot allocate memory for animation targets data with selector '%s'", selector);
206 g_free(selector);
207
208 return(NULL);
209 }
210
211 data->refCount=1;
212 data->targetSelector=(inTargetSelector ? g_object_ref(inTargetSelector) : NULL);
213 data->origin=inOrigin;
214 data->timeline=g_object_ref(inTimelineSource);
215 data->properties=NULL;
216
217 return(data);
218 }
219
_xfdashboard_theme_animation_targets_free(XfdashboardThemeAnimationTargets * inData)220 static void _xfdashboard_theme_animation_targets_free(XfdashboardThemeAnimationTargets *inData)
221 {
222 g_return_if_fail(inData);
223
224 #ifdef DEBUG
225 if(inData->refCount>1)
226 {
227 g_critical("Freeing animation targets data at %p with a reference counter of %d greater than one",
228 inData,
229 inData->refCount);
230 }
231 #endif
232
233 /* Release allocated resources */
234 if(inData->targetSelector) g_object_unref(inData->targetSelector);
235 if(inData->timeline) g_object_unref(inData->timeline);
236 if(inData->properties) g_slist_free_full(inData->properties, (GDestroyNotify)_xfdashboard_theme_animation_targets_property_unref);
237 g_free(inData);
238 }
239
_xfdashboard_theme_animation_targets_ref(XfdashboardThemeAnimationTargets * inData)240 static XfdashboardThemeAnimationTargets* _xfdashboard_theme_animation_targets_ref(XfdashboardThemeAnimationTargets *inData)
241 {
242 g_return_val_if_fail(inData, NULL);
243
244 inData->refCount++;
245 return(inData);
246 }
247
_xfdashboard_theme_animation_targets_unref(XfdashboardThemeAnimationTargets * inData)248 static void _xfdashboard_theme_animation_targets_unref(XfdashboardThemeAnimationTargets *inData)
249 {
250 g_return_if_fail(inData);
251
252 if(inData->refCount==1) _xfdashboard_theme_animation_targets_free(inData);
253 else inData->refCount--;
254 }
255
256 /* Add property name and from-to values to animation targets data */
_xfdashboard_theme_animation_targets_add_property(XfdashboardThemeAnimationTargets * inData,XfdashboardThemeAnimationTargetsProperty * inProperty)257 static void _xfdashboard_theme_animation_targets_add_property(XfdashboardThemeAnimationTargets *inData,
258 XfdashboardThemeAnimationTargetsProperty *inProperty)
259 {
260 g_return_if_fail(inData);
261 g_return_if_fail(inProperty);
262
263 /* Add property to animation targets data */
264 inData->properties=g_slist_prepend(inData->properties, _xfdashboard_theme_animation_targets_property_ref(inProperty));
265 }
266
267
268 /* Create, destroy, ref and unref animation specification data */
_xfdashboard_theme_animation_spec_new(const gchar * inID,XfdashboardCssSelector * inSenderSelector,const gchar * inSignal)269 static XfdashboardThemeAnimationSpec* _xfdashboard_theme_animation_spec_new(const gchar *inID,
270 XfdashboardCssSelector *inSenderSelector,
271 const gchar *inSignal)
272 {
273 XfdashboardThemeAnimationSpec *data;
274
275 g_return_val_if_fail(inID && *inID, NULL);
276 g_return_val_if_fail(XFDASHBOARD_IS_CSS_SELECTOR(inSenderSelector), NULL);
277 g_return_val_if_fail(inSignal && *inSignal, NULL);
278
279 /* Create animation specification data */
280 data=g_new0(XfdashboardThemeAnimationSpec, 1);
281 if(!data)
282 {
283 gchar *selector;
284
285 selector=xfdashboard_css_selector_to_string(inSenderSelector);
286 g_critical("Cannot allocate memory for animation specification data with sender '%s' and signal '%s'",
287 selector,
288 inSignal);
289 g_free(selector);
290
291 return(NULL);
292 }
293
294 data->refCount=1;
295 data->id=g_strdup(inID);
296 data->senderSelector=g_object_ref(inSenderSelector);
297 data->signal=g_strdup(inSignal);
298 data->targets=NULL;
299
300 return(data);
301 }
302
_xfdashboard_theme_animation_spec_free(XfdashboardThemeAnimationSpec * inData)303 static void _xfdashboard_theme_animation_spec_free(XfdashboardThemeAnimationSpec *inData)
304 {
305 g_return_if_fail(inData);
306
307 #ifdef DEBUG
308 if(inData->refCount>1)
309 {
310 g_critical("Freeing animation specification data at %p with a reference counter of %d greater than one",
311 inData,
312 inData->refCount);
313 }
314 #endif
315
316 /* Release allocated resources */
317 if(inData->id) g_free(inData->id);
318 if(inData->senderSelector) g_object_unref(inData->senderSelector);
319 if(inData->signal) g_free(inData->signal);
320 if(inData->targets) g_slist_free_full(inData->targets, (GDestroyNotify)_xfdashboard_theme_animation_targets_unref);
321 g_free(inData);
322 }
323
_xfdashboard_theme_animation_spec_ref(XfdashboardThemeAnimationSpec * inData)324 static XfdashboardThemeAnimationSpec* _xfdashboard_theme_animation_spec_ref(XfdashboardThemeAnimationSpec *inData)
325 {
326 g_return_val_if_fail(inData, NULL);
327
328 inData->refCount++;
329 return(inData);
330 }
331
_xfdashboard_theme_animation_spec_unref(XfdashboardThemeAnimationSpec * inData)332 static void _xfdashboard_theme_animation_spec_unref(XfdashboardThemeAnimationSpec *inData)
333 {
334 g_return_if_fail(inData);
335
336 if(inData->refCount==1) _xfdashboard_theme_animation_spec_free(inData);
337 else inData->refCount--;
338 }
339
340 /* Add a animation target to animation specification */
_xfdashboard_theme_animation_spec_add_targets(XfdashboardThemeAnimationSpec * inData,XfdashboardThemeAnimationTargets * inTargets)341 static void _xfdashboard_theme_animation_spec_add_targets(XfdashboardThemeAnimationSpec *inData,
342 XfdashboardThemeAnimationTargets *inTargets)
343 {
344 g_return_if_fail(inData);
345 g_return_if_fail(inTargets);
346
347 /* Add target to specification */
348 inData->targets=g_slist_prepend(inData->targets, _xfdashboard_theme_animation_targets_ref(inTargets));
349 }
350
351 /* Find best matching animation specification for sender and signal */
_xfdashboard_theme_animation_find_matching_animation_spec(XfdashboardThemeAnimation * self,XfdashboardStylable * inSender,const gchar * inSignal)352 static XfdashboardThemeAnimationSpec* _xfdashboard_theme_animation_find_matching_animation_spec(XfdashboardThemeAnimation *self,
353 XfdashboardStylable *inSender,
354 const gchar *inSignal)
355 {
356 XfdashboardThemeAnimationPrivate *priv;
357 GSList *iter;
358 gint score;
359 XfdashboardThemeAnimationSpec *spec;
360 gint bestScore;
361 XfdashboardThemeAnimationSpec *bestAnimation;
362
363 g_return_val_if_fail(XFDASHBOARD_IS_THEME_ANIMATION(self), NULL);
364 g_return_val_if_fail(XFDASHBOARD_IS_STYLABLE(inSender), NULL);
365 g_return_val_if_fail(inSignal && *inSignal, NULL);
366
367 priv=self->priv;
368 bestScore=0;
369 bestAnimation=NULL;
370
371 /* Iterate through all animation specification and get its score against sender.
372 * If the iterated specification gets a higher score than the previous one,
373 * remember this specification for return value.
374 */
375 for(iter=priv->specs; iter; iter=g_slist_next(iter))
376 {
377 /* Get currently iterated specification */
378 spec=(XfdashboardThemeAnimationSpec*)iter->data;
379 if(!spec) continue;
380
381 /* Skip animation specification if its signal definition does not match
382 * the emitted signal.
383 */
384 if(g_strcmp0(spec->signal, inSignal)!=0) continue;
385
386 /* Get score of currently iterated specification against sender.
387 * Skip this iterated specification if score is zero or lower.
388 */
389 score=xfdashboard_css_selector_score(spec->senderSelector, inSender);
390 if(score<=0) continue;
391
392 /* If score is higher than the previous best matching one, then release
393 * old specificationr remembered and ref new one.
394 */
395 if(score>bestScore)
396 {
397 /* Release old remembered specification */
398 if(bestAnimation) _xfdashboard_theme_animation_spec_unref(bestAnimation);
399
400 /* Remember new one and take a reference */
401 bestScore=score;
402 bestAnimation=_xfdashboard_theme_animation_spec_ref(spec);
403 }
404 }
405
406 /* Return best matching animation specification found */
407 return(bestAnimation);
408 }
409
410 /* Lookup animation specification for requested ID */
_xfdashboard_theme_animation_find_animation_spec_by_id(XfdashboardThemeAnimation * self,const gchar * inID)411 static XfdashboardThemeAnimationSpec* _xfdashboard_theme_animation_find_animation_spec_by_id(XfdashboardThemeAnimation *self,
412 const gchar *inID)
413 {
414 XfdashboardThemeAnimationPrivate *priv;
415 GSList *iter;
416 XfdashboardThemeAnimationSpec *spec;
417
418 g_return_val_if_fail(XFDASHBOARD_IS_THEME_ANIMATION(self), NULL);
419 g_return_val_if_fail(inID && *inID, NULL);
420
421 priv=self->priv;
422
423 /* Iterate through all animation specification and check if it matches
424 * the requested ID.
425 */
426 for(iter=priv->specs; iter; iter=g_slist_next(iter))
427 {
428 /* Get currently iterated specification */
429 spec=(XfdashboardThemeAnimationSpec*)iter->data;
430 if(!spec) continue;
431
432 /* Return animation specification if ID matches requested one */
433 if(g_strcmp0(spec->id, inID)==0)
434 {
435 _xfdashboard_theme_animation_spec_ref(spec);
436 return(spec);
437 }
438 }
439
440 /* If we get here no animation specification with requested ID was found */
441 return(NULL);
442 }
443
444 /* Find actors matching an animation target data */
_xfdashboard_theme_animation_find_actors_for_animation_targets_traverse_callback(ClutterActor * inActor,gpointer inUserData)445 static gboolean _xfdashboard_theme_animation_find_actors_for_animation_targets_traverse_callback(ClutterActor *inActor, gpointer inUserData)
446 {
447 GSList **actors=(GSList**)inUserData;
448
449 *actors=g_slist_prepend(*actors, inActor);
450
451 return(XFDASHBOARD_TRAVERSAL_CONTINUE);
452 }
453
_xfdashboard_theme_animation_find_actors_for_animation_targets(XfdashboardThemeAnimation * self,XfdashboardThemeAnimationTargets * inTargetSpec,ClutterActor * inSender)454 static GSList* _xfdashboard_theme_animation_find_actors_for_animation_targets(XfdashboardThemeAnimation *self,
455 XfdashboardThemeAnimationTargets *inTargetSpec,
456 ClutterActor *inSender)
457 {
458 GSList *actors;
459
460 g_return_val_if_fail(XFDASHBOARD_IS_THEME_ANIMATION(self), NULL);
461 g_return_val_if_fail(inTargetSpec, NULL);
462 g_return_val_if_fail(CLUTTER_IS_ACTOR(inSender), NULL);
463
464 /* Traverse through actors beginning at the root node and collect each actor
465 * matching the target selector but only if a target selector is set. If
466 * target selector is NULL then set up a single-item list containing only
467 * the sender actor as list of actors found.
468 */
469 actors=NULL;
470 if(inTargetSpec->targetSelector)
471 {
472 ClutterActor *rootNode;
473
474 /* Depending on origin at animation target data select root node to start
475 * traversal and collecting matching actors.
476 */
477 rootNode=NULL;
478 if(inTargetSpec->origin==XFDASHBOARD_THEME_ANIMATION_APPLY_TO_ORIGIN_SENDER)
479 {
480 rootNode=inSender;
481 }
482
483 xfdashboard_traverse_actor(rootNode,
484 inTargetSpec->targetSelector,
485 _xfdashboard_theme_animation_find_actors_for_animation_targets_traverse_callback,
486 &actors);
487 }
488 else
489 {
490 actors=g_slist_prepend(actors, inSender);
491 }
492
493 /* Correct order in list */
494 actors=g_slist_reverse(actors);
495
496 /* Return list of actors found */
497 return(actors);
498 }
499
500 /* Callback to add each successfully parsed animation specifications to list of
501 * known animations of this theme.
502 */
_xfdashboard_theme_animation_ref_and_add_to_theme(gpointer inData,gpointer inUserData)503 static void _xfdashboard_theme_animation_ref_and_add_to_theme(gpointer inData, gpointer inUserData)
504 {
505 XfdashboardThemeAnimation *self;
506 XfdashboardThemeAnimationPrivate *priv;
507 XfdashboardThemeAnimationSpec *spec;
508
509 g_return_if_fail(XFDASHBOARD_IS_THEME_ANIMATION(inUserData));
510 g_return_if_fail(inData);
511
512 self=XFDASHBOARD_THEME_ANIMATION(inUserData);
513 priv=self->priv;
514 spec=(XfdashboardThemeAnimationSpec*)inData;
515
516 /* Increase reference of specified animation specification and add to list
517 * of known ones.
518 */
519 priv->specs=g_slist_prepend(priv->specs, _xfdashboard_theme_animation_spec_ref(spec));
520 }
521
522 /* Check if an animation specification with requested ID exists */
_xfdashboard_theme_animation_has_id(XfdashboardThemeAnimation * self,XfdashboardThemeAnimationParserData * inParserData,const gchar * inID)523 static gboolean _xfdashboard_theme_animation_has_id(XfdashboardThemeAnimation *self,
524 XfdashboardThemeAnimationParserData *inParserData,
525 const gchar *inID)
526 {
527 XfdashboardThemeAnimationPrivate *priv;
528 GSList *ids;
529 gboolean hasID;
530 XfdashboardThemeAnimationSpec *spec;
531
532 g_return_val_if_fail(XFDASHBOARD_IS_THEME_ANIMATION(self), TRUE);
533
534 priv=self->priv;
535 hasID=FALSE;
536
537 /* Check that ID to lookup is specified */
538 g_assert(inID && *inID);
539
540 /* Lookup ID first in currently parsed file if specified */
541 if(inParserData)
542 {
543 for(ids=inParserData->specs; !hasID && ids; ids=g_slist_next(ids))
544 {
545 spec=(XfdashboardThemeAnimationSpec*)ids->data;
546 if(spec && g_strcmp0(spec->id, inID)==0) hasID=TRUE;
547 }
548 }
549
550 /* If ID was not found in currently parsed effects xml file (if specified)
551 * continue search in already parsed and known effects.
552 */
553 if(!hasID)
554 {
555 for(ids=priv->specs; !hasID && ids; ids=g_slist_next(ids))
556 {
557 spec=(XfdashboardThemeAnimationSpec*)ids->data;
558 if(spec && g_strcmp0(spec->id, inID)==0) hasID=TRUE;
559 }
560 }
561
562 /* Return lookup result */
563 return(hasID);
564 }
565
566 /* Convert string to integer and throw error if conversion failed */
_xfdashboard_theme_animation_string_to_gint(const gchar * inNumberString,gint * outNumber,GError ** outError)567 static gboolean _xfdashboard_theme_animation_string_to_gint(const gchar *inNumberString, gint *outNumber, GError **outError)
568 {
569 gint64 convertedNumber;
570 gchar *outNumberStringEnd;
571
572 g_return_val_if_fail(inNumberString && *inNumberString, FALSE);
573 g_return_val_if_fail(outNumber, FALSE);
574 g_return_val_if_fail(outError==NULL || *outError==NULL, FALSE);
575
576 /* Try to convert string to number */
577 convertedNumber=g_ascii_strtoll(inNumberString, &outNumberStringEnd, 10);
578
579 /* Check if invalid base was specified */
580 if(errno==EINVAL)
581 {
582 /* Set error */
583 g_set_error(outError,
584 XFDASHBOARD_THEME_ANIMATION_ERROR,
585 XFDASHBOARD_THEME_ANIMATION_ERROR_ERROR,
586 "Invalid base for conversion");
587 return(FALSE);
588 }
589
590 /* Check if integer is out of range */
591 if(errno==ERANGE)
592 {
593 /* Set error */
594 g_set_error(outError,
595 XFDASHBOARD_THEME_ANIMATION_ERROR,
596 XFDASHBOARD_THEME_ANIMATION_ERROR_ERROR,
597 "Integer out of range");
598 return(FALSE);
599 }
600
601 /* If converted integer resulted in zero check if end pointer
602 * has moved and does not match start pointer and points to a
603 * NULL byte (as NULL-terminated strings must be provided).
604 */
605 if(convertedNumber==0 &&
606 (outNumberStringEnd==inNumberString || *outNumberStringEnd!=0))
607 {
608 /* Set error */
609 g_set_error(outError,
610 XFDASHBOARD_THEME_ANIMATION_ERROR,
611 XFDASHBOARD_THEME_ANIMATION_ERROR_ERROR,
612 "Cannot convert string '%s' to integer",
613 inNumberString);
614 return(FALSE);
615 }
616
617 /* Set converted number - the integer */
618 *outNumber=((gint)convertedNumber);
619
620 /* Return TRUE for successful conversion */
621 return(TRUE);
622 }
623
624 /* Helper function to set up GError object in this parser */
_xfdashboard_theme_animation_parse_set_error(XfdashboardThemeAnimationParserData * inParserData,GMarkupParseContext * inContext,GError ** outError,XfdashboardThemeAnimationErrorEnum inCode,const gchar * inFormat,...)625 static void _xfdashboard_theme_animation_parse_set_error(XfdashboardThemeAnimationParserData *inParserData,
626 GMarkupParseContext *inContext,
627 GError **outError,
628 XfdashboardThemeAnimationErrorEnum inCode,
629 const gchar *inFormat,
630 ...)
631 {
632 GError *tempError;
633 gchar *message;
634 va_list args;
635
636 /* Get error message */
637 va_start(args, inFormat);
638 message=g_strdup_vprintf(inFormat, args);
639 va_end(args);
640
641 /* Create error object */
642 tempError=g_error_new_literal(XFDASHBOARD_THEME_ANIMATION_ERROR, inCode, message);
643 if(inParserData)
644 {
645 g_prefix_error(&tempError,
646 "Error on line %d char %d: ",
647 inParserData->lastLine,
648 inParserData->lastPosition);
649 }
650
651 /* Set error */
652 g_propagate_error(outError, tempError);
653
654 /* Release allocated resources */
655 g_free(message);
656 }
657
658 /* General callbacks which can be used for any tag */
_xfdashboard_theme_animation_parse_general_no_text_nodes(GMarkupParseContext * inContext,const gchar * inText,gsize inTextLength,gpointer inUserData,GError ** outError)659 static void _xfdashboard_theme_animation_parse_general_no_text_nodes(GMarkupParseContext *inContext,
660 const gchar *inText,
661 gsize inTextLength,
662 gpointer inUserData,
663 GError **outError)
664 {
665 XfdashboardThemeAnimationParserData *data=(XfdashboardThemeAnimationParserData*)inUserData;
666 gchar *realText;
667
668 /* Check if text contains only whitespace. If we find any non-whitespace
669 * in text then set error.
670 */
671 realText=g_strstrip(g_strdup(inText));
672 if(*realText)
673 {
674 const GSList *parents;
675
676 parents=g_markup_parse_context_get_element_stack(inContext);
677 if(parents) parents=g_slist_next(parents);
678
679 _xfdashboard_theme_animation_parse_set_error(data,
680 inContext,
681 outError,
682 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
683 "Unexpected text node '%s' at tag <%s>",
684 realText,
685 parents ? (gchar*)parents->data : "document");
686 }
687 g_free(realText);
688 }
689
690 /* Determine tag name and ID */
_xfdashboard_theme_animation_get_tag_by_name(const gchar * inTag)691 static gint _xfdashboard_theme_animation_get_tag_by_name(const gchar *inTag)
692 {
693 g_return_val_if_fail(inTag && *inTag, -1);
694
695 /* Compare string and return type ID */
696 if(g_strcmp0(inTag, "animations")==0) return(TAG_ANIMATIONS);
697 if(g_strcmp0(inTag, "trigger")==0) return(TAG_TRIGGER);
698 if(g_strcmp0(inTag, "timeline")==0) return(TAG_TIMELINE);
699 if(g_strcmp0(inTag, "apply")==0) return(TAG_APPLY);
700 if(g_strcmp0(inTag, "property")==0) return(TAG_PROPERTY);
701
702 /* If we get here we do not know tag name and return invalid ID */
703 return(-1);
704 }
705
_xfdashboard_theme_animation_get_tag_by_id(guint inTagType)706 static const gchar* _xfdashboard_theme_animation_get_tag_by_id(guint inTagType)
707 {
708 /* Compare ID and return string */
709 switch(inTagType)
710 {
711 case TAG_DOCUMENT:
712 return("document");
713
714 case TAG_ANIMATIONS:
715 return("animations");
716
717 case TAG_TRIGGER:
718 return("trigger");
719
720 case TAG_TIMELINE:
721 return("timeline");
722
723 case TAG_APPLY:
724 return("apply");
725
726 case TAG_PROPERTY:
727 return("property");
728
729 default:
730 break;
731 }
732
733 /* If we get here we do not know tag name and return NULL */
734 return(NULL);
735 }
736
737 /* Parser callbacks for <property> node */
_xfdashboard_theme_animation_parse_property_start(GMarkupParseContext * inContext,const gchar * inElementName,const gchar ** inAttributeNames,const gchar ** inAttributeValues,gpointer inUserData,GError ** outError)738 static void _xfdashboard_theme_animation_parse_property_start(GMarkupParseContext *inContext,
739 const gchar *inElementName,
740 const gchar **inAttributeNames,
741 const gchar **inAttributeValues,
742 gpointer inUserData,
743 GError **outError)
744 {
745 XfdashboardThemeAnimationParserData *data=(XfdashboardThemeAnimationParserData*)inUserData;
746 gint currentTag=TAG_PROPERTY;
747 gint nextTag;
748
749 /* Update last position for more accurate line and position in error messages */
750 data->lastLine=data->currentLine;
751 data->lastPosition=data->currentPostition;
752 g_markup_parse_context_get_position(inContext, &data->currentLine, &data->currentPostition);
753
754 /* Get tag of next element */
755 nextTag=_xfdashboard_theme_animation_get_tag_by_name(inElementName);
756 if(nextTag==-1)
757 {
758 _xfdashboard_theme_animation_parse_set_error(data,
759 inContext,
760 outError,
761 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
762 "Unknown tag <%s>",
763 inElementName);
764 return;
765 }
766
767 /* If we get here the given element name cannot follow this tag */
768 _xfdashboard_theme_animation_parse_set_error(data,
769 inContext,
770 outError,
771 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
772 "Tag <%s> cannot contain tag <%s>",
773 _xfdashboard_theme_animation_get_tag_by_id(currentTag),
774 inElementName);
775 }
776
777 /* Parser callbacks for <apply> node */
_xfdashboard_theme_animation_parse_apply_start(GMarkupParseContext * inContext,const gchar * inElementName,const gchar ** inAttributeNames,const gchar ** inAttributeValues,gpointer inUserData,GError ** outError)778 static void _xfdashboard_theme_animation_parse_apply_start(GMarkupParseContext *inContext,
779 const gchar *inElementName,
780 const gchar **inAttributeNames,
781 const gchar **inAttributeValues,
782 gpointer inUserData,
783 GError **outError)
784 {
785 XfdashboardThemeAnimationParserData *data=(XfdashboardThemeAnimationParserData*)inUserData;
786 gint currentTag=TAG_APPLY;
787 gint nextTag;
788 GError *error=NULL;
789
790 /* Update last position for more accurate line and position in error messages */
791 data->lastLine=data->currentLine;
792 data->lastPosition=data->currentPostition;
793 g_markup_parse_context_get_position(inContext, &data->currentLine, &data->currentPostition);
794
795 /* Get tag of next element */
796 nextTag=_xfdashboard_theme_animation_get_tag_by_name(inElementName);
797 if(nextTag==-1)
798 {
799 _xfdashboard_theme_animation_parse_set_error(data,
800 inContext,
801 outError,
802 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
803 "Unknown tag <%s>",
804 inElementName);
805 return;
806 }
807
808 /* Check if element name is <property> and follows expected parent tags:
809 * <apply>
810 */
811 if(nextTag==TAG_PROPERTY)
812 {
813 static GMarkupParser propertyParser=
814 {
815 _xfdashboard_theme_animation_parse_property_start,
816 NULL,
817 _xfdashboard_theme_animation_parse_general_no_text_nodes,
818 NULL,
819 NULL,
820 };
821
822 const gchar *propertyName=NULL;
823 const gchar *propertyFrom=NULL;
824 const gchar *propertyTo=NULL;
825 XfdashboardThemeAnimationTargetsProperty *property=NULL;
826
827 g_assert(data->currentTargets!=NULL);
828
829 /* Get tag's attributes */
830 if(!g_markup_collect_attributes(inElementName,
831 inAttributeNames,
832 inAttributeValues,
833 &error,
834 G_MARKUP_COLLECT_STRING,
835 "name",
836 &propertyName,
837 G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL,
838 "from",
839 &propertyFrom,
840 G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL,
841 "to",
842 &propertyTo,
843 G_MARKUP_COLLECT_INVALID))
844 {
845 g_propagate_error(outError, error);
846 return;
847 }
848
849 /* Check tag's attributes */
850 if(strlen(propertyName)==0)
851 {
852 _xfdashboard_theme_animation_parse_set_error(data,
853 inContext,
854 outError,
855 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
856 "Empty 'name' at tag '%s'",
857 inElementName);
858 return;
859 }
860
861 if(propertyFrom && strlen(propertyFrom)==0)
862 {
863 _xfdashboard_theme_animation_parse_set_error(data,
864 inContext,
865 outError,
866 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
867 "Empty 'from' at tag '%s'",
868 inElementName);
869 return;
870 }
871
872 if(propertyTo && strlen(propertyTo)==0)
873 {
874 _xfdashboard_theme_animation_parse_set_error(data,
875 inContext,
876 outError,
877 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
878 "Empty 'to' at tag '%s'",
879 inElementName);
880 return;
881 }
882
883 /* Create new animation property and add to current targets */
884 property=_xfdashboard_theme_animation_targets_property_new(propertyName, propertyFrom, propertyTo);
885 _xfdashboard_theme_animation_targets_add_property(data->currentTargets, property);
886 _xfdashboard_theme_animation_targets_property_unref(property);
887
888 /* Set up context for tag <apply> */
889 g_markup_parse_context_push(inContext, &propertyParser, inUserData);
890 return;
891 }
892
893 /* If we get here the given element name cannot follow this tag */
894 _xfdashboard_theme_animation_parse_set_error(data,
895 inContext,
896 outError,
897 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
898 "Tag <%s> cannot contain tag <%s>",
899 _xfdashboard_theme_animation_get_tag_by_id(currentTag),
900 inElementName);
901 }
902
_xfdashboard_theme_animation_parse_apply_end(GMarkupParseContext * inContext,const gchar * inElementName,gpointer inUserData,GError ** outError)903 static void _xfdashboard_theme_animation_parse_apply_end(GMarkupParseContext *inContext,
904 const gchar *inElementName,
905 gpointer inUserData,
906 GError **outError)
907 {
908 /* Restore previous parser context */
909 g_markup_parse_context_pop(inContext);
910 }
911
912 /* Parser callbacks for <timeline> node */
_xfdashboard_theme_animation_parse_timeline_start(GMarkupParseContext * inContext,const gchar * inElementName,const gchar ** inAttributeNames,const gchar ** inAttributeValues,gpointer inUserData,GError ** outError)913 static void _xfdashboard_theme_animation_parse_timeline_start(GMarkupParseContext *inContext,
914 const gchar *inElementName,
915 const gchar **inAttributeNames,
916 const gchar **inAttributeValues,
917 gpointer inUserData,
918 GError **outError)
919 {
920 XfdashboardThemeAnimationParserData *data=(XfdashboardThemeAnimationParserData*)inUserData;
921 gint currentTag=TAG_TIMELINE;
922 gint nextTag;
923 GError *error=NULL;
924
925 /* Update last position for more accurate line and position in error messages */
926 data->lastLine=data->currentLine;
927 data->lastPosition=data->currentPostition;
928 g_markup_parse_context_get_position(inContext, &data->currentLine, &data->currentPostition);
929
930 /* Get tag of next element */
931 nextTag=_xfdashboard_theme_animation_get_tag_by_name(inElementName);
932 if(nextTag==-1)
933 {
934 _xfdashboard_theme_animation_parse_set_error(data,
935 inContext,
936 outError,
937 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
938 "Unknown tag <%s>",
939 inElementName);
940 return;
941 }
942
943 /* Check if element name is <apply> and follows expected parent tags:
944 * <timeline>
945 */
946 if(nextTag==TAG_APPLY)
947 {
948 static GMarkupParser propertyParser=
949 {
950 _xfdashboard_theme_animation_parse_apply_start,
951 _xfdashboard_theme_animation_parse_apply_end,
952 _xfdashboard_theme_animation_parse_general_no_text_nodes,
953 NULL,
954 NULL,
955 };
956
957 const gchar *applyToText=NULL;
958 XfdashboardCssSelector *applyTo=NULL;
959 const gchar *applyOriginText=NULL;
960 gint applyOrigin;
961
962 g_assert(data->currentTargets==NULL);
963
964 /* Get tag's attributes */
965 if(!g_markup_collect_attributes(inElementName,
966 inAttributeNames,
967 inAttributeValues,
968 &error,
969 G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL,
970 "to",
971 &applyToText,
972 G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL,
973 "origin",
974 &applyOriginText,
975 G_MARKUP_COLLECT_INVALID))
976 {
977 g_propagate_error(outError, error);
978 return;
979 }
980
981 /* Check tag's attributes */
982 if(applyToText && strlen(applyToText)==0)
983 {
984 _xfdashboard_theme_animation_parse_set_error(data,
985 inContext,
986 outError,
987 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
988 "Empty 'to' at tag '%s'",
989 inElementName);
990 return;
991 }
992
993 if(applyOriginText && strlen(applyOriginText)==0)
994 {
995 _xfdashboard_theme_animation_parse_set_error(data,
996 inContext,
997 outError,
998 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
999 "Empty 'origin' at tag '%s'",
1000 inElementName);
1001 return;
1002 }
1003
1004 /* Convert tag's attributes' value to usable values */
1005 applyOrigin=XFDASHBOARD_THEME_ANIMATION_APPLY_TO_ORIGIN_SENDER;
1006 if(applyOriginText)
1007 {
1008 if(g_strcmp0(applyOriginText, "sender")==0) applyOrigin=XFDASHBOARD_THEME_ANIMATION_APPLY_TO_ORIGIN_SENDER;
1009 else if(g_strcmp0(applyOriginText, "stage")==0) applyOrigin=XFDASHBOARD_THEME_ANIMATION_APPLY_TO_ORIGIN_STAGE;
1010 else
1011 {
1012 _xfdashboard_theme_animation_parse_set_error(data,
1013 inContext,
1014 outError,
1015 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
1016 "Invalid value '%s' for 'origin' at tag '%s'",
1017 applyOriginText,
1018 inElementName);
1019 return;
1020 }
1021 }
1022
1023 /* Create new animation timeline with timeline data */
1024 applyTo=NULL;
1025 if(applyToText)
1026 {
1027 applyTo=xfdashboard_css_selector_new_from_string(applyToText);
1028 }
1029
1030 data->currentTargets=_xfdashboard_theme_animation_targets_new(applyTo, applyOrigin, data->currentTimeline);
1031
1032 if(applyTo) g_object_unref(applyTo);
1033
1034 /* Set up context for tag <apply> */
1035 g_markup_parse_context_push(inContext, &propertyParser, inUserData);
1036 return;
1037 }
1038
1039 /* If we get here the given element name cannot follow this tag */
1040 _xfdashboard_theme_animation_parse_set_error(data,
1041 inContext,
1042 outError,
1043 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
1044 "Tag <%s> cannot contain tag <%s>",
1045 _xfdashboard_theme_animation_get_tag_by_id(currentTag),
1046 inElementName);
1047 }
1048
_xfdashboard_theme_animation_parse_timeline_end(GMarkupParseContext * inContext,const gchar * inElementName,gpointer inUserData,GError ** outError)1049 static void _xfdashboard_theme_animation_parse_timeline_end(GMarkupParseContext *inContext,
1050 const gchar *inElementName,
1051 gpointer inUserData,
1052 GError **outError)
1053 {
1054 XfdashboardThemeAnimationParserData *data=(XfdashboardThemeAnimationParserData*)inUserData;
1055
1056 g_assert(data->currentSpec);
1057 g_assert(data->currentTargets);
1058
1059 /* Add targets to animation specification */
1060 _xfdashboard_theme_animation_spec_add_targets(data->currentSpec, data->currentTargets);
1061 _xfdashboard_theme_animation_targets_unref(data->currentTargets);
1062 data->currentTargets=NULL;
1063
1064 /* Restore previous parser context */
1065 g_markup_parse_context_pop(inContext);
1066 }
1067
1068 /* Parser callbacks for <trigger> node */
_xfdashboard_theme_animation_parse_trigger_start(GMarkupParseContext * inContext,const gchar * inElementName,const gchar ** inAttributeNames,const gchar ** inAttributeValues,gpointer inUserData,GError ** outError)1069 static void _xfdashboard_theme_animation_parse_trigger_start(GMarkupParseContext *inContext,
1070 const gchar *inElementName,
1071 const gchar **inAttributeNames,
1072 const gchar **inAttributeValues,
1073 gpointer inUserData,
1074 GError **outError)
1075 {
1076 XfdashboardThemeAnimationParserData *data=(XfdashboardThemeAnimationParserData*)inUserData;
1077 gint currentTag=TAG_TRIGGER;
1078 gint nextTag;
1079 GError *error=NULL;
1080
1081 /* Update last position for more accurate line and position in error messages */
1082 data->lastLine=data->currentLine;
1083 data->lastPosition=data->currentPostition;
1084 g_markup_parse_context_get_position(inContext, &data->currentLine, &data->currentPostition);
1085
1086 /* Get tag of next element */
1087 nextTag=_xfdashboard_theme_animation_get_tag_by_name(inElementName);
1088 if(nextTag==-1)
1089 {
1090 _xfdashboard_theme_animation_parse_set_error(data,
1091 inContext,
1092 outError,
1093 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
1094 "Unknown tag <%s>",
1095 inElementName);
1096 return;
1097 }
1098
1099 /* Check if element name is <timeline> and follows expected parent tags:
1100 * <trigger>
1101 */
1102 if(nextTag==TAG_TIMELINE)
1103 {
1104 static GMarkupParser propertyParser=
1105 {
1106 _xfdashboard_theme_animation_parse_timeline_start,
1107 _xfdashboard_theme_animation_parse_timeline_end,
1108 _xfdashboard_theme_animation_parse_general_no_text_nodes,
1109 NULL,
1110 NULL,
1111 };
1112
1113 const gchar *timelineDelayText=NULL;
1114 const gchar *timelineDurationText=NULL;
1115 const gchar *timelineModeText=NULL;
1116 const gchar *timelineRepeatText=NULL;
1117 gint timelineDelay;
1118 gint timelineDuration;
1119 gint timelineMode;
1120 gint timelineRepeat;
1121
1122 g_assert(data->currentTimeline==NULL);
1123
1124 /* Get tag's attributes */
1125 if(!g_markup_collect_attributes(inElementName,
1126 inAttributeNames,
1127 inAttributeValues,
1128 &error,
1129 G_MARKUP_COLLECT_STRING,
1130 "delay",
1131 &timelineDelayText,
1132 G_MARKUP_COLLECT_STRING,
1133 "duration",
1134 &timelineDurationText,
1135 G_MARKUP_COLLECT_STRING,
1136 "mode",
1137 &timelineModeText,
1138 G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL,
1139 "repeat",
1140 &timelineRepeatText,
1141 G_MARKUP_COLLECT_INVALID))
1142 {
1143 g_propagate_error(outError, error);
1144 return;
1145 }
1146
1147 /* Check tag's attributes */
1148 if(!timelineDelayText || strlen(timelineDelayText)==0)
1149 {
1150 _xfdashboard_theme_animation_parse_set_error(data,
1151 inContext,
1152 outError,
1153 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
1154 "Missing or empty delay at tag '%s'",
1155 inElementName);
1156 return;
1157 }
1158
1159 if(!timelineDurationText || strlen(timelineDurationText)==0)
1160 {
1161 _xfdashboard_theme_animation_parse_set_error(data,
1162 inContext,
1163 outError,
1164 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
1165 "Missing or empty duration at tag '%s'",
1166 inElementName);
1167 return;
1168 }
1169
1170 if(!timelineModeText || strlen(timelineModeText)==0)
1171 {
1172 _xfdashboard_theme_animation_parse_set_error(data,
1173 inContext,
1174 outError,
1175 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
1176 "Missing or empty mode at tag '%s'",
1177 inElementName);
1178 return;
1179 }
1180
1181 if(timelineRepeatText && strlen(timelineRepeatText)==0)
1182 {
1183 _xfdashboard_theme_animation_parse_set_error(data,
1184 inContext,
1185 outError,
1186 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
1187 "Empty repeat at tag '%s'",
1188 inElementName);
1189 return;
1190 }
1191
1192 /* Convert tag's attributes' value to usable values */
1193 if(!_xfdashboard_theme_animation_string_to_gint(timelineDelayText, &timelineDelay, &error))
1194 {
1195 g_propagate_error(outError, error);
1196 return;
1197 }
1198
1199 if(!_xfdashboard_theme_animation_string_to_gint(timelineDurationText, &timelineDuration, &error))
1200 {
1201 g_propagate_error(outError, error);
1202 return;
1203 }
1204
1205 timelineMode=xfdashboard_get_enum_value_from_nickname(CLUTTER_TYPE_ANIMATION_MODE, timelineModeText);
1206 if(timelineMode==G_MININT)
1207 {
1208 /* Set error */
1209 g_set_error(outError,
1210 XFDASHBOARD_THEME_ANIMATION_ERROR,
1211 XFDASHBOARD_THEME_ANIMATION_ERROR_ERROR,
1212 "Invalid mode '%s'",
1213 timelineModeText);
1214 return;
1215 }
1216
1217 timelineRepeat=0;
1218 if(timelineRepeatText &&
1219 !_xfdashboard_theme_animation_string_to_gint(timelineRepeatText, &timelineRepeat, &error))
1220 {
1221 g_propagate_error(outError, error);
1222 return;
1223 }
1224
1225 /* Create new animation timeline with timeline data */
1226 data->currentTimeline=clutter_timeline_new(timelineDuration);
1227 clutter_timeline_set_delay(data->currentTimeline, timelineDelay);
1228 clutter_timeline_set_progress_mode(data->currentTimeline, timelineMode);
1229 clutter_timeline_set_repeat_count(data->currentTimeline, timelineRepeat);
1230
1231 /* Set up context for tag <timeline> */
1232 g_markup_parse_context_push(inContext, &propertyParser, inUserData);
1233 return;
1234 }
1235
1236 /* If we get here the given element name cannot follow this tag */
1237 _xfdashboard_theme_animation_parse_set_error(data,
1238 inContext,
1239 outError,
1240 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
1241 "Tag <%s> cannot contain tag <%s>",
1242 _xfdashboard_theme_animation_get_tag_by_id(currentTag),
1243 inElementName);
1244 }
1245
_xfdashboard_theme_animation_parse_trigger_end(GMarkupParseContext * inContext,const gchar * inElementName,gpointer inUserData,GError ** outError)1246 static void _xfdashboard_theme_animation_parse_trigger_end(GMarkupParseContext *inContext,
1247 const gchar *inElementName,
1248 gpointer inUserData,
1249 GError **outError)
1250 {
1251 XfdashboardThemeAnimationParserData *data=(XfdashboardThemeAnimationParserData*)inUserData;
1252
1253 g_assert(data->currentTimeline);
1254
1255 /* Add targets to animation specification */
1256 g_object_unref(data->currentTimeline);
1257 data->currentTimeline=NULL;
1258
1259 /* Restore previous parser context */
1260 g_markup_parse_context_pop(inContext);
1261 }
1262
1263 /* Parser callbacks for <animations> node */
_xfdashboard_theme_animation_parse_animations_start(GMarkupParseContext * inContext,const gchar * inElementName,const gchar ** inAttributeNames,const gchar ** inAttributeValues,gpointer inUserData,GError ** outError)1264 static void _xfdashboard_theme_animation_parse_animations_start(GMarkupParseContext *inContext,
1265 const gchar *inElementName,
1266 const gchar **inAttributeNames,
1267 const gchar **inAttributeValues,
1268 gpointer inUserData,
1269 GError **outError)
1270 {
1271 XfdashboardThemeAnimationParserData *data=(XfdashboardThemeAnimationParserData*)inUserData;
1272 gint currentTag=TAG_ANIMATIONS;
1273 gint nextTag;
1274 GError *error=NULL;
1275
1276 /* Update last position for more accurate line and position in error messages */
1277 data->lastLine=data->currentLine;
1278 data->lastPosition=data->currentPostition;
1279 g_markup_parse_context_get_position(inContext, &data->currentLine, &data->currentPostition);
1280
1281 /* Get tag of next element */
1282 nextTag=_xfdashboard_theme_animation_get_tag_by_name(inElementName);
1283 if(nextTag==-1)
1284 {
1285 _xfdashboard_theme_animation_parse_set_error(data,
1286 inContext,
1287 outError,
1288 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
1289 "Unknown tag <%s>",
1290 inElementName);
1291 return;
1292 }
1293
1294 /* Check if element name is <trigger> and follows expected parent tags:
1295 * <animations>
1296 */
1297 if(nextTag==TAG_TRIGGER)
1298 {
1299 static GMarkupParser propertyParser=
1300 {
1301 _xfdashboard_theme_animation_parse_trigger_start,
1302 _xfdashboard_theme_animation_parse_trigger_end,
1303 _xfdashboard_theme_animation_parse_general_no_text_nodes,
1304 NULL,
1305 NULL,
1306 };
1307
1308 const gchar *triggerID=NULL;
1309 const gchar *triggerSender=NULL;
1310 const gchar *triggerSignal=NULL;
1311 XfdashboardCssSelector *selector=NULL;
1312
1313 g_assert(data->currentSpec==NULL);
1314
1315 /* Get tag's attributes */
1316 if(!g_markup_collect_attributes(inElementName,
1317 inAttributeNames,
1318 inAttributeValues,
1319 &error,
1320 G_MARKUP_COLLECT_STRING,
1321 "id",
1322 &triggerID,
1323 G_MARKUP_COLLECT_STRING,
1324 "sender",
1325 &triggerSender,
1326 G_MARKUP_COLLECT_STRING,
1327 "signal",
1328 &triggerSignal,
1329 G_MARKUP_COLLECT_INVALID))
1330 {
1331 g_propagate_error(outError, error);
1332 return;
1333 }
1334
1335 /* Check tag's attributes */
1336 if(!triggerID || strlen(triggerID)==0)
1337 {
1338 _xfdashboard_theme_animation_parse_set_error(data,
1339 inContext,
1340 outError,
1341 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
1342 "Missing or empty ID at tag '%s'",
1343 inElementName);
1344 return;
1345 }
1346
1347 if(!triggerSender || strlen(triggerSender)==0)
1348 {
1349 _xfdashboard_theme_animation_parse_set_error(data,
1350 inContext,
1351 outError,
1352 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
1353 "Missing or empty sender at tag '%s'",
1354 inElementName);
1355 return;
1356 }
1357
1358 if(!triggerSignal || strlen(triggerSignal)==0)
1359 {
1360 _xfdashboard_theme_animation_parse_set_error(data,
1361 inContext,
1362 outError,
1363 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
1364 "Missing or empty signal at tag '%s'",
1365 inElementName);
1366 return;
1367 }
1368
1369 if(!xfdashboard_is_valid_id(triggerID))
1370 {
1371 _xfdashboard_theme_animation_parse_set_error(data,
1372 inContext,
1373 outError,
1374 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
1375 "Invalid ID '%s' at tag '%s'",
1376 triggerID,
1377 inElementName);
1378 return;
1379 }
1380
1381 if(_xfdashboard_theme_animation_has_id(data->self, data, triggerID))
1382 {
1383 _xfdashboard_theme_animation_parse_set_error(data,
1384 inContext,
1385 outError,
1386 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
1387 "Multiple definition of trigger with ID '%s'",
1388 triggerID);
1389 return;
1390 }
1391
1392 /* Create new animation specification with trigger data */
1393 selector=xfdashboard_css_selector_new_from_string(triggerSender);
1394 data->currentSpec=_xfdashboard_theme_animation_spec_new(triggerID, selector, triggerSignal);
1395 g_object_unref(selector);
1396
1397 /* Set up context for tag <trigger> */
1398 g_markup_parse_context_push(inContext, &propertyParser, inUserData);
1399 return;
1400 }
1401
1402 /* If we get here the given element name cannot follow this tag */
1403 _xfdashboard_theme_animation_parse_set_error(data,
1404 inContext,
1405 outError,
1406 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
1407 "Tag <%s> cannot contain tag <%s>",
1408 _xfdashboard_theme_animation_get_tag_by_id(currentTag),
1409 inElementName);
1410 }
1411
_xfdashboard_theme_animation_parse_animations_end(GMarkupParseContext * inContext,const gchar * inElementName,gpointer inUserData,GError ** outError)1412 static void _xfdashboard_theme_animation_parse_animations_end(GMarkupParseContext *inContext,
1413 const gchar *inElementName,
1414 gpointer inUserData,
1415 GError **outError)
1416 {
1417 XfdashboardThemeAnimationParserData *data=(XfdashboardThemeAnimationParserData*)inUserData;
1418
1419 g_assert(data->currentSpec);
1420
1421 /* Add animation specification to list of animations */
1422 data->specs=g_slist_prepend(data->specs, data->currentSpec);
1423 data->currentSpec=NULL;
1424
1425 /* Restore previous parser context */
1426 g_markup_parse_context_pop(inContext);
1427 }
1428
1429 /* Parser callbacks for document root node */
_xfdashboard_theme_animation_parse_document_start(GMarkupParseContext * inContext,const gchar * inElementName,const gchar ** inAttributeNames,const gchar ** inAttributeValues,gpointer inUserData,GError ** outError)1430 static void _xfdashboard_theme_animation_parse_document_start(GMarkupParseContext *inContext,
1431 const gchar *inElementName,
1432 const gchar **inAttributeNames,
1433 const gchar **inAttributeValues,
1434 gpointer inUserData,
1435 GError **outError)
1436 {
1437 XfdashboardThemeAnimationParserData *data=(XfdashboardThemeAnimationParserData*)inUserData;
1438 gint currentTag=TAG_DOCUMENT;
1439 gint nextTag;
1440 GError *error=NULL;
1441
1442 /* Update last position for more accurate line and position in error messages */
1443 data->lastLine=data->currentLine;
1444 data->lastPosition=data->currentPostition;
1445 g_markup_parse_context_get_position(inContext, &data->currentLine, &data->currentPostition);
1446
1447 /* Get tag of next element */
1448 nextTag=_xfdashboard_theme_animation_get_tag_by_name(inElementName);
1449 if(nextTag==-1)
1450 {
1451 _xfdashboard_theme_animation_parse_set_error(data,
1452 inContext,
1453 outError,
1454 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
1455 "Unknown tag <%s>",
1456 inElementName);
1457 return;
1458 }
1459
1460 /* Check if element name is <animations> and follows expected parent tags:
1461 * <document>
1462 */
1463 if(nextTag==TAG_ANIMATIONS)
1464 {
1465 static GMarkupParser propertyParser=
1466 {
1467 _xfdashboard_theme_animation_parse_animations_start,
1468 _xfdashboard_theme_animation_parse_animations_end,
1469 _xfdashboard_theme_animation_parse_general_no_text_nodes,
1470 NULL,
1471 NULL,
1472 };
1473
1474 /* Get tag's attributes */
1475 if(!g_markup_collect_attributes(inElementName,
1476 inAttributeNames,
1477 inAttributeValues,
1478 &error,
1479 G_MARKUP_COLLECT_INVALID,
1480 NULL))
1481 {
1482 g_propagate_error(outError, error);
1483 }
1484
1485 /* Set up context for tag <animations> */
1486 g_markup_parse_context_push(inContext, &propertyParser, inUserData);
1487 return;
1488 }
1489
1490 /* If we get here the given element name cannot follow this tag */
1491 _xfdashboard_theme_animation_parse_set_error(data,
1492 inContext,
1493 outError,
1494 XFDASHBOARD_THEME_ANIMATION_ERROR_MALFORMED,
1495 "Tag <%s> cannot contain tag <%s>",
1496 _xfdashboard_theme_animation_get_tag_by_id(currentTag),
1497 inElementName);
1498 }
1499
_xfdashboard_theme_animation_parse_document_end(GMarkupParseContext * inContext,const gchar * inElementName,gpointer inUserData,GError ** outError)1500 static void _xfdashboard_theme_animation_parse_document_end(GMarkupParseContext *inContext,
1501 const gchar *inElementName,
1502 gpointer inUserData,
1503 GError **outError)
1504 {
1505 /* Restore previous parser context */
1506 g_markup_parse_context_pop(inContext);
1507 }
1508
1509 /* Parse XML from string */
_xfdashboard_theme_animation_parse_xml(XfdashboardThemeAnimation * self,const gchar * inPath,const gchar * inContents,GError ** outError)1510 static gboolean _xfdashboard_theme_animation_parse_xml(XfdashboardThemeAnimation *self,
1511 const gchar *inPath,
1512 const gchar *inContents,
1513 GError **outError)
1514 {
1515 static GMarkupParser parser=
1516 {
1517 _xfdashboard_theme_animation_parse_document_start,
1518 _xfdashboard_theme_animation_parse_document_end,
1519 _xfdashboard_theme_animation_parse_general_no_text_nodes,
1520 NULL,
1521 NULL,
1522 };
1523
1524 XfdashboardThemeAnimationParserData *data;
1525 GMarkupParseContext *context;
1526 GError *error;
1527 gboolean success;
1528
1529 g_return_val_if_fail(XFDASHBOARD_IS_THEME_ANIMATION(self), FALSE);
1530 g_return_val_if_fail(inPath && *inPath, FALSE);
1531 g_return_val_if_fail(inContents && *inContents, FALSE);
1532 g_return_val_if_fail(outError==NULL || *outError==NULL, FALSE);
1533
1534 error=NULL;
1535 success=TRUE;
1536
1537 /* Create and set up parser instance */
1538 data=g_new0(XfdashboardThemeAnimationParserData, 1);
1539 if(!data)
1540 {
1541 /* Set error */
1542 g_set_error(outError,
1543 XFDASHBOARD_THEME_ANIMATION_ERROR,
1544 XFDASHBOARD_THEME_ANIMATION_ERROR_ERROR,
1545 "Could not set up parser data for file %s",
1546 inPath);
1547 return(FALSE);
1548 }
1549
1550 context=g_markup_parse_context_new(&parser, 0, data, NULL);
1551 if(!context)
1552 {
1553 /* Set error */
1554 g_set_error(outError,
1555 XFDASHBOARD_THEME_ANIMATION_ERROR,
1556 XFDASHBOARD_THEME_ANIMATION_ERROR_ERROR,
1557 "Could not create parser for file %s",
1558 inPath);
1559
1560 g_free(data);
1561 return(FALSE);
1562 }
1563
1564 /* Now the parser and its context is set up and we can now
1565 * safely initialize data.
1566 */
1567 data->self=self;
1568 data->specs=NULL;
1569 data->currentSpec=NULL;
1570 data->currentTimeline=NULL;
1571 data->currentTargets=NULL;
1572 data->lastLine=1;
1573 data->lastPosition=1;
1574 data->currentLine=1;
1575 data->currentPostition=1;
1576
1577 /* Parse XML string */
1578 if(success && !g_markup_parse_context_parse(context, inContents, -1, &error))
1579 {
1580 g_propagate_error(outError, error);
1581 success=FALSE;
1582 }
1583
1584 if(success && !g_markup_parse_context_end_parse(context, &error))
1585 {
1586 g_propagate_error(outError, error);
1587 success=FALSE;
1588 }
1589
1590 /* Handle collected data if parsing was successful */
1591 if(success)
1592 {
1593 g_slist_foreach(data->specs, (GFunc)_xfdashboard_theme_animation_ref_and_add_to_theme, self);
1594 }
1595
1596 /* Clean up resources */
1597 #ifdef DEBUG
1598 if(!success)
1599 {
1600 // TODO: g_slist_foreach(data->specs, (GFunc)_xfdashboard_theme_animation_print_parsed_objects, "Animation specs (this file):");
1601 // TODO: g_slist_foreach(self->priv->specs, (GFunc)_xfdashboard_theme_animation_print_parsed_objects, "Animation specs (parsed before):");
1602 XFDASHBOARD_DEBUG(self, THEME,
1603 "PARSER ERROR: %s",
1604 (outError && *outError) ? (*outError)->message : "unknown error");
1605 }
1606 #endif
1607
1608 g_markup_parse_context_free(context);
1609
1610 g_slist_free_full(data->specs, (GDestroyNotify)_xfdashboard_theme_animation_spec_unref);
1611 g_free(data);
1612
1613 return(success);
1614 }
1615
1616 /* Find matching entry in list of provided default value and set up value if found */
_xfdashboard_theme_animation_find_default_property_value(XfdashboardThemeAnimation * self,XfdashboardAnimationValue ** inDefaultValues,XfdashboardActor * inSender,const gchar * inProperty,ClutterActor * inActor,GValue * ioValue)1617 static gboolean _xfdashboard_theme_animation_find_default_property_value(XfdashboardThemeAnimation *self,
1618 XfdashboardAnimationValue **inDefaultValues,
1619 XfdashboardActor *inSender,
1620 const gchar *inProperty,
1621 ClutterActor *inActor,
1622 GValue *ioValue)
1623 {
1624 XfdashboardAnimationValue *foundValue;
1625
1626 g_return_val_if_fail(XFDASHBOARD_IS_THEME_ANIMATION(self), FALSE);
1627 g_return_val_if_fail(XFDASHBOARD_IS_ACTOR(inSender), FALSE);
1628 g_return_val_if_fail(inProperty && *inProperty, FALSE);
1629 g_return_val_if_fail(CLUTTER_IS_ACTOR(inActor), FALSE);
1630 g_return_val_if_fail(ioValue && G_VALUE_TYPE(ioValue)!=G_TYPE_INVALID, FALSE);
1631
1632 foundValue=NULL;
1633
1634 /* Skip if actor is not stylable and cannot be matched againt the selectors in values list */
1635 if(!XFDASHBOARD_IS_STYLABLE(inActor))
1636 {
1637 XFDASHBOARD_DEBUG(self, ANIMATION,
1638 "Actor %s@%p is not stylable and cannot match any selector in list of default values so fail",
1639 G_OBJECT_TYPE_NAME(inActor),
1640 inActor);
1641
1642 /* Unset value to prevent using it as it could not be converted */
1643 g_value_unset(ioValue);
1644
1645 return(FALSE);
1646 }
1647
1648 /* Iterate through list of values and find best matching entry for given actor */
1649 if(inDefaultValues)
1650 {
1651 XfdashboardAnimationValue **iter;
1652 gfloat iterScore;
1653 gfloat foundScore;
1654
1655 for(iter=inDefaultValues; *iter; iter++)
1656 {
1657 /* Check if currently iterated entry in list of value matches the actor */
1658 if((*iter)->selector)
1659 {
1660 iterScore=xfdashboard_css_selector_score((*iter)->selector, XFDASHBOARD_STYLABLE(inActor));
1661 if(iterScore<0) continue;
1662
1663 /* Check match is the best one with a higher sore as the previous one (if available) */
1664 if(foundValue && iterScore<=foundScore) continue;
1665 }
1666 else if((gpointer)inActor!=(gpointer)inSender) continue;
1667
1668 /* Check if entry matches the requested property */
1669 if(g_strcmp0((*iter)->property, inProperty)!=0) continue;
1670
1671 /* Remember this match as best one */
1672 foundValue=*iter;
1673 foundScore=iterScore;
1674 }
1675 }
1676
1677 /* If no match was found, return FALSE here */
1678 if(!foundValue) return(FALSE);
1679
1680 /* Set up value as a match was found */
1681 if(!g_value_transform(foundValue->value, ioValue))
1682 {
1683 g_warning("Could not transform default value of for property '%s' of %s from type %s to %s of class %s",
1684 foundValue->property,
1685 G_OBJECT_TYPE_NAME(inActor),
1686 G_VALUE_TYPE_NAME(foundValue->value),
1687 G_VALUE_TYPE_NAME(ioValue),
1688 G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(inActor)));
1689
1690 /* Unset value to prevent using it as it could not be converted */
1691 g_value_unset(ioValue);
1692
1693 return(FALSE);
1694 }
1695
1696 /* If we get here, we found a match and could convert the value */
1697 return(TRUE);
1698 }
1699
1700 /* Create animation by specification */
_xfdashboard_theme_animation_free_animation_actor_map_hashtable(gpointer inKey,gpointer inValue,gpointer inUserData)1701 static void _xfdashboard_theme_animation_free_animation_actor_map_hashtable(gpointer inKey, gpointer inValue, gpointer inUserData)
1702 {
1703 g_return_if_fail(inKey);
1704
1705 g_slist_free(inKey);
1706 }
1707
_xfdashboard_theme_animation_create_by_spec(XfdashboardThemeAnimation * self,XfdashboardThemeAnimationSpec * inSpec,XfdashboardActor * inSender,XfdashboardAnimationValue ** inDefaultInitialValues,XfdashboardAnimationValue ** inDefaultFinalValues)1708 static XfdashboardAnimation* _xfdashboard_theme_animation_create_by_spec(XfdashboardThemeAnimation *self,
1709 XfdashboardThemeAnimationSpec *inSpec,
1710 XfdashboardActor *inSender,
1711 XfdashboardAnimationValue **inDefaultInitialValues,
1712 XfdashboardAnimationValue **inDefaultFinalValues)
1713 {
1714 XfdashboardAnimation *animation;
1715 XfdashboardAnimationClass *animationClass;
1716 GSList *iterTargets;
1717 gint counterTargets;
1718 GHashTable *animationActorMap;
1719 #ifdef DEBUG
1720 gboolean doDebug=FALSE;
1721 #endif
1722
1723 g_return_val_if_fail(XFDASHBOARD_IS_THEME_ANIMATION(self), NULL);
1724 g_return_val_if_fail(XFDASHBOARD_IS_ACTOR(inSender), NULL);
1725 g_return_val_if_fail(inSpec, NULL);
1726
1727 animation=NULL;
1728 animationActorMap=NULL;
1729
1730 /* Create animation for animation specification */
1731 animation=g_object_new(XFDASHBOARD_TYPE_ANIMATION,
1732 "id", inSpec->id,
1733 NULL);
1734 if(!animation)
1735 {
1736 g_critical("Cannot allocate memory for animation '%s'",
1737 inSpec->id);
1738 return(NULL);
1739 }
1740
1741 animationClass=XFDASHBOARD_ANIMATION_GET_CLASS(animation);
1742 if(!animationClass->add_animation)
1743 {
1744 g_warning("Will not be able to add animations to actors as object of type %s does not implement required virtual function XfdashboardAnimation::%s",
1745 G_OBJECT_TYPE_NAME(self),
1746 "add_animation");
1747 }
1748
1749 /* Create actor-animation-list-mapping via a hash-table */
1750 animationActorMap=g_hash_table_new(g_direct_hash, g_direct_equal);
1751
1752 /* Iterate through animation targets of animation specification and create
1753 * property transition for each target and property found.
1754 */
1755 for(counterTargets=0, iterTargets=inSpec->targets; iterTargets; iterTargets=g_slist_next(iterTargets), counterTargets++)
1756 {
1757 XfdashboardThemeAnimationTargets *targets;
1758 GSList *actors;
1759 GSList *iterActors;
1760 gint counterActors;
1761
1762 /* Get currently iterate animation targets */
1763 targets=(XfdashboardThemeAnimationTargets*)iterTargets->data;
1764 if(!targets) continue;
1765
1766 /* Find targets to apply property transitions to */
1767 actors=_xfdashboard_theme_animation_find_actors_for_animation_targets(self, targets, CLUTTER_ACTOR(inSender));
1768 if(!actors) continue;
1769 XFDASHBOARD_DEBUG(self, ANIMATION,
1770 "Target #%d of animation specification '%s' applies to %d actors",
1771 counterTargets,
1772 inSpec->id,
1773 g_slist_length(actors));
1774
1775 /* Iterate through actor, create a transition group to collect
1776 * property transitions at, create a property transition for each
1777 * property specified in animation target, determine "from" value
1778 * where missing, add property transition to transition group and
1779 * finally add transition group with currently iterated actor to
1780 * animation object.
1781 */
1782 for(counterActors=0, iterActors=actors; iterActors; iterActors=g_slist_next(iterActors), counterActors++)
1783 {
1784 GSList *iterProperties;
1785 ClutterActor *actor;
1786 int counterProperties;
1787
1788 /* Get actor */
1789 actor=(ClutterActor*)iterActors->data;
1790 if(!actor) continue;
1791
1792 /* Iterate through properties and create a property transition
1793 * with cloned timeline from animation target. Determine "from"
1794 * value if missing in animation targets property specification
1795 * and add the property transition to transition group.
1796 */
1797 for(counterProperties=0, iterProperties=targets->properties; iterProperties; iterProperties=g_slist_next(iterProperties), counterProperties++)
1798 {
1799 XfdashboardThemeAnimationTargetsProperty *propertyTargetSpec;
1800 GParamSpec *propertySpec;
1801 GValue fromValue=G_VALUE_INIT;
1802 GValue toValue=G_VALUE_INIT;
1803
1804 /* Get target's property data to animate */
1805 propertyTargetSpec=(XfdashboardThemeAnimationTargetsProperty*)iterProperties->data;
1806 if(!propertyTargetSpec) continue;
1807
1808 /* Check if actor has property to animate */
1809 propertySpec=g_object_class_find_property(G_OBJECT_GET_CLASS(actor), propertyTargetSpec->name);
1810 if(!propertySpec && CLUTTER_IS_ANIMATABLE(actor))
1811 {
1812 propertySpec=clutter_animatable_find_property(CLUTTER_ANIMATABLE(actor), propertyTargetSpec->name);
1813 }
1814
1815 if(!propertySpec)
1816 {
1817 g_warning("Cannot create animation '%s' for non-existing property '%s' at actor of type '%s'",
1818 inSpec->id,
1819 propertyTargetSpec->name,
1820 G_OBJECT_TYPE_NAME(actor));
1821
1822 /* Skip this property as it does not exist at actor */
1823 continue;
1824 }
1825
1826 /* If no "from" value is set at target's property data, get
1827 * current value. Otherwise convert "from" value from target's
1828 * property data to expected type of property.
1829 */
1830 if(G_VALUE_TYPE(&propertyTargetSpec->from)!=G_TYPE_INVALID)
1831 {
1832 g_value_init(&fromValue, G_PARAM_SPEC_VALUE_TYPE(propertySpec));
1833 if(!g_value_transform(&propertyTargetSpec->from, &fromValue))
1834 {
1835 g_warning("Could not transform 'from'-value of '%s' for property '%s' to type %s of class %s",
1836 g_value_get_string(&propertyTargetSpec->from),
1837 propertyTargetSpec->name,
1838 g_type_name(G_PARAM_SPEC_VALUE_TYPE(propertySpec)),
1839 G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(actor)));
1840
1841 /* Unset "from" value to skip it, means it will no transition will be
1842 * create and it will not be added to transition group.
1843 */
1844 g_value_unset(&fromValue);
1845 }
1846 #ifdef DEBUG
1847 if(doDebug)
1848 {
1849 gchar *valueString;
1850
1851 valueString=g_strdup_value_contents(&propertyTargetSpec->from);
1852 XFDASHBOARD_DEBUG(self, ANIMATION,
1853 "%s 'from'-value %s of type %s for property '%s' to type %s of class %s for target #%d and actor #%d (%s@%p) of animation specification '%s'",
1854 (G_VALUE_TYPE(&fromValue)!=G_TYPE_INVALID) ? "Converted" : "Could not convert",
1855 valueString,
1856 G_VALUE_TYPE_NAME(&propertyTargetSpec->from),
1857 propertyTargetSpec->name,
1858 g_type_name(G_PARAM_SPEC_VALUE_TYPE(propertySpec)),
1859 G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(actor)),
1860 counterTargets,
1861 counterActors,
1862 G_OBJECT_TYPE_NAME(actor),
1863 actor,
1864 inSpec->id);
1865 g_free(valueString);
1866 }
1867 #endif
1868 }
1869 else
1870 {
1871 g_value_init(&fromValue, G_PARAM_SPEC_VALUE_TYPE(propertySpec));
1872 if(inDefaultInitialValues && _xfdashboard_theme_animation_find_default_property_value(self, inDefaultInitialValues, inSender, propertyTargetSpec->name, actor, &fromValue))
1873 {
1874 #ifdef DEBUG
1875 if(doDebug)
1876 {
1877 gchar *valueString;
1878
1879 valueString=g_strdup_value_contents(&fromValue);
1880 XFDASHBOARD_DEBUG(self, ANIMATION,
1881 "Using provided default 'from'-value %s for property '%s' from target #%d and actor #%d (%s@%p) of animation specification '%s' as no 'from' value was specified",
1882 valueString,
1883 propertyTargetSpec->name,
1884 counterTargets,
1885 counterActors,
1886 G_OBJECT_TYPE_NAME(actor),
1887 actor,
1888 inSpec->id);
1889 g_free(valueString);
1890 }
1891 #endif
1892 }
1893 else
1894 {
1895 g_object_get_property(G_OBJECT(actor),
1896 propertyTargetSpec->name,
1897 &fromValue);
1898 #ifdef DEBUG
1899 if(doDebug)
1900 {
1901 gchar *valueString;
1902
1903 valueString=g_strdup_value_contents(&fromValue);
1904 XFDASHBOARD_DEBUG(self, ANIMATION,
1905 "Fetching current 'from'-value %s for property '%s' from target #%d and actor #%d (%s@%p) of animation specification '%s' as no 'from' value was specified",
1906 valueString,
1907 propertyTargetSpec->name,
1908 counterTargets,
1909 counterActors,
1910 G_OBJECT_TYPE_NAME(actor),
1911 actor,
1912 inSpec->id);
1913 g_free(valueString);
1914 }
1915 #endif
1916 }
1917 }
1918
1919 /* If "to" value is set at target's property data, convert it
1920 * from target's property data to expected type of property.
1921 */
1922 if(G_VALUE_TYPE(&propertyTargetSpec->to)!=G_TYPE_INVALID)
1923 {
1924 g_value_init(&toValue, G_PARAM_SPEC_VALUE_TYPE(propertySpec));
1925 if(!g_value_transform(&propertyTargetSpec->to, &toValue))
1926 {
1927 g_warning("Could not transform 'to'-value of '%s' for property '%s' to type %s of class %s",
1928 g_value_get_string(&propertyTargetSpec->to),
1929 propertyTargetSpec->name,
1930 g_type_name(G_PARAM_SPEC_VALUE_TYPE(propertySpec)),
1931 G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(actor)));
1932
1933 /* Unset "to" value to prevent setting it at transition.
1934 * The animation will set a value when starting the
1935 * animation.
1936 */
1937 g_value_unset(&toValue);
1938 }
1939 #ifdef DEBUG
1940 if(doDebug)
1941 {
1942 gchar *valueString;
1943
1944 valueString=g_strdup_value_contents(&propertyTargetSpec->to);
1945 XFDASHBOARD_DEBUG(self, ANIMATION,
1946 "%s 'to'-value %s of type %s for property '%s' to type %s of class %s for target #%d and actor #%d (%s@%p) of animation specification '%s'",
1947 (G_VALUE_TYPE(&toValue)!=G_TYPE_INVALID) ? "Converted" : "Could not convert",
1948 valueString,
1949 G_VALUE_TYPE_NAME(&propertyTargetSpec->to),
1950 propertyTargetSpec->name,
1951 g_type_name(G_PARAM_SPEC_VALUE_TYPE(propertySpec)),
1952 G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(actor)),
1953 counterTargets,
1954 counterActors,
1955 G_OBJECT_TYPE_NAME(actor),
1956 actor,
1957 inSpec->id);
1958 g_free(valueString);
1959 }
1960 #endif
1961 }
1962 else
1963 {
1964 g_value_init(&toValue, G_PARAM_SPEC_VALUE_TYPE(propertySpec));
1965 if(inDefaultFinalValues && _xfdashboard_theme_animation_find_default_property_value(self, inDefaultFinalValues, inSender, propertyTargetSpec->name, actor, &toValue))
1966 {
1967 #ifdef DEBUG
1968 if(doDebug)
1969 {
1970 gchar *valueString;
1971
1972 valueString=g_strdup_value_contents(&toValue);
1973 XFDASHBOARD_DEBUG(self, ANIMATION,
1974 "Using provided default 'to'-value %s for property '%s' from target #%d and actor #%d (%s@%p) of animation specification '%s' as no 'to' value was specified",
1975 valueString,
1976 propertyTargetSpec->name,
1977 counterTargets,
1978 counterActors,
1979 G_OBJECT_TYPE_NAME(actor),
1980 actor,
1981 inSpec->id);
1982 g_free(valueString);
1983 }
1984 #endif
1985 }
1986 }
1987
1988 /* Create property transition for property with cloned timeline
1989 * and add this new transition to transition group if from value
1990 * is not invalid.
1991 */
1992 if(G_VALUE_TYPE(&fromValue)!=G_TYPE_INVALID)
1993 {
1994 ClutterTransition *propertyTransition;
1995 GSList *animationList;
1996
1997 /* Create property transition */
1998 propertyTransition=clutter_property_transition_new(propertyTargetSpec->name);
1999 if(!propertyTransition)
2000 {
2001 g_critical("Cannot allocate memory for transition of property '%s' of animation specification '%s'",
2002 propertyTargetSpec->name,
2003 inSpec->id);
2004
2005 /* Release allocated resources */
2006 g_value_unset(&fromValue);
2007 g_value_unset(&toValue);
2008
2009 /* Skip this property transition */
2010 continue;
2011 }
2012
2013 /* Clone timeline configuration from animation target */
2014 clutter_timeline_set_duration(CLUTTER_TIMELINE(propertyTransition), clutter_timeline_get_duration(targets->timeline));
2015 clutter_timeline_set_delay(CLUTTER_TIMELINE(propertyTransition), clutter_timeline_get_delay(targets->timeline));
2016 clutter_timeline_set_progress_mode(CLUTTER_TIMELINE(propertyTransition), clutter_timeline_get_progress_mode(targets->timeline));
2017 clutter_timeline_set_repeat_count(CLUTTER_TIMELINE(propertyTransition), clutter_timeline_get_repeat_count(targets->timeline));
2018
2019 /* Set "from" value */
2020 clutter_transition_set_from_value(propertyTransition, &fromValue);
2021
2022 /* Set "to" value if valid */
2023 if(G_VALUE_TYPE(&toValue)!=G_TYPE_INVALID)
2024 {
2025 clutter_transition_set_to_value(propertyTransition, &toValue);
2026 }
2027
2028 /* Add animation to list of animations of target actor */
2029 animationList=g_hash_table_lookup(animationActorMap, actor);
2030 animationList=g_slist_prepend(animationList, propertyTransition);
2031 g_hash_table_insert(animationActorMap, actor, animationList);
2032
2033 XFDASHBOARD_DEBUG(self, ANIMATION,
2034 "Created transition for property '%s' at target #%d and actor #%d (%s@%p) of animation specification '%s'",
2035 propertyTargetSpec->name,
2036 counterTargets,
2037 counterActors,
2038 G_OBJECT_TYPE_NAME(actor),
2039 actor,
2040 inSpec->id);
2041 }
2042
2043 /* Release allocated resources */
2044 g_value_unset(&fromValue);
2045 g_value_unset(&toValue);
2046 }
2047 }
2048
2049 /* Release allocated resources */
2050 g_slist_free(actors);
2051 }
2052
2053 /* Now iterate through actor-animation-list-mapping, create a transition group for each actor
2054 * and add its animation to this newly created group.
2055 */
2056 if(animationActorMap)
2057 {
2058 GHashTableIter hashIter;
2059 ClutterActor *hashIterActor;
2060 GSList *hashIterList;
2061
2062 g_hash_table_iter_init(&hashIter, animationActorMap);
2063 while(g_hash_table_iter_next(&hashIter, (gpointer*)&hashIterActor, (gpointer*)&hashIterList))
2064 {
2065 ClutterTransition *transitionGroup;
2066 GSList *listIter;
2067 guint groupDuration;
2068 gint groupLoop;
2069
2070 /* Skip empty but warn */
2071 if(!hashIterList)
2072 {
2073 g_critical("Empty animation list when creating animation for animation specification '%s'",
2074 inSpec->id);
2075 continue;
2076 }
2077
2078 /* Create transition group to collect property transitions at */
2079 transitionGroup=xfdashboard_transition_group_new();
2080 if(!transitionGroup)
2081 {
2082 g_critical("Cannot allocate memory for transition group of animation specification '%s'",
2083 inSpec->id);
2084
2085 /* Release allocated resources */
2086 g_hash_table_foreach(animationActorMap, _xfdashboard_theme_animation_free_animation_actor_map_hashtable, NULL);
2087 g_hash_table_destroy(animationActorMap);
2088 g_object_unref(animation);
2089
2090 return(NULL);
2091 }
2092
2093 XFDASHBOARD_DEBUG(self, ANIMATION,
2094 "Created transition group at %p for %d properties for actor %s@%p of animation specification '%s'",
2095 transitionGroup,
2096 g_slist_length(hashIterList),
2097 G_OBJECT_TYPE_NAME(hashIterActor),
2098 hashIterActor,
2099 inSpec->id);
2100
2101 /* Add animations to transition group */
2102 groupDuration=0;
2103 groupLoop=0;
2104 for(listIter=hashIterList; listIter; listIter=g_slist_next(listIter))
2105 {
2106 ClutterTransition *transition;
2107 gint transitionLoop;
2108 guint transitionDuration;
2109
2110 /* Get transition to add */
2111 transition=(ClutterTransition*)listIter->data;
2112 if(!transition) continue;
2113
2114 /* Adjust timeline values for transition group to play animation fully */
2115 transitionDuration=clutter_timeline_get_delay(CLUTTER_TIMELINE(transition))+clutter_timeline_get_duration(CLUTTER_TIMELINE(transition));
2116 if(transitionDuration>groupDuration) groupDuration=transitionDuration;
2117
2118 transitionLoop=0;
2119 if(groupLoop>=0)
2120 {
2121 transitionLoop=clutter_timeline_get_repeat_count(CLUTTER_TIMELINE(transition));
2122 if(transitionLoop>groupLoop) groupLoop=transitionLoop;
2123 }
2124
2125 /* Add property transition to transition group */
2126 xfdashboard_transition_group_add_transition(XFDASHBOARD_TRANSITION_GROUP(transitionGroup), transition);
2127
2128 XFDASHBOARD_DEBUG(self, ANIMATION,
2129 "Added transition %s@%p (duration %u ms in %d loops) for actor %s@%p of animation specification '%s' to group %s@%p",
2130 G_OBJECT_TYPE_NAME(transition),
2131 transition,
2132 transitionDuration,
2133 transitionLoop,
2134 G_OBJECT_TYPE_NAME(hashIterActor),
2135 hashIterActor,
2136 inSpec->id,
2137 G_OBJECT_TYPE_NAME(transitionGroup),
2138 transitionGroup);
2139 }
2140
2141 /* Set up timeline configuration for transition group of target actor */
2142 clutter_timeline_set_duration(CLUTTER_TIMELINE(transitionGroup), groupDuration);
2143 clutter_timeline_set_delay(CLUTTER_TIMELINE(transitionGroup), 0);
2144 clutter_timeline_set_progress_mode(CLUTTER_TIMELINE(transitionGroup), CLUTTER_LINEAR);
2145 clutter_timeline_set_repeat_count(CLUTTER_TIMELINE(transitionGroup), groupLoop);
2146
2147 XFDASHBOARD_DEBUG(self, ANIMATION,
2148 "Set up timeline of group %s@%p with duration of %u ms and %d loops for actor %s@%p",
2149 G_OBJECT_TYPE_NAME(transitionGroup),
2150 transitionGroup,
2151 groupDuration,
2152 groupLoop,
2153 G_OBJECT_TYPE_NAME(hashIterActor),
2154 hashIterActor);
2155
2156 /* Add transition group with collected property transitions
2157 * to actor.
2158 */
2159 if(animationClass->add_animation)
2160 {
2161 animationClass->add_animation(animation, hashIterActor, transitionGroup);
2162 XFDASHBOARD_DEBUG(self, ANIMATION,
2163 "Added transition group %s@%p to actor %s@%p of animation specification '%s'",
2164 G_OBJECT_TYPE_NAME(transitionGroup),
2165 transitionGroup,
2166 G_OBJECT_TYPE_NAME(hashIterActor),
2167 hashIterActor,
2168 inSpec->id);
2169 }
2170
2171 /* Release allocated resources */
2172 g_object_unref(transitionGroup);
2173 g_slist_free_full(hashIterList, g_object_unref);
2174 }
2175
2176 /* Destroy hash table */
2177 g_hash_table_destroy(animationActorMap);
2178 }
2179
2180 return(animation);
2181 }
2182
2183
2184 /* IMPLEMENTATION: GObject */
2185
2186 /* Dispose this object */
_xfdashboard_theme_animation_dispose(GObject * inObject)2187 static void _xfdashboard_theme_animation_dispose(GObject *inObject)
2188 {
2189 XfdashboardThemeAnimation *self=XFDASHBOARD_THEME_ANIMATION(inObject);
2190 XfdashboardThemeAnimationPrivate *priv=self->priv;
2191
2192 /* Release allocated resources */
2193 if(priv->specs)
2194 {
2195 g_slist_free_full(priv->specs, (GDestroyNotify)_xfdashboard_theme_animation_spec_unref);
2196 priv->specs=NULL;
2197 }
2198
2199 /* Call parent's class dispose method */
2200 G_OBJECT_CLASS(xfdashboard_theme_animation_parent_class)->dispose(inObject);
2201 }
2202
2203 /* Class initialization
2204 * Override functions in parent classes and define properties
2205 * and signals
2206 */
xfdashboard_theme_animation_class_init(XfdashboardThemeAnimationClass * klass)2207 void xfdashboard_theme_animation_class_init(XfdashboardThemeAnimationClass *klass)
2208 {
2209 GObjectClass *gobjectClass=G_OBJECT_CLASS(klass);
2210
2211 /* Override functions */
2212 gobjectClass->dispose=_xfdashboard_theme_animation_dispose;
2213 }
2214
2215 /* Object initialization
2216 * Create private structure and set up default values
2217 */
xfdashboard_theme_animation_init(XfdashboardThemeAnimation * self)2218 void xfdashboard_theme_animation_init(XfdashboardThemeAnimation *self)
2219 {
2220 XfdashboardThemeAnimationPrivate *priv;
2221
2222 priv=self->priv=xfdashboard_theme_animation_get_instance_private(self);
2223
2224 /* Set default values */
2225 priv->specs=NULL;
2226 }
2227
2228 /* IMPLEMENTATION: Errors */
2229
xfdashboard_theme_animation_error_quark(void)2230 GQuark xfdashboard_theme_animation_error_quark(void)
2231 {
2232 return(g_quark_from_static_string("xfdashboard-theme-animation-error-quark"));
2233 }
2234
2235 /* IMPLEMENTATION: Public API */
2236
2237 /* Create new instance */
xfdashboard_theme_animation_new(void)2238 XfdashboardThemeAnimation* xfdashboard_theme_animation_new(void)
2239 {
2240 return(XFDASHBOARD_THEME_ANIMATION(g_object_new(XFDASHBOARD_TYPE_THEME_ANIMATION, NULL)));
2241 }
2242
2243 /* Load a XML file into theme */
xfdashboard_theme_animation_add_file(XfdashboardThemeAnimation * self,const gchar * inPath,GError ** outError)2244 gboolean xfdashboard_theme_animation_add_file(XfdashboardThemeAnimation *self,
2245 const gchar *inPath,
2246 GError **outError)
2247 {
2248 gchar *contents;
2249 gsize contentsLength;
2250 GError *error;
2251
2252 g_return_val_if_fail(XFDASHBOARD_IS_THEME_ANIMATION(self), FALSE);
2253 g_return_val_if_fail(inPath!=NULL && *inPath!=0, FALSE);
2254 g_return_val_if_fail(outError==NULL || *outError==NULL, FALSE);
2255
2256 /* Load XML file, parse it and build objects from file */
2257 error=NULL;
2258 if(!g_file_get_contents(inPath, &contents, &contentsLength, &error))
2259 {
2260 g_propagate_error(outError, error);
2261 return(FALSE);
2262 }
2263
2264 _xfdashboard_theme_animation_parse_xml(self, inPath, contents, &error);
2265 if(error)
2266 {
2267 g_propagate_error(outError, error);
2268 g_free(contents);
2269 return(FALSE);
2270 }
2271 XFDASHBOARD_DEBUG(self, THEME, "Loaded animation file '%s'", inPath);
2272
2273 /* Release allocated resources */
2274 g_free(contents);
2275
2276 /* If we get here loading and parsing XML file was successful
2277 * so return TRUE here
2278 */
2279 return(TRUE);
2280 }
2281
2282 /* Build requested animation for sender and its signal */
xfdashboard_theme_animation_create(XfdashboardThemeAnimation * self,XfdashboardActor * inSender,const gchar * inSignal,XfdashboardAnimationValue ** inDefaultInitialValues,XfdashboardAnimationValue ** inDefaultFinalValues)2283 XfdashboardAnimation* xfdashboard_theme_animation_create(XfdashboardThemeAnimation *self,
2284 XfdashboardActor *inSender,
2285 const gchar *inSignal,
2286 XfdashboardAnimationValue **inDefaultInitialValues,
2287 XfdashboardAnimationValue **inDefaultFinalValues)
2288 {
2289 XfdashboardThemeAnimationSpec *spec;
2290 XfdashboardAnimation *animation;
2291 gboolean animationEnabled;
2292
2293 g_return_val_if_fail(XFDASHBOARD_IS_THEME_ANIMATION(self), NULL);
2294 g_return_val_if_fail(XFDASHBOARD_IS_ACTOR(inSender), NULL);
2295 g_return_val_if_fail(inSignal && *inSignal, NULL);
2296
2297 animation=NULL;
2298
2299 /* Check if user wants animation at all. If user does not want any animation,
2300 * return NULL.
2301 */
2302 animationEnabled=xfconf_channel_get_bool(xfdashboard_application_get_xfconf_channel(NULL),
2303 ENABLE_ANIMATIONS_XFCONF_PROP,
2304 DEFAULT_ENABLE_ANIMATIONS);
2305 if(!animationEnabled)
2306 {
2307 XFDASHBOARD_DEBUG(self, ANIMATION,
2308 "User disabled animations so do not lookup animation for sender '%s' and signal '%s'",
2309 G_OBJECT_TYPE_NAME(inSender),
2310 inSignal);
2311
2312 /* Return NULL as user does not want any animation */
2313 return(NULL);
2314 }
2315
2316 /* Get best matching animation specification for sender and signal.
2317 * If no matching animation specification is found, return the empty one.
2318 */
2319 spec=_xfdashboard_theme_animation_find_matching_animation_spec(self, XFDASHBOARD_STYLABLE(inSender), inSignal);
2320 if(!spec)
2321 {
2322 XFDASHBOARD_DEBUG(self, ANIMATION,
2323 "Could not find an animation specification for sender '%s' and signal '%s'",
2324 G_OBJECT_TYPE_NAME(inSender),
2325 inSignal);
2326
2327 /* Return NULL as no matching animation specification was found */
2328 return(NULL);
2329 }
2330
2331 XFDASHBOARD_DEBUG(self, ANIMATION,
2332 "Found animation specification '%s' for sender '%s' and signal '%s' with %d targets",
2333 spec->id,
2334 G_OBJECT_TYPE_NAME(inSender),
2335 inSignal,
2336 g_slist_length(spec->targets));
2337
2338 /* Create animation for found animation specification */
2339 animation=_xfdashboard_theme_animation_create_by_spec(self, spec, inSender, inDefaultInitialValues, inDefaultFinalValues);
2340
2341 /* Release allocated resources */
2342 if(spec) _xfdashboard_theme_animation_spec_unref(spec);
2343
2344 return(animation);
2345 }
2346
2347 /* Build requested animation by animation ID */
xfdashboard_theme_animation_create_by_id(XfdashboardThemeAnimation * self,XfdashboardActor * inSender,const gchar * inID,XfdashboardAnimationValue ** inDefaultInitialValues,XfdashboardAnimationValue ** inDefaultFinalValues)2348 XfdashboardAnimation* xfdashboard_theme_animation_create_by_id(XfdashboardThemeAnimation *self,
2349 XfdashboardActor *inSender,
2350 const gchar *inID,
2351 XfdashboardAnimationValue **inDefaultInitialValues,
2352 XfdashboardAnimationValue **inDefaultFinalValues)
2353 {
2354 XfdashboardThemeAnimationSpec *spec;
2355 XfdashboardAnimation *animation;
2356 gboolean animationEnabled;
2357
2358 g_return_val_if_fail(XFDASHBOARD_IS_THEME_ANIMATION(self), NULL);
2359 g_return_val_if_fail(XFDASHBOARD_IS_ACTOR(inSender), NULL);
2360 g_return_val_if_fail(inID && *inID, NULL);
2361
2362 animation=NULL;
2363
2364 /* Check if user wants animation at all. If user does not want any animation,
2365 * return NULL.
2366 */
2367 animationEnabled=xfconf_channel_get_bool(xfdashboard_application_get_xfconf_channel(NULL),
2368 ENABLE_ANIMATIONS_XFCONF_PROP,
2369 DEFAULT_ENABLE_ANIMATIONS);
2370 if(!animationEnabled)
2371 {
2372 XFDASHBOARD_DEBUG(self, ANIMATION,
2373 "User disabled animations so do not lookup animation with ID '%s'",
2374 inID);
2375
2376 /* Return NULL as user does not want any animation */
2377 return(NULL);
2378 }
2379
2380 /* Find animation specification by looking up requested ID.
2381 * If no matching animation specification is found, return the empty one.
2382 */
2383 spec=_xfdashboard_theme_animation_find_animation_spec_by_id(self, inID);
2384 if(!spec)
2385 {
2386 XFDASHBOARD_DEBUG(self, ANIMATION,
2387 "Could not find an animation specification with ID '%s'",
2388 inID);
2389
2390 /* Return NULL as no matching animation specification was found */
2391 return(NULL);
2392 }
2393
2394 XFDASHBOARD_DEBUG(self, ANIMATION,
2395 "Found animation specification '%s' with %d targets",
2396 spec->id,
2397 g_slist_length(spec->targets));
2398
2399 /* Create animation for found animation specification */
2400 animation=_xfdashboard_theme_animation_create_by_spec(self, spec, inSender, inDefaultInitialValues, inDefaultFinalValues);
2401
2402 /* Release allocated resources */
2403 if(spec) _xfdashboard_theme_animation_spec_unref(spec);
2404
2405 return(animation);
2406 }
2407
2408 /* Look up ID of animation specification for sender and its signal*/
xfdashboard_theme_animation_lookup_id(XfdashboardThemeAnimation * self,XfdashboardActor * inSender,const gchar * inSignal)2409 gchar* xfdashboard_theme_animation_lookup_id(XfdashboardThemeAnimation *self,
2410 XfdashboardActor *inSender,
2411 const gchar *inSignal)
2412 {
2413 XfdashboardThemeAnimationSpec *spec;
2414 gchar *id;
2415
2416 g_return_val_if_fail(XFDASHBOARD_IS_THEME_ANIMATION(self), NULL);
2417 g_return_val_if_fail(XFDASHBOARD_IS_ACTOR(inSender), NULL);
2418 g_return_val_if_fail(inSignal && *inSignal, NULL);
2419
2420 id=NULL;
2421
2422 /* Get best matching animation specification for sender and signal.
2423 * If no matching animation specification is found, return the empty one.
2424 */
2425 spec=_xfdashboard_theme_animation_find_matching_animation_spec(self, XFDASHBOARD_STYLABLE(inSender), inSignal);
2426 if(spec)
2427 {
2428 /* Get ID of animation specification to return */
2429 id=g_strdup(spec->id);
2430
2431 /* Release allocated resources */
2432 _xfdashboard_theme_animation_spec_unref(spec);
2433 }
2434
2435 /* Return found ID or NULL */
2436 return(id);
2437 }
2438