Merge remote-tracking branch 'remotes/origin/cairocanvas' into windows
[ardour.git] / libs / clearlooks-newer / animation.c
1 /* Clearlooks theme engine
2  *
3  * Copyright (C) 2006 Kulyk Nazar <schamane@myeburg.net>
4  * Copyright (C) 2006 Benjamin Berg <benjamin@sipsolutions.net>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library 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 GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  *
21  */
22
23 /* This code is responsible for the clearlooks animation support. The code
24  * works by forcing a redraw on the animated widget.
25  */
26
27 #include "animation.h"
28
29 #ifdef HAVE_ANIMATION
30 #include <glib.h>
31
32 struct _AnimationInfo {
33         GTimer *timer;
34         
35         gdouble start_modifier;
36         gdouble stop_time;
37         GtkWidget *widget;
38 };
39 typedef struct _AnimationInfo AnimationInfo;
40
41 struct _SignalInfo {
42         GtkWidget *widget;
43         gulong handler_id;
44 };
45 typedef struct _SignalInfo SignalInfo;
46
47 static GSList     *connected_widgets  = NULL;
48 static GHashTable *animated_widgets   = NULL;
49 static int         animation_timer_id = 0;
50
51
52 static gboolean animation_timeout_handler (gpointer data);
53
54 /* This forces a redraw on a widget */
55 static void
56 force_widget_redraw (GtkWidget *widget)
57 {
58         if (GE_IS_PROGRESS_BAR (widget))
59                 gtk_widget_queue_resize (widget);
60         else
61                 gtk_widget_queue_draw (widget);
62 }
63
64 /* ensures that the timer is running */
65 static void
66 start_timer (void)
67 {
68         if (animation_timer_id == 0)
69                 animation_timer_id = g_timeout_add (ANIMATION_DELAY, animation_timeout_handler, NULL);
70 }
71
72 /* ensures that the timer is stopped */
73 static void
74 stop_timer (void)
75 {
76         if (animation_timer_id != 0)
77         {
78                 g_source_remove(animation_timer_id);
79                 animation_timer_id = 0;
80         }
81 }
82
83
84 /* destroys an AnimationInfo structure including the GTimer */
85 static void
86 animation_info_destroy (AnimationInfo *animation_info)
87 {
88         g_timer_destroy (animation_info->timer);
89         g_free (animation_info);
90 }
91
92
93 /* This function does not unref the weak reference, because the object
94  * is beeing destroyed currently. */
95 static void
96 on_animated_widget_destruction (gpointer data, GObject *object)
97 {
98         /* steal the animation info from the hash table (destroying it would
99          * result in the weak reference to be unrefed, which does not work
100          * as the widget is already destroyed. */
101         g_hash_table_steal (animated_widgets, object);
102         animation_info_destroy ((AnimationInfo*) data);
103 }
104
105 /* This function also needs to unref the weak reference. */
106 static void
107 destroy_animation_info_and_weak_unref (gpointer data)
108 {
109         AnimationInfo *animation_info = data;
110         
111         /* force a last redraw. This is so that if the animation is removed,
112          * the widget is left in a sane state. */
113         force_widget_redraw (animation_info->widget);
114         
115         g_object_weak_unref (G_OBJECT (animation_info->widget), on_animated_widget_destruction, data);
116         animation_info_destroy (animation_info);
117 }
118
119 /* Find and return a pointer to the data linked to this widget, if it exists */
120 static AnimationInfo*
121 lookup_animation_info (const GtkWidget *widget)
122 {
123         if (animated_widgets)
124                 return g_hash_table_lookup (animated_widgets, widget);
125         
126         return NULL;
127 }
128
129 /* Create all the relevant information for the animation, and insert it into the hash table. */
130 static void
131 add_animation (const GtkWidget *widget, gdouble stop_time)
132 {
133         AnimationInfo *value;
134         
135         /* object already in the list, do not add it twice */
136         if (lookup_animation_info (widget))
137                 return;
138         
139         if (animated_widgets == NULL)
140                 animated_widgets = g_hash_table_new_full (g_direct_hash, g_direct_equal,
141                                                           NULL, destroy_animation_info_and_weak_unref);
142         
143         value = g_new(AnimationInfo, 1);
144         
145         value->widget = (GtkWidget*) widget;
146         
147         value->timer = g_timer_new ();
148         value->stop_time= stop_time;
149         value->start_modifier = 0.0;
150
151         g_object_weak_ref (G_OBJECT (widget), on_animated_widget_destruction, value);
152         g_hash_table_insert (animated_widgets, (GtkWidget*) widget, value);
153         
154         start_timer ();
155 }
156
157 /* update the animation information for each widget. This will also queue a redraw
158  * and stop the animation if it is done. */
159 static gboolean
160 update_animation_info (gpointer key, gpointer value, gpointer user_data)
161 {
162         AnimationInfo *animation_info;
163         GtkWidget *widget = key;
164         
165         animation_info = value;
166         (void) user_data;
167         
168         g_assert ((widget != NULL) && (animation_info != NULL));
169         
170         /* remove the widget from the hash table if it is not drawable */
171         if (!GTK_WIDGET_DRAWABLE (widget))
172         {
173                 return TRUE;
174         }
175         
176         if (GE_IS_PROGRESS_BAR (widget))
177         {
178                 gfloat fraction = gtk_progress_bar_get_fraction (GTK_PROGRESS_BAR (widget));
179                 
180                 /* stop animation for filled/not filled progress bars */
181                 if (fraction <= 0.0 || fraction >= 1.0)
182                         return TRUE;
183         }
184         
185         force_widget_redraw (widget);
186         
187         /* stop at stop_time */
188         if (animation_info->stop_time != 0 &&
189             g_timer_elapsed (animation_info->timer, NULL) > animation_info->stop_time)
190                 return TRUE;
191         
192         return FALSE;
193 }
194
195 /* This gets called by the glib main loop every once in a while. */
196 static gboolean
197 animation_timeout_handler (gpointer data)
198 {
199         (void) data;
200         
201         /*g_print("** TICK **\n");*/
202         
203         /* enter threads as update_animation_info will use gtk/gdk. */
204         gdk_threads_enter ();
205         g_hash_table_foreach_remove (animated_widgets, update_animation_info, NULL);
206         /* leave threads again */
207         gdk_threads_leave ();
208         
209         if(g_hash_table_size(animated_widgets)==0)
210         {
211                 stop_timer ();
212                 return FALSE;
213         }
214         
215         return TRUE;
216 }
217
218 static void
219 on_checkbox_toggle (GtkWidget *widget, gpointer data)
220 {
221         AnimationInfo *animation_info;
222         (void) data;
223         
224         animation_info = lookup_animation_info (widget);
225         
226         if (animation_info != NULL)
227         {
228                 gfloat elapsed = g_timer_elapsed (animation_info->timer, NULL);
229                 
230                 animation_info->start_modifier = elapsed - animation_info->start_modifier;
231         }
232         else
233         {
234                 add_animation (widget, CHECK_ANIMATION_TIME);
235         }
236 }
237
238 static void
239 on_connected_widget_destruction (gpointer data, GObject *widget)
240 {
241         (void) widget;
242         
243         connected_widgets = g_slist_remove (connected_widgets, data);
244         g_free (data);
245 }
246
247 static void
248 disconnect_all_signals (void)
249 {
250         GSList * item = connected_widgets;
251         while (item != NULL)
252         {
253                 SignalInfo *signal_info = (SignalInfo*) item->data;
254                 
255                 g_signal_handler_disconnect (signal_info->widget, signal_info->handler_id);
256                 g_object_weak_unref (G_OBJECT (signal_info->widget), on_connected_widget_destruction, signal_info);
257                 g_free (signal_info);
258                 
259                 item = g_slist_next (item);
260         }
261         
262         g_slist_free (connected_widgets);
263         connected_widgets = NULL;
264 }
265
266 /* helper function for clearlooks_animation_connect_checkbox */
267 static gint
268 find_signal_info (gconstpointer signal_info, gconstpointer widget)
269 {
270         if (((const SignalInfo*)signal_info)->widget == widget)
271                 return 0;
272         else
273                 return 1;
274 }
275
276
277 /* external interface */
278
279 /* adds a progress bar */
280 void
281 clearlooks_animation_progressbar_add (GtkWidget *progressbar)
282 {
283         gdouble fraction = gtk_progress_bar_get_fraction (GTK_PROGRESS_BAR (progressbar));
284         
285         if (fraction < 1.0 && fraction > 0.0)
286                 add_animation ((GtkWidget*) progressbar, 0.0);
287 }
288
289 /* hooks up the signals for check and radio buttons */
290 void
291 clearlooks_animation_connect_checkbox (GtkWidget *widget)
292 {
293         if (GE_IS_CHECK_BUTTON (widget))
294         {
295                 if (!g_slist_find_custom (connected_widgets, widget, find_signal_info))
296                 {
297                         SignalInfo * signal_info = g_new (SignalInfo, 1);
298                         
299                         signal_info->widget = widget;
300                         signal_info->handler_id = g_signal_connect ((GObject*)widget, "toggled", G_CALLBACK (on_checkbox_toggle), NULL);
301                         
302                         connected_widgets = g_slist_append (connected_widgets, signal_info);
303                         g_object_weak_ref (G_OBJECT (widget), on_connected_widget_destruction, signal_info);
304                 }
305         }
306 }
307
308 /* returns TRUE if the widget is animated, and FALSE otherwise */
309 gboolean
310 clearlooks_animation_is_animated (GtkWidget *widget)
311 {
312         return lookup_animation_info (widget) != NULL ? TRUE : FALSE;
313 }
314
315 /* returns the elapsed time for the animation */
316 gdouble
317 clearlooks_animation_elapsed (gpointer data)
318 {
319         AnimationInfo *animation_info = lookup_animation_info (data);
320         
321         if (animation_info)
322                 return   g_timer_elapsed (animation_info->timer, NULL)
323                        - animation_info->start_modifier;
324         else
325                 return 0.0;
326 }
327
328 /* cleans up all resources of the animation system */
329 void
330 clearlooks_animation_cleanup (void)
331 {
332         disconnect_all_signals ();
333         
334         if (animated_widgets != NULL)
335         {
336                 g_hash_table_destroy (animated_widgets);
337                 animated_widgets = NULL;
338         }
339         
340         stop_timer ();
341 }
342 #else /* !HAVE_ANIMATION */
343 static void clearlooks_animation_dummy_function_so_wall_shuts_up_when_animations_is_disabled()
344 {
345         clearlooks_animation_dummy_function_so_wall_shuts_up_when_animations_is_disabled();
346 }
347 #endif /* HAVE_ANIMATION */