2008-09-24 Claudio Saavedra <csaavedra@igalia.com>
[hildon] / src / hildon-helper.c
1 /*
2  * This file is a part of hildon
3  *
4  * Copyright (C) 2005, 2006 Nokia Corporation, all rights reserved.
5  *
6  * Contact: Michael Dominic Kostrzewa <michael.kostrzewa@nokia.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public License
10  * as published by the Free Software Foundation; version 2.1 of
11  * the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21  * 02110-1301 USA
22  *
23  */
24
25 /**
26  * SECTION:hildon-helper
27  * @short_description: A collection of useful utilities and functions.
28  *
29  * Hildon provides some helper functions that can be used for commonly
30  * performed tasks and functionality blocks. This includes operations
31  * on widget styles and probing functions for touch events.
32  */
33
34 #ifdef                                          HAVE_CONFIG_H
35 #include                                        <config.h>
36 #endif
37
38 #include                                        "hildon-helper.h"
39 #include                                        "hildon-banner.h"
40
41 #define                                         HILDON_FINGER_PRESSURE_THRESHOLD 0.4
42
43 #define                                         HILDON_FINGER_BUTTON 8
44
45 #define                                         HILDON_FINGER_ALT_BUTTON 1
46
47 #define                                         HILDON_FINGER_ALT_MASK GDK_MOD4_MASK
48
49 #define                                         HILDON_FINGER_SIMULATE_BUTTON 2
50
51 struct                                          _HildonLogicalElement
52 {
53     gboolean is_color;                          /* If FALSE, it's a logical font def */
54     GtkRcFlags rc_flags;
55     GtkStateType state;
56     gchar *logical_color_name;
57     gchar *logical_font_name;
58 } typedef                                       HildonLogicalElement;
59
60 static void
61 hildon_logical_element_list_free                (GSList *list)
62 {
63     GSList *iterator = list;
64
65     while (iterator) {
66         HildonLogicalElement *element = (HildonLogicalElement *) iterator->data;
67
68         g_free (element->logical_color_name);
69         g_free (element->logical_font_name);
70         g_slice_free (HildonLogicalElement, element);
71
72         iterator = iterator->next;
73     }
74
75     /* Free the list itself */
76     g_slist_free (list);
77 }
78
79 static GQuark
80 hildon_helper_logical_data_quark                (void)
81 {
82     static GQuark quark = 0;
83
84     if (G_UNLIKELY (quark == 0))
85         quark = g_quark_from_static_string ("hildon-logical-data");
86
87     return quark;
88 }
89
90 static HildonLogicalElement*
91 attach_blank_element                            (GtkWidget *widget, 
92                                                  GSList **style_list)
93 {
94     gboolean first = (*style_list == NULL) ? TRUE : FALSE;
95
96     HildonLogicalElement *element = g_slice_new (HildonLogicalElement);
97     
98     element->is_color = FALSE;
99     element->rc_flags = 0;
100     element->state = 0;
101     element->logical_color_name = NULL;
102     element->logical_font_name = NULL;
103
104     *style_list = g_slist_append (*style_list, element);
105
106     if (first) 
107         g_object_set_qdata_full (G_OBJECT (widget), hildon_helper_logical_data_quark (), *style_list, (GDestroyNotify) hildon_logical_element_list_free);
108
109     return element;
110 }
111
112 static GSList*
113 attach_new_font_element                         (GtkWidget *widget, 
114                                                  const gchar *font_name)
115 {
116     GSList *style_list = g_object_get_qdata (G_OBJECT (widget), hildon_helper_logical_data_quark ());
117     HildonLogicalElement *element = NULL;
118    
119     /* Try to find an element that already sets a font */
120     GSList *iterator = style_list;
121     while (iterator) {
122         element = (HildonLogicalElement *) iterator->data;
123
124         if (element->is_color == FALSE) {
125             /* Reusing ... */
126             g_free (element->logical_font_name);
127             element->logical_font_name = g_strdup (font_name);
128             return style_list;
129         }
130
131         iterator = iterator->next;
132     }
133
134     /* It was not found so we need to create a new one and attach it */
135     element = attach_blank_element (widget, &style_list);
136     element->is_color = FALSE;
137     element->logical_font_name = g_strdup (font_name);
138     return style_list;
139 }
140
141 static GSList*
142 attach_new_color_element                        (GtkWidget *widget, 
143                                                  GtkRcFlags flags, 
144                                                  GtkStateType state, 
145                                                  const gchar *color_name)
146 {
147     GSList *style_list = g_object_get_qdata (G_OBJECT (widget), hildon_helper_logical_data_quark ());
148     HildonLogicalElement *element = NULL;
149    
150     /* Try to find an element that has same flags and state */
151     GSList *iterator = style_list;
152     while (iterator) {
153         element = (HildonLogicalElement *) iterator->data;
154
155         if (element->rc_flags == flags &&
156             element->state == state &&
157             element->is_color == TRUE) {
158             /* Reusing ... */
159             element->logical_color_name = g_strdup (color_name);
160             return style_list;
161         }
162
163         iterator = iterator->next;
164     }
165
166     /* It was not found so we need to create a new one and attach it */
167     element = attach_blank_element (widget, &style_list);
168     element->is_color = TRUE;
169     element->state = state;
170     element->rc_flags = flags;
171     element->logical_color_name = g_strdup (color_name);
172     return style_list;
173 }
174
175 static void 
176 hildon_change_style_recursive_from_list         (GtkWidget *widget, 
177                                                  GtkStyle *prev_style, 
178                                                  GSList *list)
179 {
180     g_assert (GTK_IS_WIDGET (widget));
181
182     /* Change the style for child widgets */
183     if (GTK_IS_CONTAINER (widget)) {
184         GList *iterator, *children;
185         children = gtk_container_get_children (GTK_CONTAINER (widget));
186         for (iterator = children; iterator != NULL; iterator = g_list_next (iterator))
187             hildon_change_style_recursive_from_list (GTK_WIDGET (iterator->data), prev_style, list);
188         g_list_free (children);
189     }
190
191     /* gtk_widget_modify_*() emit "style_set" signals, so if we got here from
192        "style_set" signal, we need to block this function from being called
193        again or we get into inifinite loop.
194
195     FIXME: Compiling with gcc > 3.3 and -pedantic won't allow
196     conversion between function and object pointers. GLib API however
197     requires an object pointer for a function, so we have to work
198     around this.
199     See http://bugzilla.gnome.org/show_bug.cgi?id=310175
200     */
201
202     G_GNUC_EXTENSION
203         g_signal_handlers_block_matched (G_OBJECT (widget), G_SIGNAL_MATCH_ID | G_SIGNAL_MATCH_FUNC,
204                 g_signal_lookup ("style_set", G_TYPE_FROM_INSTANCE (widget)),
205                 0, NULL,
206                 (gpointer) hildon_change_style_recursive_from_list,
207                 NULL);
208
209     /* We iterate over all list elements and apply each style
210      * specification. */
211
212     GSList *iterator = list;
213     while (iterator) {
214     
215         HildonLogicalElement *element = (HildonLogicalElement *) iterator->data;
216
217         if (element->is_color == TRUE) {
218
219             /* Changing logical color */
220             GdkColor color;
221             gtk_widget_ensure_style (widget);
222             if (gtk_style_lookup_color (widget->style, element->logical_color_name, &color) == TRUE) {
223                
224                 switch (element->rc_flags)
225                 {
226                     case GTK_RC_FG:
227                         gtk_widget_modify_fg (widget, element->state, &color);
228                         break;
229
230                     case GTK_RC_BG:
231                         gtk_widget_modify_bg (widget, element->state, &color);
232                         break;
233
234                     case GTK_RC_TEXT:
235                         gtk_widget_modify_text (widget, element->state, &color);
236                         break;
237
238                     case GTK_RC_BASE:
239                         gtk_widget_modify_base (widget, element->state, &color);
240                         break;
241                 }
242             }
243         } else {
244             
245             /* Changing logical font */
246             GtkStyle *font_style = gtk_rc_get_style_by_paths (gtk_settings_get_default (), element->logical_font_name, NULL, G_TYPE_NONE);
247             if (font_style != NULL) {
248                 PangoFontDescription *font_desc = font_style->font_desc;
249
250                 if (font_desc != NULL)
251                     gtk_widget_modify_font (widget, font_desc);
252             }
253         }
254
255         iterator = iterator->next;
256     } 
257
258     /* FIXME: Compilation workaround for gcc > 3.3 + -pedantic again */
259
260     G_GNUC_EXTENSION
261         g_signal_handlers_unblock_matched (G_OBJECT (widget), G_SIGNAL_MATCH_ID | G_SIGNAL_MATCH_FUNC,
262                 g_signal_lookup ("style_set", G_TYPE_FROM_INSTANCE (widget)),
263                 0, NULL,
264                 (gpointer) hildon_change_style_recursive_from_list,
265                 NULL);
266 }
267
268 /**
269  * hildon_helper_event_button_is_finger:
270  * @event: A #GtkEventButton to check
271  *
272  * Checks if the given button event is a finger event.
273  *
274  * Return value: TRUE if the event is a finger event.
275  **/
276 gboolean 
277 hildon_helper_event_button_is_finger            (GdkEventButton *event)
278 {
279     gdouble pressure;
280
281     if (gdk_event_get_axis ((GdkEvent*) event, GDK_AXIS_PRESSURE, &pressure) &&
282         pressure > HILDON_FINGER_PRESSURE_THRESHOLD)
283         return TRUE;
284
285     if (event->button == HILDON_FINGER_BUTTON)
286         return TRUE;
287
288     if (event->button == HILDON_FINGER_ALT_BUTTON &&
289         event->state & HILDON_FINGER_ALT_MASK)
290         return TRUE;
291
292     if (event->button == HILDON_FINGER_SIMULATE_BUTTON)
293         return TRUE;
294
295     return FALSE;
296 }
297
298 /**
299  * hildon_helper_set_logical_font:
300  * @widget: a #GtkWidget to assign this logical font for.
301  * @logicalfontname: a gchar* with the logical font name to assign to the widget.
302  *
303  * This function assigns a defined logical font to the @widget and all its child widgets.
304  * it also connects to the "style_set" signal which will retrieve & assign the new font
305  * for the given logical name each time the theme is changed
306  * The returned signal id can be used to disconnect the signal.
307  * When calling multiple times the previous signal (obtained by calling this function) is disconnected
308  * automatically and should not be used.
309  *
310  * Return value: the signal id that is triggered every time theme is changed. 0 if font set failed.
311  **/
312 gulong
313 hildon_helper_set_logical_font                  (GtkWidget *widget, 
314                                                  const gchar *logicalfontname)
315 {
316     gulong signum = 0;
317     GSList *list;
318
319     g_return_val_if_fail (GTK_IS_WIDGET (widget), 0);
320     g_return_val_if_fail (logicalfontname != NULL, 0);
321
322     list = attach_new_font_element (widget, logicalfontname);
323
324     /* Disconnects the previously connected signals. That calls the closure notify
325      * and effectively disposes the allocated data (hildon_logical_data_free) */
326     g_signal_handlers_disconnect_matched (G_OBJECT (widget), G_SIGNAL_MATCH_FUNC, 
327                                           0, 0, NULL, 
328                                           G_CALLBACK (hildon_change_style_recursive_from_list), NULL);
329
330     /* Change the font now */
331     hildon_change_style_recursive_from_list (widget, NULL, list);
332
333     /* Connect to "style_set" so that the font gets changed whenever theme changes. */
334     signum = g_signal_connect_data (G_OBJECT (widget), "style_set",
335                                     G_CALLBACK (hildon_change_style_recursive_from_list),
336                                     list, NULL, 0);
337
338     return signum;
339 }
340
341 static GQuark
342 hildon_helper_insensitive_message_quark         (void)
343 {
344     static GQuark quark = 0;
345
346     if (G_UNLIKELY (quark == 0))
347         quark = g_quark_from_static_string ("hildon-insensitive-message");
348
349     return quark;
350 }
351
352 static void
353 show_insensitive_message                        (GtkWidget *widget, 
354                                                  gpointer user_data)
355 {
356     gchar *message = NULL;
357
358     g_assert (GTK_IS_WIDGET (widget));
359
360     message = (gchar*) g_object_get_qdata (G_OBJECT (widget),
361             hildon_helper_insensitive_message_quark ());
362
363     if (message)
364         hildon_banner_show_information (widget, NULL, message);
365 }
366
367
368 /**
369  * hildon_helper_set_insensitive_message:
370  * @widget: A #GtkWidget to assign a banner to
371  * @message: A message to display to the user
372  *
373  * This function assigns an insensitive message to a @widget. When the @widget is
374  * in an insensitive state and the user activates it, the @message will be displayed
375  * using a standard #HildonBanner.
376  **/
377 void
378 hildon_helper_set_insensitive_message           (GtkWidget *widget,
379                                                  const gchar *message)
380 {
381     g_return_if_fail (GTK_IS_WIDGET (widget));
382
383     /* Clean up any previous instance of the insensitive message */
384     g_signal_handlers_disconnect_matched (G_OBJECT (widget), G_SIGNAL_MATCH_FUNC,
385                                           0, 0, NULL,
386                                           G_CALLBACK (show_insensitive_message), NULL);
387     
388     /* We need to dup the string because the pointer might not be valid when the
389      insensitive-press signal callback is executed */
390     g_object_set_qdata_full (G_OBJECT (widget), hildon_helper_insensitive_message_quark (), 
391                              (gpointer)g_strdup (message),
392                              g_free);
393
394     if (message != NULL) {
395       g_signal_connect (G_OBJECT (widget), "insensitive-press",
396                         G_CALLBACK (show_insensitive_message), NULL);
397     }
398 }
399
400 /**
401  * hildon_helper_set_insensitive_messagef:
402  * @widget: A #GtkWidget to assign a banner to
403  * @format: a printf-like format string
404  * @Varargs: arguments for the format string
405  *
406  * A version of hildon_helper_set_insensitive_message with string formatting.
407  **/
408 void
409 hildon_helper_set_insensitive_messagef          (GtkWidget *widget,
410                                                  const gchar *format,
411                                                  ...)
412 {
413     g_return_if_fail (GTK_IS_WIDGET (widget));
414
415     gchar *message;
416     va_list args;
417
418     va_start (args, format);
419     message = g_strdup_vprintf (format, args);
420     va_end (args);
421
422     hildon_helper_set_insensitive_message (widget, message);
423
424     g_free (message);
425 }
426
427 /**
428  * hildon_helper_set_logical_color:
429  * @widget: A #GtkWidget to assign this logical font for.
430  * @rcflags: #GtkRcFlags enumeration defining whether to assign to FG, BG, TEXT or BASE style.
431  * @state: #GtkStateType indicating to which state to assign the logical color
432  * @logicalcolorname: A gchar* with the logical font name to assign to the widget.
433  * 
434  * This function assigns a defined logical color to the @widget and all it's child widgets.
435  * It also connects to the "style_set" signal which will retrieve & assign the new color 
436  * for the given logical name each time the theme is changed.
437  * The returned signal id can be used to disconnect the signal.
438  * When calling multiple times the previous signal (obtained by calling this function) is disconnected 
439  * automatically and should not be used. 
440  * 
441  * Example : If the style you want to modify is bg[NORMAL] then set rcflags to GTK_RC_BG and state to GTK_STATE_NORMAL.
442  * 
443  * Return value: The signal id that is triggered every time theme is changed. 0 if color set failed.
444  **/
445 gulong 
446 hildon_helper_set_logical_color                 (GtkWidget *widget, 
447                                                  GtkRcFlags rcflags,
448                                                  GtkStateType state, 
449                                                  const gchar *logicalcolorname)
450 {
451     gulong signum = 0;
452     GSList *list = NULL;
453
454     g_return_val_if_fail (GTK_IS_WIDGET (widget), 0);
455     g_return_val_if_fail (logicalcolorname != NULL, 0);
456     
457     list = attach_new_color_element (widget, rcflags, state, logicalcolorname);
458
459     /* Disconnects the previously connected signals. */
460     g_signal_handlers_disconnect_matched (G_OBJECT (widget), G_SIGNAL_MATCH_FUNC, 
461                                           0, 0, NULL, 
462                                           G_CALLBACK (hildon_change_style_recursive_from_list), NULL);
463
464     /* Change the colors now */
465     hildon_change_style_recursive_from_list (widget, NULL, list);
466
467     /* Connect to "style_set" so that the colors gets changed whenever theme */
468     signum = g_signal_connect_data (G_OBJECT (widget), "style_set",
469                                     G_CALLBACK (hildon_change_style_recursive_from_list),
470                                     list, NULL, 0);
471
472     return signum;
473 }
474
475
476 /**
477  * hildon_helper_set_thumb_scrollbar:
478  * @win: A #GtkScrolledWindow to use as target
479  * @thumb: TRUE to enable the thumb scrollbar, FALSE to disable
480  *
481  * This function enables a thumb scrollbar on a given scrolled window. It'll convert the
482  * existing normal scrollbar into a larger, finger-usable scrollbar that works without a stylus.
483  * As fingerable list rows are fairly high, consider using the whole available vertical space
484  * of your application for the content in order to have as many rows as possible
485  * visible on the screen at once.
486  *
487  * Finger-Sized scrollbar should always be used together with finger-sized content.
488  **/
489 void
490 hildon_helper_set_thumb_scrollbar               (GtkScrolledWindow *win, 
491                                                  gboolean thumb)
492 {
493     g_return_if_fail (GTK_IS_SCROLLED_WINDOW (win));
494
495     if (win->vscrollbar) 
496         gtk_widget_set_name (win->vscrollbar, (thumb) ? "hildon-thumb-scrollbar" : NULL);
497 }
498
499
500
501