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