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