add new sigc++2 directory
[ardour.git] / libs / clearlooks / 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/gtimer.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 ()
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 ()
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 = value;
163         GtkWidget *widget = key;
164         
165         g_assert ((widget != NULL) && (animation_info != NULL));
166         
167         /* remove the widget from the hash table if it is not drawable */
168         if (!GTK_WIDGET_DRAWABLE (widget))
169         {
170                 return TRUE;
171         }
172         
173         if (GE_IS_PROGRESS_BAR (widget))
174         {
175                 gfloat fraction = gtk_progress_bar_get_fraction (GTK_PROGRESS_BAR (widget));
176                 
177                 /* stop animation for filled/not filled progress bars */
178                 if (fraction <= 0.0 || fraction >= 1.0)
179                         return TRUE;
180         }
181         
182         force_widget_redraw (widget);
183         
184         /* stop at stop_time */
185         if (animation_info->stop_time != 0 &&
186             g_timer_elapsed (animation_info->timer, NULL) > animation_info->stop_time)
187                 return TRUE;
188         
189         return FALSE;
190 }
191
192 /* This gets called by the glib main loop every once in a while. */
193 static gboolean
194 animation_timeout_handler (gpointer data)
195 {
196         /*g_print("** TICK **\n");*/
197         
198         /* enter threads as update_animation_info will use gtk/gdk. */
199         gdk_threads_enter ();
200         g_hash_table_foreach_remove (animated_widgets, update_animation_info, NULL);
201         /* leave threads again */
202         gdk_threads_leave ();
203         
204         if(g_hash_table_size(animated_widgets)==0)
205         {
206                 stop_timer ();
207                 return FALSE;
208         }
209         
210         return TRUE;
211 }
212
213 static void
214 on_checkbox_toggle (GtkWidget *widget, gpointer data)
215 {
216         AnimationInfo *animation_info = lookup_animation_info (widget);
217         
218         if (animation_info != NULL)
219         {
220                 gfloat elapsed = g_timer_elapsed (animation_info->timer, NULL);
221                 
222                 animation_info->start_modifier = elapsed - animation_info->start_modifier;
223         }
224         else
225         {
226                 add_animation (widget, CHECK_ANIMATION_TIME);
227         }
228 }
229
230 static void
231 on_connected_widget_destruction (gpointer data, GObject *widget)
232 {
233         connected_widgets = g_slist_remove (connected_widgets, data);
234         g_free (data);
235 }
236
237 static void
238 disconnect_all_signals ()
239 {
240         GSList * item = connected_widgets;
241         while (item != NULL)
242         {
243                 SignalInfo *signal_info = (SignalInfo*) item->data;
244                 
245                 g_signal_handler_disconnect (signal_info->widget, signal_info->handler_id);
246                 g_object_weak_unref (G_OBJECT (signal_info->widget), on_connected_widget_destruction, signal_info);
247                 g_free (signal_info);
248                 
249                 item = g_slist_next (item);
250         }
251         
252         g_slist_free (connected_widgets);
253         connected_widgets = NULL;
254 }
255
256 /* helper function for clearlooks_animation_connect_checkbox */
257 static gint
258 find_signal_info (gconstpointer signal_info, gconstpointer widget)
259 {
260         if (((SignalInfo*)signal_info)->widget == widget)
261                 return 0;
262         else
263                 return 1;
264 }
265
266
267 /* external interface */
268
269 /* adds a progress bar */
270 void
271 clearlooks_animation_progressbar_add (GtkWidget *progressbar)
272 {
273         gdouble fraction = gtk_progress_bar_get_fraction (GTK_PROGRESS_BAR (progressbar));
274         
275         if (fraction < 1.0 && fraction > 0.0)
276                 add_animation ((GtkWidget*) progressbar, 0.0);
277 }
278
279 /* hooks up the signals for check and radio buttons */
280 void
281 clearlooks_animation_connect_checkbox (GtkWidget *widget)
282 {
283         if (GE_IS_CHECK_BUTTON (widget))
284         {
285                 if (!g_slist_find_custom (connected_widgets, widget, find_signal_info))
286                 {
287                         SignalInfo * signal_info = g_new (SignalInfo, 1);
288                         
289                         signal_info->widget = widget;
290                         signal_info->handler_id = g_signal_connect ((GObject*)widget, "toggled", G_CALLBACK (on_checkbox_toggle), NULL);
291                         
292                         connected_widgets = g_slist_append (connected_widgets, signal_info);
293                         g_object_weak_ref (G_OBJECT (widget), on_connected_widget_destruction, signal_info);
294                 }
295         }
296 }
297
298 /* returns TRUE if the widget is animated, and FALSE otherwise */
299 gboolean
300 clearlooks_animation_is_animated (GtkWidget *widget)
301 {
302         return lookup_animation_info (widget) != NULL ? TRUE : FALSE;
303 }
304
305 /* returns the elapsed time for the animation */
306 gdouble
307 clearlooks_animation_elapsed (gpointer data)
308 {
309         AnimationInfo *animation_info = lookup_animation_info (data);
310         
311         if (animation_info)
312                 return   g_timer_elapsed (animation_info->timer, NULL)
313                        - animation_info->start_modifier;
314         else
315                 return 0.0;
316 }
317
318 /* cleans up all resources of the animation system */
319 void
320 clearlooks_animation_cleanup ()
321 {
322         disconnect_all_signals ();
323         
324         if (animated_widgets != NULL)
325         {
326                 g_hash_table_destroy (animated_widgets);
327                 animated_widgets = NULL;
328         }
329         
330         stop_timer ();
331 }
332 #else /* !HAVE_ANIMATION */
333 static void clearlooks_animation_dummy_function_so_wall_shuts_up_when_animations_is_disabled()
334 {
335         clearlooks_animation_dummy_function_so_wall_shuts_up_when_animations_is_disabled();
336 }
337 #endif /* HAVE_ANIMATION */